C++で配列のサイズを半分に削減するアルゴリズム
問題概要
配列 arr が与えられているとします。私たちはいくつかの整数からなる集合を選び、その集合に含まれるすべての整数を配列から取り除くことができます。ここで求めたいのは、配列の要素の少なくとも半分が取り除かれるようにするために必要な「集合の最小サイズ」です。
例として、arr = [3,3,3,3,5,5,5,2,2,7] の場合を考えてみましょう。このとき出力は 2 になります。{3,7} を選択すると、新しい配列は [5,5,5,2,2] となり、そのサイズが5(元の配列サイズ10のちょうど半分)になるためです。
サイズ2の候補としては {3,5}、{3,2}、{5,2} などがあります。一方で {2,7} を選ぶことはできません。この場合、新しい配列は [3,3,3,3,5,5,5] となり、サイズが7と元の配列の半分よりも大きくなってしまうからです。
解決のためのアプローチ
この問題は、次の手順に従うことで解決できます。
マップ
mを定義し、nに配列のサイズを代入します。さらに、配列内の各要素の出現回数(頻度)をマップmに記録します。一時的な配列
tempを定義し、szをnに、結果用の変数retを0に初期化します。マップ
m内の各キー・バリューのペアitについて、その値(=出現回数)をtempに追加します。temp配列を降順にソートします。i を 0 から
tempのサイズまでループさせます。もし
sz <= n / 2であれば、ループを抜けます。retを1増やします。szからtemp[i]を引きます。
最後に
retを返します。
この手法のポイントは貪欲法(グリーディ法)にあると言えます。出現回数の多い要素から順に除去していくことで、最小の集合サイズで半分以上の要素を削除できる可能性が高まります。
C++による実装例
理解を深めるために、次の実装例を見てみましょう。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
int minSetSize(vector<int>& arr) {
unordered_map <int, int> m;
int n = arr.size();
for(int i = 0; i < n; i++){
m[arr[i]]++;
}
vector <int> temp;
unordered_map <int, int> :: iterator it = m.begin();
int sz = n;
int ret = 0;
while(it != m.end()){
temp.push_back(it->second);
it++;
}
sort(temp.rbegin(), temp.rend());
for(int i = 0; i < temp.size(); i++){
if(sz <= n / 2)break;
ret++;
sz -= temp[i];
}
return ret;
}
};
main(){
vector<int> v = {3,3,3,3,5,5,5,2,2,7};
Solution ob;
cout << (ob.minSetSize(v));
}入力
[3,3,3,3,5,5,5,2,2,7]
出力
2
まとめ
この問題では、各要素の出現頻度を数え上げ、頻度の高い要素から順に選んでいく貪欲な戦略が有効です。計算量は、ハッシュマップへの頻度記録に O(n)、ソートに O(k log k)(k はユニークな要素数)程度であり、多くの場合で効率的に動作します。頻度の高い要素ほど除去効果が大きいため、直感的にも理論的にも最短手順に近い結果が得られるのが特徴です。
-
画像ファイルのサイズを小さくする方法|画質を保ちながら軽量化するテクニック
以前、エクスプローラーのメール送信オプションや「Image Resizer for Windows」というデスクトップアプリを使って画像ファイルのサイズを縮小する方法をご紹介しました。どちらも優れた方法ですが、画像を最適化するアプローチは他にもいくつかあります。また、多くのサイトではペイント(Paint)の使用を推奨していますが、実際に試してみると画質が大幅に低下してしまうため、おすすめできません。 まず理解しておきたいのは、画像サイズを縮小する手法の中には画質が低下してしまうものがあるという点です。Web表示用であれば問題ないことも多いですが、印刷用途には向きません。これは一般的に、画像の解
-
C++で解く迷路問題:転がるボールが目的地に止まれるかをBFSで判定する方法
迷路の中にボールがあるとします。迷路には空きスペース(通路)と壁があります。ボールは上下左右のいずれかの方向に転がって空き通路を進むことができますが、壁にぶつかるまで止まりません。ボールが停止したときに、次の方向を選べます。この問題では、ボールの開始位置、目的地、そして迷路そのものが与えられ、「ボールが目的地の位置で停止できるかどうか」を判定する必要があります。迷路は2次元配列で表現され、1は壁、0は空きスペースを意味します。迷路の外周はすべて壁になっています。開始位置と目的地は行・列のインデックス(座標)で与えられます。問題例たとえば、次のような2次元配列で表される迷路を考えてみましょう。0