LeetCode Hot100-Day 1
LeetCode Hot100-Day 1
Created by: Yuanpeng QU
Created time: 2025年8月14日 11:27
15. 三数之和[mid]
给一个数组nums,求 满足nums[i]+nums[j]+nums[k]==0的所有[nums[i],nums[j],nums[k]]组合,顺序没有要求,但是不能有重复的组合出现
1 | class Solution(object): |
- 我想到的地方:先把nums排序,这样就能用双指针做,也就是先固定一个k,再求
nums[i]+nums[j]判断其是否等于-nums[k]从而得到一组结果。但是错误的是我想着同时挪动两个指针往中间靠了,所以没写出来。 - 关键1:去重,k和left和right所对应的nums的值可能和上一个或下一个重复,需要跳过,并且需要一个list保存结果
- 关键2:
left, right = k+1, n-1要在for 里面这样才能确保left能够更新,并且nums[left] + nums[right] + nums[k] < 0的时候是left+=1而不是>0的时候,仔细想想就知道如果nums[left] + nums[right]的值要小过结果的话,只有把nums[left]往大的值挪才有可能让结果==0。对于nums[right]也同理。 只有最后等于0的结果出现了才需要left和right都往中间挪,这是因为它们现在所在位置的值都被用上了,避免重复使用。
283. 移动零[easy]
给一个数组nums,将所有0移动到最后面
1 | class Solution(object): |
- 关键思路:双指针(快慢指针版)
- 一开始就想错了不能用朝中间靠拢版的双指针,问题的关键在于使用fast遍历整个数组,slow则用于储存非0的数后+1,并且两个指针开始都指向0,这样就能保证slow和其左边的值都是非0的,最后等fast遍历完后将slow的右边全部赋0就可以了。
- 改进:用for循环替代
while fast<len(nums): fast+=1和while slow < len(nums): slow += 1。这会更加python!
75. 颜色分类[mid]
给一个数组nums里面元素只有0,1,2,按顺序表示红白蓝,现在要求按照这个顺序把nums内所有的元素原地排序,不能用sort,进阶方法是只遍历一次
1 | import collections |
- 我的做法:哈希储存+遍历2次
- 简单直观的想法就是遍历第一次找一个地方储存0,1,2出现的次数,再遍历一次将出现0,1,2出现的次数分别填上去
- 缺点是空间复杂度高
- 最优解是3指针,
p0:指向0区的末尾。初始时p0 = 0。[0, p0-1]区间内的都是0。p2:指向2区的开头。初始时p2 = len(nums) - 1。[p2+1, n-1]区间内的都是2。curr:当前遍历到的元素的指针。初始时curr = 0。
我们让 curr 指针从头到尾遍历数组,在 curr <= p2 的条件下循环,根据 nums[curr] 的值来执行不同的操作:
- 如果
nums[curr] == 0:- 说明这个
0应该被放到0区。 - 我们将
nums[curr]和nums[p0]进行交换。 - 交换后,
p0指向的元素已经处理完毕,所以p0向右移动一位 (p0 += 1)。 curr指针也向右移动一位 (curr += 1),继续检查下一个元素。
- 说明这个
- 如果
nums[curr] == 2:- 说明这个
2应该被放到2区。 - 我们将
nums[curr]和nums[p2]进行交换。 - 交换后,
p2指向的元素已经处理完毕,所以p2向左移动一位 (p2 -= 1)。 - 注意: 此时
curr指针不能移动!因为从p2交换过来的那个新nums[curr]还没有被检查过,它可能是0、1或2,需要下一轮循环来处理它。
- 说明这个
- 如果
nums[curr] == 1:- 说明这个
1就在它应该在的位置(在0区和2区之间)。 - 我们不需要做任何交换,只需要移动
curr指针,继续检查下一个元素 (curr += 1)。
- 说明这个
循环在 curr 指针“追上”或超过 p2 指针时结束。此时,整个数组的排序就完成了。
1 | from typing import List |
42.接雨水[hard]
典中典

