记录3.20和3.21做过的一些力扣的思考

本文用于个人复习,记录3.20和3.21做过的一些力扣的思考~

文章目录

对撞双指针系列

167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)

利用题目非递减顺序排列的性质:

注意返回值的坑点:索引+1

15. 三数之和 - 力扣(LeetCode)

先排序!

16. 最接近的三数之和 - 力扣(LeetCode)

先排序!

和三数之和一个写法

不断循环更新最小差值,目前的差值是三数之和-target 的值,而最后返回的是target+最小差值--->最接近三数之和

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

先排序!

枚举两个数,左右指针控制剩下两个数,同样的控制枚举两个数的重复值,控制答案的重复值,之后同时收缩指针,要注意四数之和可能int越界,用long long

2824. 统计和小于目标的下标对数目 - 力扣(LeetCode)

先排序!

小于目标值,整个区间都成立

611. 有效三角形的个数 - 力扣(LeetCode)

先排序!

固定好最大值,用最小和次大值判断,成立,整个区间都满足

11. 盛最多水的容器 - 力扣(LeetCode)

木桶效应

短板确定了之后,不管另一边的板多高,整个盛水能力都是由短板决定,所以要移动短板!

42. 接雨水 - 力扣(LeetCode)

竖着算接水量

任意i位置能承载水的多少,由两边短的那面墙决定:

即使两面墙不挨着也没关系,它周围还有水墙挡着

怎么确定两边的短板呢?

  • left向右遍历,right向左遍历
  • leftMax和rightMax记录当前最大值

两个最大值指针怎么移动呢?

移动较小的最大值指针:

小的指针短板上限已经确定,后续遇到再高的墙都是由短板决定;

而如果动大的指针,后续可能会遇到更高的墙,那么自己的短板就被抬高到大的指针了,所以大的指针那边的短板还不能确定

2105. 给植物浇水 II - 力扣(LeetCode)

纯场景模拟题,根据题目要求写代码就行,唯一注意的是,最后两个人相遇谁浇水的翻译:我只需要知道最后一次到底要不要取水,那么目前要浇水量大于二者剩余水量最大值,必定要取水!

我对对撞双指针的一些理解

对撞双指针建立在列表元素有序 的前提下,整个序列是单调的 ,用 O ( 1 ) O(1) O(1)的搜索获取 O ( n ) O(n) O(n)的信息量,如果暴力搜索,那么就没有利用好单调性

题目明确序列有序/没排序但允许排序且不关注源序列索引,寻找和/差对

这就赋予了序列方向感:左指针往右一定变大,右指针往左一定变小

题目明确了物理短板的限制,需要求最值(容量/面积)

距离/宽度在不断缩小,想要容积变大,只能指望短板变长永远抛弃当前的短板,去赌会有更长的短板!

滑动窗口系列

209. 长度最小的子数组 - 力扣(LeetCode)

求和,右指针向右不断扩大窗口,和>=目标值是循环满足条件,现在需要找到目前满足条件的最短子数组,答案在循环里更新

3. 无重复字符的最长子串 - 力扣(LeetCode)

题目明确字符种类,用数组模拟哈希表,128位够覆盖ASCII了

哈希对应值++,向右扩大窗口,不符合条件了,循环不断缩小窗口,哈希值--,

最长,窗口外才是满足题意的,窗口外更新答案

713. 乘积小于 K 的子数组 - 力扣(LeetCode)

先判断边界:k<=1,返回0

只要右窗口乘积进入,满足条件,整个区间内都是满足条件的

在循环里更新答案,更新完答案后缩小窗口,这两步都在循环里完成,因为是要统计数目!

2958. 最多 K 个重复元素的最长子数组 - 力扣(LeetCode)

用一个哈希表存数字出现次数

右窗口扩张,哈希值++,当哈希值超过k时,缩小左窗口

最长循环外更新

1004. 最大连续1的个数 III - 力扣(LeetCode)

正难则反

我要找反转后1的最长字串,那么就是找反转前,在k限定下的最长子串(包含0和1)

cnt记录反转次数,右窗口扩张,遇到0,cnt++,直到cnt>k,这就是循环条件,开始出窗口了,遇到0,cnt--,缩小窗口

记录最长,循环外更新答案

