Leetcode解题之3Sum

本文详细解析了LeetCode经典题目3Sum的求解思路,通过对数组排序、使用计数统计等方法,最终实现寻找所有唯一三元组使得它们的和为0的目标。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写在前面

身为一个程序猿,工作了两年才第一次写所谓的技术博客,可以说是很不称职的,不过在这两年的提升过程中遇见了大大小小的问题,有的简单却容易出错,有的看似复杂却妙趣横生。于是,出于治疗我懒癌的原因,我选择坚持写写博客,把我遇到的问题与大家分享,当然还有那微不足道的经验。好了,废话说的差不多了(毕竟我不是语文专业的,措辞肯定让人味同嚼蜡),下面就开始我的第一次编程之旅吧。

leetcode代码题之3Sum

先说说这个题的序号,应该是15,众所周知,leetcode是按照题目更新顺序排列序号的,说明这个题是建站初期就有的题,那时候的人还是真不留情面,若是这个题是现在的面试题,并且要求不能测试并一次性A过的话,我想,除了那些已经把代码当成交流语言的人,“正常人”都可以直接换行业了,并且到现在为止也只有不到百分之20的通过率,此题难度可见一斑啊。那么下面我们就先说说这个题的要求吧。

Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero.

用中国话的意思就行,给一个包含n个整数的数组S,在S数组中可能存在3个元素a,b,c将满足a+b+c =0这个条件,找出符合这个条件的三个整数的不同组合情况。

还有一个小提示在下面**The solution set must not contain duplicate triplets.**意思是解集中不存在相同的3元素数组,什么意思呢就是咱们{1,-1,0}和{-1,1,0}去领礼品,{1,-1,0}领完了,换了个马甲{-1,1,0}又去领了,服务员急道“小样你换个马甲我就不认识你了?”,当然这种情况都不行,那么两个{1,-1,0}去领两次奖品的话,服务员就该动手了。

好了,既然我们知道要求了,就得知道给了我们什么东西,我们要做成什么东西,一个数组nums是leet给我们的条件,那么List<List<Integer>>则是我们的要求了。有了这些东西,OK,我们来动手吧,小编我可不是耍嘴皮子的人。第一步,我们要取3个元素,那么数组至少要大于等于3个元素才有可能一种情况

if(nums.length<3) return list

然后,我们知道他是整数数组,这时候脑子中应该反应出这么几种方法,排序,二分法,计数统计及set存储,首先我们看到最简单的方法(最简单当然不是效率最高,往往别人给你封装好的方法包含了一系列的处理方式,导致了空间及时间上都并不是那么速度)set方法,set存储独特子集,相同的元素无法再次存储,可以解决我们的换马甲问题,当然存进去之前我们要对我们的数组进行排序,存入顺序肯定是由小到大,毕竟set也区分不开{-1,1,0}和{1,-1,0};最后将set转换成list;有两种转换方法addAll()与直接构造生成。我用了这个方法以后,当然只有一个结论time limit,先不说我的运算流程,光是这转换来转换去,存之前还要排序,就够我们喝一壶的。那么我就要想到能为我提供便利的方式了,不需要来回跑可以直接解决问题。

bingo,技术统计,对,就是它。我们大部分人都知道计数统计是记录数字出现的次数及种类,计数数组的容量为数组最大值-最小值+1;于是第二个问题出现了,求最小值,排序问题,我就不说了(小编为了省事直接Arrays.sort了有兴趣的可以试试for取最小值看看哪个快)在取最小值之后,我们应该想到,我们要3个数相加等于0,那么就有以下几种特殊情况最小值等于0,最小值大于0,最大值等于0,最大值小于0,第二,四种情况我们直接进行判断就可以了,这两种情况都为空,所以直接返回定义的空List就可以。而一,三种情况还需要具体判断,那么就先统计出来吧。

Arrays.sort(nums);
if(nums[0]>0||nums[nums.length-1]<0) return list;
int[] count = new int[nums[nums.length-1]-nums[0]];
for(n:nums){count[n-nums[0]++]};

