15.三数之和
给你一个整数数组 nums
,判断是否存在三元组 [nums[i], nums[j], nums[k]]
满足 i != j
、i != k
且 j != k
,同时还满足 nums[i] + nums[j] + nums[k] == 0
。请
你返回所有和为 0
且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入:nums = [-1,0,1,2,-1,-4] 输出:[[-1,-1,2],[-1,0,1]] 解释: nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。 nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。 nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。 不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。 注意,输出的顺序和三元组的顺序并不重要。
思路:参考之前的博客,注意一下双指针收缩时机代码随想录算法训练营第六天|454. 四数相加 II,383. 赎金信,第15题. 三数之和,第18题. 四数之和-CSDN博客
代码:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++){
if(nums[i]>0){
return result;
}
if(i>0&&nums[i]==nums[i-1]){
continue;
}
int left=i+1;
int right=nums.size()-1;
while(right>left){
if(nums[i]+nums[left]+nums[right]>0){
right--;
}else if(nums[i]+nums[left]+nums[right]<0){
left++;
}else{
result.push_back(vector<int>{nums[i],nums[left],nums[right]});
//收缩双指针了
while(right>left&&nums[right]==nums[right-1]) right--;
while(right>left&&nums[left]==nums[left+1]) left++;
//最后向里收缩,作为下一组合的起点
right--;
left++;
}
}
}
return result;
}
};
42.接雨水
给定 n
个非负整数表示每个宽度为 1
的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
示例 1:
输入:height = [0,1,0,2,1,0,1,3,2,1,2,1] 输出:6 解释:上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。
力扣上面官方解答有些看不懂,我自己捋一遍。。。。(仅供参考)
思路:
一、动态规划解法
一个位置能不能接着雨水取决于它左边高度和右边高度中最小的高度(和木桶理论一样,请牢记这点)
①先考虑左边高度,我们假设一个右边高度永远都比左边高度大,再去填雨水,就会得到下图
②先考虑右边高度,我们假设一个左边高度永远都比右边高度大,再去填雨水,就会得到下图
显然以上两个图的结果不是真正的结果,只需要两个结果叠加答案就出来了,如下图
这里就有两个疑问:
一、为什么要分开考虑
我们在考虑每个位置的接水量时要同时考虑左右高度限制,但是某个位置的接水量是不是严格由它相邻的左右高度确定的?答案显然是否定的。例如下图,①位置是,②位置不是。既然有时候是由相邻决定,有时候又不是,我们干脆就只考虑一边,之和再叠加。
二、叠加的时候,对于每一个位置的接水是怎么确定的
通过叠加图我们会发现,实际上每个位置的左最大高度和右最大高度都确定了(这里最大高度不是相邻最大高度,而是遍历出来的最大高度),我们只要考虑最小的高度就可以确定接水量。
以上都是动态规划的解法,下面引出双指针的解法。
二、双指针解法
动态规划先左右遍历确定每个位置的左最大高度和右最大高度了,这一步可以转换成双指针。实际上我们只关注左右高度的最小值,并不关注最大值具体是多少,所以没必要左右都遍历出来再去叠加,只要确定当前位置最小高度,就可以用最小高度减去height[i]就可以得到当前位置接的雨水量。
下面我们按步骤分析,双指针从0开始和从末端开始,leftmax代表左高度,rightmax代表右高度(不是相邻的左右高度,是当前遍历出来的),num表示接水量。
height = [0,1,0,2,1,0,1,3,2,1,2,1]
步骤一:
i=left时,leftmax=0,rightmax=1,num=leftmax-height[left]=0;
第一个疑问:动态规划里righmax=3,这里rightmax=1,为什么可以不一样?
答:我们已经说过了,我们只关注左右高度的最小值,并不关注最大值具体是多少,请牢记这点,righmax=3或者=1对于leftmax=0本质上没有区别。
第二个疑问:接水量算出来之后,左右指针移动谁?
答:首先明白移动指针是为干嘛,目的有两个:确定height[i](也就是哪个位置去接雨水)和更新左右高度,(更新左右高度是在移动指针后更新,所以不是移动依据)考虑到雨水肯定是先填满最低的,所以height[left]和height[right]谁小,就移动哪个指针。
步骤二:
移动了左指针,此时,leftmax=rightmax,
第一个疑问:遇到这种情况那我们是计算height[left]还是height[right]呢?
答:都可以,只要保证遇到这种情况处理方式是一样的就可以。
我们这里就统一计算height[right], i=right时,leftmax=1,rightmax=1,num=rightmax-height[right]=0;
第二个疑问:左右指针移动谁?
答:也是都可以,只要保证遇到这种情况处理方式是一样的就可以。我们这里统一移动右指针。
步骤三:
移动右指针(上一步确定了),此时height[left]位置的leftmax=1小于rightmax=2(我们只关注左右高度的最小值,并不关注最大值具体是多少),所以 i=left时,leftmax=1,rightmax=2,num=leftmax-height[left]=0。
步骤四:
移动左值针,i=left时,leftmax=1(注意这里leftmax没有变,是从上一步顺延过来的),rightmax=2,num=leftmax-height[left]=1。
分析到这里,我们已经总结出规律了,后面步骤可以省略了。
代码:
class Solution {
public:
int trap(vector<int>& height) {
int num=0;
int left=0,right=height.size()-1;
int leftmax=0,rightmax=0;
while(left<right){
leftmax=max(leftmax,height[left]);//更新当前左高度
rightmax=max(rightmax,height[right]);//更新当前右高度
if(height[left]<height[right]){//雨水先填满最低的
num+=leftmax-height[left];//计算接雨量
++left;//移动指针
}else{
num+=rightmax-height[right];
--right;
}
}
return num;
}
};
以上是本人简单的分析,有错误还请指出。