每日两道力扣,day7
每日两道力扣,day7

每日两道力扣,今天是:
第一题:四数之和


1.思路:
还记得我们昨天写的三数之和吗?那个题是先排序,再用一个for循环里面嵌套一个双指针,时间复杂度为O(N2),for循环控制外围的num[i],双指针控制里层的nums[left],nums[right]。但是三数之和的target是固定的,为0。咱们这题的target是不固定的,不过不要紧啦,有小编在,我带你速通四数之和。
我们先分析暴力的可行性,昨天的三数之和,我们暴力求解得用三个for循环,时间复杂度为O(N3),已经是一个很没意义的算法了。倘若四数之和也走暴力for循环老路,那绝对是不可行的。因此我们就需要寻找其他解法。
既然两数之和,三数之和,咱们都能用排序+双指针,解决,那么我们四数之和也当然可以用排序+双指针解决啦。
步骤如下:
(1)先对数组排序,sort
(2)写一个for循环用来控制外围的nums[i],再到这个for循环里面嵌套一个for循环,用来控制中层的nums[j],最后再到中层循环里面写上咱们的双指针算法。
(3)大体优化细节与三数之和一模一样。对num[i],nums[j],双指针内的nums[left],nums[right]进行优化,只要遇见重复的答案就移动一步。
(4)需要注意的是,这里的数据量很大。我们必须定义一个long long 类型的temp,同时算式应为这样:
temp = (long long) target - nums[i] - nums[j];
temp,作为中间量,来决定双指针内部的移动逻辑。
2.代码实现:
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>>ret;
int n = nums.size();
long long temp = 0;
sort(nums.begin(),nums.end());
for(int i = 0;i < n; )
{
for(int j = i + 1;j < n; )
{
temp = (long long) target - nums[i] - nums[j];
int left = j + 1,right = n - 1;
while(left < right)
{
if(nums[left] + nums[right] > temp)
right--;
else if(nums[left] + nums[right] < temp)
left++;
else
{
ret.push_back({nums[i],nums[j],nums[left],nums[right]});
left++;
right--;
while(left < right && nums[left] == nums[left-1])
left++;
while(left < right && nums[right] == nums[right+1])
right--;
}
}
j++;
while(j < n && nums[j] == nums[j-1])
j++;
}
i++;
while(i < n && nums[i] == nums[i-1])
i++;
}
return ret;
}
};
3.细节优化:

需要注意的是,这里的数据量很大。我们必须定义一个long long 类型的temp,同时算式应为这样:
temp = (long long) target - nums[i] - nums[j];
temp,作为中间量,来决定双指针内部的移动逻辑。
第二题:接雨水(2d版)


