- LeetCode Hot 100 是最常被考察的题目集合,涵盖了面试中常见的算法和数据结构问题。
- 刷 Hot 100可以让你在有限的时间内集中精力解决最常考的问题。
- 不仅要写出代码,还要理解问题的本质、优化解法和复杂度分析。
- 多复习多回顾,欢迎大家评论区交流!
从一些看似简单的题目开始
哈希表
在刷题时候,经常体现为字典或者集合。
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
暴力解法: 直接枚举,两个循环。时间复杂度O(n**2),空间复杂度O(1).
哈希解法: 遍历到字典,每次去字典查是否有值能匹配。时间复杂度O(n),空间复杂度O(n).
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {} # 用于存遍历过的元素
result = [] # 存放两个结果索引
for i in range(len(nums)):
if target-nums[i] not in record: # 当前元素之前是否有匹配的元素
record[nums[i]] = i # 没有则将当前元素放入字典,键为元素值,值为索引
else:
result.append(i) # 匹配上了将两个索引找出,放到结果数组
result.append(record[target-nums[i]])
return result
简化版:
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
record = {} # 用于存遍历过的元素
for i,x in enumerate(nums):
if target-x in record: # 当前元素之前是否有匹配的元素
return [i, record[target-x]] # 匹配上了将两个索引找出,放到结果数组
record[x] = i # 保存nums[i]和i
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的所有字母得到的一个新单词。
**思路:**给每个字符串元素排序存起来,放到字典里;遍历就往排序后相同的加。时间复杂度O(n),空间复杂度O(n).
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = defaultdict(list) # 创建字典,defaultdict无键时默认空列表
for s in strs:
d[''.join(sorted(s))].append(s) # 按排序后的作为键加入
return list(d.values()) # 按输出格式要求,把视图转化成列表
- defaultdict:在访问一个不存在的键时,defaultdict 会创建该键并初始化为一个空列表。
- d.values() 是Python 中字典对象的方法,用于返回字典中所有值的一个视图对象。它不包括键,只返回字典中存储的值。
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
请你设计并实现时间复杂度为 O(n) 的算法解决此问题。
思路: 用哈希集合方便查找;找序列起点,分别判断有多长。时间复杂度O(n),空间复杂度O(n).
class Solution:
def longestConsecutive(self, nums: List[int]) -> int:
st = set(nums) # 将所有元素存入集合,快速查找
ans = 0
for x in st:
if x-1 not in st: # 判断是否是序列起点
cur_x = x # 记录下来序列起点
cur_len = 1 # 起点序列长度至少为1
while cur_x + 1 in st: # 不断判断之后的数字是否在集合中
cur_x += 1 # 序列长度和数字都加1
cur_len += 1
ans = max(ans, cur_len) # 更新最长序列长度
return ans
双指针
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
思路: 双指针,快指针遍历,遇到非0即存到慢指针;最后将慢指针后所有空位置为0. 时间复杂度O(n),空间复杂度O(1).
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
slow = fast = 0
for fast in range(len(nums)): # 快指针遍历
if nums[fast] != 0: # 找到非零元素靠慢指针移动位置
nums[slow] = nums[fast]
slow += 1
for i in range(slow,len(nums)): # 所有非零元素前移后,剩下位置补0
nums[i] = 0
另一思路: 找到非零元素后,就将快慢指针位置元素调换。时间复杂度O(n),空间复杂度O(1).
class Solution:
def moveZeroes(self, nums: List[int]) -> None:
i0 = 0
for i in range(len(nums)):
if nums[i]:
nums[i], nums[i0] = nums[i0], nums[i]
i0 += 1
给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
思路: 排序数组,固定一个元素,双指针找另外俩元素。注意排除重复元素。
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
nums.sort() # 先排序,方便比较找
res = []
for i in range(len(nums)): # 固定一个数
if i>0 and nums[i] == nums[i-1]:
continue
target = 0 - nums[i]
left, right = i+1, len(nums)-1 # 双指针,根据前后相加大小移动
while left < right:
sum_2 = nums[left] + nums[right]
if sum_2 < target: # 和小,右移左指针
left += 1
elif sum_2 > target: # 和大,左移右指针
right -= 1
elif sum_2 == target:
res.append([nums[i], nums[left], nums[right]])
while left < right and nums[left] == nums[left+1]: # 跳过相同元素
left += 1
while left < right and nums[right] == nums[right-1]: # 跳过相同元素
right -= 1
left += 1 # 分别移动指针
right -= 1
return res
类似题目:167. 两数之和Ⅱ-输入有序数组
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
numbers.sort() # 排序方便比较查找
left, right = 0, len(numbers)-1
while left < right: # 双指针,从前后分别逼近
sum = numbers[left] + numbers[right]
if sum < target:
left += 1
elif sum > target:
right -= 1
elif sum == target:
return [left+1, right+1]
数组
给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组是数组中的一个连续部分。
思路 :记录加上元素后是否大于最大值,大于就更新。如果之前加小于0,对后续没有贡献,重置为0.
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
result = float("-inf") # 设置全局最大值为负无穷
sum = 0 # 以num结尾的连续子数组的和
for num in nums:
sum += num # 大于0,则累加新的元素
if sum > result: # 累加值超过result,出现新的大和数组
result = sum
if sum < 0: # 之前的和为负数,无贡献,置为0
sum = 0
return result
56. 合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
思路: 按照左边界大小给各区间排序,然后判断是否重叠,重叠则更新右边界。
class Solution:
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x:x[0]) # 按照左边界排序,方便合并
res = []
res.append(intervals[0]) # 先将最小左边界放进来
for i in range(1, len(intervals)):
if intervals[i][0] > res[-1][1]: # 判断是否重叠,不重叠直接放入
res.append(intervals[i])
else:
res[-1][1] = max(res[-1][1], intervals[i][1]) # 有重叠则比较右边界,更新
return res
链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
思路: 分别用cur、pre指针,改变链表方向反转。
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
cur = head # 指针cur
pre = None # 指针pre
while cur:
nxt = cur.next # 记录当前节点cur的next
cur.next = pre # 反转,将当前节点的next指向pre
pre = cur # 向后移动指针
cur = nxt # 向后移动指针
return pre
回溯
思路:
- 需要搞清楚什么时候出现一个答案,我们把它存下来?(本题中,路径和=元素数,即是一个排列,满足条件)
- 回溯具体怎么操作【核心思路是递归】?(将本层元素纳入,递归返回后,撤销所有操作尝试其他可能选择)
- 什么情况是不需要处理的情况?(使用一个列表used记录使用过的元素,前面层用过元素后面层不再用)
class Solution:
def permute(self, nums: List[int]) -> List[List[int]]:
path = []
self.result = [] # 类的属性,可以不用作为参数传递
used = [0] * len(nums)
self.backtracking(path, nums, used)
return self.result
def backtracking(self, path, nums, used):
if len(nums) == len(path): # 判断出现了结果,需要加入结果列表
self.result.append(path[:])
for i in range(len(nums)): # 全排列,从0开始遍历,每次每个都有机会取到
if used[i] == 1: # 遇到标记之前层取过的,这层需要跳过
continue
path.append(nums[i]) # 加入path之后,标记取过
used[i] = 1
self.backtracking(path, nums, used) # 进入下一层
used[i] = 0 # 回溯,回到本层没有操作的状态
path.pop() # 回溯
动态规划
class Solution:
def climbStairs(self, n: int) -> int:
dp = [0]*(n+1) # 爬楼梯,动态规划
dp[0] = 1
dp[1] = 1
for i in range(2, n+1):
dp[i] = dp[i-1] + dp[i-2]
return dp[n]
给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
思路:
- 正整数数组分割两部分,和为奇数排除。和为偶数,判断其中部分元素能否刚好凑成和的一半。
- 给定一些元素,给定一个目标值,判断目标约束下的最大价值是否等于目标值。【01背包问题】
class Solution:
def canPartition(self, nums: List[int]) -> bool:
if sum(nums) % 2 != 0: # 排除和不为偶数的情况
return False
target = sum(nums)//2 # 动态规划,01背包,能否填满target
dp = [0] * (target+1) # 目标:dp[target]刚好等于target, True
for num in nums: # 01背包,遍历物品
for j in range(target, num-1, -1): # 01背包,遍历容量
dp[j] = max(dp[j],dp[j-num]+num)
return dp[-1] == target
第二个循环倒序原因:dp[j] 更新依赖于dp [j-num],如果从前向后遍历,可能会重复使用。
- dp一维数据的解法,需要先遍历物品,再遍历容量。
- dp二维数组的解法,先遍历物品和先遍历容量没有区别:
class Solution:
def canPartition(self, nums: List[int]) -> bool:
if sum(nums) % 2 != 0: # 排除和不为偶数的情况
return False
target = sum(nums)//2 # 动态规划,01背包,能否填满target
dp = [[0]*(target+1) for _ in range(len(nums)+1)] # 前i个物品中,能装入容量j的最大价值
for i in range(1, len(nums)+1): # 遍历物品
num = nums[i-1]
for j in range(target+1): # 遍历容量
if j >= num:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-num] + num) # 更新最大价值
else:
dp[i][j] = dp[i-1][j]
return dp[len(nums)][target] == target
&spm=1001.2101.3001.5002&articleId=144592896&d=1&t=3&u=47769e082aa747808484705bd803ea46)
1157

被折叠的 条评论
为什么被折叠?



