一周总结
不知不觉记录力扣已经一周了,昨晚看到一篇文章,文章主要想告诉我们【表达输出>知识输入】!我们在看书,写题的过程中,一遍又一遍,一本又一本,每天都会觉得自己吸收了很多知识,但是当需要我们去发表观点的时候,我们常常脑袋里是一片空白,或者说模模糊糊,总感觉想表达的观点是这样子,但具体是什么又想不起来,这就是因为我们往往更看重知识输入而忽略了表达输出! 渐渐地,我们习惯了这种学习模式,我们的表达能力也会变得越来越差。那么怎么做到表达输出这种思维模式呢,那就是总结+表达。
我回想了自己最开始记录力扣的初衷,就是为了记录自己的解题过程,就算是自己不会的题目也要用自己的语言去表达出来,但是后面的几天我感觉我为了应付自己的每日两题而去直接复制一些题解的表达,那么这也就照成了我其实看上去是做了这道题目,但是题目的解法核心我是似懂非懂的,所以我又回去重新看了那几篇很潦草的博客,我发现我已经有一点忘记我当时是怎么解这道题目了。看了昨天那篇文章之后我算是醍醐灌顶,之后我的题解我都会用自己的语言表达出来,就像是我昨天的题解一样,就是因为我最近一直在加强线性代数所以我看到纵向扫描的时候,我就会立马想到字符串的转置去表达它,所以我觉得如果能保持训练你的表达输出这种能力,那么时间长了,你的大脑也会养成这种习惯!!继续加油吧。今天只看一道新题目,我会重新回顾自己前一星期的题解巩固一下。
2021.5.10
题目:
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
难度:中等
力扣地址:https://leetcode-cn.com/problems/3sum/
解题思路:
解法一:暴力破解
最开始我们肯定想到的是三层for循环列举每个数相加,但是因为题目还有一个关键点就是不可包含重复的三元组,所以三层for循环里面还需要一个函数去判断是否重复,那么加起来就是时间复杂度O(n^4),显然肯定是超时的,所以这种解法需要优化!!
解法二:双指针法
三层for循环我们怎么去优化它呢?
- 首先我们来说明一下不重复的本质是什么?我们在保持三重循环不变的情况下,只要满足第二重循环的数b不小于当前第一重循环的a,第三重循环的数c不小于当前第二重循环的b。即a<=b<=c,也就是保证只有(a,b,c)这个顺序会被枚举到,而不会出现多余的(b,a,c,),(c,b,a)等等。为了便于实现这一点,我们可以将数组从小到大排序,然后再使用三层for循环。
- 去重优化完后我们考虑优化三重循环。首先我们可以先确定第一个数a,然后满足条件的另外两个数b和c则有,b+c=0-a=-a。到这里我们就把所求三个数的和变成求两数之和!(是不是听起来有点熟悉,没错,这就是力扣的第一道题两数之和https://leetcode-cn.com/problems/two-sum/。但是我们这道题和它还是有点区别,因为力扣的这道题是只求一个结果,而我们这里是求所有结果,但这还不是问题关键,关键是这道题需要去重!!)
- 确定第一个元素时,如果它已经比0大,那么可以直接跳出循环,因为数组是排序好,所有后面的数肯定都大于0,所以a+b+c不可能等于0
- 确定第一个元素时,如果它与它前面的值一样,那么就会产生重复数组,所以需要排除这种情况。比如: nums [-1, -1, 0, 1], 在第一轮后,已经选出了 {-1, 0, 1}, 现在 i = 1,nums[i] == nums[i - 1], 为了避免重复,排除这种情况。
- 接下来确定第二个元素b和第三个元素c,我们可以发现,a+b+c=0已经满足要求,如果b变大,那么还想继续满足a+b+c=0,c必然得减小。也就是说我们可以从小到大枚举b(意味着最开始位于数组左边),同时从大到小枚举c(意味着最开始位于数组右边),即第二重循环和第三重循环实际上是并列的关系。所以显然我们可以用双指针法分别指向b和c,请记住,当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法。
- 左指针left指向i+1,右指针right执向n(数组长度)-1。同时注意一定要去重,方法和第一个元素去重是一样的,判断它与它前面的值一样是否一样,是的话就跳过直到不是为止
代码:
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> ans=new ArrayList<>();
if(nums==null || nums.length<=2) return ans;
Arrays.sort(nums);//1、排序
//2、第一层循环
for(int i=0;i<nums.length;i++){
if(nums[i]>0) //第一个数大于0意味着后面的数都大于0
return ans;
if(i==0 || (i>0 && nums[i]!=nums[i-1])){ //2.1 去掉重复情况
int left=i+1,right=nums.length-1;
int target=-nums[i];//也就是-a
while(left<right){
if(nums[left] + nums[right]<target)
left++;
else if(nums[left]+nums[right]>target)
right--;
else{
List<Integer> list=new ArrayList<>();
list.add(nums[i]);
list.add(nums[left]);
list.add(nums[right]);
ans.add(list);
//前面这四句话可以用 ans.add(new ArrayList<>(Arrays.asList(nums[i], nums[left], nums[right])));替代
left++;right--; //找到一个目标三元组之后,左右指针同时移动寻找下一个三元组。这里不可能只移动一个指针,比方说只移动右指针,那么a和b还是和之前的一样,那么能满足条件的c也就跟之前的一样,所以会出现重复!!
//这里需要排除b和c的重复,比方说nums=[-3,-1,-1,4],i=0,left=1,right=3,满足条件,即[-3,-1,4]加入数组后,需要排除重复的-1.
while(left<right && nums[left] == nums[left-1])
left++;
while(left<right && nums[right] == nums[right+1])
right--;
}
}
}
}
return ans;
}
}