LeetCode-解决nSum问题
nSum问题#
两数之和#
题目描述:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
利用双指针解决,代码如下:
public class TwoSum {
public int[] twoSum(int[] nums, int target) {
// 因为后边要对数组排序,要返回原数组的下标,所以这里拷贝一份
int[] numsCopy = (int[]) Arrays.copyOf(nums,nums.length);
Arrays.sort(nums);
int lo = 0,hi = nums.length-1;
int[] res = new int[2];
while(lo < hi) {
int sum = nums[lo] + nums[hi];
if(sum<target) {
lo++;
}else if(sum>target) {
hi--;
}else if(sum == target){
break;
}
}
for(int i=0;i<numsCopy.length;i++) {
// 这里要防止第一个下标和第二个下标重复
if(lo<numsCopy.length&&numsCopy[i] == nums[lo]) {
res[0] = i;
// 赋值之后就不会再进入这个if体内,保证了lo不会后移
lo = numsCopy.length;
}else if(hi<numsCopy.length&&numsCopy[i] == nums[hi]) {
res[1] = i;
hi = numsCopy.length;
}else if(lo == hi) {
return res;
}
}
return res;
}
}
继续魔改题目,把这个题目变得更泛化,更困难一点:
nums
中可能有多对儿元素之和都等于 target
,请你的算法返回所有和为 target
的元素对儿,其中不能出现重复。
比如说输入为 nums = [1,3,1,2,2,3], target = 4
,那么算法返回的结果就是:[[1,3],[2,2]]
。
对于修改后的问题,关键难点是现在可能有多个和为 target
的数对儿,还不能重复,比如上述例子中 [1,3]
和 [3,1]
就算重复,只能算一次。
首先,基本思路肯定还是排序加双指针:
但是,这样实现会造成重复的结果,比如说 nums = [1,1,1,2,2,3,3], target = 4
,得到的结果中 [1,3]
肯定会重复。
出问题的地方在于 sum == target
条件的 if 分支,当给 res
加入一次结果后,lo
和 hi
不应该改变 1 的同时,还应该跳过所有重复的元素:
所以,可以对双指针的 while 循环做出如下修改:
while(lo < hi) {
int left = nums[lo];
int right = nums[hi];
int sum = left +right;
if(sum<target) {
// 跳过所有重复的元素
while(lo<hi&&nums[lo] == left){
lo++;
}
}else if(sum > target) {
// 跳过所有重复的元素
while(lo<hi&&nums[hi] == right) {
hi--;
}
}else if(sum == target) {
res.add(Arrays.asList(nums[lo],nums[hi]));
// 跳过所有重复的元素
while(lo<hi&&nums[lo] == left){
lo++;
}
while(lo<hi&&nums[hi] == right) {
hi--;
}
}
}
一个通用化的 twoSum
函数就写出来了.
3Sum问题#
题目描述:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
再泛化一下题目,不要光和为 0 的三元组了,计算和为 target
的三元组吧,同上面的 twoSum
一样,也不允许重复的结果:
利用穷举的思想,想找和为 target
的三个数字,那么对于第一个数字,可能是什么?nums
中的每一个元素 nums[i]
都有可能!
那么,确定了第一个数字之后,剩下的两个数字可以是什么呢?其实就是和为 target - nums[i]
的两个数字呗,那不就是 twoSum
函数解决的问题么🤔
可以直接写代码了,需要把 twoSum
函数稍作修改即可复用:
public class ThreeSum {
public List<List<Integer>> threeSum(int[] nums,int target) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> res = new ArrayList<>();
for(int i=0;i<n;i++) {
List<List<Integer>> temps = getTwoSum(nums,i+1,target-nums[i]);
for (List<Integer> temp : temps) {
// temp是java.util.Arrays.ArrayList,他是Arrays类下的一个内部类,并没有实现add方法和remove方法,需要转化为java.util.ArrayList
List<Integer> list=new ArrayList(temp);
list.add(nums[i]);
res.add(list);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while(i<n-1&&nums[i] == nums[i+1]) {
i++;
}
}
return res;
}
private List<List<Integer>> getTwoSum(int[] nums,int start,int target) {
List<List<Integer>> res = new ArrayList<>();
int lo = start ,hi = nums.length-1;
while(lo < hi) {
int left = nums[lo];
int right = nums[hi];
int sum = left +right;
if(sum<target) {
// 跳过所有重复的元素
while(lo<hi&&nums[lo] == left){
lo++;
}
}else if(sum > target) {
// 跳过所有重复的元素
while(lo<hi&&nums[hi] == right) {
hi--;
}
}else if(sum == target) {
res.add(Arrays.asList(nums[lo],nums[hi]));
// 跳过所有重复的元素
while(lo<hi&&nums[lo] == left){
lo++;
}
while(lo<hi&&nums[hi] == right) {
hi--;
}
}
}
return res;
}
}
关键点在于,不能让第一个数重复,至于后面的两个数,我们复用的 twoSum
函数会保证它们不重复。所以代码中必须用一个 while 循环来保证 3Sum
中第一个元素不重复。
至此,3Sum
问题就解决了,时间复杂度不难算,排序的复杂度为 O(NlogN)
,twoSumTarget
函数中的双指针操作为 O(N)
,threeSumTarget
函数在 for 循环中调用 twoSumTarget
所以总的时间复杂度就是 O(NlogN + N^2) = O(N^2)
。
4sum问题#
题目描述:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
4Sum
完全就可以用相同的思路:穷举第一个数字,然后调用 3Sum
函数计算剩下三个数,最后组合出和为 target
的四元组。
public class FourSum {
public List<List<Integer>> fourSum(int[] nums, int target) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> res = new ArrayList<>();
for(int i = 0;i<n;i++) {
List<List<Integer>> threes = threeSum(nums,i+1,target-nums[i]);
for (List<Integer> three : threes) {
// temp是java.util.Arrays.ArrayList,他是Arrays类下的一个内部类,并没有实现add方法和remove方法,需要转化为java.util.ArrayList
List<Integer> list=new ArrayList(three);
list.add(nums[i]);
res.add(list);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while(i<n-1&&nums[i] == nums[i+1]) {
i++;
}
}
return res;
}
private List<List<Integer>> threeSum(int[] nums,int start,int target) {
Arrays.sort(nums);
int n = nums.length;
List<List<Integer>> res = new ArrayList<>();
for(int i=start;i<n;i++) {
List<List<Integer>> temps = getTwoSum(nums,i+1,target-nums[i]);
for (List<Integer> temp : temps) {
// temp是java.util.Arrays.ArrayList,他是Arrays类下的一个内部类,并没有实现add方法和remove方法,需要转化为java.util.ArrayList
List<Integer> list=new ArrayList(temp);
list.add(nums[i]);
res.add(list);
}
// 跳过第一个数字重复的情况,否则会出现重复结果
while(i<n-1&&nums[i] == nums[i+1]) {
i++;
}
}
return res;
}
private List<List<Integer>> getTwoSum(int[] nums,int start,int target) {
List<List<Integer>> res = new ArrayList<>();
int lo = start ,hi = nums.length-1;
while(lo < hi) {
int left = nums[lo];
int right = nums[hi];
int sum = left +right;
if(sum<target) {
while(lo<hi&&nums[lo] == left){
lo++;
}
}else if(sum > target) {
while(lo<hi&&nums[hi] == right) {
hi--;
}
}else if(sum == target) {
res.add(Arrays.asList(nums[lo],nums[hi]));
while(lo<hi&&nums[lo] == left){
lo++;
}
while(lo<hi&&nums[hi] == right) {
hi--;
}
}
}
return res;
}
}
按照相同的套路,4Sum
问题就解决了,时间复杂度的分析和之前类似,for 循环中调用了 threeSumTarget
函数,所以总的时间复杂度就是 O(N^3)
。
nSum问题:#
先附上代码:
public class NSum {
// 思路:把之前的题目解法合并起来了,n == 2 时是 twoSum 的双指针解法,n > 2 时就是穷举第一个数字,然后递归调用计算 (n-1)Sum,组装答案
// 这里的参数n是指nSum里的n
public List<List<Integer>> nSum(int[] nums, int n,int start,int target) {
// Arrays.sort(nums);
int len = nums.length;
List<List<Integer> > res = new ArrayList<>();
if(n<2||len<n) {
return res;
}
if(n==2) {
// 双指针那一套
int lo = start ,hi = len-1;
while(lo < hi) {
int left = nums[lo];
int right = nums[hi];
int sum = left +right;
if(sum<target) {
while(lo<hi&&nums[lo] == left){
lo++;
}
}else if(sum > target) {
while(lo<hi&&nums[hi] == right) {
hi--;
}
}else if(sum == target) {
res.add(Arrays.asList(nums[lo],nums[hi]));
while(lo<hi&&nums[lo] == left){
lo++;
}
while(lo<hi&&nums[hi] == right) {
hi--;
}
}
}
}else {
// n > 2 时,递归计算 (n-1)Sum 的结果
for(int i = start;i<len;i++) {
List<List<Integer>> temps = nSum(nums,n-1,i+1,target-nums[i]);
for(List<Integer> temp:temps) {
List<Integer> list = new ArrayList<>(temp);
list.add(nums[i]);
res.add(list);
}
while(i<n-1&&nums[i] == nums[i+1]) {
i++;
}
}
}
return res;
}
}
实际上就是把之前的题目解法合并起来了,n == 2
时是 twoSum
的双指针解法,n > 2
时就是穷举第一个数字,然后递归调用计算 (n-1)Sum
,组装答案。
需要注意的是,调用这个 nSum
函数之前一定要先给 nums
数组排序
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 从HTTP原因短语缺失研究HTTP/2和HTTP/3的设计差异
· 三行代码完成国际化适配,妙~啊~