少し前に、LeetCodeに古典的なアルゴリズムの問題[2つの数値の合計の問題]を導入しました。
今回は、問題を少し拡張して、合計が「特定の値」になる配列内の3つの数値を見つけようとします。
トピックの特定の要件は何ですか?次の整数配列が与えられます:
13などの特定の値を任意に選択し、3つの数値の合計が13に等しいすべての組み合わせを見つけるように依頼します。
5 + 6 + 2 = 13、5 + 1 + 7 = 13、3 + 9 + 1 = 13であるため、最終的な出力結果は次のようになります。
【5、6、2】
【5、1、7】
【3、9、1】
Xiao Huiのアイデアは、元の「3つの数の合計の問題」を「2つの数の合計の問題」にn回変換することです。
上記の配列を例として取り上げ、13の特定の値を選択し、XiaoHuiの特定のアイデアを示します。
最初のラウンドでは、配列の最初の要素5にアクセスし、問題を次の要素から合計8(13-5)になる2つの数値を見つけることに変換します。
合計が8である2つの数字を見つける方法は?前回の発言によると、ハッシュテーブルを使用して効率的に解決できます。
2回目のラウンドでは、配列の2番目の要素12にアクセスし、問題を次の要素から合計1(13-12)になる2つの数値を見つけるように変換します。
第3ラウンドでは、配列の3番目の要素6にアクセスし、問題を次の要素から合計7(13-6)になる2つの数値を見つけることに変換します。
類推すると、アレイ全体を常にトラバースすることは、2つの数値の合計の問題をn回解決することと同じです。
public static List<List<Integer>> threeSum(int[] nums, int target) {
List<List<Integer>> resultList = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
Map<Integer, Integer> map = new HashMap<>();
int d1 = target - nums[i];
//寻找两数之和等于d1的组合
for (int j = i+1; j < nums.length; j++) {
int d2 = d1 - nums[j];
if (map.containsKey(d2)) {
resultList.add(Arrays.asList(nums[i], d2, nums[j]));
}
map.put(nums[j], j);
}
}
return resultList;
}
上記のコードでは、各ラウンドで「2つの数値の合計問題」を解く時間の複雑さはO(n)であり、合計n回の反復であるため、解の合計時間の複雑さはO(n²)です。
スペースの複雑さに関しては、同じハッシュテーブルが繰り返し作成され、ハッシュテーブルには最大でn-1のキーと値のペアがあるため、ソリューションのスペースの複雑さはO(n)です。
前の配列を例として使用して、配列を昇順で並べ替えます。
これは少し抽象的です。詳しく説明しましょう。
最初のラウンドでは、配列の最初の要素1にアクセスし、問題を次の要素から合計12(13-1)になる2つの数値を見つけることに変換します。
合計が12である2つの数字を見つける方法は?2つのポインターを設定します。ポインターjは残りの要素の左端の要素2を指し、ポインターkは右端の要素12を指します。
2つのポインターの対応する要素の合計2+ 12 = 14> 12を計算すると、結果が大きすぎます。
配列は昇順で配置されるため、kの左側の要素はk未満である必要があります。したがって、ポインターkを左に移動します。
2つのポインターの対応する要素の合計、2 + 9 = 11 <12を計算します。今回は結果が小さすぎます。
jの右側の要素はjより大きくなければならないので、ポインタjを1つ右に移動します。
2つのポインターの対応する要素の合計3+ 9 = 12を計算します。これは、要件を満たしています。
:我々が正常に一致する組み合わせの集合たので1、3、9
しかし、これで終わりではありません。他の組み合わせを探し続ける必要があります。ポインタkを左に動かし続けます。
2つのポインターの対応する要素の合計を計算します(3 + 7 = 10 <12)。結果は小さすぎます。
したがって、ポインタjを右に移動します。
2つのポインターの対応する要素の合計5+ 7 = 12を計算し、要件を満たすグループを見つけます。
1、5、7
引き続き検索を行い、ポインタkを左に移動します。
2つのポインターの対応する要素の合計を計算します(5 + 6 = 11 <12)。結果は小さすぎます。
したがって、ポインタjを右に移動します。
この時点で、2つのポインターは一致しており、移動を続けると、以前に見つかった組み合わせが繰り返される可能性があるため、サイクルを直接終了します。
2回目のラウンドでは、配列の2番目の要素2にアクセスし、問題を変換して、次の要素から合計11(13-2)になる2つの数値を見つけます。
まだ2つのポインターを設定しています。ポインターjは残りの要素の左端の要素3を指し、ポインターkは右端の要素12を指します。
2つのポインターの対応する要素の合計を計算します(3 + 12 = 15> 11)。結果が大きすぎます。
ポインタkを左に移動します。
2つのポインターの対応する要素の合計を計算します(3 + 9 = 12> 11)。結果はまだ大きすぎます。
ポインタkを左に移動し続けます。
2つのポインターの対応する要素の合計を計算します(3 + 7 = 10 <11)。結果は小さすぎます。
ポインタjを右に移動します。
2つのポインターの対応する要素の合計を計算します(5 + 7 = 12> 11)。結果が大きすぎます。
ポインタkを左に移動します。
2つのポインターの対応する要素の合計5+ 6 = 11を計算して、要件を満たすグループを見つけます。
2、5、6
引き続き検索を行い、ポインタkを左に移動します。
この時点で、2つのポインターが再び重なり、サイクルが終了します。
この考えによれば、私たちはアレイ全体をトラバースしてきました。
このように、2つのポインターを使用して配列の両端を指し、常に近づいて中央に調整し、一致する組み合わせを見つけます。これは、「クランプ方法」とも呼ばれるダブルポインター方法です。
public static List<List<Integer>> threeSumv2(int[] nums, int target) {
Arrays.sort(nums);
List<List<Integer>> resultList = new ArrayList<List<Integer>>();
//大循环
for (int i = 0; i < nums.length; i++) {
int d = target - nums[i];
// j和k双指针循环定位,j在左端,k在右端
for (int j=i+1,k=nums.length-1; j<nums.length; j++) {
// k指针向左移动
while (j<k && (nums[j]+nums[k])>d) {
k--;
}
//双指针重合,跳出本次循环
if (j == k) {
break;
}
if (nums[j] + nums[k] == d) {
List<Integer> list = Arrays.asList(nums[i], nums[j], nums[k]);
resultList.add(list);
}
}
}
return resultList;
}
上記のコードは表面に3つのループがありますが、各ラウンドでのポインターjとkの移動数は合計でn-1回になるため、ソリューションの全体的な時間の複雑さはO(n²)です。
最も重要なことは、このソリューションは追加のセットを使用しない(ソートは入力配列で直接実行される)ため、スペースの複雑さはO(1)のみであるということです。
- - -終わり - - -
この記事が好きな友達は、公式アカウントプログラマーのXiaohuiをフォローして、 もっとエキサイティングなコンテンツを見てください。
点个[在看],是对小灰最大的支持!