1.思路:
联想一下前面盛最多水的容器,和木桶效应。我们可能会讲这个题也看成一个个桶,然后去累加这个桶的容积,最后得到答案。可事实是,这完全是个错误思路,我们无法找到规律求出桶的容积。
以下是小编的错误思路的bug以及破局方法:
"横向大桶法"与消失的 m m m
-
初次直觉: 试图模仿"盛最多水的容器",找到左右两个边界组成一个"大桶",算出大桶的矩形总面积,再减去中间所有柱子的体积 m m m。
-
致命陷阱: 只要地形稍微复杂(中间有高低不平的柱子,甚至出现了比左右边界还要高的山峰),这个"大桶"就会被内部的地形拦腰截断。寻找"合格的大桶"以及"计算区间内 m m m"的边界条件会呈指数级增长,最终让你陷入逻辑死结。
-
破局心法: 放弃横向看面,改为纵向看线!
只盯住宽度为 1 的每一列柱子,它头顶的积水量只受一个绝对公式控制:
单列积水 = min(左边绝对最高点, 右边绝对最高点) - 当前柱子高度
其实这道题有两个解法,dp和双指针。咱们先讲dp
(1)标准方法:动态规划(DP)------ 空间换时间
基于上面的纵向公式,DP 采取的是最稳妥的"记账"策略。
-
核心思想: 既然每一列都要回头找自己的"左边最高"和"右边最高",为了避免重复找(导致超时),我不如提前花点时间,把所有人的左右最高点都查清楚,写在备忘录里。
-
执行三步曲:
-
准备备忘录: 开辟两个长度为 N N N 的数组
left_max和right_max。状态转移(填表):
求
left_max:你要知道第 5 列左边最高的柱子,需要从第 0 列扫描到第 5 列吗?不需要! 你只需要问第 4 列:"喂,你左边最高的是多少?" 然后拿它的答案,跟第 5 列自己的高度比一下,较大的那个就是第 5 列的左侧最高点。 这就是代码中的:left_max[i] = max(left_max[i - 1], height[i]);求
right_max:同理,从右往左扫一遍。第 5 列右边最高,等于第 6 列右边最高和第 5 列自己高度的较大值。 代码:right_max[i] = max(right_max[i + 1], height[i]);- 从左往右扫一遍:
left_max[i] = max(left_max[i-1], height[i])。 - 从右往左扫一遍:
right_max[i] = max(right_max[i+1], height[i])。
- 从左往右扫一遍:
-
拿着账本收网: 最后遍历一次数组,直接套公式
min(left_max[i], right_max[i]) - height[i],累加水量即可。
-
-
代价: 时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( N ) O(N) O(N)(需要额外开辟数组)。
(2)巅峰操作:双指针 ------ DP 的终极空间压缩
双指针是建立在 DP 逻辑之上的一次完美优化。它发现:既然木桶效应永远是**"短板决定水位"**,那我根本没必要记住所有的极值!
- 核心思想: 让两个指针从首尾向中间夹击。不记录全局,只维护当前双方走过的"左边界极值
l_max"和"右边界极值r_max"。 - 执行秘诀(谁矮听谁的):
- 如果
l_max < r_max:说明对于左边的指针left来说,它的**"短板"已经绝对确定在了左边**(不管右边中间还藏着什么神仙山峰,水都会从左边漏走)。所以直接算left头顶的水,然后left++。 - 如果
l_max >= r_max:同理,右边指针right的短板已经确定在了右侧,直接算right头顶的水,然后right--。
- 如果
- 代价: 时间复杂度 O ( N ) O(N) O(N),空间复杂度 O ( 1 ) O(1) O(1)(全程只需两个指针和两个极值变量,内存消耗几乎为零)。
2.代码实现:
(1)方法一:(dp)
#include <vector>
#include <algorithm>
using namespace std;
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
if (n == 0) return 0;
// 1. 定义动态规划的"备忘录"数组
vector<int> left_max(n); // left_max[i] 记录第 i 列左边(含自己)的最高柱子
vector<int> right_max(n); // right_max[i] 记录第 i 列右边(含自己)的最高柱子
// 2. 从左往右遍历,填满 left_max 数组
left_max[0] = height[0];
for (int i = 1; i < n; i++) {
// 状态转移方程:当前位置的左边最高 = max(前一个位置的左边最高, 当前柱子的高度)
left_max[i] = max(left_max[i - 1], height[i]);
}
// 3. 从右往左遍历,填满 right_max 数组
right_max[n - 1] = height[n - 1];
for (int i = n - 2; i >= 0; i--) {
// 状态转移方程:当前位置的右边最高 = max(后一个位置的右边最高, 当前柱子的高度)
right_max[i] = max(right_max[i + 1], height[i]);
}
// 4. 最后遍历一次,计算总水量
int sum = 0;
for (int i = 0; i < n; i++) {
// 核心公式依然是木桶效应: min(左边最高, 右边最高) - 当前高度
sum += min(left_max[i], right_max[i]) - height[i];
}
return sum;
}
};
(2)方法二:(双指针)
class Solution {
public:
int trap(vector<int>& height) {
int n = height.size();
int left = 0,right = n - 1;
int sum = 0;
int l_max = 0,r_max = 0;
while(left < right)
{
l_max = max(l_max,height[left]);
r_max = max(r_max,height[right]);
if(l_max < r_max)
{
sum += l_max - height[left];
left++;
}
else if(l_max >= r_max)
{
sum += r_max - height[right];
right--;
}
}
return sum;
}
};
3.细节优化:
我们这个题数据要求不是很大,但这个题注重思维。面试的时候,既要会标准的dp解法,更要会巅峰解法------双指针,这样才能展现你的基本功,以及突出你的思维深度。
这个其实是2d版的接雨水,等我们把hot100更新完,我们再更新3d版的接雨水,字节之前很喜欢考这个题。
好了,今天的每日两道力扣到这里就算是结束了,看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。