【难】三数之和问题 | 豆包MarsCode AI刷题

139 阅读3分钟

题目指路-->三数之和问题 - MarsCode

问题描述

小U有一个整数数组 arr,他希望找到其中三个元素 i, j, k 满足条件 i < j < k 且 arr[i] + arr[j] + arr[k] == target。由于可能存在大量的元组,结果需要对 10^9 + 7 取模。

例如:当 arr = [1,1,2,2,3,3,4,4,5,5] 且 t = 8 时,有多个元组满足条件,你需要返回这些元组的数量。

解题思路

根据题目得知,我们需要遍历整数数组 arr找出所有三数之和为目标值的可能性。除了暴力搜索之外,我们还可以通过双指针算法解决这个问题。

双指针是一种优化搜索范围的经典算法,特别适用于有序数组中的问题。通过设置两个指针分别从两端开始(或一端和另一端),我们可以根据条件动态调整指针的位置,避免冗余的暴力搜索。

核心思想:通过排序将数组变为有序,利用指针的位置关系快速缩小范围,从而将复杂度从 O(n3)O(n^3)O(n3) 降低到 O(n2)O(n^2)O(n2)。

  1. 数组排序:先对数组进行升序排序,满足使用双指针的条件。

  2. 三重循环的优化

    • 固定第一个元素 arr[i],从 i+1i+1i+1 到末尾范围内寻找满足条件的另外两个数。
    • 使用双指针,一个从左边起 (l = i + 1),另一个从右边起 (r = len - 1)。
  3. 双指针扫描

    • 计算当前三数之和 sum = arr[i] + arr[l] + arr[r]

    • 根据 sumtarget 的关系调整指针:

      • sum < target,左指针右移;

      • sum > target,右指针左移;

      • sum == target,统计左右相同数字的组合数:

        • 如果左右指针数字相同,计算从 lr 的组合数。
        • 如果左右指针数字不同,统计左边和右边的相同数字数量,并计算组合数。
    • 更新答案。

  4. 取模:在结果返回之前对 109+710^9 + 7109+7 取模。

时间复杂度:O(n^2)

空间复杂度: O(1)

详细代码及注释:

import java.util.*;

public class Main {

    public static int solution(int[] arr, int t) {
        int MOD = 1000000007;
        long ans = 0; // 防止溢出
        int len=arr.length;
        //排序数组
        Arrays.sort(arr); 

        for (int i=0; i<len-2; i++) {
            int l=i+1; // 左指针
            int r=len-1; // 右指针

            while (l<r) {
                int sum=arr[i]+arr[l]+arr[r];

                if (sum == t) {
                    if (arr[l] == arr[r]) {
                        ans += ((r-l+1) * (r-l) / 2); // 计算组合数
                        break; // 直接跳出
                    } 
                    else {
                        int lCount=1, rCount=1;

                        // 统计左指针相同元素数量
                        while (l+1<r&&arr[l]==arr[l+1]) {
                            l++;
                            lCount++;
                        }

                        // 统计右指针相同元素数量
                        while (r-1>l&&arr[r]==arr[r - 1]){
                            r--;
                            rCount++;
                        }

                        ans+=(lCount * rCount); // 组合数

                        l++; 
                        r--; 
                    }
                } 
                else if(sum < t)l++;
                else r--; 
            }
        }

        return (int) (ans % MOD); // 对结果取模
    }

    public static void main(String[] args) {
        System.out.println(solution(new int[]{1, 1, 2, 2, 3, 3, 4, 4, 5, 5}, 8) == 20);
        System.out.println(solution(new int[]{2, 2, 2, 2}, 6) == 4);
        System.out.println(solution(new int[]{1, 2, 3, 4, 5}, 9) == 2);
        System.out.println(solution(new int[]{1, 1, 1, 1}, 3) == 4);
    }
}

双指针算法的注意事项:

1. 确保数组有序性

  • 双指针通常需要有序的数组或字符串(升序或降序)。如果数组无序,直接应用可能会出现各种问题。

2. 左右指针的初始化

  • 常见方式

    • 左右两端型left = 0right = arr.length - 1
    • 左中右型(如三数求和问题):外层固定一个元素,双指针在其右侧范围内操作。
  • 注意事项

    • 确保指针初始化的范围合理,避免直接越界。
    • 如果问题涉及多种方向的搜索,需要明确各自的范围。

3. 边界条件处理

  • 问题:指针移动可能导致越界或遗漏特定情况。

  • 解决方法

    • 明确 while (left < right) 的终止条件,避免指针重合或跳过目标元素。

    • 在更新指针位置时,确保新值在合法范围内。