LeetCode 第一题就是很经典的题目: twoSum
,twoSum
的解法有很多种,最经典的方法就是 排序+双指针
,如果我们掌握了这个核心解法,那么去解 NSum
的问题将会很easy!
划重点 排序+双指针
是我们解决 NSum
的核心思想。
一. twoSum
问题
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [0, 1]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这倒提的解法可以看我的 Github Day1120TowNum
由于这道题是让求数组的下标,跟我们要求数组里面的值相加等于target有点出入,所以这里不展开描述了。项目里面提供了两个解法,一个是利用HashMap去解,另一个是利用 排序+双指针
思路去解。
下面我们将题目描述变一下:
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的
值
。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
示例:
给定 nums = [2, 7, 11, 15], target = 9
因为 nums[0] + nums[1] = 2 + 7 = 9
所以返回 [2, 7]
解决这个问题的核心是使用排序+双指针
:
- 首先对数组进行排序
- 利用双指针,从两端相向相加跟target的值进行比较
问题的关键在于不能重复,假如数组为 [2, 7, 11, 15] ,target = 9
,那么输出 [2,7],[7,2]
就是重复的项,所以我们需要注意将重复的值去掉。
如果数组为[2, 7, 2, 7, 11, 15] ,target = 9
,那么输出的 [2,7],[2,7]
也属于重复的项,同样需要将重复的项去掉。
代码如下:
public static List> twoSum(int[] nums, int start, int target) {
List> result = new ArrayList>();
//1.排序
Arrays.sort(nums);
//2.定义两个指针 low heigh
int low = start, heigh = nums.length - 1;
while (low < heigh) {
int sum = nums[low] + nums[heigh];
//3.定义两个临时变量用来存储 当前两个指针的值 left 跟 right
int left = nums[low], right = nums[heigh];
if (sum < target) {
//受4的启发 跳过所有重复的元素
while (low < heigh && nums[low] == left) {
low++;
}
} else if (sum > target) {
//受4的启发 跳过所有重复的元素
while (low < heigh && nums[heigh] == right) {
heigh--;
}
} else {
List list = new ArrayList();
list.add(left);
list.add(right);
result.add(list);
//4.跳过所有重复的元素
while (low < heigh && nums[low] == left) {
low++;
}
while (low < heigh && nums[heigh] == right) {
heigh--;
}
}
}
return result;
}
这样我们就写出来一个通用的 twoSum
模板了,针对题目变种后,问题将会迎刃而解。
- 时间复杂度: 循环是O(N),排序是O(NlogN),所以整体的时间复杂度是 O(NlogN)
二. 3Sum
问题
题目描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
[-1, 0, 1],
[-1, -1, 2]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
问题上升到 3Sum
,其实我们只有将这个问题转化成 twoSum
,问题就会迎刃而解,怎么转换呢?穷举。我们求三个数的和是 target
,那么也就是说我们可以固定住第一个数,然后去求除第一个数以外的其他数任选两个数的和为 target-num[0]
,依次类推,就是求target-num[i]
的 twoSum
。
所以代码如下:
public static List> twoSum(int[] nums, int start, int target) {
List> result = new ArrayList>();
Arrays.sort(nums);
//左指针改成 start,其他不变
int low = start, heigh = nums.length - 1;
while (low < heigh) {
...
}
return result;
}
public static List> threeSum(int[] nums, int start, int target) {
Arrays.sort(nums);
int n = nums.length;
List> result = new ArrayList>();
for (int i = start; i < nums.length; i++) {
List> listData = twoSum(nums, i + 1, target - nums[i]);
for (List data : listData) {
data.add(nums[i]);
result.add(data);
}
//跳出第一个数字重复的情况
while (i < n - 1 && nums[i] == nums[i + 1]) {
i++;
}
}
return result;
}
- 时间复杂度 排序O(NlogN),
twoSum
O(N),threeSum
里面循环调用twoSum
,所以时间复杂度是 O(NlogN+N*N)=O(N^2)
4Sum
问题
题目描述:
给定一个包含 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]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/4sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解决 4Sum
的思路跟解决 3Sum
的思路一样,我们通过穷举将问题转换成 3Sum
,问题就得到了解决。
代码如下:
public static List> fourSum(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
List> result = new ArrayList>();
for (int i = 0; i < nums.length; i++) {
List> listData = threeSum(nums, i + 1, target - nums[i]);
for (List data : listData) {
data.add(nums[i]);
result.add(data);
}
while (i < n - 1 && nums[i] == nums[i + 1]) {
i++;
}
}
return result;
}
public static List> threeSum(int[] nums, int start, int target) {
Arrays.sort(nums);
int n = nums.length;
List> result = new ArrayList>();
// i的起始值改为start,其他保持不变
for (int i = start; i < nums.length; i++) {
...
}
return result;
}
- 时间复杂度:
4Sum
循环了3Sum
,所以时间复杂度是 O(N^3)
NSum
问题
5Sum
根据 4Sum
可以得出,4Sum
根据 3Sum
可以得出,3Sum
根据 2Sum
可以得出,所以那就 递归
吧。。。
-
递归条件
nums ,n,start,target
-
递归终止条件
n如果小于2则退出递归
所以代码大概的递归框架如下:
public static List> nSum(int[] nums, int n, int start, int target) {
List> result = new ArrayList>();
//递归结束条件
if(n<2){
return result;
}
if(n==2){
// twoSum
}else {
//递归逻辑
}
}
我们现在根据递归框架,往代码里面进行填肉:
public static List> nSum(int[] nums, int n, int start, int target) {
Arrays.sort(nums);
List> result = new ArrayList>();
//递归结束条件
int sz=nums.length;
if (n < 2) {
return result;
}
if (n == 2) {
// twoSum
result = twoSum(nums, start, target);
} else {
//当n>2 时,递归计算 (n-1)Sum的结果
for (int i = start; i < nums.length; i++) {
List> lists = nSum(nums, n - 1, i + 1, target - nums[i]);
for (List data : lists) {
data.add(nums[i]);
result.add(data);
}
while (i < nums.length - 1 && nums[i] == nums[i + 1]) {
i++;
}
}
}
return result;
}
总结
总结一下:NSum
的核心思想就是 排序+双指针
,当然我们为了求 NSum
,最后用到了递归。虽然方法不是最快的,但是这个思路是最方便解这样的题的。
参考资料
- LeetCode labuladong题解