算法系列 -- 三/四数之和
三数之和
题目描述
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意: 答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ]
思路解析
我想到了下面几个思路:
- 首先固定一个数,其余情况就是两数之和,时间复杂度O(
) - 三指针,大小指针相向移动,中间用二分搜索找出对应的值,如果能实现那么时间复杂度是O(nlogn)
- 三指针,固定一个数,另外两个左右指针相向移动,时间复杂度O(
)
第一种方法的难点在于去重逻辑的实现,据参考来源所说这种方法非常麻烦。
我最开始考虑的是第二种方法,但是仔细想来这种nlogn的方法是不可能实现的,因为如果根据大小指针相加的情况来移动指针的话,被移动掉的那个数的很多组合还不能被排除掉!所以这种方法会忽略掉很多种情况。
所以考虑采用第三种方法,但是为什么第三种方法的原理能够成立呢?因为内层循环能够穷尽所有两数之和,对于已经排过序的数组来说,采用大小指针的方式不断收缩,能够准确地搜索出每一个对应的两数之和组合。我们只需要在外层循环下套一个while(left < right)的条件循环即可。
1 | for (int i = 0 ; i < nums.size();i++){ |
注意代码中三个剪枝操作。这里解释一下后两个。
第二个剪枝操作关注的是如何在i移动的过程中去重,判断的是i位置的值是不是和上一个一样,因为如果和紧挨着的上一个值一样,那么可以构造的组合肯定已经包含在上一个值所处理的组合中了。注意这里我们不能判断当前值是否等于下一个值,因为当前值若与下一个值一样,还可以构造出一个额外的组合 - - 当前值加上下一个值再加上一个数等于0,所以我们需要这样的代码。
1 | if (i > 0 && nums[i] == nums[i-1]) continue; |
对于第三个去重方式,我们关注的是如何在left和right移动的过程中不加入重复的组合。对于一个已经找到的组合,我们需要移动left指针直至它指向一个更大的值,移动right指针直至它指向一个更小的值,但是还需要在这个过程中注意left < right
的条件,如果不加上这个条件的话就会出现下标访问超界的报错。(例子:[0,0,0,0])
1 | while (left<right && nums[left]==nums[left+1]) left++; |
经过这个操作,left和right其实还是指向的是与找到的组合相同的值,我们还需要将其相向移动一次,然后要么是不重复的情况,要么就left > right从而退出循环,进而将i加1
经过这两个去重操作,就无须构造set来去重了,就可以保证每一次加入到ans中的都是之前没有出现过的组合。
四数之和
题目描述
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:
答案中不可以包含重复的四元组。
示例: 给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。 满足要求的四元组集合为: [ [-1, 0, 0, 1], [-2, -1, 1, 2], [-2, 0, 0, 2] ]
思路解析
这个题只需要在三数之和问题外再套一层循环即可,但还有一些细节的逻辑需要注意。在三数之和中,当遍历指针指向0的时候就退出循环,但是在这个题中不能这样剪枝,因为我们是判断和为target,不是固定的0.
1 | vector<vector<int>> fourSum(vector<int>& nums, int target) { |
注意代码中的三个去重操作,和三数之和中的思想是类似的。