1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)

每次只能从首尾移除,那么中间数组就是连续子数组,求的是最小操作次数,反过来就是求最长连续子数组,并且保证子数组的和为数组总和-x

还是要先考虑边界,数组总和-x < 0 说明全部移除x都到不了0

问题转换成功寻找和为数组总和 - x的最长连续子数组

这里更新答案需要注意,一定要是连续子数组和 == 数组总和 - x才能更新!,条件严格

最后返回的答案是数组长度-更新的最终答案

2302. 统计得分小于 K 的子数组数目 - 力扣(LeetCode)

坑点是答案要用long long存,防止溢出

看清楚题意,元素和 × 元素个数 元素和\times元素个数 元素和×元素个数

扩张右窗口只用加元素即可,判断条件用** 和 × 元素数量 和\times元素数量 和×元素数量**判断,然后缩小左窗口,循环外才是满足条件的,循环外更新数量

我对滑动窗口的理解

滑动窗口利用了状态的单调性,在连续的物理空间进行试探性的扩张的缩小 ,就像毛毛虫一样蠕动,虽然两层循环嵌套,但是只是用一个区间不回头进行一次遍历,是 O ( n ) O(n) O(n)

用一个区间维护合法的情况,右边不断寻找合法情况,左边不断排除非法情况,对于结果更新,最长放到循环外,最短放到循环里,更新数量要根据循环到底是合法还是非法考虑在外还是在内

题目中出现**连续子数组/子串,求最长/最短/总个数并且保证数组元素为正数,我们的操作对象就有单调性!**可以考虑滑动窗口

二分搜索

34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)

需求 写法 解释
>=x的第一个元素索引 lowerBound(nums,x)
>x的第一个元素索引 lowerBound(nums,x+1) 大于等于目标值+1
<x的最后一个元素索引 lowerBound(nums,x) - 1 大于等于目标值左边的那个位置
<=x的最后一个元素索引 lowerBound(nums,x+1)-1 大于等于目标值+1且左边的那个位置

我对二分的理解

现在固定一个函数模板:lowerBound (寻找第一个 >= x 的位置)

二分本质不是找数字,而是寻找特征突变的边界

我写的开区间(L,R)

  • 左指针L:永远指向红色区域(最后一个不合法位置)
  • 右指针R:永远指向蓝色区域(第一个合法位置)
  • L + 1 == R:两指针相遇,红蓝交界,R就是要找的答案
cpp 复制代码
int lowerBound(vector<int>& nums, int target) {
    int L = -1;             // L 永远在红色区域 (< target)
    int R = nums.size();    // R 永远在蓝色区域 (>= target)
    
    while (L + 1 < R) {     // 当 L 和 R 相邻时,界碑找到,循环结束!
        int M = L + (R - L) / 2;
        if (nums[M] >= target) {
            R = M;          // M 是蓝色,R 标记过来
        } else {
            L = M;          // M 是红色,L 标记过来
        }
    }
    return R; // R 永远指向第一个 >= target 的元素
}

再详细说明一下转化表:

业务需求 转化公式 物理画面(红蓝界碑) 说明
>= x 的第一个 lowerBound(nums, x) 直接找蓝色区域的第一个人 (R) if (R == n) 说明全比 x 小
> x 的第一个 lowerBound(nums, x + 1) 把蓝色条件变得更苛刻,找 >= x+1 的人 if (R == n) 说明全比 x 小或等于
< x 的最后一个 lowerBound(nums, x) - 1 >= x 第一个人的左边那个兄弟 (R - 1,其实就是 L!) if (R == 0) 说明全比 x 大或等于,前面没人了
<= x 的最后一个 lowerBound(nums, x + 1) - 1 > x 第一个人的左边那个兄弟 (R - 1,其实就是 L!) if (R == 0) 说明全比 x 大,前面没人了

1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)

用string模拟栈的行为:

  • 栈不为空且查找元素等于栈顶元素->出战
  • 否则入栈

844. 比较含退格的字符串 - 力扣(LeetCode)

可以先写一个字符变换的函数:

string模拟栈:

  • 字符不为#就入栈
  • 否则,在栈不为空的情况下出栈

最后调用函数比较两字符

227. 基本计算器 II - 力扣(LeetCode)

