本文用于个人复习,记录3.20和3.21做过的一些力扣的思考~
文章目录
-
- 对撞双指针系列
- [[167. 两数之和 II - 输入有序数组 - 力扣(LeetCode)](https://leetcode.cn/problems/two-sum-ii-input-array-is-sorted/description/)](#167. 两数之和 II - 输入有序数组 - 力扣(LeetCode))
- [[15. 三数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/3sum/description/)](#15. 三数之和 - 力扣(LeetCode))
- [[16. 最接近的三数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/3sum-closest/)](#16. 最接近的三数之和 - 力扣(LeetCode))
- [[18. 四数之和 - 力扣(LeetCode)](https://leetcode.cn/problems/4sum/description/)](#18. 四数之和 - 力扣(LeetCode))
- [[2824. 统计和小于目标的下标对数目 - 力扣(LeetCode)](https://leetcode.cn/problems/count-pairs-whose-sum-is-less-than-target/)](#2824. 统计和小于目标的下标对数目 - 力扣(LeetCode))
- [[611. 有效三角形的个数 - 力扣(LeetCode)](https://leetcode.cn/problems/valid-triangle-number/description/)](#611. 有效三角形的个数 - 力扣(LeetCode))
- [[11. 盛最多水的容器 - 力扣(LeetCode)](https://leetcode.cn/problems/container-with-most-water/description/)](#11. 盛最多水的容器 - 力扣(LeetCode))
- [[42. 接雨水 - 力扣(LeetCode)](https://leetcode.cn/problems/trapping-rain-water/description/)](#42. 接雨水 - 力扣(LeetCode))
- [[2105. 给植物浇水 II - 力扣(LeetCode)](https://leetcode.cn/problems/watering-plants-ii/description/)](#2105. 给植物浇水 II - 力扣(LeetCode))
- 我对对撞双指针的一些理解
- 滑动窗口系列
-
- [[209. 长度最小的子数组 - 力扣(LeetCode)](https://leetcode.cn/problems/minimum-size-subarray-sum/description/)](#209. 长度最小的子数组 - 力扣(LeetCode))
- [[3. 无重复字符的最长子串 - 力扣(LeetCode)](https://leetcode.cn/problems/longest-substring-without-repeating-characters/description/)](#3. 无重复字符的最长子串 - 力扣(LeetCode))
- [[713. 乘积小于 K 的子数组 - 力扣(LeetCode)](https://leetcode.cn/problems/subarray-product-less-than-k/description/)](#713. 乘积小于 K 的子数组 - 力扣(LeetCode))
- [[2958. 最多 K 个重复元素的最长子数组 - 力扣(LeetCode)](https://leetcode.cn/problems/length-of-longest-subarray-with-at-most-k-frequency/description/)](#2958. 最多 K 个重复元素的最长子数组 - 力扣(LeetCode))
- [[1004. 最大连续1的个数 III - 力扣(LeetCode)](https://leetcode.cn/problems/max-consecutive-ones-iii/submissions/710229690/)](#1004. 最大连续1的个数 III - 力扣(LeetCode))
- [[1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode)](https://leetcode.cn/problems/minimum-operations-to-reduce-x-to-zero/description/)](#1658. 将 x 减到 0 的最小操作数 - 力扣(LeetCode))
- [[2302. 统计得分小于 K 的子数组数目 - 力扣(LeetCode)](https://leetcode.cn/problems/count-subarrays-with-score-less-than-k/description/)](#2302. 统计得分小于 K 的子数组数目 - 力扣(LeetCode))
- 我对滑动窗口的理解
- 二分搜索
-
- [[34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode)](https://leetcode.cn/problems/find-first-and-last-position-of-element-in-sorted-array/description/)](#34. 在排序数组中查找元素的第一个和最后一个位置 - 力扣(LeetCode))
- 我对二分的理解
- 栈
-
- [[1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode)](https://leetcode.cn/problems/remove-all-adjacent-duplicates-in-string/description/)](#1047. 删除字符串中的所有相邻重复项 - 力扣(LeetCode))
- [[844. 比较含退格的字符串 - 力扣(LeetCode)](https://leetcode.cn/problems/backspace-string-compare/description/)](#844. 比较含退格的字符串 - 力扣(LeetCode))
- [[227. 基本计算器 II - 力扣(LeetCode)](https://leetcode.cn/problems/basic-calculator-ii/)](#227. 基本计算器 II - 力扣(LeetCode))
- [[394. 字符串解码 - 力扣(LeetCode)](https://leetcode.cn/problems/decode-string/description/)](#394. 字符串解码 - 力扣(LeetCode))
- [[946. 验证栈序列 - 力扣(LeetCode)](https://leetcode.cn/problems/validate-stack-sequences/description/)](#946. 验证栈序列 - 力扣(LeetCode))
- [[739. 每日温度 - 力扣(LeetCode)](https://leetcode.cn/problems/daily-temperatures/description/)](#739. 每日温度 - 力扣(LeetCode))
- [[496. 下一个更大元素 I - 力扣(LeetCode)](https://leetcode.cn/problems/next-greater-element-i/description/)](#496. 下一个更大元素 I - 力扣(LeetCode))
- [[503. 下一个更大元素 II - 力扣(LeetCode)](https://leetcode.cn/problems/next-greater-element-ii/description/)](#503. 下一个更大元素 II - 力扣(LeetCode))
- [[1475. 商品折扣后的最终价格 - 力扣(LeetCode)](https://leetcode.cn/problems/final-prices-with-a-special-discount-in-a-shop/description/)](#1475. 商品折扣后的最终价格 - 力扣(LeetCode))
- 我对栈和单调栈的一些理解
- 单调栈的理解
- 链表
-
- [[206. 反转链表 - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list/description/)](#206. 反转链表 - 力扣(LeetCode))
- [[92. 反转链表 II - 力扣(LeetCode)](https://leetcode.cn/problems/reverse-linked-list-ii/description/)](#92. 反转链表 II - 力扣(LeetCode))
- [[21. 合并两个有序链表 - 力扣(LeetCode)](https://leetcode.cn/problems/merge-two-sorted-lists/description/)](#21. 合并两个有序链表 - 力扣(LeetCode))
- [[141. 环形链表 - 力扣(LeetCode)](https://leetcode.cn/problems/linked-list-cycle/description/) [142. 环形链表 II - 力扣(LeetCode)](https://leetcode.cn/problems/linked-list-cycle-ii/)](#141. 环形链表 - 力扣(LeetCode) 142. 环形链表 II - 力扣(LeetCode))
- [[24. 两两交换链表中的节点 - 力扣(LeetCode)](https://leetcode.cn/problems/swap-nodes-in-pairs/description/)](#24. 两两交换链表中的节点 - 力扣(LeetCode))
- 我对链表的理解
对撞双指针系列
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结束时后面不足两个节点