コミックアルゴリズムの問題:2つの数値の合計と3つの数値の合計

少し前に、XiaoHuiはleecodeに関する2つの古典的なアルゴリズムの問​​題について説明しました。

コミック:合計が配列内の「特定の値」である2つの数値を見つける方法は?

コミック:合計が配列内の「特定の値」である3つの数値を見つける方法は?

本日、シャオフイはこれら2つの質問を統合し、詳細を修正しました。訂正していただきありがとうございます。

- - - 次の日 - - -

どういう意味ですか?次の整数配列がある場合の例を見てみましょう(配列に重複する要素がないと仮定します)。

13などの特定の値をランダムに選択し、2つの数値の合計が13に等しいすべての組み合わせを見つけるように依頼します

12 + 1 = 13、6 + 7 = 13であるため、最終的な出力結果(出力は添え字)は次のようになります。

【1、6】

【2、7】

Xiao Huiが表現したいアイデアは、配列全体を直接トラバースし、要素がトラバースされるたびに他の要素に追加して、合計がその特定の値に等しいかどうかを確認することです。

最初のラウンドで、要素5と他の要素を追加します。

要件を満たす2つの要素が見つかりませんでした。

2回目のラウンドで、要素12とその他の要素を追加します。

12と1を加算した結果は13であり、要件を満たしていることがわかります。

この考えによれば、アレイ全体がトラバースされています。


————————————



詳細を示しましょう:

最初のラウンドで、要素5にアクセスし、13-5 = 8を計算します。ハッシュテーブルで8を検索し、見つからないことを確認します。

2回目のラウンドでは、要素12にアクセスして、13-12 = 1を計算します。ハッシュテーブルで1を検索し、要素1の添え字が6であることを確認します。したがって、要素12(添え字は1)と要素1(添え字は6)は結果のペアです。

第3ラウンドでは、要素6にアクセスし、13-6 = 7を計算します。ハッシュテーブルで7を検索し、要素7の添え字が7であることを確認します。したがって、要素6(添え字は2)と要素7(添え字は7)は結果のペアです。

この考えによれば、常にアレイ全体をトラバースするだけです。

public class FindSumNumbers {

    public static List<List<Integer>> twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        List<List<Integer>> resultList = new ArrayList<>();
        for (int i = 1; i < nums.length; i++) {
            map.put(nums[i], i);
        }
        for (int i = 0; i < nums.length; i++) {
            int other = target - nums[i];
            if (map.containsKey(other) && map.get(other) != i) {
                resultList.add(Arrays.asList(i,map.get(other)));
                //为防止找到重复的元素对,匹配后从哈希表删除对应元素
                map.remove(nums[i]);
            }
        }
        return resultList;
    }

    public static void main(String[] args) {
        int[] nums = {5,12,6,3,9,2,1,7};
        List<List<Integer>> resultList = twoSum(nums, 13);
        for(List<Integer> list : resultList){
            System.out.println(Arrays.toString(list.toArray()));
        }
    }

}

    public static List<List<Integer>> twoSumV2(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();
        List<List<Integer>> resultList = new ArrayList<>();
        for (int i = 0; i < nums.length; i++) {
            int other = target - nums[i];
            if (map.containsKey(other)) {
                resultList.add(Arrays.asList(map.get(other),i));
            }
            map.put(nums[i], i);
        }
        return resultList;
    }

たとえば、次の整数配列があるとします(配列に重複する要素がないと仮定します)。

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つのポインターを使用して配列の両端をポイントし、常に近づいて中央に調整し、一致する組み合わせを見つけます。これは、「クランプ方式」とも呼ばれるダブルポインター方式です。

    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をフォローして、 もっとエキサイティングなコンテンツを見てください。

点个[在看],是对小灰最大的支持!

おすすめ

転載: blog.csdn.net/bjweimengshu/article/details/109063928