存储好了计数数组,就用到我们刚才的条件了,此时的计数数组相当于先把原来的数组右移|最小值|的距离,再进行统计,那么由此可知0就向右平移了|最小值|的距离,最小值刚才已经判断了必定小于等于0,那么|最小值|也就等于
int zero = -nums[0];
知道了0存储的位置,就回想刚才一,三的情况于是乎两个判断条件
if((nums[0]==0||nums[length-1]==0)&&count[zero]<3) return list;
if(count[zero]>3){List<Integer> l = new ArrayList<Integer>();
l.add(0);
l.add(0);
l.add(0);
list.add(l)};

我的天,添加一种情况这么费事,摆明了是不想好好过了,那好吧,既然破罐子破摔,谁怕谁。List<Integer>说白了就是一个int数组,就像两个美女(先不讨论脸,如果看脸的话,出门请右拐),int数组苗条性感,放什么东西也整整齐齐;list有点胖啊,是你会的多,找东西也快,可是我就放个东西就够了没打算拿,还是要个轻的吧,公主抱也不至于断手不是。那么我们改改好了
list.add(new int[]{0,0,0})
这么一看舒服多了。特殊情况处理的差不多了,再来看看一般情况,我们知道如果3个数相加等于0,除去3个都是0的特殊情况,至少有一个正数一个负数,所以就需要两个游标,需要至少二层循环去控制,判断条件为游标所在位置的内容不为空也就是count[游标]!=0;为了方便我将游标控制和移动写在单独的方法里
public int goright(int x, int[] count,int zero){
 x++;
while(x<zero&&count[x]==0){
 x++; 
} 
return x; 
} 
public int goleft(int y, int[] count, int zero){ 
y--;
while(y>zero&&count[y]==0){ 
y--; 
} 
return y; 
}
goright方法是游标向右移动,所以为加,一开始用自加方法,防止死循环,判断条件,x向右移动但是必须保证小于0,也就是小于zero,保证自己是负数,并且当count[x]不等于0的时候就将x返回。同理,y就不说了。

最后游标都设置好了就该想想三个数相加怎么得到的0,假设一个数为z,z也在count数组上,也就相当于z也右移了-zero的距离,所以原来的数为z-zero,同理x代表x-zero,y代表y-zero;根据三数相加为0,可知(z-zero)+(x-zero)+(y-zero)=0,合并同类项最后得z = 3*zero -x -y;那么游标的位置我们就求出来了,不过此时还没有完结,毕竟不都是{-1,0,1}这种情况,还有{-1,-1,2}这种情况,当两数相同的时候,还需要判断count[游标]是否大于1;另外因为x与y都是从左右两端遍历过来的,所以z小于x或者大于y的情况必定在遍历过程中已经被存储,所以,只需要判断z大于x且z小于y的情况就能保证数组的唯一性。代码如下

for(int x=0;x<zero;x=goright(x,count,zero){ 
for(int y=count.length-1;y>zero;y=goleft(y,count,zero){ 
int z = 3*zero-x-y; 
if(z==x&&count[z]>1||z==y&&count[z]>1||z>x&&z<y&&count[z]>0) list.add(new int[]{z-zero,x-zero,y-zero});
} 
}

综上这个题算是竣工了,简简单单一个题,想起来却错综复杂,每一处都是惊险万分,下面附上完整代码
public class Solution {
	public List<List<Integer>> threeSum(int[] nums) {
	    List list = new ArrayList<>();
		int len = nums.length;
		if (len <= 2)
			return list;
		Arrays.sort(nums);
		if (nums[0] > 0 || nums[len - 1] < 0)
			return list;
		int[] count = new int[nums[len - 1] - nums[0] + 1];
		for (int i = 0; i < nums.length; i++) {
			count[nums[i] - nums[0]]++;
		}
		int zero = -nums[0];
		if (count[zero] >= 3)
			list.add(new int[] { 0, 0, 0 });
		for (int x = 0; x < zero; x = goright(x, count, zero)) {
			for (int y = count.length - 1; y > zero; y = goleft(y, count, zero)) {
				int z = 3 * zero - x - y;
				if(z==x&&count[z]>1||z==y&&count[z]>1||z>x&&z<y&&count[z]>0){
					list.add(new int[]{z-zero,x-zero,y-zero});
				}
			}
		}
		return list;
	}

	public int goright(int x, int[] count, int zero) {
		x++;
		while (count[x] == 0&&x<zero) {
			x++;
		}
		return x;
	}

	public int goleft(int y, int[] count, int zero) {
		y--;
		while (count[y] == 0&&y>zero) {
			y--;
		}
		return y;
	}
}

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值