【C++】二分探索でxに最も近いK個の要素を効率的に求める方法
問題概要
ソート済みの整数配列と、2つの整数 k および x が与えられたとします。このとき、配列の中から x に最も近い k 個の要素を求めます。結果は昇順で返す必要があり、距離が等しい要素が存在する場合は、より小さい値の要素を優先します。
例えば、入力が [1,2,3,4,5]、k = 4、x = 3 の場合、出力は [1,2,3,4] となります。
アルゴリズムの考え方(二分探索)
この問題は二分探索を活用することで、O(log(n − k) + k) の計算量で効率的に解くことができます。ポイントは、「答えとなる k 個の連続した部分配列の開始位置」を二分探索で絞り込むことです。
候補となるウィンドウの左端 mid に対して、x − arr[mid](左端との距離)が arr[mid + k] − x(右端との距離)より大きい場合は、ウィンドウを右へずらすべきなので low = mid + 1 とします。そうでなければ、答えは mid またはそれより左側にあるため high = mid とします。この条件式により、距離が同点の場合には自動的に小さい要素側が優先されます。
手順
- 結果を格納する配列
ansを用意する low = 0、high = 配列のサイズ − kと初期化するlow < highの間、以下を繰り返す:mid = low + (high − low) / 2を計算するx − arr[mid] > arr[mid + k] − xならばlow = mid + 1、そうでなければhigh = mid
iをlowからlow + k − 1まで動かしながら、arr[i]をansに追加するansを返す
C++での実装例
以下の実装を見ると、より理解が深まるでしょう。
#include <bits/stdc++.h>
using namespace std;
void print_vector(vector<auto> v){
cout << "[";
for(int i = 0; i<v.size(); i++){
cout << v[i] << ", ";
}
cout << "]"<<endl;
}
class Solution {
public:
vector<int> findClosestElements(vector<int>& arr, int k, int x) {
vector <int> ans;
int low = 0;
int high = arr.size() - k;
while(low < high){
int mid = low + (high - low)/2;
if(x - arr[mid] > arr[mid + k] - x){
low = mid + 1;
}
else high = mid;
}
for(int i = low ; i < low + k ; i++)ans.push_back(arr[i]);
return ans;
}
};
main(){
Solution ob;
vector<int> v = {1,2,3,4,5};
print_vector(ob.findClosestElements(v, 4, 3));
}
入力
[1,2,3,4,5]
4
3
出力
[1,2,3,4]
計算量の評価
時間計算量は、二分探索部分が O(log(n − k))、結果配列の構築が O(k) であるため、全体で O(log(n − k) + k) となります。単純に全要素の絶対差を計算してソートする方法(O(n log n))と比べても、非常に効率的なアプローチだと言えます。補助的に使用するメモリは出力を除き O(1) です。
-
C++で汚染された二分木を復元して要素を検索する方法
問題の概要次のようなルールに従う二分木を考えます。root.val == 0 であるtreeNode.val が x であり、treeNode.left が NULL でない場合、treeNode.left.val = 2 * x + 1 となるtreeNode.val が x であり、treeNode.right が NULL でない場合、treeNode.right.val = 2 * x + 2 となるここで、この二分木は「汚染」されているものとします。つまり、すべてのノードの値が -1 に書き換えられている状態です。まず二分木を復元した上で、以下の FindElements クラスを実
-
C++で二分木における最も近い葉ノードまでの距離を求める方法
二分木が与えられ、その葉ノードはそれぞれ異なるレベルに存在するとします。さらに、あるノードを指すポインタが与えられ、そのノードから最も近い葉ノードまでの距離を求める必要があります。例として、次のような二分木を考えてみましょう。この木における葉ノードは 2、-2、6 の3つです。もしポインタがノード -5 を指している場合、-5 から最も近い葉ノードまでの距離は 1 となります。解決のアプローチこの問題を解くには、次の手順で考えます。まず、指定されたノードを根とする部分木を走査し、その部分木内で最も近い葉ノードを見つけて距離を記録します。次に、木の根から全体を走査します。ノード x が左部分木に