每日两道力扣,day7

每日两道力扣,day7

每日两道力扣,day7

每日两道力扣,今天是:

18. 四数之和 - 力扣(LeetCode)

42. 接雨水 - 力扣(LeetCode)

第一题:四数之和

18. 四数之和 - 力扣(LeetCode)

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版)

42. 接雨水 - 力扣(LeetCode)

1.思路:

联想一下前面盛最多水的容器,和木桶效应。我们可能会讲这个题也看成一个个桶,然后去累加这个桶的容积,最后得到答案。可事实是,这完全是个错误思路,我们无法找到规律求出桶的容积。

以下是小编的错误思路的bug以及破局方法:

"横向大桶法"与消失的 m m m

  • 初次直觉: 试图模仿"盛最多水的容器",找到左右两个边界组成一个"大桶",算出大桶的矩形总面积,再减去中间所有柱子的体积 m m m。

  • 致命陷阱: 只要地形稍微复杂(中间有高低不平的柱子,甚至出现了比左右边界还要高的山峰),这个"大桶"就会被内部的地形拦腰截断。寻找"合格的大桶"以及"计算区间内 m m m"的边界条件会呈指数级增长,最终让你陷入逻辑死结。

  • 破局心法: 放弃横向看面,改为纵向看线!

    只盯住宽度为 1 的每一列柱子,它头顶的积水量只受一个绝对公式控制:

    单列积水 = min(左边绝对最高点, 右边绝对最高点) - 当前柱子高度


其实这道题有两个解法,dp和双指针。咱们先讲dp

(1)标准方法:动态规划(DP)------ 空间换时间

基于上面的纵向公式,DP 采取的是最稳妥的"记账"策略。

  • 核心思想: 既然每一列都要回头找自己的"左边最高"和"右边最高",为了避免重复找(导致超时),我不如提前花点时间,把所有人的左右最高点都查清楚,写在备忘录里。

  • 执行三步曲:

    1. 准备备忘录: 开辟两个长度为 N N N​​ 的数组 left_maxright_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])
    2. 拿着账本收网: 最后遍历一次数组,直接套公式 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"。
  • 执行秘诀(谁矮听谁的):
    1. 如果 l_max < r_max:说明对于左边的指针 left 来说,它的**"短板"已经绝对确定在了左边**(不管右边中间还藏着什么神仙山峰,水都会从左边漏走)。所以直接算 left 头顶的水,然后 left++
    2. 如果 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版的接雨水,字节之前很喜欢考这个题。

好了,今天的每日两道力扣到这里就算是结束了,看完是不是感觉有所收获呢?如果学有所获的话,麻烦给个三连支持一下呗。感谢观看,您的支持,将是我前进路上的重要动力。

相关推荐
sz66cm3 小时前
算法基础 -- Kahn 算法简介(C语言版本)
c语言·算法
学嵌入式的小杨同学3 小时前
STM32 进阶封神之路(四十)FreeRTOS 队列、信号量、互斥锁精讲|任务通信、同步、资源保护(超详细图文版)
c++·stm32·单片机·嵌入式硬件·mcu·架构·硬件架构
2401_892070981 天前
【Linux C++ 日志系统实战】LogFile 日志文件管理核心:滚动策略、线程安全与方法全解析
linux·c++·日志系统·日志滚动
yuzhuanhei1 天前
Visual Studio 配置C++opencv
c++·学习·visual studio
小O的算法实验室1 天前
2026年ASOC,基于深度强化学习的无人机三维复杂环境分层自适应导航规划方法,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
‎ദ്ദിᵔ.˛.ᵔ₎1 天前
LIST 的相关知识
数据结构·list
不爱吃炸鸡柳1 天前
C++ STL list 超详细解析:从接口使用到模拟实现
开发语言·c++·list
M--Y1 天前
Redis常用数据类型
数据结构·数据库·redis
十五年专注C++开发1 天前
RTTR: 一款MIT 协议开源的 C++ 运行时反射库
开发语言·c++·反射