classSolution(object):
deflongestValidParentheses(self, s):
""" :type s: str :rtype: int """
stack = []
ans = 0# 存储最大长度的答案
stack.append(-1) # 为了后面计算下标差值时,有足够的元素减for i inrange(len(s)):
if s[i]=="(":
stack.append(i)
else:
stack.pop()
if stack: # 非空
ans = max(ans, i - stack[-1])
else:
stack.append(i)
return ans
647. 回文子串【中等】
思路:中心扩展(分奇数和偶数长度的回文)
classSolution(object):
defcountSubstrings(self, s):
""" :type s: str :rtype: int """
n = len(s)
defsolve(left, right):
cnt = 0# 中心扩展法while0<= left < n and0<= right < n and s[left]== s[right]:
cnt += 1
left -= 1
right += 1return cnt
ans = 0for i inrange(n):
ans += solve(i, i) # 奇数长度回文
ans += solve(i, i+1) # 偶数长度回文return ans
139. 单词拆分【中等】
思路:DP
classSolution:
defwordBreak(self, s:str, wordDict: List[str])->bool:
n = len(s)
dp = [True]+[False]* n
for i inrange(n):
for j inrange(i+1, n+1):
if dp[i] and s[i:j] in wordDict: # 前面的字符串满足,且后面的字符串满足
dp[j]=Truereturn dp[-1]
394. 字符串解码【中等】
思路:用栈去逐步提取字母和数字
classSolution:
defdecodeString(self, s:str)->str:
stack = []
for c in s:
if c !=']':
stack.append(c) # 不是']'则入栈if c ==']': # 是']'则出栈# 提取'[]'之间的所有字母
chars = ''while stack and stack[-1]!='[':
chars = stack[-1]+ chars # 把'['右边的字符弹出
stack.pop()
stack.pop() # 把栈顶的'['弹出# 提取'['左边的数字
num = ''while stack and stack[-1].isdigit():
num = stack[-1]+ num
stack.pop()
num = int(num)
chars_repeat = num * chars
stack.append(chars_repeat)
ans = ''while stack:
ans = stack[-1]+ ans # 可能有多段结果需要拼接
stack.pop()
return ans
72. 编辑距离【中等】
思路:DP
classSolution:
defminDistance(self, word1:str, word2:str)->int:
m, n = len(word1),len(word2)
dp =[[0]*(n +1)for _ inrange(m +1)] # (m+1)x(n+1):dp[i][j]表示 word1 前 i 个字符=>word2 前 j 个字符的操作数(多加 1 行 1 列目的:word1/word2 长度为 0 的边界情况)for i inrange(m +1): # word2 长度为 0,从 word1=>到长度为 i 的 word2 需要删除 i 步
dp[i][0]= i
for i inrange(n +1): # word1 长度为 0,从 word1=>长度为 i 的 word2 需要插入 i 步
dp[0][i]= i
for i inrange(1, m +1):
for j inrange(1, n +1):
if word1[i -1]== word2[j -1]: # 如果某 2 个字符相同,则这一步不需要操作
dp[i][j]= dp[i -1][j -1]
else: # 需要操作
dp[i][j]=min(dp[i][j -1], dp[i -1][j], dp[i -1][j -1])+1# 增、删、改三种情况取最小""" dp[i][j - 1] + 1: 增
dp[i - 1][j] + 1: 删
dp[i - 1][j - 1] + 1: 改 """return dp[m][n] # 从 word1 的前 m 个字符=>word2 的前 n 个字符的最小操作数
168. Excel 表列名称【简单】
思路:26 个一组逐步计算
classSolution:
defconvertToTitle(self, columnNumber:int)->str:
ans = []
while columnNumber >0:
columnNumber -=1# A 是从 1 开始编码的
ch =chr(columnNumber %26+ord('A'))
ans.append(ch)
columnNumber //=26return"".join(ans[::-1])
415. 字符串相加【简单】
思路:从低位往高位相加进位
classSolution:
defaddStrings(self, num1:str, num2:str)->str:
ans =""
carry =0while num1 or num2:
x =int(num1[-1])if num1 else0 # 取最后一位
y =int(num2[-1])if num2 else0 # 取最后一位
sum_val = x + y + carry
value =sum_val%10
ans =str(value)+ ans
carry =sum_val//10# 0 or 1
num1 = num1[:-1] # 丢掉最后一位
num2 = num2[:-1] # 丢掉最后一位if carry:
ans =str(carry)+ ans
return ans
搜索查找
448. 找到所有数组中消失的数字【简单】
思路:不能用 python 的 if i in list,因为其实底层也是循环,会超时。最简单的做法是用辅助的 flag 来记录哪些位置有元素
classSolution:
deffindDisappearedNumbers(self, nums: List[int])-> List[int]:
n = len(nums)
ans = []
flag = [0]*(n +1) # 因为元素范围是 1~n,所以只在下标 1~n 的位置记录for x in nums:
flag[x]=1for i inrange(1, n +1): # 只在下标 1~n 的位置记录if flag[i]==0:
ans.append(i)
return ans
136. 只出现一次的数字【简单】
思路:集合 set 去重,然后用两倍 sum 减原来 sum 得到那个单个数
classSolution(object):
defsingleNumber(self, nums):
""" :type nums: List[int] :rtype: int """
nums_set =set(nums) # 去重
nums_set_sum =sum(nums_set) # 去重后集合的和
gap = nums_set_sum *2-sum(nums) # 去重后两倍元素的和 - 原来的和return gap
704. 二分查找【简单】
思路:二分查找
classSolution(object):
defsearch(self, nums, target):
""" :type nums: List[int] :type target: int :rtype: int """
left =0
right =len(nums)-1while left <= right:
mid =(left + right)//2if nums[mid]== target:
return mid
elif nums[mid]> target:
right = mid -1elif nums[mid]< target:
left = mid +1return-1
121. 买卖股票的最佳时机【简单】
暴力写法,复杂度 O(n^2) 会 timeout:
classSolution:
defmaxProfit(self, prices: List[int])->int:
ans =0
n =len(prices)
for i inrange(0, n):
for j inrange(i+1, n):
cur_profit = prices[j]- prices[i]
ans =max(ans, cur_profit)
return ans
其实只需要在一次遍历过程中记录最低点和当前最大收益:
classSolution:
defmaxProfit(self, prices: List[int])->int:
min_price =1e4
max_profit =0
n =len(prices)
for i inrange(n):
min_price =min(min_price, prices[i]) # 动态更新当前最低点
max_profit =max(max_profit, prices[i]- min_price) # 记录当前最大收益return max_profit
122. 买卖股票的最佳时机 II【中等】
思路:贪心,累加所有上涨趋势
classSolution:
defmaxProfit(self, prices: List[int])->int:
n =len(prices)
ans =0# 如果仅一条数据(如 [1]),则当天买入卖出,结果是 0for i inrange(1, n):
ans +=max(prices[i]- prices[i -1],0)
return ans
classSolution:
defmajorityElement(self, nums: List[int])->int:
n =len(nums)
threshold = n //2
count_dict ={}
for num in nums:
if num in count_dict.keys():
count_dict[num]+=1else:
count_dict.update({num:1})
ans =0
max_cnt =0for k, v in count_dict.items():
if v >= threshold and v > max_cnt: # 不仅要数量达到阈值,还需要取数量最大的那个
max_cnt = v
ans = k
return ans
739. 每日温度【中等】
思路:单调栈
最暴力的写法是 O(n^2) 的,会 timeout:
classSolution:
defdailyTemperatures(self, temperatures: List[int])-> List[int]:
n =len(temperatures)
ans =[0]* n
for i inrange(n):
for j inrange(i+1, n):
if temperatures[j]> temperatures[i]:
ans[i]= j - i
breakreturn ans
从右往左构造单调栈:
classSolution:
defdailyTemperatures(self, temperatures: List[int])-> List[int]:
n =len(temperatures)
ans =[0]* n
stack =[] # 用栈存 indexfor i inrange(n-1,-1,-1):
while stack and temperatures[i]>= temperatures[stack[-1]]: # 单调栈
stack.pop()
if stack: # 右边存在更大的数(如不存在,则就是 0)
ans[i]= stack[-1]- i
stack.append(i) # 更新栈顶return ans
还可以从左往右构造单调栈:
classSolution:
defdailyTemperatures(self, temperatures: List[int])-> List[int]:
n =len(temperatures)
ans =[0]* n
stack =[] # 用栈存 indexfor i inrange(n):
while stack and temperatures[i]> temperatures[stack[-1]]:
j = stack.pop()
ans[j]= i - j
stack.append(i)
return ans
1. 两数之和【简单】
思路:用 hash 存储
最暴力的做法就是双重循环:
classSolution:
deftwoSum(self, nums: List[int], target:int)-> List[int]:
n =len(nums)
for i inrange(n):
for j inrange(i+1, n):
if nums[i]+ nums[j]== target:
return[i, j]
这个问题的本质:给一个 x,判断 targe-x 是否存在于这个数组中?构建一个 hash 表,核心是判断 diff in hash_table:
classSolution:
deftwoSum(self, nums: List[int], target:int)-> List[int]:
n =len(nums)
hash_table ={}
for i inrange(n):
diff = target - nums[i] # 先判断再 update 哈希表,避免重复使用当前元素if diff in hash_table:
return[i, hash_table[diff]]
else:
hash_table.update({nums[i]: i})
167. 两数之和 II - 输入有序数组【中等】
思路:和第 1 题没本质区别,可以延用 hash 检索,注意 index 从 1 开始,并且输出的 index 要升序
classSolution:
deftwoSum(self, numbers: List[int], target:int)-> List[int]:
n =len(numbers)
hash_table ={}
for i inrange(n):
diff = target - numbers[i] # 先判断再 update 哈希表,避免重复使用当前元素if diff in hash_table:
return[i +1, hash_table[diff]+1]if i +1< hash_table[diff]+1else[hash_table[diff]+1, i +1] # index 从 1 开始,并且注意输出 index 升序else:
hash_table[numbers[i]]= i
但这样做没有利用到数组单增的特点,可以采用类似二分查找的思路,用(相向)双指针解决
classSolution:
deftwoSum(self, numbers: List[int], target:int)-> List[int]:
n =len(numbers)
left, right =0, n -1# 相向双指针while left < right:
s = numbers[left]+ numbers[right]
if s < target: # 如果求和偏小,说明 left 应该向右走
left +=1elif s > target: # 如果求和偏大,说明 right 应该向左走
right -=1else:
return[left +1, right +1]
15. 三数之和【中等】
思路:先变成递增数组,然后指定第一个元素,在剩下两个元素值上用相向双指针
classSolution:
defthreeSum(self, nums: List[int])-> List[List[int]]:
n =len(nums)
nums.sort() # 有序,即可用相向双指针
ans =[]
for i inrange(n-2):
a = nums[i]
if i >=1and a == nums[i-1]: # 如果当前遍历的值和上一步相同,就跳过(避免重复),注意第 2 个元素开始才有前置元素continue
left, right = i +1, n -1# 相向双指针while left < right:
s = a + nums[left]+ nums[right]
if s <0:
left +=1elif s >0:
right -=1elif s ==0:
ans.append([a, nums[left], nums[right]]) # 结果# 继续找结果:left 指针向右(避开重复)、right 指针向左(避开重复)
left +=1while left < right and nums[left]== nums[left -1]:
left +=1
right -=1while left < right and nums[right]== nums[right +1]:
right -=1return ans
classSolution:
deftopKFrequent(self, nums: List[int], k:int)-> List[int]:
n =len(nums)
ans =[]
hash_table ={}
for i inrange(n):
if nums[i]notin hash_table:
hash_table[nums[i]]=1else:
hash_table[nums[i]]+=1# 降序排列:[(k1, v1), (k2, v2), ...]
rank_value =sorted(hash_table.items(), key=lambda x:x[1], reverse=True)
i =0for key, value in rank_value:
i +=1
ans.append(key)
if i == k:
breakreturn ans
287. 寻找重复数【中等】
思路:用 hash 存储遍历的值,如果发现重复则返回结果
classSolution:
deffindDuplicate(self, nums: List[int])->int:
ifnot nums:
return-1
n =len(nums)
hash_table =set()
for i inrange(n):
if nums[i]in hash_table:
return nums[i]
hash_table.add(nums[i])
42. 接雨水【困难】
思路:相向双指针
classSolution:
deftrap(self, height: List[int])->int:
n =len(height)
max_left, max_right =0,0# 记录从左往右最高点、左右往左最高点
left, right =0, n -1# 相向双指针
ans =0while left < right:
max_left, max_right =max(height[left], max_left),max(height[right], max_right) # 动态更新两边的最高点# 较低的那边去接雨水(说明有容纳雨水的可能性)if max_left <= max_right:
ans += max_left - height[left] # 统计低洼的情况
left +=1if max_left > max_right:
ans += max_right - height[right] # 统计低洼的情况
right -=1return ans
11. 盛最多水的容器【中等】
思路:相向双指针,比第 42 题接雨水还是要简单不少
classSolution:
defmaxArea(self, height: List[int])->int:
n =len(height)
if n ==2:
returnmin(height[0], height[1])*1
ans =0
left, right =0, n -1while left < right:
width = right - left
cur_s = width *min(height[left], height[right])
ans =max(ans, cur_s) # 面积取决于最矮的那边,所以哪边矮哪边就往前走一步,保留高的那边if height[left]<= height[right]:
left +=1elif height[left]> height[right]:
right -=1return ans
LCR 172. 统计目标成绩的出现次数【简单】
思路:二分法搜索边界
classSolution:
defcountTarget(self, scores: List[int], target:int)->int:
n =len(scores)
defhelper(target):
left, right =0, n -1while left <= right:
mid =(left + right)//2if scores[mid]< target:
left = mid +1elif scores[mid]>= target:
right = mid -1return left
l = helper(target)
r = helper(target +1)
return r - l
34. 在排序数组中查找元素的第一个和最后一个位置【中等】
思路:同第 172 题,二分法搜索边界
classSolution:
defsearchRange(self, nums: List[int], target:int)-> List[int]:
ifnot nums:
return[-1,-1]
n =len(nums)
defhelper(target):
left, right =0, n -1while left <= right:
mid =(left + right)//2if nums[mid]< target:
left = mid +1else:
right = mid -1return left
l = helper(target)
r = helper(target +1)
if r > l:
return[l, r -1]
return[-1,-1]
1287. 有序数组中出现次数超过 25% 的元素【简单】
思路:用 hash/数组计数
classSolution:
deffindSpecialInteger(self, arr: List[int])->int:
n =len(arr)
if n ==1:
return arr[0]
thre =int(n *0.25)
hash_table ={}
for a in arr:
if a notin hash_table:
hash_table[a]=1else:
hash_table[a]+=1# 得先统计上当前的元素if hash_table[a]> thre:
return a
215. 数组中的第 K 个最大元素【中等】
思路:基于快速排序思想
classSolution:
deffindKthLargest(self, nums: List[int], k:int)->int:
import random
defquick_select(nums, k):
pivot = random.choice(nums) # 随机选择基准数
big, equal, small =[],[],[] # 将大于、小于、等于 pivot 的元素划分到三个列表中for x in nums:
if x > pivot:
big.append(x)
elif x < pivot:
small.append(x)
else:
equal.append(x)
iflen(big)>= k: # 第 k 大的元素在 big 中,递归划分 bigreturn quick_select(big, k)
iflen(big)+len(equal)< k: # 第 k 大的元素在 small 中,递归划分 smallreturn quick_select(small, k -len(big)-len(equal))
return pivot # 第 k 大的元素在 equal 中,返回 pivotreturn quick_select(nums, k)
209. 长度最小的子数组【中等】
思路:滑动窗口,枚举右端点,收缩左端点,记录全局左端点和滑窗元素和
classSolution:
defminSubArrayLen(self, target:int, nums: List[int])->int:
n =len(nums)
ans = n +1
s =0# 初始化滑窗中的和
left =0# 初始化滑窗的左端点# 枚举滑窗右端点for right, x inenumerate(nums): # x: nums[right]
s += x # 加一个右边的端点进来while s >= target:
ans =min(ans, right - left +1)
s -= nums[left]
left +=1return ans if ans <= n else0
优化一下 while 循环的代码,下面的代码更直观:
classSolution:
defminSubArrayLen(self, target:int, nums: List[int])->int:
n =len(nums)
ans = n +1
s =0# 初始化滑窗中的和
left =0# 初始化滑窗的左端点# 枚举滑窗右端点for right, x inenumerate(nums): # x: nums[right]
s += x # 加一个右边的端点进来while s - nums[left]>= target: # 如果减去左端点还>=targe,可以安全丢掉
s -= nums[left]
left +=1if s >= target:
ans =min(ans, right - left +1)
return ans if ans <= n else0
76. 最小覆盖子串【困难】
思路:滑动窗口,和第 209 题思路一致,这里用到字符计数器 Count()
classSolution:
defminWindow(self, s:str, t:str)->str:
from collections import Counter
cnt_win = Counter() # s 的滑窗字符计数器
cnt_t = Counter(t) # t 的字符计数器
ans_left, ans_right =-float('inf'), float('inf')
left =0for right, x inenumerate(s): # 枚举右端点
cnt_win[x]+=1while cnt_win >= cnt_t: # s 滑窗能覆盖 tif right - left < ans_right - ans_left: # 找到了更短字符串,更新答案
ans_left, ans_right = left, right
cnt_win[s[left]]-=1# 丢掉左端点
left +=1return s[ans_left:ans_right+1]if ans_left >=0else""
3. 无重复字符的最长子串【中等】
思路:滑动窗口
classSolution:
deflengthOfLongestSubstring(self, s:str)->int:
n =len(s)
ans =0
visited =set() # 用 set 检测重复元素
left =0for right, x inenumerate(s): # x: s[right]while x in visited: # 当前元素重复了
visited.remove(s[left]) # 丢掉左端点
left +=1
visited.add(x) # 加上右端点
ans =max(ans, right - left +1) # 更新最大长度答案return ans
classSolution:
defsortArray(self, nums: List[int])-> List[int]:
import random
defquicksort(arr, low, high):
if low < high:
pivot_idx = partition(arr, low, high)
quicksort(arr, low, pivot_idx -1)
quicksort(arr, pivot_idx +1, high)
return arr
defpartition(arr, low, high):
r = random.randint(low, high)
arr[r], arr[high]= arr[high], arr[r]
pivot = arr[high]
slow = fast = low
while fast <= high:
if arr[fast]< pivot:
arr[fast], arr[slow]= arr[slow], arr[fast]
slow +=1
fast +=1
arr[high], arr[slow]= arr[slow], arr[high]
return slow
n =len(nums)
nums = quicksort(nums,0, n -1)
return nums
283. 移动零【简单】
思路:双指针(都从左侧开始,才能保证元素的相对顺序),实现 inplace 排列
classSolution(object):
defmoveZeroes(self, nums):
""" :type nums: List[int] :rtype: None Do not return anything, modify nums in-place instead. """
left =0for right inrange(len(nums)):
if nums[right]!=0:
nums[right], nums[left]= nums[left], nums[right]
left +=1return nums
❌ 如果用双指针(从两端开始),无法保证元素的相对顺序:
classSolution(object):
defmoveZeroes(self, nums):
""" :type nums: List[int] :rtype: None Do not return anything, modify nums in-place instead. """
right =len(nums)-1
left =0while left < right:
if nums[left]==0:
nums[right], nums[left]= nums[left], nums[right]
right -=1
left +=1return nums
当然也可以先把所有非零数放到左边,最后右边填补 0:
classSolution(object):
defmoveZeroes(self, nums):
""" :type nums: List[int] :rtype: None Do not return anything, modify nums in-place instead. """
idx_non_zero =0for i inrange(len(nums)):
if nums[i]!=0:
nums[idx_non_zero]= nums[i]
idx_non_zero +=1for i inrange(idx_non_zero,len(nums)):
nums[i]=0return nums
75. 颜色分类【中等】
思路:三指针,p0 左侧保持全 0,p2 右侧保持全 2,i 从左往右遍历数组
细节:如果 i 遇到 0,swap 后 i 继续走,如果 i 遇到 2,swap 后 i 先别动(因为可能把后面的 0 交换到前面了,下次应该直接在该位置 i 和 p0 交换)
classSolution:
defsortColors(self, nums: List[int])->None:
""" Do not return anything, modify nums in-place instead. """
n =len(nums)
p0, p2 =0, n -1
i =0while i <= p2:
if nums[i]==2:
nums[i], nums[p2]= nums[p2], nums[i]
p2 -=1elif nums[i]==0:
nums[i], nums[p0]= nums[p0], nums[i]
p0 +=1
i +=1elif nums[i]==1:
i +=1
406. 根据身高重建队列【中等】
思路:对身高降序,k 升序,然后根据 k 进行插入
classSolution:
defreconstructQueue(self, people: List[List[int]])-> List[List[int]]:
n =len(people)
ans =[]
# 身高 h 降序,相同 h 中 k 升序:先处理高个子,相同身高里先处理前面人少的
people_sort =sorted(people, key=lambda x:(-x[0], x[1]))
for person in people_sort:
ans.insert(person[1], person)
return ans
看下具体的输出帮助理解:
977. 有序数组的平方【简单】
思路:相向双指针
classSolution:
defsortedSquares(self, nums: List[int])-> List[int]:
n =len(nums)
left, right =0, n -1
ans =[]
while left <= right:
if nums[left]**2<= nums[right]**2:
ans.append(nums[right]**2)
right -=1elif nums[left]**2> nums[right]**2:
ans.append(nums[left]**2)
left +=1return ans[::-1]
31. 下一个排列【中等】
思路:先从右往左找到第一个较小元素,然后和右侧比其大一点的元素交换,再把右侧元素翻转为升序
classSolution:
defnextPermutation(self, nums: List[int])->None:
""" Do not return anything, modify nums in-place instead. """
n =len(nums)
# 从右往左找到第一个小于右侧邻居的元素 nums[i],nums[i]右侧一定是递减# 为什么 nums[i]右侧一定是递减?反证法:如果不是,说明 nums[i]不应该是第一个被找到的
i = n -2while i >=0and nums[i]>= nums[i +1]:
i -=1# 如果找到了 nums[i],继续找 nums[i]右边大于它且最小的元素 nums[j]if i >=0:
j = n -1while nums[j]<= nums[i]:
j -=1
nums[i], nums[j]= nums[j], nums[i] # 交换两个元素# 将右侧的元素翻转:从递减到递增,保证右侧字典序最小# 如果找不到(即 i=-1),说明当前是最大排列,直接翻转即可得到最小排列
left, right = i +1, n -1while left < right:
nums[left], nums[right]= nums[right], nums[left]
left +=1
right -=1
组合
49. 字母异位词分组【中等】
思路:用字典的相同 key 映射多个字母异位词(用 value 列表存)
classSolution(object):
defgroupAnagrams(self, strs):
""" :type strs: List[str] :rtype: List[List[str]] """
d ={}
for s in strs:
sorted_s =''.join(sorted(s)) # 把字符串按字符排序
d[sorted_s]= d.get(sorted_s,[])+[s] # 返回 d 中 key=sorted_s 的 value,如果 value 为空,返回 []# 字典存储:key=排序后的字符串,value=原始的字符串# 将具有相同排序后的字符串放在同一个 value 列表中return d.values()
78. 子集【中等】
思路:维护一个 ans 列表,遍历 nums 中所有元素,每个元素都将和 ans 列表中每一项进行组合,然后放进 ans 列表中(难点在于区分 python 中 list 的加法和 append,很容易在[][]嵌套结构上出错。加法是把两个列表中的元素合并起来,append 是往列表中添加一整个元素)
classSolution(object):
defsubsets(self, nums):
""" :type nums: List[int] :rtype: List[List[int]] """
ans =[[]] # 初始化包含空列表的答案for num in nums:
tmp =[]
for i in ans:
tmp.append([num]+ i)
ans += tmp
return ans
classSolution(object):
defisPalindrome(self, head):
""" :type head: Optional[ListNode] :rtype: bool """
l =[]
while head:
l.append(head.val)
head = head.next
ans =(l == l[::-1])
return ans
160. 相交链表【简单】
思路:用 hash 存储一条链表的节点,然后去搜索另一条链表中哪个节点存在于 hash
classSolution:
defgetIntersectionNode(self, headA: ListNode, headB: ListNode)-> Optional[ListNode]:
ifnot headA ornot headB:
returnNone
link_set =set() # 用 hash 避免 timeout,因为 hash 的搜素是 O(1),而 list 是 O(n)
cur = headA
while cur:
link_set.add(cur)
cur = cur.next
cur = headB
while cur:
if cur in link_set: # 判断节点的存在性return cur
cur = cur.nextreturnNone
141. 环形链表【简单】
思路:用 hash 存储遍历到的节点,如果遍历到重复节点,说明是环形
classSolution:
defhasCycle(self, head: Optional[ListNode])->bool:
ifnot head:
returnFalse
link_set =set() # 用 hash 而不是 list 的原因:hash 的搜索更快
cur = head
while cur:
if cur in link_set: # 如果是环形,则会返回 True 跳出循环returnTrue
link_set.add(cur)
cur = cur.next# 如果不是环形,则一定会执行到 cur=None 跳出循环returnFalse
如果只用 O(1) 空间解决这个问题,需要快慢指针(追及问题)
classSolution:
defhasCycle(self, head: Optional[ListNode])->bool:
ifnot head:
returnFalse# 核心:设置快慢指针,慢指针每次走 1 步,快指针走 2 步# 如果不存在环,则快指针会走到尽头,返回 False# 如果存在环,则快指针迟早会在环中追上慢指针,返回 True
slow, fast = head, head.nextwhile fast and fast.next:
if fast == slow:
returnTrue
slow = slow.next
fast = fast.next.nextreturnFalse
142. 环形链表 II【中等】
思路:和上一题并无区别,改成返回节点
classSolution:
defdetectCycle(self, head: Optional[ListNode])-> Optional[ListNode]:
ifnot head:
returnNone
link_set =set() # 用 hash 而不是 list 的原因:hash 的搜索更快
cur = head
while cur:
if cur in link_set: # 如果是环形,则会返回 cur 跳出循环return cur
link_set.add(cur)
cur = cur.next# 如果不是环形,则一定会执行到 cur=None 跳出循环returnNone
如果想用 O(1) 空间解决,同样需要快慢指针,但是还需要多一个步骤定位环的入口
classSolution:
defdetectCycle(self, head: Optional[ListNode])-> Optional[ListNode]:
ifnot head:
returnNone# 设置快慢指针,定位相遇时 slow 指针在环中的位置
fast, slow = head, head
flag =False# flag 标记是否有环while fast and fast.next:
slow = slow.next
fast = fast.next.nextif slow == fast:
flag =Truebreak# 如果不存在环,则返回 Noneifnot flag:
returnNone# 如果存在环,则进一步计算环的入口# 再加入一个指针从头开始走,速度为 1,和环中的 slow 指针相遇时,即环的入口
tmp = head
while tmp != slow:
slow = slow.next
tmp = tmp.nextreturn slow
148. 排序链表【中等】
最暴力的做法,转列表排序后放回链表:
classSolution:
defsortList(self, head: Optional[ListNode])-> Optional[ListNode]:
cur = head # 链表转列表
head_list =[]
while cur:
head_list.append(cur.val)
cur = cur.next
head_list.sort() # 列表升序排列# 列表转链表
cur = head
i =0while cur:
cur.val = head_list[i]
i +=1
cur = cur.nextreturn head
归并排序
classSolution:
defsortList(self, head: Optional[ListNode])-> Optional[ListNode]:
ifnot head ornot head.next:
return head
# 步骤 1. 递归每次分割成 2 组
slow, fast = head, head.nextwhile fast and fast.next: # 定位中点的位置
slow = slow.next# slow 走一步
fast = fast.next.next# fast 走两步
mid = slow.next
slow.next=None
left = self.sortList(head) # 递归
right = self.sortList(mid) # 递归# 步骤 2. 迭代两两合并在一起(有序)
ans = ListNode()
cur = ans
while left and right: # 处理相同长度if left.val < right.val:
cur.next= left
left = left.nextelse:
cur.next= right
right = right.next
cur = cur.next# 处理不同长度if left:
cur.next= left
else:
cur.next= right
return ans.next
classSolution:
defdeleteDuplicates(self, head: Optional[ListNode])-> Optional[ListNode]:
dummy = ListNode(next=head)
cur = dummy
while cur.nextand cur.next.next: # 保证下个节点和下下个节点存在
val = cur.next.val # 下个节点的值if cur.next.next.val == val: # 发现相同值的节点了!要通过循环从 cur.next 开始跳过while cur.nextand cur.next.val == val:
cur.next= cur.next.nextelse:
cur = cur.nextreturn dummy.next
为什么需要哨兵节点,因为头节点可能被删除。
203. 移除链表元素【简单】
思路:用哨兵节点,迭代判断
classSolution:
defremoveElements(self, head: Optional[ListNode], val:int)-> Optional[ListNode]:
ifnot head:
returnNone
dummy = ListNode(next=head) # 哨兵节点,处理头节点被删除的情况
cur = dummy
while cur and cur.next:
if cur.next.val == val: # 注意提前一个节点开始判断,方便跨过
cur.next= cur.next.nextelse:
cur = cur.nextreturn dummy.next
2. 两数相加【中等】
思路:参照第 989 题的加法模板,该题从左往右即数字的低位开始。
classSolution:
defaddTwoNumbers(self, l1: Optional[ListNode], l2: Optional[ListNode])-> Optional[ListNode]:
ans = ListNode(0) # 用来存答案的链表
cur = ans
carry =0# 进位while l1 or l2: # 两个链表长度可能不同,只要有一个没遍历完,则继续算
x = l1.val if l1 else0 # 长度不够就补 0
y = l2.val if l2 else0 # 长度不够就补 0
s = x + y + carry
value = s %10
carry = s //10
cur.next= ListNode(value)
cur = cur.nextif l1:
l1 = l1.nextif l2:
l2 = l2.nextif carry:
cur.next= ListNode(carry)
return ans.next
21. 合并两个有序链表【简单】
思路:双指针,取较小的,并移动其指针
classSolution:
defmergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode])-> Optional[ListNode]:
ifnot list1 andnot list2:
returnNone
ans = ListNode() # 答案链表,初始化一个 dummy 节点,答案节点都存储在后面
cur = ans
while list1 and list2: # 先处理共同长度的部分# 思路:先取较小的,然后移动其指针if list1.val < list2.val:
cur.next= list1
list1 = list1.nextelse:
cur.next= list2
list2 = list2.next
cur = cur.next# 处理不等长情况:可能有一个链表更长,还没有被遍历完if list1:
cur.next= list1
elif list2:
cur.next= list2
return ans.next# 跳过初始化的第一个 dummy 节点
23. 合并 K 个升序链表【困难】
思路:两两合并,分治处理
classSolution:
defmergeKLists(self, lists: List[Optional[ListNode]])-> Optional[ListNode]:
# 先写两个有序链表的合并defmergeTwoLists(list1, list2):
dummy = ListNode() # 哨兵节点简化代码
cur = dummy
while list1 and list2: # 先处理链表等长的部分if list1.val < list2.val:
cur.next= list1
list1 = list1.nextelse:
cur.next= list2
list2 = list2.next
cur = cur.next# 链表不等长,还有剩余的情况if list1:
cur.next= list1
elif list2:
cur.next= list2
return dummy.next# 主代码
n =len(lists)
if n ==0:
returnNoneif n ==1:
return lists[0]
left = self.mergeKLists(lists[:n //2]) # 递归合并前半部分
right = self.mergeKLists(lists[n //2:]) # 递归合并后半部分return mergeTwoLists(left, right) # 合并前后两个部分
位运算
461. 汉明距离【简单】
比特位的模板解法:
classSolution:
defhammingDistance(self, x:int, y:int)->int:
bit_num =32
dist =0# 初始化答案for i inrange(bit_num):
one_bit_x =1if x &(1<< i)else0 # x 的每一位是否为 1
one_bit_y =1if y &(1<< i)else0 # y 的每一位是否为 1if one_bit_x != one_bit_y:
dist +=1return dist
思路:直接异或再计数,注意 bin()的输出是 string
classSolution(object):
defhammingDistance(self, x, y):
""" :type x: int :type y: int :rtype: int """
xor =bin(x ^ y) # 得到二进制异或结果
dist =list(xor[2:]).count('1') # 统计 1 的数目return dist
338. 比特位计数【简单】
比特位的模板解法:
classSolution:
defcountBits(self, n:int)-> List[int]:
bit_num =32
ans =[]
for i inrange(n +1): # n 个 sample
one_bit =[]
for b inrange(bit_num):
if(1<< b)& i:
one_bit.append(1)
ans.append(sum(one_bit))
return ans
思路:转二进制然后计数
classSolution(object):
defcountBits(self, n):
""" :type n: int :rtype: List[int] """
ans =[]
for i inrange(n+1):
bin_num =list(bin(i))[2:]
ans.append(bin_num.count('1'))
return ans
191. 位 1 的个数【简单】
思路:通过'与'进行逐位判断
classSolution:
defhammingWeight(self, n:int)->int:
bit_num =32
one_bit =[] # 记录每一位是否为 1(是=1,否=0)for i inrange(bit_num): # if n & (2 ** i):if n &(1<< i): # 从低往高判断每一位是否为 1(比如 5&010=101&010=000,则说明第 2 位不是 1)
one_bit.append(1)
ans =sum(one_bit)
return ans
231. 2 的幂【简单】
思路:转二进制后判断 1 的个数
classSolution:
defisPowerOfTwo(self, n:int)->bool:
if n <=0: # 2 的幂次一定是正数!returnFalse
num_bits =32
bits =[]
for i inrange(num_bits):
cur_bit =1if(2** i)& n else0
bits.append(cur_bit)
# 2 的幂次转二进制一定有且只有一个 1
returnTrueif bits.count(1)==1elseFalse
数组计算
238. 除自身以外数组的乘积【中等】
思路:分别计算某个数左右的乘积,再汇总起来
classSolution:
defproductExceptSelf(self, nums: List[int])-> List[int]:
n =len(nums)
ans =[0]* n
# 计算前缀元素乘积
front_mul =[1]+[0]*(n -1) # [1, 0, 0, ...]for i inrange(1, n):
front_mul[i]= front_mul[i -1]* nums[i -1]
# 计算后缀元素乘积
behind_mul =[0]*(n -1)+[1] # [0, ..., 0, 1]for i inrange(n-2,-1,-1):
behind_mul[i]= behind_mul[i +1]* nums[i +1]
# 把前缀乘积和后缀成绩再乘起来得到答案for i inrange(n):
ans[i]= front_mul[i]* behind_mul[i]
return ans
classSolution:
defmaxProduct(self, nums: List[int])->int:
n =len(nums)
if n ==1:
return nums[0]
cur_max =[0]* n
cur_min =[0]* n
ans = nums[0]
cur_max[0], cur_min[0]= nums[0], nums[0]
for i inrange(n):
cur_max[i]=max(nums[i]* cur_min[i -1], nums[i]* cur_max[i -1], nums[i])
cur_min[i]=min(nums[i]* cur_min[i -1], nums[i]* cur_max[i -1], nums[i])
ans =max(ans, cur_max[i])
return ans
53. 最大子数组和【中等】
思路:这是 152 题的普通版,典型 DP 模板
classSolution:
defmaxSubArray(self, nums: List[int])->int:
n =len(nums)
if n ==1:
return nums[0]
dp =[0]* n
dp[0]= nums[0]
for i inrange(n):
dp[i]=max(nums[i], dp[i -1]+ nums[i])
returnmax(dp)
300. 最长递增子序列【中等】
思路:DP
classSolution:
deflengthOfLIS(self, nums: List[int])->int:
n =len(nums)
if n ==1:
return1
dp =[1]* n
for i inrange(n):
for j inrange(i):
if nums[i]> nums[j]:
dp[i]=max(dp[i], dp[j]+1)
returnmax(dp)
128. 最长连续序列【中等】
思路:用 hash 存储方便查找元素,先找到序列的开头,然后不断 +1 去判断存在性
classSolution:
deflongestConsecutive(self, nums: List[int])->int:
n =len(nums)
hash_set =set(nums) # 去重且查找元素的效率高
ans =0for num in hash_set:
if num -1in hash_set: # 说明 num 不是连续序列的开头continue# 跳过,因为希望从连续序列的开头开始计数elif num -1notin hash_set: # 说明 num 是连续序列的开头,可以计数了
length_from_here =1# 从本次起点出发,能达到的最大长度
cur_num = num
while cur_num +1in hash_set:
cur_num +=1
length_from_here +=1
ans =max(ans, length_from_here) # 更新最大答案长度return ans
438. 找到字符串中所有字母异位词【中等】
下面是最简单,但是会 timeout 的写法:
classSolution:
deffindAnagrams(self, s:str, p:str)-> List[int]:
window_size =len(p)
n =len(s)
ans =[]
p_cnt =[0]*26for c in p:
p_cnt[ord(c)-ord('a')]+=1for i inrange(n):
window_s = s[i:i+window_size] # "cba"
iflen(window_s)!= window_size:
continue# if sorted(window_s) == sorted(p): # O(nlogn)会 timeout# ans.append(i)
window_cnt =[0]*26for c in window_s:
window_cnt[ord(c)-ord('a')]+=1# print(p_cnt, window_cnt)if p_cnt == window_cnt:
ans.append(i)
return ans
下面是完美的做法,每次滑窗实际上只需要更新左右两端两个元素的统计情况:
classSolution:
deffindAnagrams(self, s:str, p:str)-> List[int]:
window_size =len(p)
n =len(s)
ans =[]
window_cnt =[0]*26# 维持一个随着窗口更新的字符统计分布(每次只改变 2 个)
p_cnt =[0]*26for c in p:
p_cnt[ord(c)-ord('a')]+=1for c in s[:window_size]:
window_cnt[ord(c)-ord('a')]+=1# print(p_cnt, window_cnt)if p_cnt == window_cnt:
ans.append(0)
for i inrange(1, n - window_size +1):
left_del_char, right_new_char = s[i -1], s[i + window_size -1] # 记录要删除的左字符、要添加的右字符# print(left_del_char, right_new_char)
window_cnt[ord(left_del_char)-ord('a')]-=1
window_cnt[ord(right_new_char)-ord('a')]+=1# print(p_cnt, window_cnt)if p_cnt == window_cnt:
ans.append(i)
return ans
989. 数组形式的整数加法【简单】
思路:逐位计算模板
classSolution:
defaddToArrayForm(self, num: List[int], k:int)-> List[int]:
n =len(num)
i = n -1# 从右往左遍历 num 的每一位
carry =0
ans =[]
while i >=0or k !=0: # num 和 k 长度可能不一致,只要还有没遍历完的,就要继续算
x = num[i]if i >=0else0 # 不够长就补 0
y = k %10if k !=0else0 # 不够长就补 0
s = x + y + carry
value = s %10# 值
carry = s //10# 进位:注意是除完取整!
ans.append(value)
i -=1
k //=10if carry:
ans.append(carry)
return ans[::-1]
119. 杨辉三角 II【简单】
思路:递推每行的值,但是要注意每行首尾两个元素默认为 1
classSolution:
defgetRow(self, rowIndex:int)-> List[int]:
row_num = rowIndex +1# rowIndex 是从 0 开始的,注意行数要 +1
T =[[1]*(i+1)for i inrange(row_num)] # rowIndex * 1,2,3,...for i inrange(row_num):
for j inrange(1, i): # 不算每行的首尾,默认是 1
T[i][j]= T[i-1][j-1]+ T[i-1][j]
return T[rowIndex]
198. 打家劫舍【中等】
思路:DP
classSolution:
defrob(self, nums: List[int])->int:
n =len(nums)
if n ==1:
return nums[0]
if n ==2:
returnmax(nums[0], nums[1]) # 两间房,只能偷一家最有钱的
dp =[0]* n # dp[i]表示前 i 间房最高金额
dp[0]= nums[0]
dp[1]=max(nums[0], nums[1])
for i inrange(2, n): # 当前 dp[i]的可能性:# 1)前一间偷过了 dp[i-1],这间不能偷;2)前一间没偷,这间还可以偷 nums[i]
dp[i]=max(dp[i -1], dp[i -2]+ nums[i])
return dp[-1]
213. 打家劫舍 II【中等】
思路:DP,和第 198 题没有本质区别,不过首尾两间不能同时偷,所以需要分情况讨论首尾偷哪个
classSolution:
defrob(self, nums: List[int])->int:
# 核心:第 0 天和第 n-1 天不能同时偷
n =len(nums)
if n ==1:
return nums[0]
if n ==2:
returnmax(nums[0], nums[1])
dp =[0]* n
# 情况 1:如果第 0 天可以偷,第 n-1 天不能偷
dp[0]= nums[0]
dp[1]=max(nums[0], nums[1])
for i inrange(2, n -1):
dp[i]=max(dp[i -1], dp[i -2]+ nums[i])
ans1 = dp[-2]
# 情况 2:第 n-1 天可以偷,第 0 天不能偷
dp[1]= nums[1]
dp[2]=max(nums[1], nums[2])
for i inrange(3, n):
dp[i]=max(dp[i -1], dp[i -2]+ nums[i])
ans2 = dp[-1]
returnmax(ans1, ans2)
classSolution:
defmaxSlidingWindow(self, nums: List[int], k:int)-> List[int]:
n =len(nums)
ans =[]
q = deque() # 双端队列for i, x inenumerate(nums): # 入队while q and x >= nums[q[-1]]:
q.pop()
q.append(i) # 记录 index,方便后面判断距离进行出队# 出队if i - q[0]>= k: # 控制不要超过窗口距离
q.popleft()
# 记录答案if i >= k -1: # 刚开始没到窗口大小时不记录答案
ans.append(nums[q[0]]) # 队列左端点是最大值return ans
56. 合并区间【中等】
思路:先排序左端点,然后从左往右依次判断包含关系
classSolution:
defmerge(self, intervals: List[List[int]])-> List[List[int]]:
intervals.sort(key=lambda p: p[0]) # 按左端点升序排列
ans =[]
for p in intervals:
if ans and p[0]<= ans[-1][1]: # 当前区间左端点<=上一个区间右端点(可融合)
ans[-1][1]=max(p[1], ans[-1][1]) # 更新上一个区间右端点=最大右端点值else: # 当前区间和上一个区间不重叠
ans.append(p)
return ans
classSolution:
defnumIslands(self, grid: List[List[str]])->int:
defdfs(grid, r, c):
row_num =len(grid)
col_num =len(grid[0]) # 边界条件处理if r <0or r > row_num -1or c <0or c > col_num -1:
return# 指 dfs 退一层if grid[r][c]!="1": # 如果当前访问的位置不是 1,则退出递归(0 说明不满足;2 说明访问过了,不能走重复路)return# 指 dfs 退一层
grid[r][c]="2"# 定义 2 表示已访问(修改当前位置的状态)# 搜索前后左右的位置
dfs(grid, r+1, c)
dfs(grid, r-1, c)
dfs(grid, r, c+1)
dfs(grid, r, c-1)
row_num =len(grid)
col_num =len(grid[0])
ans =0for r inrange(row_num):
for c inrange(col_num):
if grid[r][c]=='1': # 遍历到了岛屿
dfs(grid, r, c) # 递归:深度优先搜索
ans +=1# 答案 +1return ans
221. 最大正方形【中等】
思路:DP
classSolution:
defmaximalSquare(self, matrix: List[List[str]])->int:
rows, cols =len(matrix),len(matrix[0])
if rows ==0or cols ==0: # 如果正方形不存在,返回 0
return0
dp =[[0]* cols for _ inrange(rows)] # dp[i][j]表示以 (i, j) 为右下角的正方形的边长
max_side =0for i inrange(rows):
for j inrange(cols):
if matrix[i][j]=='1': # 只有当遍历到'1'这个位置,才能去探索if i ==0and j ==0: # 右下角在边界上,边长只能是 1
dp[i][j]=1else: # 当前右下角最大边长取决于左、上、左上角最大边长的最小值
dp[i][j]=min(dp[i-1][j], dp[i][j-1], dp[i-1][j-1])+1
max_side =max(max_side, dp[i][j]) # 更新最大边长return max_side **2
73. 矩阵置零【中等】
思路:两次遍历,第一次记录行列存在 0 的位置,第二次修改原矩阵
classSolution:
defsetZeroes(self, matrix: List[List[int]])->None:
""" Do not return anything, modify matrix in-place instead. """
rows =len(matrix)
cols =len(matrix[0])
row_flag, col_flag =[0]* rows,[0]* cols # 设置行列 flag:初始化 0,matrix 某行某列出现 0 时则改成 1for r inrange(rows):
for c inrange(cols):
if matrix[r][c]==0: # 如果 matrix 某个元素是 0,则把行列 flag 改成 1
row_flag[r]=1
col_flag[c]=1for r inrange(rows):
for c inrange(cols):
if row_flag[r]or col_flag[c]: # 如果行或列 flag 是 1,就需要把 matrix 的整行整列改成 0
matrix[r][c]=0
48. 旋转图像【中等】
思路:顺时针旋转 90 度=水平翻转 + 主对角线翻转(转置)
classSolution:
defrotate(self, matrix: List[List[int]])->None:
""" Do not return anything, modify matrix in-place instead. """# 顺时针旋转 90 度 = 水平翻转 + 主对角线翻转
n =len(matrix)
# 水平翻转for i inrange(n //2):
for j inrange(n):
matrix[i][j], matrix[n - i -1][j]= matrix[n - i -1][j], matrix[i][j]
# 主对角线翻转for i inrange(n):
for j inrange(i):
matrix[i][j], matrix[j][i]= matrix[j][i], matrix[i][j]
也可以用辅助矩阵实现变换
classSolution:
defrotate(self, matrix: List[List[int]])->None:
""" Do not return anything, modify matrix in-place instead. """# 顺时针旋转 90 度:原来的 i 行 j 列 => 旋转后的倒数 i 列 j 行
n =len(matrix)
matrix_rot =[[0]* n for _ inrange(n)]
for i inrange(n):
for j inrange(n):
matrix_rot[j][n - i -1]= matrix[i][j]
matrix[:]= matrix_rot
在此基础上,可以进一步避免辅助矩阵【TODO】
classSolution:
defrotate(self, matrix: List[List[int]])->None:
n =len(matrix)
for i inrange(n //2):
for j inrange((n +1)//2):
matrix[i][j], matrix[n - j -1][i], matrix[n - i -1][n - j -1], matrix[j][n - i -1] \
= matrix[n - j -1][i], matrix[n - i -1][n - j -1], matrix[j][n - i -1], matrix[i][j]
867. 转置矩阵【简单】
思路:行列元素交换,注意不一定是方阵
classSolution:
deftranspose(self, matrix: List[List[int]])-> List[List[int]]:
rows =len(matrix)
cols =len(matrix[0])
ans =[[0]* rows for _ inrange(cols)] # cols * rowsfor i inrange(rows):
for j inrange(cols):
ans[j][i]= matrix[i][j]
return ans
数学计算
69. x 的平方根【简单】
思路:二分法
classSolution:
defmySqrt(self, x:int)->int:
left, right =0, x
ans =-1while left <= right:
mid =(left + right)//2if mid **2<= x:
ans = mid
left = mid +1else:
right = mid -1return ans
defRandN(self, n:int): # 以 1/n 的概率返回 1~n 之间的数# int(res, 2)指二进制转十进制
k = math.log2(n)+1
res =''for _ inrange(k):
res +=str(Rand1())
ifint(res,2)> n:
return RandN(n) # 重新生成else:
returnint(res,2)
343. 整数拆分【中等】
思路:DP
classSolution:
defintegerBreak(self, n:int)->int:
if n ==1:
return1
dp =[0]*(n +1) # dp[i]表示整数 i 时的乘积最大值
dp[1]=1for i inrange(2, n +1):
for j inrange(0, i): # 对于整数 i,可以拆分成 j 和 i-j
dp[i]=max(dp[i], j *(i - j), j * dp[i - j]) # j*(i-j)表示两段都不能拆,j*dp[i-j]表示有一段还能拆(和 dp[j]*(i-j)是对称的)return dp[n]
263. 丑数【简单】
思路:循环除 2、3、5,检查最后是否为 1
classSolution:
defisUgly(self, n:int)->bool:
if n <=0:
returnFalse
while n %2==0:
n //=2while n %3==0:
n //=3while n %5==0:
n //=5
returnTrueif n ==1elseFalse
classSolution:
defaddDigits(self, num:int)->int:
if num ==0:
return0
while num >=10: # 只要不是个位数,就要继续
sum_val =0while num >0:
sum_val += num %10
num //=10
num =sum_val
return num
classSolution:
defaddDigits(self, num:int)->int:
if num ==0:
return0
if num %9==0:
return9
return num %9
也可以统一写法如下:
classSolution:
defaddDigits(self, num:int)->int:
if num ==0:
return0
return(num -1)%9+1
202. 快乐数【简单】
思路:哈希判断重复元素
classSolution:
defisHappy(self, n:int)->bool:
if n ==1:
returnTrue
defcompute_next(n):
sum_val =0while n >0:
sum_val +=(n %10)**2
n //=10return sum_val
# 核心思想:如果结果回不到 1,则一定会出现重复的数(当然也可以用快慢指针做,参考 141.环形链表)
hash_table =set()
while n !=1:
if n in hash_table:
break
hash_table.add(n)
n = compute_next(n)
if n ==1:
returnTrue
returnFalse
如果空间复杂度 O(1),则用快慢指针判断重复元素,参考 141. 环形链表。
classSolution:
defisHappy(self, n:int)->bool:
if n ==1:
returnTrue
defcompute_next(n):
sum_val =0while n >0:
sum_val +=(n %10)**2
n //=10return sum_val
# 核心思想:快慢指针判断环,参考 141.环形链表
slow, fast = n, compute_next(n)
whileTrue:
if slow == fast: # 快慢指针相遇,说明出现重复数,一定到不了 1
returnFalse
if fast ==1: # 如果能到 1,则一定是 fast 指针先到
returnTrue
slow = compute_next(slow) # slow 指针走一步
fast = compute_next(compute_next(fast)) # fast 指针走两步
图
207. 课程表【中等】
思路:用 DFS 判断图是否存在环
classSolution:
defcanFinish(self, numCourses:int, prerequisites: List[List[int]])->bool:
# 思路:判断节点构成的图是否存在环,存在说明无法存在冲突,则无法完成课程# 构图:存储节点与其后继节点
graph =[[]for _ inrange(numCourses)]
for a, b in prerequisites:
graph[b].append(a)
states =[0]* numCourses # 定义节点状态""" 0:节点 x 未被访问
1:节点 x 正在访问中,dfs(x)尚未结束
2:节点 x 已经完全访问完,说明从 x 出发无法找到环 """# DFS:返回 True 表示找到了环defdfs(x):
states[x]=1for node in graph[x]:
if states[node]==1or(states[node]==0and dfs(node)==True):
returnTrue
states[x]=2
returnFalse
for i, s inenumerate(states):
if s ==0and dfs(i)==True: # 存在环
returnFalse
returnTrue