热题100学习笔记
热题100学习笔记
哈希
字母异位词分组
- 字母异位词分组:49.字母异位词分组
- 思路:这道题目通过长度为
26
的string
记录字母数量一样的字符串(由于unordered_map
要求Key
是可哈希的,因此Key
不能够存vector
类型),通过unordered_map
来找到属于相同字母异位词的字符串,最后将其合并到数组中。
- 代码:
1 |
|
双指针
移动零
- 移动零:283.移动零
- 思路:这道题目采用双指针的解法,首先定义
left
和right
同时指向0
位置,移动right
指针,当right
所指的元素不等于0
时,交换left
和right
所指的元素,并且left
右移一位。- 需要注意的是不需要保证
left
初始指向的是0
,left
如果初始指向的是非0
,那么left
和right
就会指向同一个位置,同一个位置互换等于没有操作,之后left
和right
指针一起往右边移动一位。只有当left
第一次值为0
的时候,right
就会和left
指针分离,直到找到一个非0
的数然后跟left
去进行交换。
- 需要注意的是不需要保证
- 代码:
1 |
|
滑动窗口
无重复字符的最长子串
- 无重复字符的最长子串:3.无重复字符的最长子串
- 思路:用两个指针
left
和right
表示当前考察的窗口区间[left, right)
。unordered_set<char>
用来存储窗口内的字符,保证窗口内不出现重复。(滑动窗口+哈希集合)的问题。- 当
s[right]
不在集合里时,说明窗口可以安全扩张,将s[right]
插入集合,right++
向右移动,更新最大长度result = max(result, right - left)
。 - 当
s[right]
已经在集合中时,说明出现重复字符,删除s[left]
,left++
收缩窗口,直到窗口内没有重复字符。
- 当
- 代码:
1 |
|
子串
和为 K 的子数组
- 和为 K 的子数组:560.和为 K 的子数组
- 思路:这道题是典型的前缀和+哈希表解法,用来统计和为
k
的连续子数组个数。- 在开始时放入
mp[0] = 1
,表示「前缀和为 0 出现过 1 次」,这样可以处理从数组开头开始的子数组。 - 用一个哈希表
mp
存储某个前缀和出现的次数。在遍历过程中,此时前缀和为sum
时,只要sum - k
在哈希表中存在,说明之前有某个前缀和能让子数组和刚好为k
。于是把mp[sum - k]
累加到答案ans
中。
- 在开始时放入
- 代码:
1 |
|
普通数组
轮转数组
- 轮转数组:189.轮转数组
- 思路:先翻转整个数组,再翻转前
k
个元素和后k
个元素就能得到答案。 - 代码:
1 |
|
矩阵
矩阵置零
- 矩阵置零:73.矩阵置零
- 思路:首先找到找到
0
元素所在位置,再分别按照0
元素所在位置的行和列都置为0
。 - 代码:
1 |
|
链表
回文链表
- 回文链表:234.回文链表
- 思路:该题的思路分为三部分:
- 通过快慢指针找到链表中间,初始的时候(
ListNode* slow = head;
、ListNode* fast = head->next;
) - 翻转后半部分链表 (从
slow->next
开始,ListNode* node = reverse(slow->next);
) - 判断链表的前后两部分是否相等。
- 通过快慢指针找到链表中间,初始的时候(
- 代码:
1 |
|
二叉树
二叉树的直径
- 二叉树的直径:543.二叉树的直径
- 思路:计算每个节点左子树的高度和右子树的高度相加即为经过该节点的最长直径,主要二叉树的最长直径不一定经过根节点,因此在递归计算每个节点的最长直径后取最大值保存,最终即为答案。
- 代码:
1 |
|
回溯
括号生成
- 括号生成:22.括号生成
- 思路:定义
left
和right
分别表示左右括号的数量,终止条件为当左右括号的数量都为n
的时候。递归过程中有两种情况:- 一是左括号数量小于
n
(left < n
),此时能够往path
里面加入左括号; - 二是
right<n && left>right
这个条件下可以往path
里面加右括号。
- 一是左括号数量小于
- 代码:
1 |
|
单词搜索
- 单词搜索:79.单词搜索
- 思路:
- 这是一道典型的回溯搜索(DFS)问题。核心思想是从每个格子出发,尝试走一条路径,如果走不通就回溯。
- 起点枚举:因为单词可能从任意格子开始,所以在
exist
里用两层循环,把每个格子作为起点尝试。 - 递归函数设计:
- 参数带上当前坐标
(i, j)
,以及全局的path
。 - 判断是否匹配当前字符,不匹配就直接剪枝返回。
- 如果已经匹配到
word.size()
长度,说明找到了整条路径,返回成功。
- 参数带上当前坐标
- 四方向扩展:递归尝试上下左右四个方向。
- 访问标记与回溯:用
visit
数组标记已访问的格子,走完这条路要恢复标记和路径(回溯),保证其他路径能用。 - 剪枝:
- 越界时立即返回;
- 字符不匹配立即返回;
- 找到答案时立即停止继续搜索。
- 当前格子字符和
word[path.size()]
相等→ 继续搜索; - 否则 → 回溯。直到
path.size() == word.size()
,返回true
。
- 代码:
1 |
|
二分查找
搜索插入位置
- 搜索插入位置:35.搜索插入位置
- 思路:经典二分查找问题,可以通过模拟查找的过程找出问题所在。
- 代码:
1 |
|
栈
最小栈
- 最小栈:155.最小栈
- 思路:这道题首先定义两个栈,一个栈正常存储元素,另一个栈为最小值栈,当入栈的元素小于最小栈元素就将该最小值加入到最小栈中,否则去最小栈的栈顶元素重新加入到最小栈。
- 代码:
1 |
|
字符串编码
- 字符串编码:394.字符串编码
- 思路:定义一个
string
类型的辅助栈,- 1、遍历字符串并压栈直到遇到
"]"
; - 2、定义辅助
string
类型的tmp
并弹出栈直到遇到"["
,将字符串记录至tmp
,注意从左边加到右边,最后弹出"["
; - 3、定义辅助
string
类型的num
并弹出栈直到字符不再是数字,将重复的次数加到num
中,注意从左边加到右边; - 4、通过
stoi(num)
将string
类型转换为int
类型,并采用for
循环构建重复的字符串,并重新压入栈中; - 5、最后从左边到右边拼接栈中字符串即可得到结果。
- 1、遍历字符串并压栈直到遇到
- 代码:
1 |
|
堆
数据流的中位数
- 数据流的中位数:295.数据流的中位数
- 思路:直接排序复杂度高,所以引入 两个堆:
- 大根堆(
left_que
):存储较小的一半数,堆顶是这部分的最大值。 - 小根堆(
right_que
):存储较大的一半数,堆顶是这部分的最小值。 - 为了能直接取出中位数,必须维持两个不变量:
- 有序性不变量:所有左边的元素 ≤ 所有右边的元素,即:
left_que.top() <= right_que.top()
,这样两个堆的交界就是中间。 - 规模平衡不变量:两个堆的元素个数差不超过
1
(注意这里不要用left_que.size() - right_que.size() > 1
来判断,因为size_t
是无符号数,负数会被解释成一个非常大的正整数),保证总数为奇数时,中位数就是元素更多的那边的堆顶,总数为偶数时,中位数就是两个堆顶的平均。
- 有序性不变量:所有左边的元素 ≤ 所有右边的元素,即:
- 大根堆(
- 代码:
1 |
|
动态规划
杨辉三角
- 杨辉三角:118.杨辉三角
- 思路:先特殊处理前两行,然后从第三行开始,每一行的首尾固定为
1
,中间元素由上一行相邻两个数相加,逐行生成直至numRows
。 - 代码:
1 |
|
乘积最大子数组
- 乘积最大子数组:152.乘积最大子数组
- 思路:
- 按照动态规划五部曲进行分析:
- 确定
dp
数组及下标的含义:dp_max[i]
和dp_min[i]
分别表示以i
结尾的最大子数组乘积最小值和最大值。 - 确定递推公式:
- 由于
nums[i]
可能存在负数的情况,所以要把最小值也记录下来,最后从递推公式中分别取出最大值和最小值。 - 最大值:
dp_max[i] = max(dp_max[i-1]*nums[i], max(dp_min[i-1]*nums[i], nums[i]));
- 最小值:
dp_min[i] = min(dp_max[i-1]*nums[i], min(dp_min[i-1]*nums[i], nums[i]));
- 由于
dp
数组如何初始化:除了dp_max[0]
和dp_min[i]
初始化成nums[0]
,其他全部初始化成0
;- 确定遍历顺序:在
i
上正序遍历; - 举例推导
dp
数组符合要求。
- 确定
- 按照动态规划五部曲进行分析:
- 代码:
1 |
|
最长有效括号
- 最长有效括号:32.最长有效括号
- 思路:定义
dp[i]
:表示以索引i
结尾的最长有效括号子串长度。遍历字符串,从i=1
开始(因为dp[0]
无法形成括号):- 情况一:
s[i] == ')' 且 s[i-1] == '('
- 形如
"...()"
- 那么
dp[i] = dp[i-2] + 2
(如果i-2 >= 0
,否则就是2
)。
- 形如
- 情况二:
s[i] == ')' 且 s[i-1] == ')'
- 形如
"...))"
- 那么需要去找与当前
')'
匹配的'('
: - 找位置
m = i - dp[i-1] - 1
(即前一个有效括号子串之前的那个位置)。- 如果
m >= 0 且 s[m] == '('
,说明匹配成功:dp[i] = dp[i-1] + 2 + dp[m-1]
(如果m-1 >= 0
,否则dp[i] = dp[i-1] + 2
)。
- 如果
- 形如
- 遍历过程中,维护
ans = max(ans, dp[i])
。
- 情况一:
- 代码:
1 |
|
多维动态规划
最小路径和
- 最小路径和:64.最小路径和
- 思路:
- 按照动态规划五部曲进行分析:
- 确定
dp
数组及下标的含义:dp[i][j]
表示从grid[0][0]
走到grid[i-1][j-1]
的最小路径和。 - 确定递推公式:
dp[i][j]
只由它的左边和上边元素递推得到,取两者的最小值即可,即dp[i][j] = min(dp[i-1][j], dp[i][j-1])+grid[i-1][j-1];
dp
数组如何初始化:除了dp[0][1]
和dp[1][0]
初始化成0
,其他全部初始化成INT_MAX
;- 确定遍历顺序:在
i
上正序遍历,在j
上正序遍历; - 举例推导
dp
数组符合要求。
- 确定
- 按照动态规划五部曲进行分析:
- 代码:
1 |
|
技巧
只出现一次的数字
- 只出现一次的数字:136.只出现一次的数字
- 思路:根据任意数与
0
异或:a ^ 0 = a
,任意数与自身异或:a ^ a = 0
,数组里所有数异或在一起,成对出现的数会两两抵消为0
,最后只剩下那个只出现一次的数。 - 代码:
1 |
|
多数元素
- 多数元素:169.多数元素
- 思路:设置一个候选值
res
和计数器count
。- 遍历数组:
- 如果
count == 0
,把当前数设为候选人res
,并把count
置为 1。 - 如果当前数等于
res
,count++
。 - 如果当前数不等于
res
,count--
。
- 如果
- 遍历完成后,
res
就是多数元素。
- 遍历数组:
- 代码(摩尔投票法):
1 |
|
- 代码(普通哈希表法):
1 |
|
热题100学习笔记
http://example.com/2025/09/07/hot100/