给一个数组height表示各个位置上的方块个数(高度),求整个数组表示的图形能接多少个水方块
1 | class Solution(object): |
- 核心就是双指针(往中间靠拢版)
- 关键1:这题和11. 盛最多水的容器类似,都是双指针,不同的地方在于11可以通过算left和right围起来的面积来求最大值,而这题可能有多个水坑可以接水,所以需要设置左边和右边都要有个最大高度来确保装水的量。而最大高度则通过每次遍历指针的时候与其对比不断更新。
- 关键2:确保有最大高度后,只需要计算在遍历的时候当前的位置头顶上能够顶几个水方块即可,注意不需要知道当前水坑能够装多少,只需要计算每一个位置的水方块最后加起来就可以。而这个方块数则可以通过最大高度-当前高度来得到。
- 关键3:总数小于等于2的时候没法围成水坑,所以直接返回0
3. 无重复字符的最长子串[mid]
给一个字符串s,求它里面最长的无重复字符的子串长度,其中子串和子序列的区别要搞清楚。
1 |
|
- 模式:滑动窗口+哈希储存
- 想象一个窗口,从左开始,一直把不重复的元素放入set中,直到right遍历到set里重复的元素,这个时候就需要按顺序从左开始删除元素,直到删除到set里没有重复元素为止,所以要用while而不是if,最后right走到终点后
right-left+1就是最大长度 - 关键:使用set储存窗口元素,为什么不用defaultdict(int)?是因为我们只需要判断x字符在不在哈希表里,而不需要判断有多少个x字符在不在。set集合可以用add和remove来添加和删除
- 需要不断更新max_len是因为right到最后,但是最长子串可能在中间,时刻更新max_len确保一直是最大的
239. 滑动窗口最大值[hard]
给定一个数组nums,再给定一个滑动窗口大小k,输出滑动窗口从头到尾中每个窗口的最大值组成的list
1 | 输入:nums = [1,3,-1,-3,5,3,6,7], k = 3 |
1 | class Solution(object): |
- 我直接暴力解决,但是超时了,因为时间复杂度为O(N*k), N和k都很大时运算量爆炸
- 最佳解法是用双向队列deque解决,时间复杂度为O(N)
1 | deque = collections.deque() # 创建小队 |
为什么要用双向队列?
- 用暴力法
O(N*k)之所以慢,是因为每当窗口向右滑动一格时,我们都重新在k个元素里找了一遍最大值。这里面有大量的重复劳动。 - 比如窗口从
[1, 3, -1]滑动到[3, -1, -3]。在[1, 3, -1]中,3是最大值。当1离开,-3加入时,我们其实不需要重新比较3和-1。我们只需要让新来的-3和之前的最大值3比较一下就行。 - 但问题是,如果离开的元素恰好是最大值怎么办?比如从
[3, -1, -3]滑动到[-1, -3, 5],此时最大值3离开了,我们就需要知道剩下元素里的“第二大值”是谁。 - 所以,核心难点在于:我们需要一个数据结构,它能帮我们快速知道“当旧的最大值离开窗口后,新的最大值是谁?”
- 用暴力法
用一个“精英小队”来解决问题 (双端队列 Deque 的比喻)
想象一下,我们不让窗口里的所有
k个成员都参与“比武招亲”,而是组织一个“精英小队”(这就是我们的双端队列 Deque)。这个小队有几条非常严格的规矩:
- 队员身份:小队里只记录队员的编号(即数组索引
index)。 - 入队规矩(从队尾进):新人
A想要入队时,要从队尾开始,跟前面的老队员B、C、D… 挨个比武。如果老队员打不过新人A(nums[老队员] < nums[A]),那么这个老队员就直接被踢出队伍(从队尾踢出)。A会一直踢到遇见一个比他强的,或者队伍里没人了,他才从队尾站进去。 - 出队规矩(从队头出):我们只关心队头的那个人。如果发现他的编号太老了(已经滑出窗口范围了),就把他从队头请出去。
这个规矩带来了什么神奇效果?
- 因为规矩2的存在,这个“精英小队”从队头到队尾,永远是按“武力值”(即
nums的值)从大到小排列的。 - 因此,队头的那个队员,永远是当前小队里最能打的,也就是当前窗口的最大值!
- 队员身份:小队里只记录队员的编号(即数组索引
这样顺序就是,先创建一个deque,用来储存index,nums里面的第一个值进来后先判断规则3和规则2(最前面有判断deque是否为空)保证第一个元素能直接进来。然后第二个元素进来后需要和第一个元素所对应的nums值进行比较,如果赢了那就把第一个踢掉,值最大的index当队长!保证队头的index对应的值一定是这次for里最大的。
以此类推,先等到遍历到窗口长度的时候(
i≥k-1,也就是nums的下标到长度k的时候)取出当前窗口最大的队头记录,然后到下一轮循环的时候根据规则3判断窗口的左边界i-k是否已经比队头的index大了将小的index对应的队头移出去就完成移动了。注意最后的
if i≥k-1表示是否已经形成窗口,形成窗口后窗口就不会消失了也就是说之后的for循环代表的窗口头边界往前移一位那就会产生一个新的窗口最大值。
438. 找到字符串中所有字母异位词[mid]
给两个字符串s和p,找出s中满足p的所有异位词,异位词就是不管字母顺序只管字符串长度是否相等,字母是否相同。
1 | import collections |
- 这题用滑动窗口+哈希,看到固定字符串长度就想到滑动窗口
- 问题的关键在于我们创建的两个带默认值的哈希表,和239题不一样,这次的滑动窗口需要我们先填充元素,也就是说我们创建p的哈希表和window哈希表,p_dict初始化就是将p的值全部放进去变成{’a’:1, ‘c’:2, …}。 而window哈希表也需要初始化,内容就是把它放到s里的第一组值。
- 这样我们在初始化之后就可以开始挪动窗口了,指向窗口头部的for循环直接从p_len位置开始直到s结束位置,窗口头部指针指向的值加入window,并且将窗口尾部的指针指向的值从window里扔掉,这样就是
window[s[j]]+=1,window[s[j-p_len]] -=1 - 不出错的关键在于如果window里面被删除的元素没了的话,要使得
if window == p_dict生效,一定需要把归零的元素del 掉 - 最后通过
if window == p_dict判断窗口里的子串是否是p的异位,将是的子串的开头字符的下标j-p_len+1存入res即可。
506. 和为 K 的子数组[mid]
给定一个数组nums,求能够满足和为k的所有子数组,其中的元素是连续的
这题是经典的前缀和的应用题,什么是前缀和?给定一个长度为j+1的数组nums,有
1 | prefix_map[i] = nums[0]+…+nums[i] |
想要求一个连续子数组的和sum(nums[i,..,j])可以通过prefix_map[j]-prefix[i-1]得到
**最关键:**所以这题能够转化为想要求子数组和为k(nums[i~j])的数目,有:
prefix_map[j] = prefix_map[i-1]+k,也就是prefix_map[i-1] = prefix_map[j] - k
这个时候prefix_map[j]就是for循环遍历的指针指到的位置之前的和,可以记为curr_sum,而k是满足条件的子数组的和,prefix_map[i-1]则是要通过上面公式来在数组中比对的和
- 所以这个时候就明确了:我们需要将cur_sum每走一步就记录一次,把记录存在hashmap里,记录完了后再和k相减,看看hashmap里有没有满足相减结果的值,有的话就说明现在有一个前缀和满足了,计数器+1
- 关键1:在hashmap初始化的时候不要忘了0这个数,因为有可能会出现nums的两个值和刚好被k整减,最后为0的结果,或者是nums里面刚好有等于k的数。所以初始化一定要prefix_map[0] += 1
- 关键2:
count += prefix_map[target_sum]而不是count += 1 - 关键3:
prefix_map[curr_sum] += 1这个一定放在if后面,因为if里面涉及到了prefix_map的更新,也就是如果curr_num等于target_sum的情况(k=0时)如果先把current_sum的计数值加了1,然后又用current_sum本身作为target去查找,你就会把自己刚刚放进去的那个计数值也算进去,导致重复计算。
1 | import collections |