用数组模拟栈,char字符更新操作符(默认是+)

遍历字符串,分类讨论:

  • 当前字符为空,继续往后遍历
  • 当前字符是数字字符,提取数字
    • 要循环提取:tmp = tmp * 10 + (s[cur] - '0')
      • 当前运算符是+:数字入栈
      • 当前运算符是-:-数字入栈
      • 当前运算符是*或/:栈顶元素×÷数字
  • 改变当前字符

最后把栈内元素相加返回

394. 字符串解码 - 力扣(LeetCode)

一个字符串栈存字符串,一个数字栈存数字,字符串栈栈顶先要push一个空串

遍历字符串,分类讨论:

  • 数字字符:提取数字后入栈
    • 要循环提取:tmp = tmp * 10 + (s[cur] - '0')
  • 字符为[ : 提取[之后的完整字符后入栈
    • 要循环提取
  • 字符为]:解析字符串
    • 字符串栈和数字栈分别弹出栈顶元素
    • 根据数字个数,栈顶字符串不断尾插字符串
  • 字符为字符串:循环提取完整字符串后,字符串栈顶尾插

最后返回字符串栈顶

946. 验证栈序列 - 力扣(LeetCode)

用栈维护即可

遍历pushed数组不断入栈,如果栈不为空且栈顶元素和当前poped数组元素相同,出栈,继续往后遍历

最后看栈是否为空

739. 每日温度 - 力扣(LeetCode)

单调栈!

栈内存的是代办:现在没有找到下一个更大温度的索引!

一个数组存答案,一个栈存代办索引

遍历题目数组:

  • 栈不为空&&当前温度大于栈顶索引对应温度(循环)
    • 保存栈顶索引,出栈
    • 答案数组[栈顶索引]保存:当前索引-栈顶索引
  • 入栈索引

496. 下一个更大元素 I - 力扣(LeetCode)

用一个哈希表保存数组一值和索引的映射

用栈模拟数组二,答案数组初始化为数组一的大小,全为-1

循环遍历数组二:

  • 栈不为空&&当前元素大于栈顶元素(循环)
    • 答案数组对应的哈希映射值更新:ans[idx[st.top()]] = 值,栈里存的是值,哈希映射到索引
  • 如果哈希包含当前数组值,入栈

503. 下一个更大元素 II - 力扣(LeetCode)

循环数组,遍历两遍就好了

答案数组所有元素初始化为-1,栈存索引

循环遍历:int tmp = nums[i % n]保证不越界

  • 栈不为空&&tmp大于栈顶索引对应的值(循环)
    • 更新答案数组值后索引出栈
  • 当前索引小于数组大小,索引入栈

1475. 商品折扣后的最终价格 - 力扣(LeetCode)

这是找下一个更小的值

答案数组直接初始化成题目数组(后续找不到更小的值,就无需处理

栈存索引

遍历原数组:

  • 栈不为空&&当前价格小于等于栈顶索引对应价格(循环)
    • 栈顶索引答案数组更新二者差值,索引出栈
  • 索引入栈

我对栈和单调栈的一些理解

栈的理解

本质是有记忆功能的延迟结算系统

栈是先进后出 的记忆容器,遇到一个信息现在话不是处理的时候,必须等他后面的信息来了才能决定怎么处理

题目中遇到相邻消除->消消乐、退格/撤销操作 ,遇到嵌套、堆成、匹配属性的文本处理,可以考虑栈

只要当前操作,需要频繁回头去推翻或者结合历史发生的状态,用栈!

单调栈的理解

本质是自带末位淘汰的TODO list

栈内存的是代办,利用空间的单调性,每个元素进栈只有两个操作:要么清算(出栈),要么作为新代办

注意:栈里存索引!!!

题目中出现下一个 ,扩展成短语求左边/右边第一个比他更大/小的元素或距离

在连续的一维空间,寻找具有大小压制关系的最近边界

链表

206. 反转链表 - 力扣(LeetCode)

画图理清楚谁先谁后,最开始pre指向空,cur指向头,最后返回pre

注意:要保存cur的下一个节点,指针操作会丢失下一个节点!

92. 反转链表 II - 力扣(LeetCode)

可能会删除头部,那么就用个哨兵位节点

  • 找到反转区间前一个节点
  • 定义反转区间的开头
  • 反转操作

21. 合并两个有序链表 - 力扣(LeetCode)

创建一个哨兵位节点存拼接后的链表

直接同时遍历两个链表,小的节点尾插,总会有个链表先走完,后走完的直接尾插

最后返回

141. 环形链表 - 力扣(LeetCode) 142. 环形链表 II - 力扣(LeetCode)

思路:快慢指针,快指针会先进环,慢指针后进环

如果链表带环,快指针一定会和慢指针相遇,反之则不会

链表带环,fast走两步,slow走一步一定能追上吗?

fast先进环,slow后进环,假设slow进环时与fast距离为 N N N

每次运动,二者距离减一

这是二者距离变化:

所以fast走两步,slow一定能追上

链表带环,fast走三步,slow走一步一定能追上吗?

fast先进环,slow后进环,假设slow进环时与fast距离为 N N N

每次运动,二者距离减二

这是二者距离变化:

假设环长为 C C C,若 C − 1 C - 1 C−1为偶数,下一轮就追上了,若 C − 1 C - 1 C−1为奇数,则仍需分析

考虑一下:永远追不上的条件: C − 1 C - 1 C−1为奇数并且 N N N为奇数

这个条件能成立吗?

假设链表头到环的入口点距离为 L L L,环长 C C C,入口点到相遇点距离 X X X

此时fast走两步,slow走一步

相遇时:

slow 走了 L + X L+X L+X,

fast已经绕环 x x x圈,fast走了 L + x C + X L+xC+X L+xC+X

fast走两步,slow走一步,二者可以得到距离关系:

L + x C + X = 2 ( L + X ) L+xC+X= 2(L+X) L+xC+X=2(L+X)

化简可得:

L = ( x − 1 ) C + ( C − X ) L = (x-1)C + (C-X) L=(x−1)C+(C−X)

得到结论:一个指针从链表头开始走,一个指针从相遇点开始走,最终会在环的入口点相遇(两个指针走一样的步长)

所以,对于环形链表Ⅱ,先找相遇点,然后创建两个新的指针,一个从链表头开始,一个从相遇点开始,同时走,相遇的节点就是入口点

假设链表头到入口点距离 L L L,环长 C C C,slow进环时,距离fast为 N N N,此时fast已经走了 x x x圈,并多走了 C − N C-N C−N

根据数量关系可得:

3 L = L + x C + C − N 3L = L + xC + C-N 3L=L+xC+C−N

化简得:

2 L = ( x + 1 ) C − N 2L = (x+1)C - N 2L=(x+1)C−N

C − 1 C - 1 C−1为奇数,那么 C C C就是偶数,任意数乘以偶数还是偶数, N N N为奇数

根据等式: 偶数 = 偶数 - 奇数 这是个错误结论!所以我们之前的设想:永远追不上的猜测是错误的,fast走三步,slow走一步也可以追上!

24. 两两交换链表中的节点 - 力扣(LeetCode)

我对链表的理解

什么时候用哨兵位?

只要头节点会被修改/删除/替换

循环条件什么时候是cur?什么时候是cur->next?

  • cur结束时cur为空
  • cur->next结束停在最后一个非空节点
  • cur->next && cur->next->next结束时后面不足两个节点
相关推荐
原来是猿2 小时前
Linux-【ELF文件】
linux·运维·服务器
qq_358589612 小时前
sylar 配置系统
java·c++·算法
似水এ᭄往昔2 小时前
【Linux】--基础开发工具->gcc/g++
linux·运维·服务器
顶点多余2 小时前
Linux中库的制作和原理详解
linux·运维·服务器
ryrhhhh2 小时前
矩阵跃动自研技术:小陌GEO动态监测算法,30分钟快速适配大模型更新
人工智能·算法·矩阵
01二进制代码漫游日记2 小时前
动态顺序表的实现(修改)
数据结构·算法
佩奇大王2 小时前
P103 日期问题
java·开发语言·算法
feng_you_ying_li2 小时前
liunx指令的介绍(2)
linux·运维·服务器
计算机安禾2 小时前
【C语言程序设计】第38篇:链表数据结构(二):链表的插入与删除操作
c语言·开发语言·数据结构·c++·算法·链表