图解LeetCode18:四数之和(排序+双指针,递归)

LeetCode18:四数之和

给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):

0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。

示例 :

输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]

输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]

提示:

1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109

思路1:暴力循环

这个题目和LeetCode15:三数之和,解法大致相同,只是多了一个数字,又多了许多的细节。

同样可以使用暴力解法,四个循环进行求解,维护四个指针,逐渐后移,直到出现所有的结果
在这里插入图片描述

然后逐次遍历
在这里插入图片描述

这里就会出现两个问题

①复杂度为O(n^4)

②会出现重复解

对于第一个问题该方法无法解决,对于第二个问题可以选择使用hash表去掉重复值

思路2:排序+双指针

如果使用递归的暴力解法,使用四个循环,也可以得出解,只是时间复杂度为O(n^4),所以依然可以使用双指针和排序的方法求解。

先排序,就可以根据与target的比较控制指针的移动方向

然后维护四个指针,i、j、m、n,i从0开始,j=i+1,m从j+1开始,n为最后一个字符

比较四数之和与target的大小

①如果sum>target,说明只有将数字变小,才有可能与target相等,就把尾指针n左移动

②如果sum<target,说明需要将数字变大,才有可能与target相等,就把m指针向右移动。这时后如果m指针与n指针碰撞,而且sum<target,则移动j指针,知道与target相同,或者与n指针碰撞

③如果sum=target,说明一致,记录四个值,将指针同时移动到下一点继续

解法:

对于一个列表,先排序,就可以根据与target的比较控制指针的移动方向
在这里插入图片描述

然后维护四个指针,i、j、m、n,i从0开始,j=i+1,m从j+1开始,n为最后一个字符
在这里插入图片描述

在这里插入图片描述

第一次比较与target的大小
在这里插入图片描述

指针后移
在这里插入图片描述

然后将i和j后移,重置m和n指针
在这里插入图片描述

数字一致,继续向后移动
在这里插入图片描述

到此为止,遍历结束,输出结果

注意:循环到头的标志

①m指针碰撞n指针,而且sum>target

②m指针碰撞n指针,sum<target,然后j指针碰撞m指针

③i、j、m、n四个指针碰撞,结束循环

代码

public List<List<Integer>> fourSum(int[] nums, int target) {
    // 声明一个结果列表
    List<List<Integer>> res = new ArrayList<List<Integer>>();
    // 如果列表为空或者长度小于4,直接返回
    if (nums == null || nums.length < 4) {
        return es
    }
    // 对数组排序
    Arrays.sort(nums);
    // 数组长度
    int length = nums.length;
    // 维护四个指针,i、j、m、n,i从0开始,j=i+1,m从j+1开始,n为最后一个字符
    for (int i = 0; i < length - 3; i++) {
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue;
        }
        // 前四个数字的和,如果大于target,说明后面一定大于,直接退出
        if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
            break;
        }
        // 有小于的再继续
        if ((long) nums[i] + nums[length - 3] + nums[length - 2] + nums[length - 1] < target) {
            continue;
        }
        // 让j=i+1
        for (int j = i + 1; j < length - 2; j++) {
            // 数值一样跳过
            if (j > i + 1 && nums[j] == nums[j - 1]) {
                continue;
            }
            // 每次循环的前四位数字大于target,就直接跳出
            if ((long) nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                break;
            }
            // 小于跳过本次,移动指针
            if ((long) nums[i] + nums[j] + nums[length - 2] + nums[length - 1] < target) {
                continue;
            }
            // 移动指针
            int m = j + 1, n = length - 1;
            // 指针碰撞退出
            while (m < n) {
                // 四数之和
                int sum = nums[i] + nums[j] + nums[m] + nums[n];
                // 满足结果
                if (sum == target) {
                    // 添加到结果
                    res.add(Arrays.asList(nums[i], nums[j], nums[m], nums[n]));
                    // 数值相同,跳过
                    while (m < n && nums[m] == nums[m + 1]) {
                        m++;
                    }
                    // m指针后移
                    m++;
                    // 数值相同,跳过
                    while (m < n && nums[n] == nums[n - 1]) {
                        n--;
                    }
                    // n指针前移
                    n--;
                } else if (sum < target) {
                    m++;
                } else {
                    n--;
                }
            }
        }
    }
    // 返回结果列表
    return res;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

牧码文

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值