247 KiB
247 KiB
两指针
27. 移除元素
快慢指针法
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
# 快慢指针
fast = 0 # 快指针
slow = 0 # 慢指针
size = len(nums)
while fast < size: # 不加等于是因为,a = size 时,nums[a] 会越界
# slow 用来收集不等于 val 的值,如果 fast 对应值不等于 val,则把它与 slow 替换
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
# 15. 三数之和
# 判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 请你找出所有满足条件且不重复的三元组。两指针
class Solution:
def threeSum(self, nums: List[int]) -> List[List[int]]:
result = []
nums.sort()
for i in range(len(nums)):
# 如果第一个元素已经大于0,不需要进一步检查
if nums[i] > 0:
return result
# 跳过相同的元素以避免重复
if i > 0 and nums[i] == nums[i - 1]:
continue
left = i + 1
right = len(nums) - 1
while right > left:
sum_ = nums[i] + nums[left] + nums[right]
if sum_ < 0:
left += 1
elif sum_ > 0:
right -= 1
else:
result.append([nums[i], nums[left], nums[right]])
# 跳过相同的元素以避免重复
while right > left and nums[right] == nums[right - 1]:
right -= 1
while right > left and nums[left] == nums[left + 1]:
left += 1
right -= 1
left += 1
return result
# 1 4Sum/四数之和 (Medium)
class Solution:
def fourSum(self, nums: List[int], target: int) -> List[List[int]]:
nums.sort()
n = len(nums)
res = []
for i in range(n):
if i > 0 and nums[i] == nums[i - 1]: continue
for k in range(i+1, n):
if k > i + 1 and nums[k] == nums[k-1]: continue
p = k + 1
q = n - 1
while p < q:
if nums[i] + nums[k] + nums[p] + nums[q] > target: q -= 1
elif nums[i] + nums[k] + nums[p] + nums[q] < target: p += 1
else:
res.append([nums[i], nums[k], nums[p], nums[q]])
while p < q and nums[p] == nums[p + 1]: p += 1
while p < q and nums[q] == nums[q - 1]: q -= 1
p += 1
q -= 1
return res
# 26 Remove Duplicates from Sorted Array
def removeDuplicates(nums):
if not nums:
return 0
# 使用双指针法
slow = 0 # 慢指针,指向不重复的元素
for fast in range(1, len(nums)):
if nums[fast] != nums[slow]:
slow += 1
nums[slow] = nums[fast]
return slow + 1 # 返回新数组的长度
#R 75 Sort Colors/颜色分类 (Medium)
# p0:指向 0 应该放置的位置;p0左边全是0, p0本身并不包括0
# p2:指向 2 应该放置的位置;p2右边全是0, p2本身并不包括2
# i:当前遍历位置
class Solution:
def sortColors(self, nums: List[int]) -> None:
p0, i , p2 = 0,0,len(nums) - 1
while i <= p2:
if nums[i] == 0:
nums[i], nums[p0] = nums[p0], nums[i]
p0 += 1
i += 1
elif nums[i] == 2:
nums[i], nums[p2] = nums[p2], nums[i]
p2 -= 1
# 注意:i 不增加,因为换过来的 nums[i] 还要检查
else:
i += 1
# 88 Merge Sorted Array/合并两个有序数组 (Easy)
class Solution:
def merge(self, nums1: List[int], m: int, nums2: List[int], n: int) -> None:
tail = len(nums1) - 1
i1 = m - 1
i2 = n - 1
# 个人经常用 while true,然后在循环内部列出所有情况
while True:
# 都左移到头,退出
if i1 == -1 and i2 == -1:
break
# 一方左移到头,选取另一方赋值,然后左移
elif i1 == -1:
nums1[tail] = nums2[i2]
i2 -= 1
elif i2 == -1:
nums1[tail] = nums1[i1] # 也可以省去,直接 i1 -= 1;
i1 -= 1
# 选取大的元素赋值,然后左移
elif nums1[i1] > nums2[i2]:
nums1[tail] = nums1[i1]
i1 -= 1
else:
nums1[tail] = nums2[i2]
i2 -= 1
tail -= 1
# 167 Two Sum II - Input array is sorted $/两数之和 II - 输入有序数组 (Medium)
class Solution:
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers) - 1
while i < j:
s = numbers[i] + numbers[j]
if s > target: j -= 1
elif s < target: i += 1
else: return i + 1, j + 1
return []
# 283 Move Zeroes/移动零 (Easy) 将所有 0 移动到数组的末尾
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
# 345 "Reverse Vowels of a String"
def reverseVowels(s):
vowels = set("aeiouAEIOU")
s = list(s)
left, right = 0, len(s) - 1
while left < right:
while left < right and s[left].lower() not in vowels:
left += 1
while left < right and s[right].lower() not in vowels:
right -= 1
# 交换左右两侧的元音字母
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
# 415 Add Strings/字符串相加 (Easy)
class Solution:
def addStrings(self, num1: str, num2: str) -> str:
res = ""
i, j, carry = len(num1) - 1, len(num2) - 1, 0
while i >= 0 or j >= 0:
n1 = int(num1[i]) if i >= 0 else 0
n2 = int(num2[j]) if j >= 0 else 0
tmp = n1 + n2 + carry
carry = tmp // 10
res = str(tmp % 10) + res
i, j = i - 1, j - 1
return "1" + res if carry else res
# 443 String Compression/压缩字符串 (Easy)
# ["a","2","b","2","c","3"]
# 三指针:指针1当前字符,指针2找这个字符有连续多少个,指针3当前在数组中的读写位置。
class Solution:
def compress(self, chars: List[str]) -> int:
n = len(chars)
i = 0
write = 0
while i < n:
j = i
while j < n and chars[j] == chars[i]:
j += 1
chars[write] = chars[i]
write += 1
if j - i > 1:
for c in str(j-i):
chars[write] = c
write += 1
i = j
return write
# 0 Split Array with Equal Sum $/将数组分割成和相等的子数组 (Medium)
# 子数组 (0, i - 1) , (i + 1, j - 1) , (j + 1, k - 1) , (k + 1, n - 1) 的相等,左闭右闭
class Solution:
def splitArray(self, nums: List[int]) -> bool:
n = len(nums)
presum = [0 for _ in range(n + 1)] #用的虚指(从1开始计数)
for i in range(n):
presum[i + 1] = presum[i] + nums[i]
#(1) L (2) M (3) R (4) 3个指针,4个区域
for M in range(3, n - 3):
memo = set()
for L in range(1, M - 1):
zoom1 = presum[L]
zoom2 = presum[M] - presum[L + 1]
if zoom1 == zoom2:
memo.add(zoom1)
for R in range(M + 2, n - 1):
zoom3 = presum[R] - presum[M + 1]
zoom4 = presum[n] - presum[R + 1]
if zoom3 == zoom4 and zoom3 in memo:
return True
return False
# 633 Sum of Square Numbers/平方数之和 (Easy)
class Solution:
def judgeSquareSum(self, c: int) -> bool:
low, high = 0, int(c**0.5)
while low<=high:
sumOf = low*low+high*high
if sumOf==c: return True
elif sumOf<c: low += 1
else: high -= 1
return False
#R 259 3Sum Smaller $/较小的三数之和 (Medium)
# satisfy the condition nums[i] + nums[j] + nums[k] < target
class Solution:
def threeSumSmaller(self, nums, target):
if len(nums) < 3:
return 0
res = 0
nums.sort()
n = len(nums)
for i in range(n - 2):
left, right = i + 1, n - 1
while left < right:
if nums[i] + nums[left] + nums[right] < target:
res += right - left # 所有 (nums[i], nums[left], nums[left+1]...nums[right]) 都满足条件
left += 1
else:
right -= 1
return res
# 977.有序数组的平方
class Solution:
def sortedSquares(self, nums: List[int]) -> List[int]:
l, r, i = 0, len(nums)-1, len(nums)-1
res = [float('inf')] * len(nums) # 需要提前定义列表,存放结果
while l <= r:
if nums[l] ** 2 < nums[r] ** 2: # 左右边界进行对比,找出最大值
res[i] = nums[r] ** 2
r -= 1 # 右指针往左移动
else:
res[i] = nums[l] ** 2
l += 1 # 左指针往右移动
i -= 1 # 存放结果的指针需要往前平移一位
return res
# 845 "Longest Mountain in Array" 给定一个整数数组 A,找出数组中的最长山脉的长度
# 遍历数组中的每个可能的山顶,然后从山顶向左右两侧扩展,找到山脉的左侧和右侧
def longestMountain(A):
n = len(A)
if n < 3:
return 0
max_length = 0
for peak in range(1, n - 1):
left, right = peak, peak
# 找到山脉的左侧
while left > 0 and A[left - 1] < A[left]:
left -= 1
# 找到山脉的右侧
while right < n - 1 and A[right + 1] < A[right]:
right += 1
# 确保是山脉
if left < peak < right:
max_length = max(max_length, right - left + 1)
return max_length
# 985.9 Interval List Intersections/区间列表的交集 (Medium)
class Solution:
def intervalIntersection(self, A: List[List[int]], B: List[List[int]]) -> List[List[int]]:
ans = []
i = j = 0
while i < len(A) and j < len(B):
lo = max(A[i][0], B[j][0])
hi = min(A[i][1], B[j][1])
if lo <= hi:
ans.append([lo, hi])
if A[i][1] < B[j][1]:
i += 1
else:
j += 1
return ans
# 1155.9 Swap For Longest Repeated Character Substring/单字符重复子串的最大长度 (Medium)
# 你只能交换其中两个字符一次或者什么都不做,返回单字符最长的子串的长度。
# j 指向 i,并不断地向右移动 j,直到 j 指向的字符与 i 指向的字符不同,跳过指针 j 指向的字符,用指针 k 继续向右移动,直到 k 指向的字符与 i 指向的字符不同
class Solution:
def maxRepOpt1(self, text: str) -> int:
cnt = Counter(text)
n = len(text)
ans = i = 0
while i < n:
j = i
while j < n and text[j] == text[i]:
j += 1
l = j - i
k = j + 1
while k < n and text[k] == text[i]:
k += 1
r = k - j - 1
ans = max(ans, min(l + r + 1, cnt[text[i]]))
i = j
return ans
#H 1163 Last Substring in Lexicographical Order/按字典序排在最后的子串 (Hard)
# 找出它的所有子串并按字典序排列,返回排在最后的那个子串。
# i指向的是当前找到字典序最大的字符,j指向的是当前要进行比较的字符。使用一个位移指针k,来比较i和j构成的子串[i,..,i + k]和[j,...,j + k]的顺序。i始终指向当前找到字典序最大的字符
class Solution:
def lastSubstring(self, s: str) -> str:
n = len(s)
i = 0
j = 1
k = 0
while j + k < n:
if s[i + k] == s[j + k]:
k += 1
elif s[i + k] < s[j + k]:
i += k + 1
k = 0
if i >= j:
j = i + 1
else:
j += k + 1
k = 0
return s[i:]
滑动窗口
209.长度最小的子数组
class Solution:
def minSubArrayLen(self, s: int, nums: List[int]) -> int:
l = len(nums)
left = 0
right = 0
min_len = float('inf')
cur_sum = 0 #当前的累加值
while right < l:
cur_sum += nums[right]
while cur_sum >= s: # 当前累加值大于目标值
min_len = min(min_len, right - left + 1)
cur_sum -= nums[left]
left += 1
right += 1
return min_len if min_len != float('inf') else 0
# Max Consecutive Ones II 你可以改变其中最多 k 个 0 的位,求该数组中最大连续1的个数,滑动窗口
def findMaxConsecutiveOnes(nums, k):
left, right = 0, 0
max_length = 0
zero_count = 0
while right < len(nums):
if nums[right] == 0:
zero_count += 1
while zero_count > k:
if nums[left] == 0:
zero_count -= 1
left += 1
max_length = max(max_length, right - left + 1)
right += 1
return max_length
# 340 "Longest Substring with At Most K Distinct Characters" 给定一个字符串 s,找出至多包含 k 个不同字符的最长子串的长度,滑动窗口
def lengthOfLongestSubstringKDistinct(s, k):
if k == 0:
return 0
left, right = 0, 0
char_count = {}
max_length = 0
while right < len(s):
char_count[s[right]] = char_count.get(s[right], 0) + 1
while len(char_count) > k:
char_count[s[left]] -= 1
if char_count[s[left]] == 0:
del char_count[s[left]]
left += 1
max_length = max(max_length, right - left + 1)
right += 1
return max_length
#R 1358 Number of Substrings Containing All Three Characters/包含所有三种字符的子字符串数目 (Medium)
# 请你返回 a,b 和 c 都 至少 出现过一次的子字符串数目。
# 滑动窗口的内层循环结束时,右端点固定在 right,左端点在 0,1,2,…,left−1 的所有子串都是合法的,这一共有 left 个,加入答案。
class Solution:
def numberOfSubstrings(self, s: str) -> int:
ans = left = 0
cnt = defaultdict(int)
for c in s:
cnt[c] += 1
while len(cnt) == 3:
out = s[left] # 离开窗口的字母
cnt[out] -= 1
if cnt[out] == 0:
del cnt[out]
left += 1
ans += left
return ans
#R 395 Longest Substring with At Least K Repeating Characters/至少有 K 个重复字符的最长子串 (Medium) s = "ababbc", k = 2 最长子串为 "ababb"
class Solution:
def longestSubstring(self, s: str, k: int) -> int:
ans = 0
for i in range(1,27): #分治:遍历可能的字符种类数量(1~26)
cnt = Counter() #计数
unique = 0 #字符种类
num_k = 0 #满足出现次数大于等于k的字符串数量
left = 0
for right,char in enumerate(s):
if cnt[char] == 0:
unique += 1
cnt[char] += 1
if cnt[char] == k:
num_k += 1
#当字符串种类超过i时,移动左窗口
while unique > i:
left_char = s[left]
if cnt[left_char] == k:
num_k -= 1 #因为要一直移动左窗口,所以计数会一直减少,当进入循环时刚好==k时,再减一后,num_k就要减一了
cnt[left_char] -= 1
if cnt[left_char] == 0: #如果减完了,种类就少一个
unique -= 1
left += 1
if unique == i and num_k == i: #当种类满足要求,且子串里的数量都满足>=k时,就更新ans
ans = max(ans ,right - left +1)
return ans
#R 424 Longest Repeating Character Replacement/替换后的最长重复字符 (Medium) 可替换字符k次
class Solution:
def characterReplacement(self, s: str, k: int) -> int:
count = [0 for _ in range(26)] #记录当前窗口的字母出现次数
left = 0 #滑动窗口左边界
right = 0 #滑动窗口右边界
retval = 0 #最长窗口长度
while right < len(s):
count[ord(s[right])-ord('A')] += 1
benchmark = max(count) #选择出现次数最多的字母为基准
others = sum(count) - benchmark #则其他字母需要通过替换操作来变为基准
if others <= k: #通过与K进行比较来判断窗口是进行扩张?
right += 1
retval = max(retval, right-left)#记录当前有效窗口长度
else: #通过与K进行比较来判断窗口还是进行位移?
count[ord(s[left])-ord('A')] -= 1
left += 1
right += 1 #这里注意:位移操作需要整个向右移,不仅仅只是left向右
return retval #返回最长窗口长度
# 486.99 Max Consecutive Ones II $/最大连续1的个数 II (Medium)
# 最多可以翻转一个 0 ,则返回数组中连续 1 的最大个数
class Solution:
def findMaxConsecutiveOnes(self, nums: List[int]) -> int:
res=l=count_zero = 0
for r, num in enumerate(nums):
if not num :
count_zero +=1
while count_zero >1 :
if not nums[l]:
count_zero-=1
l+=1
res = max(res, r-l+1)
return res
# 1100 Find K-Length Substrings With No Repeated Characters/长度为 K 的无重复字符子串 (Medium)
# 找出所有长度为 K 且不含重复字符的子串,返回子串数目。
class Solution:
def numKLenSubstrNoRepeats(self, s: str, k: int) -> int:
ans = 0
cnt = Counter(s[: k - 1])
for i, (in_, out) in enumerate(zip(s[k - 1 :], s)):
cnt[in_] += 1
if len(cnt) == k:
ans += 1
cnt[out] -= 1
if cnt[out] == 0:
del cnt[out]
return ans
# 验证子列不带顺序 + 连续 permutation-in-string,定长滑动窗口
class Solution:
def checkInclusion(self, s1: str, s2: str) -> bool:
m, n = len(s1), len(s2)
if m > n:
return False
cnt = collections.Counter(s1) # 哈希表:记录需要匹配到的各个字符的数目
need = m # 记录需要匹配到的字符总数【need=0表示匹配到了】
for right in range(n):
# 窗口右边界
ch = s2[right] # 窗口中新加入的字符
if ch in cnt: # 新加入的字符ch位于s1中
if cnt[ch] > 0: # 此时新加入窗口中的字符ch对need有影响
need -= 1
cnt[ch] -= 1
# 窗口左边界
left = right - m
if left >= 0:
ch = s2[left]
if ch in cnt: # 刚滑出的字符位于s1中
if cnt[ch] >= 0: # 此时滑出窗口的字符ch对need有影响
need += 1
cnt[ch] += 1
if need == 0: # 找到了一个满足题意的窗口
return True
return False
# 1.最长连续公共子列,2.最长不连续公共子列,3.S 中最长重复的子列==718?
#? 4.允许swap一次的最长重复子列,5.两个字交错和
# 3. Longest Substring Without Repeating Characters 子串必须连续,不同于子序列
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic, res, i = {}, 0, -1
for j in range(len(s)):
if s[j] in dic:
i = max(dic[s[j]], i) # 更新左指针 i
dic[s[j]] = j # 哈希表记录
res = max(res, j - i) # 更新结果
return res
二分法
# 0. 二分查找
class Solution:
def search(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1 # 定义target在左闭右闭的区间里,[left, right]
while left <= right:
middle = left + (right - left) // 2
if nums[middle] > target:
right = middle - 1 # target在左区间,所以[left, middle - 1]
elif nums[middle] < target:
left = middle + 1 # target在右区间,所以[middle + 1, right]
else:
return middle # 数组中找到目标值,直接返回下标
return -1 # 未找到目标值
# 34 Search for a Range/在排序数组中查找元素的第一个和最后一个位置 (Medium) 二分法
# 实现 upper_bound (bisect.bisect_right)和 lower_bound(bisect.bisect_left)
def upper_bound(vec, target):
l, r = 0, len(vec) - 1
while l <= r:
m = (l + r) // 2
if vec[m] > target:
r = m - 1
else:
l = m + 1
return l
def lower_bound(vec, target):
l, r = 0, len(vec) - 1
while l <= r:
m = (l + r) // 2
if vec[m] >= target:
r = m - 1
else:
l = m + 1
return l
vec,tgt=[0,2,2,2,2,3,4,5,6],2
# print(upper_bound(vec,tgt),lower_bound(vec,tgt)) # ans=5,1
# 56.9 Insert Interval/插入区间 (Hard)
class Solution:
def insert(
self, intervals: List[List[int]], newInterval: List[int]
) -> List[List[int]]:
new_start, new_end = newInterval
n = len(intervals)
left = bisect.bisect_left(
range(n),
True,
key=lambda i: intervals[i][1] >= new_start,
)
right = bisect.bisect_left(
range(n),
True,
key=lambda i: intervals[i][0] > new_end,
)
if left < n:
new_start = min(new_start, intervals[left][0])
if right > 0:
new_end = max(new_end, intervals[right - 1][1])
return intervals[:left] + [[new_start, new_end]] + intervals[right:]
#H LeetCode 33 "Search in Rotated Sorted Array"
def search(nums, target):
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] == target:
return mid
# 判断哪一部分是有序的
if nums[left] <= nums[mid]: # 左半段有序
if nums[left] <= target < nums[mid]:
right = mid - 1
else:
left = mid + 1
else: # 右半段有序
if nums[mid] < target <= nums[right]:
left = mid + 1
else:
right = mid - 1
return -1 # 没有找到目标值
#H 81 Search in Rotated Sorted Array II/搜索旋转排序数组 II (Medium)
# 数组中的值不必互不相同,基于 33 题的简洁写法,只需增加一个 if
class Solution:
def search(self, nums: List[int], target: int) -> bool:
if not nums:
return False
n = len(nums)
if n == 1:
return nums[0] == target
l, r = 0, n - 1
while l <= r:
mid = (l + r) // 2
if nums[mid] == target:
return True
if nums[l] == nums[mid] and nums[mid] == nums[r]:
l += 1
r -= 1
elif nums[l] <= nums[mid]:
if nums[l] <= target and target < nums[mid]:
r = mid - 1
else:
l = mid + 1
else:
if nums[mid] < target and target <= nums[n - 1]:
l = mid + 1
else:
r = mid - 1
return False
# Search a 2D Matrix
class Solution(object):
def searchMatrix(self, matrix, target):
m = len(matrix)
if m == 0:
return False
n = len(matrix[0])
l,r = 0, m*n-1
while l <= r:
mid = (l+r)//2
i = mid//n
j = mid%n
pivot = matrix[i][j]
if pivot == target:
return True
if pivot < target:
l = mid + 1
else:
r = mid -1
return False
# LeetCode 240 "Search a 2D Matrix II"
def searchMatrix(matrix, target):
if not matrix or not matrix[0]:
return False
rows, cols = len(matrix), len(matrix[0])
row, col = 0, cols - 1 # 从矩阵的右上角开始搜索
while row < rows and col >= 0:
if matrix[row][col] == target:
return True
elif matrix[row][col] < target:
row += 1 # 如果当前元素小于目标值,说明当前行不可能存在目标值,向下移动一行
else:
col -= 1 # 如果当前元素大于目标值,说明当前列不可能存在目标值,向左移动一列
return False
# 1237 枚举 xxx,然后在二分查找;或下面的相向双指针方法
class Solution:
def findSolution(self, customfunction: 'CustomFunction', z: int) -> List[List[int]]:
ans = []
x, y = 1, 1000
while x <= 1000 and y:
res = customfunction.f(x, y)
if res < z:
x += 1
elif res > z:
y -= 1
else:
ans.append([x, y])
x += 1
y -= 1
return ans
#R 378 Kth Smallest Element in a Sorted Matrix/有序矩阵中第 K 小的元素 (Medium)
class Solution:
def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
n = len(matrix)
def check(mid):
"""遍历获取较小元素部分元素总数,并与k值比较"""
i, j = n-1, 0
num = 0
while i >= 0 and j < n:
if matrix[i][j] <= mid:
# 当前元素小于mid,则此元素及上方元素均小于mid
num += i + 1
# 向右移动
j += 1
else:
# 当前元素大于mid,则向上移动,直到找到比mid小的值,或者出矩阵
i -= 1
return num >= k
left, right = matrix[0][0], matrix[-1][-1]
while left < right:
mid = (left + right) >> 1
if check(mid):
# 满足 num >= k,范围太大,移动right至mid, 范围收缩
right = mid
else:
left = mid + 1
return left
#R 410 Split Array Largest Sum/分割数组的最大值 (Hard)
# 分成 k 个非空的连续子数组,使得这 k 个子数组各自和的最大值 最小。
class Solution:
def splitArray(self, nums: List[int], k: int) -> int:
def check(mx):
s, cnt = inf, 0
for x in nums:
s += x
if s > mx:
s = x
cnt += 1
return cnt <= k
left, right = max(nums), sum(nums)
return left + bisect_left(range(left, right + 1), True, key=check)
#R 中位数可以奇偶统一表示
def get_median_universal(nums):
sorted_nums = sorted(nums)
n = len(sorted_nums)
median = (sorted_nums[(n - 1) // 2] + sorted_nums[n // 2]) / 2.0
return median
# Median of Two Sorted Arrays
import bisect
class Solution:
def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
n = len(nums1) + len(nums2)
n1, n2 = len(nums1), len(nums2)
if n1 == 0: return (nums2[n2//2] + nums2[(n2-1)//2]) / 2
if n2 == 0: return (nums1[n1//2] + nums1[(n1-1)//2]) / 2
minN, maxN = min(nums1[0], nums2[0]), max(nums1[-1], nums2[-1])
nums = list(range(minN, maxN+1))
def func(x):
# nums1和nums2中<=x的数的个数(记为y),和x单调
k1 = bisect.bisect_right(nums1, x)
k2 = bisect.bisect_right(nums2, x)
return k1 + k2
k1 = bisect_right(nums, n//2, key=func)
if n % 2: k2 = k1
else: k2 = bisect_right(nums, (n-1)//2, key=func)
return (nums[k1] + nums[k2]) / 2
# 611 Valid Triangle Number/有效三角形的个数 (Medium)
# ni<=nj 找出最大的满足 nums[k]<nums[i]+nums[j] 的下标 k,这样一来,在 [j+1,k] 范围内的下标都可
class Solution:
def triangleNumber(self, nums: List[int]) -> int:
n = len(nums)
nums.sort()
ans = 0
for i in range(n):
for j in range(i + 1, n):
left, right, k = j + 1, n - 1, j
while left <= right:
mid = (left + right) // 2
if nums[mid] < nums[i] + nums[j]:
k = mid
left = mid + 1
else:
right = mid - 1
ans += k - j
return ans
# 981 Time Based Key-Value Store/基于时间的键值存储 (Medium)
# void set( key, value, timestamp) 存储给定时间戳 timestamp 时的键 key 和值 value。
# String get( key, timestamp) 返回一个值,该值在之前调用了 set,其中 timestamp_prev <= timestamp 。如果有多个这样的值,它将返回与最大 timestamp_prev 的值。如果没有值,则返回空字符串("")。
class TimeMap:
def __init__(self):
self.dct = defaultdict(list)
def set(self, key: str, value: str, timestamp: int) -> None:
self.dct[key].append([timestamp, value])
def get(self, key: str, timestamp: int) -> str:
a = bisect_right(self.dct[key], [timestamp, "z"*101])
if a == 0:
return ""
return (self.dct[key])[a-1][1]
# 1901 Find a Peak Element II/寻找峰值 II (中等)
# 任意两个相邻格子的值都 不相同 。我们考虑第 i 行的最大值,不妨将其下标记为 j。如果 mat[i][j]>mat[i+1][j],那么第 [0,..i] 行中必然存在一个峰值,我们只需要在第 [0,..i] 行中找到最大值即可。
class Solution:
def findPeakGrid(self, mat: List[List[int]]) -> List[int]:
l, r = 0, len(mat) - 1
while l < r:
mid = (l + r) >> 1
j = mat[mid].index(max(mat[mid]))
if mat[mid][j] > mat[mid + 1][j]:
r = mid
else:
l = mid + 1
return [l, mat[l].index(max(mat[l]))]
字符串&哈希表&数组
# 验证子列,顺序+不连续(ab, acbd):392,如果考虑有多少个这样的子列要用动态规划(115. Distinct Subsequences)
# 验证子列不带顺序 + 连续 permutation-in-string,参考滑动窗口部分
# 392. Is Subsequence 动态规划看mycodes, 双指针解法如下
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
if not s: return True
i = 0
for c in t:
if s[i] == c:
i += 1
# 若已经遍历完 s ,则提前返回 true
if i == len(s):
return True
return False
# 验证s, t是不是最多一个字母不同(即|s.size-t.size|<=1):不同字母出现时只需要验证后半段是否一样
def isOneEditDistance(s, t):
# 遍历短的字符串
for i in range(min(len(s), len(t))):
# 比较字符
if s[i] != t[i]:
# 验证后半段是否一样
if len(s) == len(t):
return s[i + 1:] == t[i + 1:]
elif len(s) < len(t):
return s[i:] == t[i + 1:]
else:
return s[i + 1:] == t[i:]
# 前段完全一样,则最后必然多一个,这里假设 s, t 一定不同
return abs(len(s) - len(t)) == 1
# 205. Isomorphic String 如果 s 中的字符可以按某种映射关系替换得到 t ,那么这两个字符串是同构的。
def isIsomorphic(self, s, t):
d1, d2 = {}, {}
for c1, c2 in zip(s, t):
if c1 in d1 and d1[c1] != c2: return False
else: d1[c1] = c2
if c2 in d2 and d2[c2] != c1: return False
else: d2[c2] = c1
return True
# string中获得多位数字
def extract_number_loop(num):
ct = 0
for char in num:
if '0' <= char <= '9':
ct = ct * 10 + int(char)
return ct
# 242.有效的字母异位词
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
record = [0] * 26
for i in s:
#并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
record[ord(i) - ord("a")] += 1
for i in t:
record[ord(i) - ord("a")] -= 1
for i in range(26):
if record[i] != 0:
#record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
return False
return True
# 1002. 查找常用字符
# 给你一个字符串数组 words ,请你找出所有在 words 的每个字符串中都出现的共用字符( 包括重复字符),并以数组形式返回。你可以按 任意顺序 返回答案。
# 示例:输入:words = ["bella","label","roller"] 输出:["e","l","l"]
class Solution:
def commonChars(self, words: List[str]) -> List[str]:
if not words: return []
result = []
hash = [0] * 26 # 用来统计所有字符串里字符出现的最小频率
for i, c in enumerate(words[0]): # 用第一个字符串给hash初始化
hash[ord(c) - ord('a')] += 1
# 统计除第一个字符串外字符的出现频率
for i in range(1, len(words)):
hashOtherStr = [0] * 26
for j in range(len(words[i])):
hashOtherStr[ord(words[i][j]) - ord('a')] += 1
# 更新hash,保证hash里统计26个字符在所有字符串里出现的最小次数
for k in range(26):
hash[k] = min(hash[k], hashOtherStr[k])
# 将hash统计的字符次数,转成输出形式
for i in range(26):
while hash[i] != 0: # 注意这里是while,多个重复的字符
result.extend(chr(i + ord('a')))
hash[i] -= 1
return result
# 349. 两个数组的交集
class Solution:
def intersection(self, nums1: List[int], nums2: List[int]) -> List[int]:
# 使用哈希表存储一个数组中的所有元素
table = {}
for num in nums1:
table[num] = table.get(num, 0) + 1
# 使用集合存储结果
res = set()
for num in nums2:
if num in table:
res.add(num)
del table[num]
return list(res)
# 1. 快乐数
# 快乐数:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1。如果 可以变为 1,那么这个数就是快乐数。
class Solution:
def isHappy(self, n: int) -> bool:
record = set()
while True:
n = self.get_sum(n)
if n == 1:
return True
# 如果中间结果重复出现,说明陷入死循环了,该数不是快乐数
if n in record:
return False
else:
record.add(n)
def get_sum(self,n: int) -> int:
new_num = 0
while n:
n, r = divmod(n, 10)
new_num += r ** 2
return new_num
# 1. 两数之和,使用字典
class Solution:
def twoSum(self, nums: List[int], target: int) -> List[int]:
records = dict()
for index, value in enumerate(nums):
if target - value in records: # 遍历当前元素,并在map中寻找是否有匹配的key
return [records[target- value], index]
records[value] = index # 如果没找到匹配对,就把访问过的元素和下标加入到map中
return []
# 453.99 四数相加II
# 给定四个包含整数的数组列表 A , B , C , D ,计算有多少个元组 (i, j, k, l) ,使得 A[i] + B[j] + C[k] + D[l] = 0。
class Solution(object):
def fourSumCount(self, nums1, nums2, nums3, nums4):
# 使用字典存储nums1和nums2中的元素及其和
hashmap = dict()
for n1 in nums1:
for n2 in nums2:
if n1 + n2 in hashmap:
hashmap[n1+n2] += 1
else:
hashmap[n1+n2] = 1
# 如果 -(n1+n2) 存在于nums3和nums4, 存入结果
count = 0
for n3 in nums3:
for n4 in nums4:
key = - n3 - n4
if key in hashmap:
count += hashmap[key]
return count
# 1198.给定一个二维整数数组 mat,其中每一行都是升序排列的,找到一个最小的正整数,使得每一行中都存在这个数。
def smallestCommonElement(mat):
# 哈希表,记录每个元素在各行中的出现次数
element_count = {}
# 遍历每一行,统计每个元素的出现次数
for row in mat:
for num in row:
if num not in element_count:
element_count[num] = 1
else:
element_count[num] += 1
# 遍历哈希表,找到所有行中都存在的最小正整数
for num, count in sorted(element_count.items()):
if count == len(mat):
return num
return -1 # 如果没有找到符合条件的元素,返回 -1
#R 9 回文数
class Solution:
def isPalindrome(self, x: int) -> bool:
# 同样地,如果数字的最后一位是 0,为了使该数字为回文,则其第一位数字也应该是 0
if x < 0 or (x % 10 == 0 and x != 0):
return False
reverted_number = 0
while x > reverted_number:
reverted_number = reverted_number * 10 + x % 10
x //= 10
# 当数字长度为奇数时,我们可以通过 reverted_number // 10 去除处于中位的数字。
# 例如,当输入为 12321 时,在 while 循环的末尾我们可以得到 x = 12,reverted_number = 123,
# 由于处于中位的数字不影响回文(它总是与自己相等),所以我们可以简单地将其去除。
return x == reverted_number or x == reverted_number // 10
# 12 整数转罗马数字
class Solution:
def intToRoman(self, num: int) -> str:
symbol = ['M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I']
value = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1]
result = ""
temp = num
for i in range(len(value)):
d = temp // value[i]
if d > 0:
result += symbol[i]*d
temp -= d*value[i]
return result
# 0 Longest Common Prefix/最长公共前缀 (Easy)
class Solution:
def longestCommonPrefix(self, strs: List[str]) -> str:
s0 = strs[0]
for j, c in enumerate(s0):
for s in strs:
if j == len(s) or s[j] != c:
return s0[:j]
return s0
# 36 Valid Sudoku/有效的数独 (Easy)
class Solution:
def isValidSudoku(self, board: List[List[str]]) -> bool:
# 初始化行、列、宫的集合
row = [set() for _ in range(9)]
col = [set() for _ in range(9)]
palace = [[set() for _ in range(3)] for _ in range(3)]
# 双重循环遍历数独
for i in range(9):
for j in range(9):
# 获取当前位置的数字
num = board[i][j]
# 跳过空格
if num == ".":
continue
# 重复数字检查
if(num in row[i] or num in col[j] or num in palace[i//3][j//3]):
return False
# 更新数字集合
row[i].add(num)
col[j].add(num)
palace[i//3][j//3].add(num)
return True
# 43 Multiply Strings/字符串相乘 (Medium)
class Solution:
def multiply(self, num1: str, num2: str) -> str:
if num1 == "0" or num2 == "0":
return "0"
m, n = len(num1), len(num2)
l = m+n
ans = [0]*l
for i in range(m-1,-1,-1):
a = int(num1[i])
for j in range(n-1,-1,-1):
b = int(num2[j])
c = a*b + ans[i+j+1]
ans[i+j+1] = c%10
ans[i+j] += c//10
if ans[0]==0:
ans.remove(0)
return ''.join(str(i) for i in ans)
# 49 Anagrams/字母异位词分组 (Medium)
class Solution:
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
d = defaultdict(list)
for s in strs:
d[''.join(sorted(s))].append(s) # sorted(s) 相同的字符串分到同一组
return list(d.values())
# 53.9 Spiral Matrix/螺旋矩阵 (Medium)
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
ans = list()
# 通过次数控制变量控制循环步数
n = len(matrix[0])
m = len(matrix)
# 初始化索引变量
i = 0
j = -1
# 移动方向变量
step = 1
while m > 0 and n > 0: # m n 变化的代表每一横纵可以行走的步数
# 横向循环
for jcnt in range(n):
# 先加再走,避免拐角重复
j += step
ans.append(matrix[i][j])
# 横向走完减少的是纵向可走的次数
m -= 1
# 纵向循环
for icnt in range(m):
# 先加再走,避免拐角重复
i += step
ans.append(matrix[i][j])
# 纵向走完减少的是横向可走的次数
n -= 1
# 改变方向
step = -step
return ans
# 60 Permutation Sequence/排列序列 (Hard)
# 选择第一个元素为 1 后的排列有 (𝑛−1)! 个
class Solution:
def getPermutation(self, n: int, k: int) -> str:
# 初始化数字列表
nums = list(range(1, n + 1))
# 转换为从 0 开始的索引
k -= 1
result = []
# 构造排列
for i in range(n, 0, -1):
fact = factorial(i - 1)
index = k // fact
result.append(str(nums.pop(index)))
k %= fact
return ''.join(result)
# 119 Pascal's Triangle II/杨辉三角 II (Easy)
class Solution:
def getRow(self, rowIndex: int) -> List[int]:
f = [1] * (rowIndex + 1)
for i in range(2, rowIndex + 1):
for j in range(i - 1, 0, -1):
f[j] += f[j - 1]
return f
# 0 反转字符串II/Reverse String II (easy)
# 给定一个字符串 s 和一个整数 k,从字符串开头算起, 每计数至 2k 个字符,就反转这 2k 个字符中的前 k 个字符;如果剩余字符少于 k 个,则将剩余字符全部反转;如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。
class Solution:
def reverseStr(self, s: str, k: int) -> str:
# 字符串末尾如果超过最大长度,则会返回至字符串最后一个值,这个特性可以避免一些边界条件的处理。
def reverse_substring(text):
left, right = 0, len(text) - 1
while left < right:
text[left], text[right] = text[right], text[left]
left += 1
right -= 1
return text
res = list(s)
for cur in range(0, len(s), 2 * k):
res[cur: cur + k] = reverse_substring(res[cur: cur + k])
return ''.join(res)
# 344.反转字符串/Reverse String (easy)
class Solution:
def reverseString(self, s: List[str]) -> None:
# Do not return anything, modify s in-place instead.
left, right = 0, len(s) - 1
# 该方法已经不需要判断奇偶数,经测试后时间空间复杂度比用 for i in range(len(s)//2)更低
# 因为while每次循环需要进行条件判断,而range函数不需要,直接生成数字,因此时间复杂度更低。推荐使用range
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
# 151 翻转字符串中的单词 Reverse Words in a String II $/? (Medium)
class Solution:
def reverseWords(self, str):
left = 0
n = len(str)
for i in range(n + 1):
if i == n or str[i] == ' ':
self.reverse(str, left, i - 1)
left = i + 1
self.reverse(str, 0, n - 1)
def reverse(self, str, left, right):
while left < right:
str[left], str[right] = str[right], str[left]
left += 1
right -= 1
#R 28. 实现 strStr(),next数组:最长的相同的真前后缀长度
class Solution:
def getNext(self, next, s):
j = -1
next[0] = j
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = next[j]
if s[i] == s[j+1]:
j += 1
next[i] = j
def strStr(self, haystack: str, needle: str) -> int:
if not needle:
return 0
next = [0] * len(needle)
self.getNext(next, needle)
j = -1
for i in range(len(haystack)):
while j >= 0 and haystack[i] != needle[j+1]:
j = next[j]
if haystack[i] == needle[j+1]:
j += 1
if j == len(needle) - 1:
return i - len(needle) + 1
return -1
# 459.重复的子字符串
class Solution:
def repeatedSubstringPattern(self, s: str) -> bool:
if len(s) == 0:
return False
nxt = [0] * len(s)
self.getNext(nxt, s)
if nxt[-1] != -1 and len(s) % (len(s) - (nxt[-1] + 1)) == 0:
return True
return False
def getNext(self, nxt, s):
nxt[0] = -1
j = -1
for i in range(1, len(s)):
while j >= 0 and s[i] != s[j+1]:
j = nxt[j]
if s[i] == s[j+1]:
j += 1
nxt[i] = j
return nxt
# 162 Find Peak Element/寻找峰值 (Medium)
# 如果nums[i]<nums[i+1],那么在下标 [i+1,n−1] 中一定存在至少一个峰值。
class Solution:
def findPeakElement(self, nums: List[int]) -> int:
left, right = -1, len(nums) - 1 # 开区间 (-1, n-1)
while left + 1 < right: # 开区间不为空
mid = (left + right) // 2
if nums[mid] > nums[mid + 1]:
right = mid
else:
left = mid
return right
# 166 Fraction to Recurring Decimal/分数到小数 (Medium)
class Solution:
def fractionToDecimal(self, numerator: int, denominator: int) -> str:
if numerator == 0: return "0"
res = []
# 首先判断结果正负, 异或作用就是 两个数不同 为 True 即 1 ^ 0 = 1 或者 0 ^ 1 = 1
if (numerator > 0) ^ (denominator > 0):
res.append("-")
numerator, denominator = abs(numerator), abs(denominator)
# 判读到底有没有小数
a, b = divmod(numerator, denominator)
res.append(str(a))
# 无小数
if b == 0:
return "".join(res)
res.append(".")
# 处理余数
# 把所有出现过的余数记录下来
loc = {b: len(res)}
while b:
b *= 10
a, b = divmod(b, denominator)
res.append(str(a))
# 余数前面出现过,说明开始循环了,加括号
if b in loc:
res.insert(loc[b], "(")
res.append(")")
break
# 在把该位置的记录下来
loc[b] = len(res)
return "".join(res)
# 169 Majority Element/多数元素 (Easy)
# 摩尔投票法: 核心理念为 票数正负抵消 。
class Solution:
def majorityElement(self, nums: List[int]) -> int:
votes = 0
for num in nums:
if votes == 0: x = num
votes += 1 if num == x else -1
return x
# 243 Shortest Word Distance $/最短单词距离 (Easy)
# shortest distance between these two words in the list
class Solution:
def shortestDistance(self, words, word1, word2):
p1, p2 = -1, -1
res = float('inf') # 使用 float('inf') 表示正无穷
for i in range(len(words)):
if words[i] == word1:
p1 = i
elif words[i] == word2:
p2 = i
if p1 != -1 and p2 != -1:
res = min(res, abs(p1 - p2))
return res
# 204 Count Primes/计数质数 (Easy)
def count_primes_py(n):
# 最小的质数是 2
if n < 2:
return 0
isPrime = [1] * n
isPrime[0] = isPrime[1] = 0 # 0和1不是质数,先排除掉
# 埃式筛,把不大于根号 n 的所有质数的倍数剔除
for i in range(2, int(n ** 0.5) + 1):
if isPrime[i]:
isPrime[i * i:n:i] = [0] * ((n - 1 - i * i) // i + 1)
return sum(isPrime)
# 0. Kth Largest Element in an Array/数组中的第K个最大元素 (Medium)
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
pq = [] # 将数组加入小顶堆,堆中维护当前值最大的k个数
for num in nums:
heapq.heappush(pq, num) # 当前元素入堆
if len(pq) > k:
heapq.heappop(pq) # 堆中元素超过k个,弹出最小的那个
return pq[0] # 最后堆顶的即为第k大的数
# 241 Different Ways to Add Parentheses/为运算表达式设计优先级 (Medium)
# 输入:expression = "2-1-1" 输出:[0,2]
# 解释: ((2-1)-1) = 0 (2-(1-1)) = 2
class Solution:
def diffWaysToCompute(self, input: str) -> List[int]:
# 如果只有数字,直接返回
if input.isdigit():
return [int(input)]
res = []
for i, char in enumerate(input):
if char in ['+', '-', '*']:
# 1.分解:遇到运算符,计算左右两侧的结果集
# 2.解决:diffWaysToCompute 递归函数求出子问题的解
left = self.diffWaysToCompute(input[:i])
right = self.diffWaysToCompute(input[i+1:])
# 3.合并:根据运算符合并子问题的解
for l in left:
for r in right:
if char == '+':
res.append(l + r)
elif char == '-':
res.append(l - r)
else:
res.append(l * r)
return res
# 264 Ugly Number II/丑数 II (Medium)
# 计算下一个丑数的候选集 res[a]⋅2 , res[b]⋅3 , res[c]⋅5 。
# 选择丑数候选集中最小的那个作为下一个丑数,填入 res 。
# 将被选中的丑数对应的指针向右移动一格。
class Solution:
def nthUglyNumber(self, n: int) -> int:
res, a, b, c = [1] * n, 0, 0, 0
for i in range(1, n):
n2, n3, n5 = res[a] * 2, res[b] * 3, res[c] * 5
res[i] = min(n2, n3, n5)
if res[i] == n2: a += 1
if res[i] == n3: b += 1
if res[i] == n5: c += 1
return res[-1]
# 289 Game of Life/生命游戏 (Medium)
# 2表示活细胞变成死细胞,3表示死细胞变成活细胞。
class Solution:
def gameOfLife(self, board: List[List[int]]) -> None:
m = len(board) # 行数
n = len(board[0]) # 列数
for i in range(m):
for j in range(n):
count = 0 # 统计每个格子周围八个位置的活细胞数,每个格子计数重置为0
for x in range(-1, 2):
for y in range(-1, 2):
# 枚举周围八个位置,其中去掉本身(x = y = 0)和越界的情况
if (x == 0 and y == 0) or i + x < 0 or i + x >= m or j + y < 0 or j + y >= n: continue
# 如果周围格子是活细胞(1)或者是活细胞变死细胞(2)的,都算一个活细胞
if board[i + x][j + y] == 1 or board[i + x][j + y] == 2: count += 1
if board[i][j] == 1 and (count < 2 or count > 3): board[i][j] = 2 # 格子本身是活细胞,周围满足变成死细胞的条件,标记为2
if board[i][j] == 0 and count == 3: board[i][j] = 3 # 格子本身是死细胞,周围满足复活条件,标记为3
for i in range(m):
for j in range(n):
# 死细胞为0,活细胞变成死细胞为2,都为偶数,模2为0,刚好是死细胞
# 活细胞为1,死细胞变成活细胞为3,都为奇数,模2为1,刚好是活细胞
board[i][j] %= 2
# 346 Moving Average from Data Stream $/数据流中的移动平均值 (Easy)
class MovingAverage:
def __init__(self, size: int):
self.l = size
self.sum = 0
self.r = []
def next(self, val: int) -> float:
if len(self.r)==self.l:
self.sum -= self.r.pop(0) #移除队头元素,这种简单情况可以用python list
self.sum += val
self.r.append(val)
return self.sum/len(self.r)
# 498 Diagonal Traverse/对角线遍历 (Medium)
# 右斜对角线是横纵坐标和为定值,一共有m+n−1个对角线
class Solution:
def findDiagonalOrder(self, mat: List[List[int]]) -> List[int]:
m, n, ans = len(mat), len(mat[0]), []
for k in range(m + n - 1):
if not k % 2:
ans += [mat[x][k-x] for x in range(min(m - 1, k), max(-1, k - n),-1)]
else:
ans += [mat[x][k-x] for x in range(max(0, k - n + 1), min(k + 1, m))]
return ans
# 697 Degree of an Array/数组的度 (Easy)
# 数组的 度 的定义是指数组里任一元素出现频数的最大值,找到与 nums 拥有相同大小的度的最短连续子数组
# 要求的最短子数组的起始和终止位置,由出现次数最多的元素 第一次和最后一次出现的位置 确定。
class Solution:
def findShortestSubArray(self, nums: List[int]) -> int:
left, right = dict(), dict()
counter = collections.Counter()
for i, num in enumerate(nums):
if num not in left:
left[num] = i
right[num] = i
counter[num] += 1
degree = max(counter.values())
res = len(nums)
for k, v in counter.items():
if v == degree:
res = min(res, right[k] - left[k] + 1)
return res
# 0 Shortest Completing Word/最短补全词 (Medium)
# 找到最短的包含所有字母的单词
class Solution:
def shortestCompletingWord(self, licensePlate: str, words: List[str]) -> str:
tmp=[]
for i in licensePlate:
if 'a'<=i<='z' or 'A'<=i<='Z':
tmp.append(i.lower())
words=sorted(words, key=lambda x:len(x))
for word in words:
l=tmp.copy()
for i in word:
if i in l:
l.remove(i)
if not l:
return word
# 835 Image Overlap/图像重叠 (Medium)
# 转换 其中一个图像,将所有的 1 向左,右,上,或下滑动任何数量的单位;最大可能的重叠数量是多少
# 计算两个图像的1之间的移动距离,取计数最大的移动方式
class Solution:
def largestOverlap(self, img1: List[List[int]], img2: List[List[int]]) -> int:
n = len(img1)
cnt = defaultdict(int)
one = []
for i in range(n):
for j in range(n):
if img1[i][j]:
one.append([i, j])
for i in range(n):
for j in range(n):
if img2[i][j]:
for a, b in one:
cnt[(i - a, j - b)] += 1
return max(cnt.values()) if cnt else 0
# 892 Surface Area of 3D Shapes/三维形体的表面积 (Easy)
# 对于四个侧面的表面积,只有在相邻位置的高度小于 v 时,对应的那个侧面才会贡献表面积,且贡献的数量为 v - nv
class Solution:
def surfaceArea(self, grid: List[List[int]]) -> int:
N = len(grid)
ans = 0
for r in range(N):
for c in range(N):
if grid[r][c]:
ans += 2
for nr, nc in ((r - 1, c), (r + 1, c), (r, c - 1), (r, c + 1)):
if 0 <= nr < N and 0 <= nc < N:
nval = grid[nr][nc]
else:
nval = 0
ans += max(grid[r][c] - nval, 0)
return ans
# 970 Powerful Integers/强整数 (Easy)
# 整数可以表示为 x^i + y^j
class Solution:
def powerfulIntegers(self, x: int, y: int, bound: int) -> List[int]:
ans = set()
a = 1
while a <= bound:
b = 1
while a + b <= bound:
ans.add(a + b)
b *= y
if y == 1:
break
if x == 1:
break
a *= x
return list(ans)
# 1010 Pairs of Songs With Total Durations Divisible by 60/总持续时间可被 60 整除的歌曲对 (Medium)
# 可被 60 整除的歌曲对的数量,长度为 60 的数组 cnt 记录每个余数 x 出现的次数。
class Solution:
def numPairsDivisibleBy60(self, time: List[int]) -> int:
cnt = Counter()
ans = 0
for x in time:
x %= 60
y = (60 - x) % 60
ans += cnt[y]
cnt[x] += 1
return ans
# 1013 Partition Array Into Three Parts With Equal Sum/将数组分成和相等的三个部分 (Easy)
# 依次寻找切分点i j
class Solution:
def canThreePartsEqualSum(self, A: List[int]) -> bool:
s = sum(A)
if s % 3 != 0:
return False
target = s // 3
n, i, cur = len(A), 0, 0
while i < n:
cur += A[i]
if cur == target:
break
i += 1
if cur != target:
return False
j = i + 1
while j + 1 < n: # 需要满足最后一个数组非空
cur += A[j]
if cur == target * 2:
return True
j += 1
return False
#R 767 Reorganize String/重构字符串 (Medium)
# 重新排布其中的字母,使得两相邻的字符不同。
# 对于每一种元素,循环在不同桶中进行填充,由于桶的个数等于字符串中最多的元素的数目,因此每个桶中不会出现相同的元素,填充完毕后将桶依次相连即为答案
class Solution:
def reorganizeString(self, s: str) -> str:
cnt, idx = Counter(s), 0
bucketNum = cnt.most_common(1)[0][1] # 桶的数目等于字符串中最多的元素的数目
if bucketNum > (len(s) + 1) // 2: return ""
buckets = [[] for _ in range(bucketNum)]
for c, num in cnt.most_common():
for _ in range(num):
buckets[idx].append(c)
idx = (idx + 1) % bucketNum # 循环在不同桶中进行填充
return "".join(["".join(bucket) for bucket in buckets])
#R 1053.9 Distant Barcodes/距离相等的条形码 (Medium)
# 请你重新排列这些条形码,使其中任意两个相邻的条形码不能相等。
# 我们先统计数组barcodes中各个数出现的次数,然后按照它们出现的次数从大到小排序,依次填入偶数后奇数
class Solution:
def rearrangeBarcodes(self, barcodes: List[int]) -> List[int]:
cnt = Counter(barcodes)
barcodes.sort(key=lambda x: (-cnt[x], x))
n = len(barcodes)
ans = [0] * len(barcodes)
ans[::2] = barcodes[: (n + 1) // 2]
ans[1::2] = barcodes[(n + 1) // 2:]
return ans
# 1232.9 Remove Sub-Folders from the Filesystem/删除子文件夹 (Medium)
# 删除该列表中的所有子文件夹
class Solution:
def removeSubfolders(self, folder: List[str]) -> List[str]:
folder.sort()
ans = [folder[0]]
for f in folder[1:]:
m, n = len(ans[-1]), len(f)
if m >= n or not (ans[-1] == f[:m] and f[m] == '/'):
ans.append(f)
return ans
# 1282 Group the People Given the Group Size They Belong To/用户分组 (Medium)
# groupSizes[i] 是第 i 个人所在的组的大小,返回用户分组[[5],[0,1,2],[3,4,6]]
class Solution:
def groupThePeople(self, groupSizes: List[int]) -> List[List[int]]:
mp, ans = defaultdict(list), []
for i, v in enumerate(groupSizes):
mp[v].append(i)
for k, lt in mp.items():
ans.extend(lt[i:i+k] for i in range(0, len(lt), k))
return ans
# 1369.9 Increasing Decreasing String/上升下降字符串 (简单)
# 从 s 剩余字符中选出比上一个添加字符更大的 最小 字符,将它 接在 结果字符串后面。
# 创建一个大小为 26 的桶记录每种字符的数量。每次提取最长的上升或下降字符串时,我们直接顺序或逆序遍历这个桶。
class Solution:
def sortString(self, s: str) -> str:
num = [0] * 26
for ch in s:
num[ord(ch) - ord('a')] += 1
ret = list()
while len(ret) < len(s):
for i in range(26):
if num[i]:
ret.append(chr(i + ord('a')))
num[i] -= 1
for i in range(25, -1, -1):
if num[i]:
ret.append(chr(i + ord('a')))
num[i] -= 1
return "".join(ret)
# 1452 People Whose List of Favorite Companies Is Not a Subset of Another List/收藏清单 (medium)
# 找出不是其他任何人收藏的公司清单的子集的收藏清单。 暴力匹配。中间加一点剪枝
class Solution:
def peopleIndexes(self, favoriteCompanies: List[List[str]]) -> List[int]:
a = list(set(f) for f in favoriteCompanies)
n = len(a)
res = []
for i in range(n):
for j in range(n):
flag = True
if i != j and len(a[i]) <= len(a[j]):
if a[i].issubset(a[j]): #### 偷懒,直接调库
flag = False
break
if flag == True:
res.append(i)
return res
# 1966 未排序数组中的可被二分搜索的数 (中等)
# 二分搜索的方法是否可以判断 target 是否存在 sequence中。输出所有可以判断的数量
# 需要找到一个下标,他左边的数比他小,右边的数比他大,返回所有这样下标的数量。
# 分别从左往右和从右往左遍历,分别记录起点到i最大值和j到终点的最小值,如果一个数同时是从左往右的最大值和从右往左的最小值,那么符合题意。
class Solution:
def binarySearchableNumbers(self, nums: List[int]) -> int:
n = len(nums)
l, r = float('-inf'), float('inf')
has = [0] * n
for i in range(n):
j = n-1-i
vi, vj = nums[i], nums[j]
if vi > l:
l = vi
has[i] += 1
if vj < r:
r = vj
has[j] += 1
return sum(i==2 for i in has)
# 681.最近时刻 中等
# "01:34" 和 "12:09" 是合法的,“1:34” 和 “12:9” 是不合法的。
# 利用当前出现过的数字构造下一个距离当前时间最近的时刻。每个出现数字都可以被无限次使用。
class Solution:
def nextClosestTime(self, time: str) -> str:
nums, hour, minute = set(time), int(time[:2]), int(time[3:])
m = hour * 60 + minute
while True:
m += 1
hour = m // 60 % 24
minute = m % 60
if hour < 10:
hour = '0' + str(hour)
else:
hour = str(hour)
if minute < 10:
minute = '0' + str(minute)
else:
minute = str(minute)
for i, ch in enumerate(hour + minute):
if ch not in nums:
break
if i == 3:
return hour + ':' + minute
链表
# 203.移除链表元素
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
# 创建虚拟头部节点以简化删除过程
dummy_head = ListNode(next = head)
# 遍历列表并删除值为val的节点
current = dummy_head
while current.next:
if current.next.val == val:
current.next = current.next.next
else:
current = current.next
return dummy_head.next
# 707.设计链表
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummy_head = ListNode()
self.size = 0
def get(self, index: int) -> int:
if index < 0 or index >= self.size:
return -1
current = self.dummy_head.next
for i in range(index):
current = current.next
return current.val
def addAtHead(self, val: int) -> None:
self.dummy_head.next = ListNode(val, self.dummy_head.next)
self.size += 1
def addAtTail(self, val: int) -> None:
current = self.dummy_head
while current.next:
current = current.next
current.next = ListNode(val)
self.size += 1
def addAtIndex(self, index: int, val: int) -> None:
if index < 0 or index > self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = ListNode(val, current.next)
self.size += 1
def deleteAtIndex(self, index: int) -> None:
if index < 0 or index >= self.size:
return
current = self.dummy_head
for i in range(index):
current = current.next
current.next = current.next.next
self.size -= 1
# 206.反转链表
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
cur = head
pre = None
while cur:
temp = cur.next # 保存一下 cur的下一个节点,因为接下来要改变cur->next
cur.next = pre #反转
#更新pre、cur指针
pre = cur
cur = temp
return pre
# 24. 两两交换链表中的节点
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
dummy_head = ListNode(next=head)
current = dummy_head
# 必须有cur的下一个和下下个才能交换,否则说明已经交换结束了
while current.next and current.next.next:
temp = current.next # 防止节点修改
temp1 = current.next.next.next
current.next = current.next.next
current.next.next = temp
temp.next = temp1
current = current.next.next
return dummy_head.next
19.删除链表的倒数第N个节点
class Solution:
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
# 创建一个虚拟节点,并将其下一个指针设置为链表的头部
dummy_head = ListNode(0, head)
# 创建两个指针,慢指针和快指针,并将它们初始化为虚拟节点
slow = fast = dummy_head
# 快指针比慢指针快 n+1 步
for i in range(n+1):
fast = fast.next
# 移动两个指针,直到快速指针到达链表的末尾
while fast:
slow = slow.next
fast = fast.next
# 通过更新第 (n-1) 个节点的 next 指针删除第 n 个节点
slow.next = slow.next.next
return dummy_head.next
# 02.07. 链表相交
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
lenA, lenB = 0, 0
cur = headA
while cur: # 求链表A的长度
cur = cur.next
lenA += 1
cur = headB
while cur: # 求链表B的长度
cur = cur.next
lenB += 1
curA, curB = headA, headB
if lenA > lenB: # 让curB为最长链表的头,lenB为其长度
curA, curB = curB, curA
lenA, lenB = lenB, lenA
for _ in range(lenB - lenA): # 让curA和curB在同一起点上(末尾位置对齐)
curB = curB.next
while curA: # 遍历curA 和 curB,遇到相同则直接返回
if curA == curB:
return curA
else:
curA = curA.next
curB = curB.next
return None
# 142.环形链表II
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
slow = head
fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
# If there is a cycle, the slow and fast pointers will eventually meet
if slow == fast:
# Move one of the pointers back to the start of the list
slow = head
while slow != fast:
slow = slow.next
fast = fast.next
return slow
# If there is no cycle, return None
return None
2. Add Two Numbers(两数相加)
def addTwoNumbers(l1, l2):
dummy = ListNode() # 创建一个哑结点
current = dummy # 初始化指针,指向哑结点
carry = 0 # 进位值
# 遍历两个链表,直到两个链表都遍历完且没有进位
while l1 or l2 or carry:
# 获取当前位置的数字
val1 = l1.val if l1 else 0
val2 = l2.val if l2 else 0
# 计算当前位置的和,考虑进位
total = val1 + val2 + carry
carry, remainder = divmod(total, 10) # 计算进位和当前位置的数字
# 创建新的结点,并将其添加到结果链表中
current.next = ListNode(remainder)
current = current.next
# 移动指针到下一个位置
if l1:
l1 = l1.next
if l2:
l2 = l2.next
return dummy.next # 返回哑结点的下一个结点,即相加结果的链表的头结点
#R 92 Reverse Linked List II/反转链表 II (Medium)
class Solution:
def reverseBetween(self, head: Optional[ListNode], left: int, right: int) -> Optional[ListNode]:
p0 = dummy = ListNode(next=head)
for _ in range(left - 1):
p0 = p0.next
pre = None
cur = p0.next
for _ in range(right - left + 1):
nxt = cur.next
cur.next = pre # 每次循环只修改一个 next,方便大家理解
pre = cur
cur = nxt
p0.next.next = cur
p0.next = pre
return dummy.next
#R 146 LRU Cache/LRU 缓存机制 (Medium)
# get 如果关键字 key 存在于缓存中,则返回关键字的值,否则 -1 。
# put 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该逐出最久未使用的关键字。
class Node:
# 提高访问属性的速度,并节省内存
__slots__ = 'prev', 'next', 'key', 'value'
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache:
def __init__(self, capacity: int):
self.capacity = capacity
self.dummy = Node() # 哨兵节点 dummy.prev指向尾部,dummy.next指向头部
self.dummy.prev = self.dummy
self.dummy.next = self.dummy
self.key_to_node = {}
# 获取 key 对应的节点,同时把该节点移到链表头部
def get_node(self, key: int) -> Optional[Node]:
if key not in self.key_to_node: # 没有这本书
return None
node = self.key_to_node[key] # 有这本书
self.remove(node) # 把这本书抽出来
self.push_front(node) # 放在最上面
return node
def get(self, key: int) -> int:
# 返回关键字的值
node = self.get_node(key) # get_node 会把对应节点移到链表头部
return node.value if node else -1
def put(self, key: int, value: int) -> None:
# 插入key-value 。如果关键字数量超过 capacity ,则应该逐出最久未使用的关键字
node = self.get_node(key)
if node: # 有这本书
node.value = value # 更新 value
return
self.key_to_node[key] = node = Node(key, value) # 新书
self.push_front(node) # 放在最上面
if len(self.key_to_node) > self.capacity: # 书太多了
back_node = self.dummy.prev
del self.key_to_node[back_node.key]
self.remove(back_node) # 去掉最后一本书
# 删除一个节点(抽出一本书)
def remove(self, x: Node) -> None:
x.prev.next = x.next
x.next.prev = x.prev
# 在链表头添加一个节点(把一本书放在最上面)
def push_front(self, x: Node) -> None:
x.prev = self.dummy
x.next = self.dummy.next
x.prev.next = x
x.next.prev = x
# 234 Palindrome Linked List/回文链表 (Easy)
class Solution:
# 876. 链表的中间结点
def middleNode(self, head: Optional[ListNode]) -> Optional[ListNode]:
slow = fast = head
while fast and fast.next:
slow = slow.next
fast = fast.next.next
return slow
# 206. 反转链表
def reverseList(self, head):
def isPalindrome(self, head: Optional[ListNode]) -> bool:
mid = self.middleNode(head)
head2 = self.reverseList(mid)
while head2:
if head.val != head2.val: # 不是回文链表
return False
head = head.next
head2 = head2.next
return True
# 287 Find the Duplicate Number/寻找重复数 (Medium) nums 只有 一个重复的整数
# 将 i 和 nums[i] 视为链表中的父子节点。可转换为环形链表
class Solution:
def findDuplicate(self, nums: List[int]) -> int:
slow = 0
fast = 0
while True:
# fast 前进两次,slow 前进一次
fast = nums[fast]
fast = nums[fast]
slow = nums[slow]
if slow == fast:
break
# ptr == slow 时说明检测到重复元素,两个重复元素同时指向环的入口。
ptr = 0
while ptr != slow:
ptr = nums[ptr]
slow = nums[slow]
return ptr
栈
# 232.用栈实现队列
class MyQueue:
def __init__(self):
self.stack_in = []
self.stack_out = []
def push(self, x: int) -> None:
self.stack_in.append(x)
def pop(self) -> int:
if self.empty():
return None
if self.stack_out:
return self.stack_out.pop()
else:
for i in range(len(self.stack_in)):
self.stack_out.append(self.stack_in.pop())
return self.stack_out.pop()
def peek(self) -> int:
ans = self.pop()
self.stack_out.append(ans)
return ans
def empty(self) -> bool:
return not (self.stack_in or self.stack_out)
# 225. 用队列实现栈
class MyStack:
def __init__(self):
self.queue1 = collections.deque()
self.queue2 = collections.deque()
def push(self, x: int) -> None:
self.queue2.append(x)
while self.queue1:
self.queue2.append(self.queue1.popleft())
self.queue1, self.queue2 = self.queue2, self.queue1
def pop(self) -> int:
return self.queue1.popleft()
def top(self) -> int:
return self.queue1[0]
def empty(self) -> bool:
return not self.queue1
# 20. 有效的括号
class Solution:
def isValid(self, s: str) -> bool:
stack = []
for item in s:
if item == '(':
stack.append(')')
elif item == '[':
stack.append(']')
elif item == '{':
stack.append('}')
elif not stack or stack[-1] != item:
return False
else:
stack.pop()
return True if not stack else False
# 1047 删除字符串中的所有相邻重复项
class Solution:
def removeDuplicates(self, s: str) -> str:
res = list()
for item in s:
if res and res[-1] == item:
res.pop()
else:
res.append(item)
return "".join(res) # 字符串拼接
# 150. 逆波兰表达式求值
from operator import add, sub, mul
class Solution:
op_map = {'+': add, '-': sub, '*': mul, '/': lambda x, y: int(x / y)}
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token not in {'+', '-', '*', '/'}:
stack.append(int(token))
else:
op2 = stack.pop()
op1 = stack.pop()
stack.append(self.op_map[token](op1, op2)) # 第一个出来的在运算符后面
return stack.pop()
# 239. 滑动窗口最大值
from collections import deque
class MyQueue: #单调队列(从大到小
def __init__(self):
self.queue = deque() #这里需要使用deque实现单调队列,直接使用list会超时
#每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。同时pop之前判断队列当前是否为空。
def pop(self, value):
if self.queue and value == self.queue[0]:
self.queue.popleft() #list.pop()时间复杂度为O(n),这里需要使用collections.deque(),保持队列里的数值从大到小。
def push(self, value):
while self.queue and value > self.queue[-1]:
self.queue.pop()
self.queue.append(value)
def front(self):
return self.queue[0]
class Solution:
def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
que = MyQueue()
result = []
for i in range(k): #先将前k的元素放进队列
que.push(nums[i])
result.append(que.front()) #result 记录前k的元素的最大值
for i in range(k, len(nums)):
que.pop(nums[i - k]) #滑动窗口移除最前面元素
que.push(nums[i]) #滑动窗口前加入最后面的元素
result.append(que.front()) #记录对应的最大值
return result
#R 0 Decode String/字符串解码 (Medium) "3[a]2[bc]",输出:"aaabcbc"
class Solution:
def decodeString(self, s: str) -> str:
stack, res, multi = [], "", 0
for c in s:
if c == '[':
stack.append([multi, res])
res, multi = "", 0
elif c == ']':
cur_multi, last_res = stack.pop()
res = last_res + cur_multi * res
elif '0' <= c <= '9':
multi = multi * 10 + int(c)
else:
res += c
return res
#R 227 Basic Calculator II/基本计算器 II (Medium)
# 输入:s = "3+2*2" 输出:7。 当前元素为符号时,更新完栈后要记得更新数字和符号
class Solution:
def calculate(self, s: str) -> int:
stack = []
num = 0; sign = '+'
for i in range(len(s)):
if s[i].isdigit():
num = num*10 + int(s[i])
if s[i] in '+-*/' or i == len(s)-1:
if sign == '+':
stack.append(num)
elif sign == '-':
stack.append(-num)
elif sign == '*':
stack.append(stack.pop() * num)
else:
stack.append(int(stack.pop() / num))
num = 0; sign = s[i]
return sum(stack)
#H 385 Mini Parser/迷你语法分析器 (Medium)
# [123,[456,[789]]] [1,[2]]
class Solution:
def deserialize(self, s: str) -> NestedInteger:
if s[0] != '[':
return NestedInteger(int(s))
stk = []
num, negative = 0, False
for i in range(len(s)):
if s[i] == '[':
stk.append(NestedInteger())
elif s[i] == '-':
negative = True
elif s[i].isdigit():
num = num * 10 + int(s[i])
elif s[i] == ',' or s[i] == ']':
if s[i - 1].isdigit():
if negative:
num *= -1
stk[-1].add(NestedInteger(num))
num, negative = 0, False
if s[i] == ']' and len(stk) > 1:
ni = stk.pop()
stk[-1].add(ni)
return stk.pop()
#R 636 Exclusive Time of Functions/函数的独占时间 (Medium)
# 当函数调用开始时,如果当前有函数正在运行,则当前正在运行函数应当停止,此时计算其的执行时间,然后将调用函数入栈。
# 当函数调用结束时,将栈顶元素弹出,并计算相应的执行时间,如果此时栈顶有被暂停的函数,则开始运行该函数。
# 输入:n = 2, logs = ["0:start:0","1:start:2","1:end:5","0:end:6"] 输出:两个函数的独占时间分别为[3,4]
class Solution:
def exclusiveTime(self, n: int, logs: List[str]) -> List[int]:
ans = [0] * n
st = []
for log in logs:
idx, tp, timestamp = log.split(':')
idx, timestamp = int(idx), int(timestamp)
if tp[0] == 's':
if st:
ans[st[-1][0]] += timestamp - st[-1][1]
st[-1][1] = timestamp
st.append([idx, timestamp])
else:
i, t = st.pop()
ans[i] += timestamp - t + 1
if st:
st[-1][1] = timestamp + 1
return ans
#R 921 Minimum Add to Make Parentheses Valid/使括号有效的最少添加 (Medium)
class Solution:
def minAddToMakeValid(self, s: str) -> int:
stk = []
for c in s:
if c == ')' and stk and stk[-1] == '(':
stk.pop()
else:
stk.append(c)
return len(stk)
单调栈
https://blog.csdn.net/zy_dreamer/article/details/131036101 从左到右遍历元素。单调递增栈:栈顶最小。
- 查找 「比当前元素大的元素」 就用 单调递增栈,查找 「比当前元素小的元素」 就用 单调递减栈。
- 从 「左侧」 查找就看 「插入栈」 时的栈顶元素,从 「右侧」 查找就看 「弹出栈」 时即将插入的元素。
#H 模板,单调递增栈(递减改为小于等于)
def monotoneIncreasingStack(nums):
stack = []
for num in nums:
while stack and num >= stack[-1]:
stack.pop()
stack.append(num)
#R 0 每日温度,观测到更高的气温,至少需要等待的天数
class Solution:
def dailyTemperatures(self, T: List[int]) -> List[int]:
n = len(T)
stack = []
ans = [0 for _ in range(n)]
for i in range(n):
while stack and T[i] > T[stack[-1]]:
index = stack.pop()
ans[index] = (i-index)
stack.append(i)
return ans
#H 0 接雨水,排列的柱子,下雨之后能接多少雨水。
# 方法总结:找上一个更大元素,在找的过程中填坑。while中加了等号,这可以让栈中没有重复元素,以节省空间。 https://www.bilibili.com/video/BV1VN411J7S7/?vd_source=3f76269c2962e01f0d61bbeac282e5d2 单调递减栈。
class Solution:
def trap(self, height: List[int]) -> int:
ans = 0
st = []
for i, h in enumerate(height):
while st and h >= height[st[-1]]:
bottom_h = height[st.pop()]
if not st: # len(st) == 0
break
left = st[-1]
dh = min(height[left], h) - bottom_h # 面积的高
ans += dh * (i - left - 1)
st.append(i)
return ans
#R 0.下一个更大元素 I, 每一nums1中的x在nums2中对应位置右侧的第一个比 x 大的元素。如果不存在 -1
class Solution:
def nextGreaterElement(self, nums1: List[int], nums2: List[int]) -> List[int]:
res = {}
stack = []
for num in reversed(nums2):
while stack and num >= stack[-1]:
stack.pop()
res[num] = stack[-1] if stack else -1
stack.append(num)
return [res[num] for num in nums1]
#H 0 柱状图中最大的矩形。n个非负整数,用来表示柱状图中各个柱子的高度。求该柱状图中勾勒出来的矩形的最大面积。
# 在i左侧的小于h的最近元素的下标left,右侧的小于h的最近元素的下标right,矩形的宽度就是right−left−1
class Solution:
def largestRectangleArea(self, heights: List[int]) -> int:
n, heights, st, ans = len(heights), [0] + heights + [0], [], 0
for i in range(n + 2):
while st and heights[st[-1]] > heights[i]:
ans = max(ans, heights[st.pop(-1)] * (i - st[-1] - 1))
st.append(i)
return ans
#H 85 Maximal Rectangle/最大矩形 (Hard)
class Solution:
def maximalRectangle(self, matrix: List[List[str]]) -> int:
m = len(matrix)
if m == 0: return 0
n = len(matrix[0])
heights = [0] * n
ans = 0
for i in range(m):
for j in range(n):
if matrix[i][j] == "0":
heights[j] = 0
else:
heights[j] += 1
ans = max(ans, self.largestRectangleArea(heights))
return ans
#H 315. 计算右侧小于当前元素的个数 困难
# counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。输入数组反过来插入一个有序数组(降序)中,插入的位置就是在原数组中位于它右侧的元素的个数。
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
sortns = []
res = []
for n in reversed(nums):
idx = bisect.bisect_left(sortns, n)
res.append(idx)
sortns.insert(idx,n)
return res[::-1]
#R 找前面比自己小的元素
def find_smaller_elements(nums):
result = []
stack = [] # 用于存储元素的值
for num in nums:
while stack and stack[-1] >= num:
stack.pop()
if not stack:
result.append(None) # 如果前面没有比当前元素小的元素,可以用 None 表示
else:
result.append(stack[-1])
stack.append(num)
return result
#R 503 Next Greater Element II/下一个更大元素 II (Medium)
class Solution:
def nextGreaterElements(self, nums: List[int]) -> List[int]:
n = len(nums)
ans = [-1] * n
stack = []
for i in range(n * 2 - 1):
while stack and nums[i % n] > nums[stack[-1]]:
ans[stack.pop()] = nums[i % n]
stack.append(i % n)
return ans
#R 768 Max Chunks To Make Sorted II/最多能完成排序的块 II (Hard)
# 分割成若干块 ,分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
# 从左到右,每个分块都有一个最大值,并且这些分块的最大值呈单调递增(非严格递增)321 65
class Solution:
def maxChunksToSorted(self, arr: List[int]) -> int:
stk = []
for v in arr:
if not stk or v >= stk[-1]:
stk.append(v)
else:
mx = stk.pop()
while stk and stk[-1] > v:
stk.pop()
stk.append(mx)
return len(stk)
堆
# 346.99 前 K 个高频元素
#优先级队列=披着队列外衣的堆 时间复杂度:O(nlogk) 空间复杂度:O(n)
import heapq
class Solution:
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
#要统计元素出现频率
map_ = {} #nums[i]:对应出现的次数
for i in range(len(nums)):
map_[nums[i]] = map_.get(nums[i], 0) + 1
#对频率排序
#定义一个小顶堆,大小为k
pri_que = [] #小顶堆
#用固定大小为k的小顶堆,扫描所有频率的数值
for key, freq in map_.items():
heapq.heappush(pri_que, (freq, key))
if len(pri_que) > k: #如果堆的大小大于了K,则队列弹出,保证堆的大小一直为k
heapq.heappop(pri_que)
#找出前K个高频元素,因为小顶堆先弹出的是最小的,所以倒序来输出到数组
result = [0] * k
for i in range(k-1, -1, -1):
result[i] = heapq.heappop(pri_que)[1]
return result
# 23 Merge k Sorted Lists/合并 K 个升序链表 (Hard)
class Solution:
def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]:
setattr(ListNode, "__lt__", lambda a, b: a.val < b.val)
pq = [head for head in lists if head]
heapify(pq)
dummy = cur = ListNode()
while pq:
node = heappop(pq)
if node.next:
heappush(pq, node.next)
cur.next = node
cur = cur.next
return dummy.next
# 295 Find Median from Data Stream/数据流的中位数 (Hard)
# 建立一个小顶堆A和大顶堆B ,各保存列表的一半元素
from heapq import *
class MedianFinder:
def __init__(self):
self.A = [] # 小顶堆,保存较大的一半
self.B = [] # 大顶堆,保存较小的一半
def addNum(self, num: int) -> None:
if len(self.A) != len(self.B):
heappush(self.A, num)
heappush(self.B, -heappop(self.A))
else:
heappush(self.B, -num)
heappush(self.A, -heappop(self.B))
def findMedian(self) -> float:
return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2.0
#R 502 IPO (Hard)
# 贪心 + 排序 + 大顶堆:选择当前可启动的最大利润项目
# 利润profits,启动该项目需要的最小资本capital。最初你的资本为w,选择最多k个项目
class Solution:
def findMaximizedCapital(self, k: int, w: int, profits: List[int], capital: List[int]) -> int:
# 生成索引序列 并 根据资本值对索引进行升序排序
n = len(capital)
indexes = sorted( [i for i in range(n)], key=lambda i: capital[i])
pq = [] # 维护堆内利润值的大顶堆
i = 0
while k > 0:
# 将启动资本小于等于当前资本的项目的利润加入大顶堆
while i < n and capital[indexes[i]] <= w:
heapq.heappush(pq, -profits[indexes[i]]) # 取相反数实现大顶堆
i += 1
if not pq: break # 没有可以启动的项目,后面启动资本更大的项目也无法启动,退出
w += -heappop(pq) # 选择启动资本满足条件的项目中利润最大的那个,更新w
k -= 1
return w
# 1046 Last Stone Weight/? (Easy)
# 从中选出两块 最重的 石头,然后将它们一起粉碎 y-x
class Solution:
def lastStoneWeight(self, stones: List[int]) -> int:
# 初始化
heap = [-stone for stone in stones]
heapq.heapify(heap)
# 模拟
while len(heap) > 1:
x,y = heapq.heappop(heap),heapq.heappop(heap)
if x != y:
heapq.heappush(heap,x-y)
if heap: return -heap[0]
return 0
# 1352.9 最多可以参加的会议数目 (Medium)
# events[i] = [startDayi, endDayi] ,表示会议 i 开始于 startDayi ,结束于 endDayi
# 贪心:对于某一天i,参加满足条件的最早结束的会议。
class Solution:
def maxEvents(self, events: List[List[int]]) -> int:
ans = 0
end = list()
events = sorted(events,reverse=True)
for i in range(1,100010,1):
while events and events[-1][0] == i: #加入这个时刻刚开始会议
heapq.heappush(end, events.pop()[1])
while end and end[0] < i: # 删除不能参加的会议,在这个时刻会议结束了
heapq.heappop(end)
if end:
heapq.heappop(end)
ans += 1
return ans
# 数字合并,可以选择加法或者乘法,每次合并的代价是两个数字的和或者积,使得最终的代价最小。
import heapq
def greedy_merge(numbers):
# 将数字转化为负数,构建最小堆
heap = [-num for num in numbers]
heapq.heapify(heap)
while len(heap) > 1:
# 从堆中取出两个最小的数字
num1 = -heapq.heappop(heap)
num2 = -heapq.heappop(heap)
# 合并两个数字,可以根据需要选择加法或者乘法
merged_num = num1 + num2 # 或者 merged_num = num1 * num2
# 将合并后的数字放回堆中
heapq.heappush(heap, -merged_num)
# 最终堆中剩下的数字即为合并后的结果
result = -heap[0]
return result
# 253.会议室
import heapq
def minMeetingRooms(intervals):
if not intervals:
return 0
# 按照会议的起始时间进行排序
intervals.sort(key=lambda x: x[0])
# 使用最小堆来存储会议的结束时间
end_times = []
heapq.heappush(end_times, intervals[0][1])
# 遍历会议,检查是否有空闲的会议室,如果有则更新结束时间,如果没有则添加新的会议室
for i in range(1, len(intervals)):
if intervals[i][0] >= end_times[0]:
heapq.heappop(end_times)
heapq.heappush(end_times, intervals[i][1])
# 最终堆的大小即为所需的最小会议室数量
return len(end_times)
排序
- 冒泡排序(Bubble Sort): 时间复杂度:平均情况 O(n^2),最坏情况 O(n^2),最好情况 O(n)(如果已经有序);空间复杂度:O(1)(原地排序)。
- 选择排序(Selection Sort): 时间复杂度:平均情况 O(n^2),最坏情况 O(n^2),最好情况 O(n^2);空间复杂度:O(1)(原地排序)。
- 插入排序(Insertion Sort): #时间复杂度:平均情况 O(n^2),最坏情况 O(n^2),最好情况 O(n)(如果已经有序); 空间复杂度:O(1)(原地排序)。
- 归并排序(Merge Sort): 时间复杂度:平均情况 O(n log n),最好情况 O(n log n),最坏情况 O(n log n); 空间复杂度:O(n)(需要额外的空间来合并子数组)。
- 快速排序(Quick Sort): 时间复杂度:平均情况 O(n log n),最好情况 O(n log n),最坏情况 O(n^2)(取决于选取的主元); 空间复杂度:O(log n)(递归调用栈的深度)。
- 堆排序(Heap Sort): #时间复杂度:平均情况 O(n log n),最好情况 O(n log n),最坏情况 O(n log n); 空间复杂度:O(1)(原地排序)。
def bubble_sort(arr):
n = len(arr)
for i in range(n):
for j in range(0, n-i-1):
if arr[j] > arr[j+1]:
arr[j], arr[j+1] = arr[j+1], arr[j]
def insertion_sort(arr):
for i in range(1, len(arr)):
key = arr[i]
j = i - 1
while j >= 0 and key < arr[j]:
arr[j + 1] = arr[j]
j -= 1
arr[j + 1] = key
def selection_sort(arr):
n = len(arr)
for i in range(n):
min_index = i
for j in range(i + 1, n):
if arr[j] < arr[min_index]:
min_index = j
arr[i], arr[min_index] = arr[min_index], arr[i]
def merge_sort(arr):
if len(arr) > 1:
mid = len(arr) // 2
left_half = arr[:mid]
right_half = arr[mid:]
# 递归地对左右两部分进行归并排序
merge_sort(left_half)
merge_sort(right_half)
i = j = k = 0
# 合并两个有序子数组
while i < len(left_half) and j < len(right_half):
if left_half[i] < right_half[j]:
arr[k] = left_half[i]
i += 1
else:
arr[k] = right_half[j]
j += 1
k += 1
# 处理剩余的元素
while i < len(left_half):
arr[k] = left_half[i]
i += 1
k += 1
while j < len(right_half):
arr[k] = right_half[j]
j += 1
k += 1
#R 快速排序
def quick_sort(arr, low=0, high=None):
if high is None:
high = len(arr) - 1
def partition(arr, low, high):
pivot = arr[high]
i = low - 1
for j in range(low, high):
# 如果当前元素小于等于基准
if arr[j] <= pivot:
# 交换元素
i += 1
arr[i], arr[j] = arr[j], arr[i]
# 将基准放到正确位置
arr[i + 1], arr[high] = arr[high], arr[i + 1]
return i + 1
def _quick_sort(arr, low, high):
if low < high:
partition_index = partition(arr, low, high)
_quick_sort(arr, low, partition_index - 1)
_quick_sort(arr, partition_index + 1, high)
_quick_sort(arr, low, high)
return arr
# BST排序
class TreeNode:
def __init__(self, value):
self.value = value
self.left = None
self.right = None
def insert(root, value):
if not root:
return TreeNode(value)
if value < root.value:
root.left = insert(root.left, value)
else:
root.right = insert(root.right, value)
return root
def in_order_traversal(root, result):
if root:
in_order_traversal(root.left, result)
result.append(root.value)
in_order_traversal(root.right, result)
def bst_sort(nums):
root = None
for num in nums:
root = insert(root, num)
result = []
in_order_traversal(root, result)
return result
#R 堆排序 https://blog.csdn.net/Solititude/article/details/129182217
def heapify(arr, n, i):
largest = i
left_child = 2 * i + 1
right_child = 2 * i + 2
if left_child < n and arr[left_child] > arr[largest]:
largest = left_child
if right_child < n and arr[right_child] > arr[largest]:
largest = right_child
if largest != i:
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
def heap_sort(arr):
n = len(arr)
# 构建最大堆
for i in range(n // 2 - 1, -1, -1):
heapify(arr, n, i)
# 逐步取出堆顶元素,进行堆排序
for i in range(n - 1, 0, -1):
arr[0], arr[i] = arr[i], arr[0]
heapify(arr, i, 0)
# 0. sort a nearly sorted array: 对每个元素离正确位置不超过K的数列排序。用insert排序,O(NK),可以二分法只搜索邻近K个(NlogK)或者直接建立k element min heap,每次移除root(min), 加入新元素再heapify.
import heapq
def sortNearlySortedArray(nums, k):
# 创建一个最小堆
min_heap = nums[:k]
heapq.heapify(min_heap)
result = []
for i in range(k, len(nums)):
min_element = heapq.heappop(min_heap)
result.append(min_element)
heapq.heappush(min_heap, nums[i])
# 将堆中剩余的元素取出并加入结果数组
while min_heap:
min_element = heapq.heappop(min_heap)
result.append(min_element)
return result
随机
# 0 水塘抽样(Reservoir Sampling),第i步以1/i的概率选取nums[i],默认选取前k个,第i步等概率产生[1,i]中的一个数r,如果r<=k 则将selected[r] 替换成 nums[i]
import random
def reservoir_sampling_single(nums):
selected = nums[0]
for i in range(2, len(nums) + 1):
# 以 1/i 的概率选取 nums[i-1]
if random.randint(1, i) == i:
selected = nums[i - 1]
return selected
def reservoir_sampling_k(nums, k):
selected = nums[:k]
for i in range(k + 1, len(nums) + 1):
# 等概率产生 [1, i] 中的一个数 r
p = random.randint(1, i)
# 如果 r <= k,则将 selected[r-1] 替换为 nums[i-1]
if p <= k:
selected[p - 1] = nums[i - 1]
return selected
# 0 按权重随机选择
import random
class Solution:
def __init__(self, w):
self.cumulative_weights = [0] * len(w)
self.cumulative_weights[0] = w[0]
for i in range(1, len(w)):
self.cumulative_weights[i] = self.cumulative_weights[i-1] + w[i]
self.sum_weights = sum(w)
def pickIndex(self) -> int:
target = random.randint(1, self.sum_weights)
# 二分查找
left, right = 0, len(self.cumulative_weights) - 1
while left < right:
mid = (left + right) // 2
if self.cumulative_weights[mid] < target:
left = mid + 1
else:
right = mid
return left
# 随机产生数字没有重复 or 打乱数组
def generate_unique_numbers(minimum, maximum):
# 初始化数组,包含[min, max]范围内的所有数字
nums = list(range(minimum, maximum + 1))
result = []
while nums:
# 生成 [0, len(nums)-1] 之间的随机索引
rand_index = random.randint(0, len(nums) - 1)
# 交换当前随机选中的数字和数组末尾的数字
nums[rand_index], nums[-1] = nums[-1], nums[rand_index]
# 将末尾数字添加到结果中,并从数组中移除
result.append(nums.pop())
return result
# Fisher-Yates 洗牌算法(也称为 Knuth shuffle)
class Solution:
def __init__(self, nums):
self.original_nums = nums[:]
self.shuffle_nums = nums[:]
def reset(self):
# 重置为原始数组
self.shuffle_nums = self.original_nums[:]
return self.shuffle_nums
def shuffle(self):
n = len(self.shuffle_nums)
for i in range(n):
# 生成 [i, n-1] 之间的随机索引
rand_index = random.randint(i, n - 1)
# 交换当前随机选中的数字和数组中第 i 个位置的数字
self.shuffle_nums[i], self.shuffle_nums[rand_index] = self.shuffle_nums[rand_index], self.shuffle_nums[i]
return self.shuffle_nums
# 470. Implement Rand10() Using Rand7()
def Rand10():
while True:
# 生成 [1, 49] 范围内的随机数
a = Rand7()
b = Rand7()
val = (a - 1) * 7 + b
# 拒绝采样,重新生成直到得到 [1, 40] 范围内的数
if val <= 40:
return val % 10 + 1
# 380 Insert Delete GetRandom O(1)/O(1) 时间插入、删除和获取随机元素 (Medium)
class RandomizedSet:
def __init__(self):
self.vals = []
self.idxs = {}
def insert(self, val: int) -> bool:
if val in self.vals:
return False
self.idxs[val] = len(self.vals)
self.vals.append(val)
return True
def remove(self, val: int) -> bool:
if val not in self.vals:
return False
idx = self.idxs[val]
self.vals[idx] = self.vals[-1]
self.idxs[self.vals[idx]] = idx
self.vals.pop()
del self.idxs[val]
return True
def getRandom(self) -> int:
return random.choice(self.vals)
#R 709.99 Random Pick with Blacklist/黑名单中的随机数 (Hard)
# 选取一个 未加入 黑名单 blacklist 的整数
# 构建一个从 [0,n−m) 范围内的黑名单数到 [n−m,n) 的白名单数的映射
class Solution:
def __init__(self, n: int, blacklist: List[int]):
m = len(blacklist)
self.bound = w = n - m
black = {b for b in blacklist if b >= self.bound}
self.b2w = {}
for b in blacklist:
if b < self.bound:
while w in black:
w += 1
self.b2w[b] = w
w += 1
def pick(self) -> int:
x = randrange(self.bound)
return self.b2w.get(x, x)
累加和
#325. Maximum Size Subarray Sum Equals k
def maxSubArrayLen(nums, k):
# 初始化累加和为0,结果为0
sum_val = 0
res = 0
# 使用字典记录累加和及其对应的下标
# 初始时,将累加和为0的下标设为-1
sum_index_map = {0: -1}
for i in range(len(nums)):
# 计算当前位置的累加和
sum_val += nums[i]
# 如果当前累加和减去目标值 k 在字典中,说明找到了符合条件的子数组
if sum_val - k in sum_index_map:
res = max(res, i - sum_index_map[sum_val - k])
# 如果当前累加和不在字典中,将其加入字典
if sum_val not in sum_index_map:
sum_index_map[sum_val] = i
return res
#R 862. Shortest Subarray with Sum at Least K
import heapq
def shortestSubarray(nums, k):
# 初始化结果为正无穷大,累加和为0,优先队列用于保存累加和及其对应的下标
res = float('inf')
sum_val = 0
pq = [(0, -1)]
for i in range(len(nums)):
# 计算当前位置的累加和
sum_val += nums[i]
# 检查队列中是否有满足条件的累加和,如果有,更新结果
while pq and sum_val - pq[0][0] >= k:
res = min(res, i - pq[0][1])
heapq.heappop(pq)
# 将当前累加和及其下标加入队列
heapq.heappush(pq, (sum_val, i))
# 如果结果仍然是正无穷大,说明没有符合条件的子数组
return -1 if res == float('inf') else res
# 525. Contiguous Array, 给定一个二进制数组, 找到含有相同数量的 0 和 1 的最长连续子数组,返回该子数组的长度。
# 遍历数组时,对于每个元素,如果是 1 就加一,如果是 0 就减一,然后将当前累加和和对应的下标加入字典中。如果相同的累加和出现多次,说明两个下标之间的 0 和 1 的个数是相等的。
def findMaxLength(nums):
# 初始化累加和为0,用字典记录每个累加和对应的下标列表
sum_map = {0: [-1]}
sum_val = 0
# 遍历数组,计算累加和,并记录每个累加和对应的下标
for i in range(len(nums)):
sum_val += 1 if nums[i] == 1 else -1
if sum_val not in sum_map:
sum_map[sum_val] = [i]
else:
sum_map[sum_val].append(i)
# 遍历字典,找出最长子数组的长度
max_length = 0
for indices in sum_map.values():
max_length = max(max_length, indices[-1] - indices[0])
return max_length
# 有k个1的子列个数(map value = vector)/最长(value = 第一个出现index)/最短(最新一个index)子列都可以用累计和。
# 如果是不超过k个1的最长子列也可以用累加和,最长子列一定是k个1,不然就是全列。但是如果是计算不超过k个1的子列个数,则只能用移动窗口了(可以发现凡是求不等式子列个数,就必须用移动窗口)
0. 有 k 个 1 的子列个数,这个问题可以通过维护一个累加和,以及使用一个字典来记录每个累加和出现的频次。具体步骤如下:
def countSubarraysWithKOnes(nums, k):
# 初始化累加和为0,字典记录每个累加和出现的频次
sum_val = 0
count_map = {0: 1}
count = 0
# 遍历数组,计算累加和和累加和为 sum_val - k 的频次
for num in nums:
sum_val += num
if sum_val - k in count_map:
count += count_map[sum_val - k]
count_map[sum_val] = count_map.get(sum_val, 0) + 1
return count
# 0 最长子列:由 0 和 1 组成的数组中找到最短的连续子列,该子列中 1 的个数等于给定的 k。这个问题可以通过维护一个累加和,并使用一个字典来记录每个累加和第一次出现的位置。
def longestSubarrayWithKOnes(nums, k):
# 初始化累加和为0,字典记录每个累加和第一次出现的位置
sum_val = 0
index_map = {0: -1}
max_length = 0
# 遍历数组,更新最长子列长度
for i, num in enumerate(nums):
sum_val += num
if sum_val not in index_map:
index_map[sum_val] = i
if sum_val - k in index_map:
max_length = max(max_length, i - index_map[sum_val - k])
return max_length
# 0 Find the Longest Substring Containing Vowels in Even Counts # 返回最长子串的长度:每个元音字母,在子字符串中都恰好出现了偶数次。
# 每当遇到一个元音字母时,更新状态 num,然后通过字典 res_dict 记录每个状态第一次出现的位置。如果相同的状态再次出现,说明中间的子列元音个数是偶数
def findTheLongestSubstring(s):
# 初始化字典,用于记录元音字母和对应的二进制位置
vowels_dict = {'a': 0, 'e': 1, 'i': 2, 'o': 3, 'u': 4}
# 初始化状态 num 为0,表示当前状态的二进制数
num = 0
# 初始化结果为0,用于记录最长子列的长度
res = 0
# 使用字典 resDict 记录每个状态第一次出现的位置
res_dict = {0: -1}
# 遍历字符串
for i in range(len(s)):
# 如果当前字符是元音字母,更新状态 num
if s[i] in vowels_dict:
num ^= (1 << vowels_dict[s[i]])
# 如果当前状态已经出现过,更新最长子列长度
if num in res_dict:
res = max(res, i - res_dict[num])
else:
# 如果当前状态第一次出现,记录当前位置
res_dict[num] = i
return res
# 532. 数组中的 k-diff 数对
class Solution:
def findPairs(self, nums: List[int], k: int) -> int:
visited, res = set(), set()
for num in nums:
if num - k in visited:
res.add(num - k)
if num + k in visited:
res.add(num)
visited.add(num)
return len(res)
# 370.给定一个长度为n的数组,初始时所有元素均为0。对该数组进行k次操作,每次操作将某一范围的元素加上一个特定的值。返回经过k次操作后得到的数组。
# 初始都是0的array上,[a,b,k]指在index [a,b]之间都加上k. 若干组pair以后问最终输出:对指标a加上k, b+1 减去k. 这样对0到n的累加和就是最终输出。
def getModifiedArray(length, updates):
result = [0] * length
# 构建差分数组
for update in updates:
start, end, inc = update
result[start] += inc
if end + 1 < length:
result[end + 1] -= inc
# 根据差分数组还原原数组
for i in range(1, length):
result[i] += result[i - 1]
return result
# 238 Product of Array Except Self/除自身以外数组的乘积 (Medium)
class Solution:
def productExceptSelf(self, nums: List[int]) -> List[int]:
n = len(nums)
pre = [1] * n
for i in range(1, n):
pre[i] = pre[i - 1] * nums[i - 1]
suf = [1] * n
for i in range(n - 2, -1, -1):
suf[i] = suf[i + 1] * nums[i + 1]
return [p * s for p, s in zip(pre, suf)]
# 523 Continuous Subarray Sum/连续的子数组和 (Medium)
# 好的子数组是:长度至少为 2 ,且子数组元素总和为 k 的倍数。有没有好的子数组。方法前缀和
class Solution:
def checkSubarraySum(self, nums, k):
d = {0: -1}
pre = 0
for index, num in enumerate(nums):
pre += num
rem = pre % k
i = d.get(rem, index)
if i == index:
d[rem] = index
elif i <= index - 2:
return True
return False
# 1109 Corporate Flight Bookings/? (Medium)
# [firsti, lasti, seatsi] 意味着在从闭区间 firsti 到 lasti 的 每个航班 上预订了 seatsi 个座位。
class Solution:
def corpFlightBookings(self, bookings: List[List[int]], n: int) -> List[int]:
diff = [0] * n
for t in range(len(bookings)):
i, j, temp = bookings[t]
diff[i-1] += temp
if j < n: # 越界情况特殊判断
diff[j] -= temp
for i in range(1, len(diff)):
diff[i] = diff[i] + diff[i-1]
return diff
# 1124 Longest Well-Performing Interval/表现良好的最长时间段 (Medium)
# 员工每天的工作小时数。返回「表现良好时间段」(=「劳累的天数」是严格 大于「不劳累的天数」)的最大长度。
# 前缀和 + 哈希表
class Solution:
def longestWPI(self, hours: List[int]) -> int:
ans = s = 0
pos = {}
for i, x in enumerate(hours):
s += 1 if x > 8 else -1
if s > 0:
ans = i + 1
elif s - 1 in pos:
ans = max(ans, i - pos[s - 1])
if s not in pos:
pos[s] = i
return ans
二进制
# 问题2: 正数int转化为2进制
def int_to_binary(n):
res = ""
while n:
res = str(n % 2) + res
n //= 2
return res if res else "0"
# 问题2: 负数int转化为2进制
def negative_int_to_binary(n):
n = -n-1
res = ""
for i in range(16):
res = str(1 - (n % 2)) + res
n //= 2
return res
# 问题3: 遍历n的每一位
# 0. Convert a Number to Hexadecimal
class Solution(object):
def toHex(self, num):
# -N和 16 ** 8 - N一样编码
if num < 0:
num = 16 ** 8 + num
# 如果0的话那么会返回‘’
if num == 0:
return '0'
char = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f']
ans = []
while num > 0:
ans.append(char[num % 16])
num = num // 16
return ''.join(reversed(ans))
# 问题4: 消去x最后一位的1
def remove_last_one(x):
return x & (x - 1)
# 获得最右边1开始至结尾
def get_rightmost_one(x):
return x & (~ (x - 1))
# 600 Non-negative Integers without Consecutive Ones
# 统计在[0, n]范围整数中,有多少个整数的二进制表示中不存在连续的1,0~2^i-1中没有连续1的个数是Fibonacci数列即可以看成10xxxx和0xxxxx
def findIntegers(n):
fib = [1, 2]
for i in range(2, 32):
fib.append(fib[-1] + fib[-2])
res, k, pre = 0, 31, 0
while k >= 0:
if n & (1 << k):
res += fib[k]
if pre:
return res # 连续出现两个1,停止
pre = 1
else:
pre = 0
k -= 1
return res + 1 # 说明 n 本身没有连续1,以上都是记录严格小于 n 的,所以要加上本身
# 问题5: 判断是不是4的幂次
def is_power_of_4(x):
chkbit = 3
if (x & (x - 1)) != 0:
return False
while (chkbit & x) == 0:
x >>= 2
return ((x & chkbit) == 1)
# 问题6: Check有没有连续的1
def has_consecutive_ones(n):
return n & (n >> 1) == 0
# 问题7: 定义加法,用 ^ 表示异或运算(XOR,Exclusive OR),两个输入值不相同时,输出为1;相同,输出为0。使用 & 表示按位与运算,而 << 表示左移操作。循环中的逻辑保持一致,直到 b 为 0 停止。这个自定义的加法实际上是直接对补码进行加法运算,因此对于负数同样成立。
def custom_addition(a, b):
while b:
a_ = a ^ b
b_ = (a & b) << 1
a, b = a_, b_
return a
# 找出只出现一次的数
# a ^ b ^ b = a
def find_single_occurrence(nums):
result = 0
for num in nums:
result ^= num
return result
# 找出两个只出现一次的数
def find_two_single_occurrences(nums):
c = 0
for num in nums:
c ^= num
k = c & (~(c - 1))
a, b = 0, 0
for num in nums:
if num & k == 0:
a ^= num
else:
b ^= num
return a, b
# 找出缺失的数, 0到2^n-1 少了一个数,全部异或起来,结果就是少的那个数。因为正常0,1都出现了偶数次,唯独少的那个数字对应的0/1出现奇数次。
#H 负二进制加法/Adding Two Negabinary Numbers
# 如果 x≥2,那么将 x 减去 2,并向高位进位 −1。即逢 2 进负 1。
# 如果 x=−1,由于 −(−2)^i=(−2)^i +(−2)^{i+1},所以我们可以将 x 置为 1,并向高位进位 1。
class Solution:
def addNegabinary(self, arr1: List[int], arr2: List[int]) -> List[int]:
i, j = len(arr1) - 1, len(arr2) - 1
c = 0
ans = []
while i >= 0 or j >= 0 or c:
a = 0 if i < 0 else arr1[i]
b = 0 if j < 0 else arr2[j]
x = a + b + c
c = 0
if x >= 2:
x -= 2
c -= 1
elif x == -1:
x = 1
c += 1
ans.append(x)
i, j = i - 1, j - 1
while len(ans) > 1 and ans[-1] == 0:
ans.pop()
return ans[::-1]
# 49.9 Pow(x, n) (Medium)
# 二进制解法
class Solution:
def myPow(self, x: float, n: int) -> float:
ans = 1
if n < 0: # x^-n = (1/x)^n
n = -n
x = 1 / x
while n: # 从低到高枚举 n 的每个比特位
if n & 1: # 这个比特位是 1
ans *= x # 把 x 乘到 ans 中
x *= x # x 自身平方
n >>= 1 # 继续枚举下一个比特位
return ans
贪心算法
# 0 整数拆分
class Solution:
def integerBreak(self, n):
if n == 2: # 当n等于2时,只有一种拆分方式:1+1=2,乘积为1
return 1
if n == 3: # 当n等于3时,只有一种拆分方式:1+1+1=3,乘积为1
return 2
if n == 4: # 当n等于4时,有两种拆分方式:2+2=4和1+1+1+1=4,乘积都为4
return 4
result = 1
while n > 4:
result *= 3 # 每次乘以3,因为3的乘积比其他数字更大
n -= 3 # 每次减去3
result *= n # 将剩余的n乘以最后的结果
return result
# 455.分发饼干
# 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。
class Solution:
def findContentChildren(self, g, s):
g.sort() # 将孩子的贪心因子排序
s.sort() # 将饼干的尺寸排序
index = len(s) - 1 # 饼干数组的下标,从最后一个饼干开始
result = 0 # 满足孩子的数量
for i in range(len(g)-1, -1, -1): # 遍历胃口,从最后一个孩子开始
if index >= 0 and s[index] >= g[i]: # 遍历饼干
result += 1
index -= 1
return result
# 55. 跳跃游戏
# 数组元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
class Solution:
def canJump(self, nums: List[int]) -> bool:
cover = 0
if len(nums) == 1: return True
i = 0
# python不支持动态修改for循环中变量,使用while循环代替
while i <= cover:
cover = max(i + nums[i], cover)
if cover >= len(nums) - 1: return True
i += 1
return False
# 45.跳跃游戏 II
# 使用最少的跳跃次数到达数组的最后一个位置。
class Solution:
def jump(self, nums):
if len(nums) == 1:
return 0
cur_distance = 0 # 当前覆盖最远距离下标
ans = 0 # 记录走的最大步数
next_distance = 0 # 下一步覆盖最远距离下标
for i in range(len(nums)):
next_distance = max(nums[i] + i, next_distance) # 更新下一步覆盖最远距离下标
if i == cur_distance: # 遇到当前覆盖最远距离下标
ans += 1 # 需要走下一步
cur_distance = next_distance # 更新当前覆盖最远距离下标(相当于加油了)
if next_distance >= len(nums) - 1: # 当前覆盖最远距离达到数组末尾,不用再做ans++操作,直接结束
break
return ans
# 1005.K次取反后最大化的数组和
# i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。返回数组可能的最大和。
class Solution:
def largestSumAfterKNegations(self, A: List[int], K: int) -> int:
A.sort(key=lambda x: abs(x), reverse=True) # 第一步:按照绝对值降序排序数组A
for i in range(len(A)): # 第二步:执行K次取反操作
if A[i] < 0 and K > 0:
A[i] *= -1
K -= 1
if K % 2 == 1: # 第三步:如果K还有剩余次数,将绝对值最小的元素取反
A[-1] *= -1
result = sum(A) # 第四步:计算数组A的元素和
return result
# 134 加油站
# 环路N个加油站,第i个加油站有汽油 gas[i] 升。你有一辆油箱容量无限的的汽车,从第 i个加油站开往第i+1 个加油站需要消耗汽油cost[i]升。你从其中的一个加油站出发,开始时油箱为空。如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。首先检查第 0 个加油站,并试图判断能否环绕一周;如果不能,就从第一个无法到达的加油站开始继续检查。因为,如果x到达不了y+1,那么x-y之间的点也不可能到达y+1,因为中间任何一点的油都是拥有前面的余量的,所以下次遍历直接从y+1 开始。
class Solution:
def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
n = len(gas)
i = 0
while i < n:
sum_of_gas = sum_of_cost = 0
cnt = 0
while cnt < n:
j = (i + cnt) % n
sum_of_gas += gas[j]
sum_of_cost += cost[j]
if sum_of_cost > sum_of_gas:
break
cnt += 1
if cnt == n:
return i
else:
i += cnt + 1
return -1
# 135. 分发糖果
# 每个孩子至少分配到 1 个糖果。相邻的孩子中,评分高的孩子必须获得更多的糖果。老师至少需要准备多少颗糖果呢
class Solution:
def candy(self, ratings: List[int]) -> int:
candyVec = [1] * len(ratings)
# 从前向后遍历,处理右侧比左侧评分高的情况
for i in range(1, len(ratings)):
if ratings[i] > ratings[i - 1]:
candyVec[i] = candyVec[i - 1] + 1
# 从后向前遍历,处理左侧比右侧评分高的情况
for i in range(len(ratings) - 2, -1, -1):
if ratings[i] > ratings[i + 1]:
candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1)
# 统计结果
result = sum(candyVec)
return result
# 860.柠檬水找零
# 向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零
class Solution:
def lemonadeChange(self, bills: List[int]) -> bool:
five = 0
ten = 0
twenty = 0
for bill in bills:
if bill == 5:
five += 1
if bill == 10:
if five <= 0:
return False
ten += 1
five -= 1
if bill == 20:
# 先尝试使用10美元和5美元找零
if five > 0 and ten > 0:
five -= 1
ten -= 1
#twenty += 1
# 如果无法使用10美元找零,则尝试使用三张5美元找零
elif five >= 3:
five -= 3
#twenty += 1
else:
return False
return True
# 452. 用最少数量的箭引爆气球
class Solution:
def findMinArrowShots(self, points: List[List[int]]) -> int:
if len(points) == 0: return 0
points.sort(key=lambda x: x[0])
result = 1
for i in range(1, len(points)):
if points[i][0] > points[i - 1][1]: # 气球i和气球i-1不挨着,注意这里不是>=
result += 1
else:
points[i][1] = min(points[i - 1][1], points[i][1]) # 更新重叠气球最小右边界
return result
# 0 无重叠区间
# 找到需要移除区间的最小数量,使剩余区间互不重叠。
class Solution:
def eraseOverlapIntervals(self, intervals: List[List[int]]) -> int:
if not intervals:
return 0
intervals.sort(key=lambda x: x[0]) # 按照左边界升序排序
count = 0 # 记录重叠区间数量
for i in range(1, len(intervals)):
if intervals[i][0] < intervals[i - 1][1]: # 存在重叠区间
intervals[i][1] = min(intervals[i - 1][1], intervals[i][1]) # 更新重叠区间的右边界
count += 1
return count
# 0 合并区间
class Solution:
def merge(self, intervals):
result = []
if len(intervals) == 0:
return result # 区间集合为空直接返回
intervals.sort(key=lambda x: x[0]) # 按照区间的左边界进行排序
result.append(intervals[0]) # 第一个区间可以直接放入结果集中
for i in range(1, len(intervals)):
if result[-1][1] >= intervals[i][0]: # 发现重叠区间
# 合并区间,只需要更新结果集最后一个区间的右边界,因为根据排序,左边界已经是最小的
result[-1][1] = max(result[-1][1], intervals[i][1])
else:
result.append(intervals[i]) # 区间不重叠
return result
# 0 单调递增的数字
# 找出小于或等于 N 的最大的整数,同时这个整数需要满足其各个位数上的数字是单调递增。
class Solution:
def monotoneIncreasingDigits(self, N: int) -> int:
# 将整数转换为字符串
strNum = str(N)
# flag用来标记赋值9从哪里开始
# 设置为字符串长度,为了防止第二个for循环在flag没有被赋值的情况下执行
flag = len(strNum)
# 从右往左遍历字符串
for i in range(len(strNum) - 1, 0, -1):
# 如果当前字符比前一个字符小,说明需要修改前一个字符
if strNum[i - 1] > strNum[i]:
flag = i # 更新flag的值,记录需要修改的位置
# 将前一个字符减1,以保证递增性质
strNum = strNum[:i - 1] + str(int(strNum[i - 1]) - 1) + strNum[i:]
# 将flag位置及之后的字符都修改为9,以保证最大的递增数字
for i in range(flag, len(strNum)):
strNum = strNum[:i] + '9' + strNum[i + 1:]
# 将最终的字符串转换回整数并返回
return int(strNum)
# 763.划分字母区间
# 划分为尽可能多的片段,同一字母最多出现在一个片段中。
class Solution:
def partitionLabels(self, s: str) -> List[int]:
last_occurrence = {} # 存储每个字符最后出现的位置
for i, ch in enumerate(s):
last_occurrence[ch] = i
result = []
start = 0
end = 0
for i, ch in enumerate(s):
end = max(end, last_occurrence[ch]) # 找到当前字符出现的最远位置
if i == end: # 如果当前位置是最远位置,表示可以分割出一个区间
result.append(end - start + 1)
start = i + 1
return result
# 1029 n个长为2数组一半取第一个i[0],一半取第二个i[1],求和最小。贪心
def twoCitySchedCost(costs):
# 按照费用差排序
costs.sort(key=lambda x: x[0] - x[1])
n = len(costs) // 2
total_cost = 0
# 前半部分去A城市,后半部分去B城市
for i in range(n):
total_cost += costs[i][0]
for i in range(n, len(costs)):
total_cost += costs[i][1]
return total_cost
# 628.给定一个整数数组,找到由数组中的三个整数组成的最大乘积,并输出这个乘积。贪心
def maximumProduct(nums):
nums.sort()
n = len(nums)
# 情况1:三个数都是正数,直接取数组最后三个数相乘
max_prod1 = nums[n - 1] * nums[n - 2] * nums[n - 3]
# 情况2:有一个或两个数是负数,取数组中最小的两个负数和最大的正数相乘
max_prod2 = nums[0] * nums[1] * nums[n - 1]
# 返回两种情况中的较大值
return max(max_prod1, max_prod2)
# 179 Largest Number/最大数 (Medium)
# 定义序,证明是一个序
class Solution:
def largestNumber(self, nums: List[int]) -> str:
def sort_rule(x, y):
a, b = x + y, y + x
if a < b: return 1
elif a > b: return -1
else: return 0
strs = [str(num) for num in nums]
strs.sort(key = cmp_to_key(sort_rule))
if strs[0] == "0":
return "0"
return ''.join(strs)
# 563.9 Find the Closest Palindrome/寻找最近的回文数 (Hard)
# 先把前一半拿出来,再倒过来给后一半补全
class Solution:
def nearestPalindromic(self, n: str) -> str:
length, int_n = len(n), int(n)
if int_n < 10 or int_n == 10 ** (length-1): return str(int_n-1)
if int_n - 1 == 10 ** (length-1): return str(int_n-2)
if int_n + 1 == 10 ** length: return str(int_n + 2)
pre = int(n[:(length+1)//2])
min_diff = float('inf')
for dx in (-1, 0, 1):
s = str(pre + dx)
candidate = s[:length//2] + s[::-1]
if candidate != n:
diff = abs(int(candidate) - int_n)
if diff < min_diff:
min_diff = diff
result = candidate
return result
树Tree
#R 二叉树的迭代遍历
# 前序遍历-迭代-LC144_二叉树的前序遍历
class Solution:
def preorderTraversal(self, root: TreeNode) -> List[int]:
# 根结点为空则返回空列表
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 右孩子先入栈
if node.right:
stack.append(node.right)
# 左孩子后入栈
if node.left:
stack.append(node.left)
return result
#R 中序遍历-迭代-LC94_二叉树的中序遍历
class Solution:
def inorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [] # 不能提前将root结点加入stack中
result = []
cur = root
while cur or stack:
# 先迭代访问最底层的左子树结点
if cur:
stack.append(cur)
cur = cur.left
# 到达最左结点后处理栈顶结点
else:
cur = stack.pop()
result.append(cur.val)
# 取栈顶元素右结点
cur = cur.right
return result
# 后序遍历-迭代-LC145_二叉树的后序遍历
class Solution:
def postorderTraversal(self, root: TreeNode) -> List[int]:
if not root:
return []
stack = [root]
result = []
while stack:
node = stack.pop()
# 中结点先处理
result.append(node.val)
# 左孩子先入栈
if node.left:
stack.append(node.left)
# 右孩子后入栈
if node.right:
stack.append(node.right)
# 将最终的数组翻转
return result[::-1]
#R 102.二叉树的层序遍历
# 利用长度法
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, val=0, left=None, right=None):
# self.val = val
# self.left = left
# self.right = right
class Solution:
def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:
if not root:
return []
queue = collections.deque([root])
result = []
while queue:
level = []
for _ in range(len(queue)):
cur = queue.popleft()
level.append(cur.val)
if cur.left:
queue.append(cur.left)
if cur.right:
queue.append(cur.right)
result.append(level)
return result
# 226.翻转二叉树
class Solution:
def invertTree(self, root: TreeNode) -> TreeNode:
if not root:
return None
root.left, root.right = root.right, root.left
self.invertTree(root.left)
self.invertTree(root.right)
return root
# 101. 对称二叉树
class Solution:
def isSymmetric(self, root: TreeNode) -> bool:
if not root:
return True
return self.compare(root.left, root.right)
def compare(self, left, right):
#首先排除空节点的情况
if left == None and right != None: return False
elif left != None and right == None: return False
elif left == None and right == None: return True
#排除了空节点,再排除数值不相同的情况
elif left.val != right.val: return False
#此时就是:左右节点都不为空,且数值相同的情况
#此时才做递归,做下一层的判断
outside = self.compare(left.left, right.right) #左子树:左、 右子树:右
inside = self.compare(left.right, right.left) #左子树:右、 右子树:左
isSame = outside and inside #左子树:中、 右子树:中 (逻辑处理)
return isSame
# 104.二叉树的最大深度
class solution:
def maxdepth(self, root: treenode) -> int:
return self.getdepth(root)
def getdepth(self, node):
if not node:
return 0
leftheight = self.getdepth(node.left) #左
rightheight = self.getdepth(node.right) #右
height = 1 + max(leftheight, rightheight) #中
return height
# 111.二叉树的最小深度
class Solution:
def getDepth(self, node):
if node is None:
return 0
leftDepth = self.getDepth(node.left) # 左
rightDepth = self.getDepth(node.right) # 右
# 当一个左子树为空,右不为空,这时并不是最低点
if node.left is None and node.right is not None:
return 1 + rightDepth
# 当一个右子树为空,左不为空,这时并不是最低点
if node.left is not None and node.right is None:
return 1 + leftDepth
result = 1 + min(leftDepth, rightDepth)
return result
def minDepth(self, root):
return self.getDepth(root)
# 222.完全二叉树的节点个数
class Solution:
def countNodes(self, root: TreeNode) -> int:
return self.getNodesNum(root)
def getNodesNum(self, cur):
if not cur:
return 0
leftNum = self.getNodesNum(cur.left) #左
rightNum = self.getNodesNum(cur.right) #右
treeNum = leftNum + rightNum + 1 #中
return treeNum
# 110.平衡二叉树
# 求深度适合用前序遍历,而求高度适合用后序遍历
class Solution:
def isBalanced(self, root: TreeNode) -> bool:
if self.get_height(root) != -1:
return True
else:
return False
def get_height(self, root: TreeNode) -> int:
# Base Case
if not root:
return 0
if (left_height := self.get_height(root.left)) == -1:
return -1
if (right_height := self.get_height(root.right)) == -1:
return -1
if abs(left_height - right_height) > 1:
return -1
else:
return 1 + max(left_height, right_height)
112. 路径总和
class Solution:
def traversal(self, cur: TreeNode, count: int) -> bool:
if not cur.left and not cur.right and count == 0: # 遇到叶子节点,并且计数为0
return True
if not cur.left and not cur.right: # 遇到叶子节点直接返回
return False
if cur.left: # 左
count -= cur.left.val
if self.traversal(cur.left, count): # 递归,处理节点
return True
count += cur.left.val # 回溯,撤销处理结果
if cur.right: # 右
count -= cur.right.val
if self.traversal(cur.right, count): # 递归,处理节点
return True
count += cur.right.val # 回溯,撤销处理结果
return False
def hasPathSum(self, root: TreeNode, sum: int) -> bool:
if root is None:
return False
return self.traversal(root, sum - root.val)
# 257. 二叉树的所有路径
class Solution:
def traversal(self, cur, path, result):
path.append(cur.val) # 中
if not cur.left and not cur.right: # 到达叶子节点
sPath = '->'.join(map(str, path))
result.append(sPath)
return
if cur.left: # 左
self.traversal(cur.left, path, result)
path.pop() # 回溯
if cur.right: # 右
self.traversal(cur.right, path, result)
path.pop() # 回溯
def binaryTreePaths(self, root):
result = []
path = []
if not root:
return result
self.traversal(root, path, result)
return result
# 404.左叶子之和
class Solution:
def sumOfLeftLeaves(self, root):
if root is None:
return 0
if root.left is None and root.right is None:
return 0
leftValue = self.sumOfLeftLeaves(root.left) # 左
if root.left and not root.left.left and not root.left.right: # 左子树是左叶子的情况
leftValue = root.left.val
rightValue = self.sumOfLeftLeaves(root.right) # 右
sum_val = leftValue + rightValue # 中
return sum_val
# 513.找树左下角的值
from collections import deque
class Solution:
def findBottomLeftValue(self, root):
if root is None:
return 0
queue = deque()
queue.append(root)
result = 0
while queue:
size = len(queue)
for i in range(size):
node = queue.popleft()
if i == 0:
result = node.val
if node.left:
queue.append(node.left)
if node.right:
queue.append(node.right)
return result
105.从前序与中序遍历序列构造二叉树
class Solution:
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
# 第一步: 特殊情况讨论: 树为空. 或者说是递归终止条件
if not preorder:
return None
# 第二步: 前序遍历的第一个就是当前的中间节点.
root_val = preorder[0]
root = TreeNode(root_val)
# 第三步: 找切割点.
separator_idx = inorder.index(root_val)
# 第四步: 切割inorder数组. 得到inorder数组的左,右半边.
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
# 第五步: 切割preorder数组. 得到preorder数组的左,右半边.
# ⭐️ 重点1: 中序数组大小一定跟前序数组大小是相同的.
preorder_left = preorder[1:1 + len(inorder_left)]
preorder_right = preorder[1 + len(inorder_left):]
# 第六步: 递归
root.left = self.buildTree(preorder_left, inorder_left)
root.right = self.buildTree(preorder_right, inorder_right)
# 第七步: 返回答案
return root
106.从中序与后序遍历序列构造二叉树
class Solution:
def buildTree(self, inorder: List[int], postorder: List[int]) -> TreeNode:
if not postorder:
return None
root_val = postorder[-1]
root = TreeNode(root_val)
separator_idx = inorder.index(root_val)
inorder_left = inorder[:separator_idx]
inorder_right = inorder[separator_idx + 1:]
postorder_left = postorder[:len(inorder_left)]
postorder_right = postorder[len(inorder_left): len(postorder) - 1]
root.left = self.buildTree(inorder_left, postorder_left)
root.right = self.buildTree(inorder_right, postorder_right)
return root
# 0 构建最大二叉树
# 二叉树的根是数组中的最大元素。左子树是通过数组中最大值左边部分构造出的最大二叉树。右子树是通过最大值右边部分构造。
class Solution:
def constructMaximumBinaryTree(self, nums: List[int]) -> TreeNode:
if len(nums) == 1:
return TreeNode(nums[0])
node = TreeNode(0)
# 找到数组中最大的值和对应的下标
maxValue = 0
maxValueIndex = 0
for i in range(len(nums)):
if nums[i] > maxValue:
maxValue = nums[i]
maxValueIndex = i
node.val = maxValue
# 最大值所在的下标左区间 构造左子树
if maxValueIndex > 0:
new_list = nums[:maxValueIndex]
node.left = self.constructMaximumBinaryTree(new_list)
# 最大值所在的下标右区间 构造右子树
if maxValueIndex < len(nums) - 1:
new_list = nums[maxValueIndex+1:]
node.right = self.constructMaximumBinaryTree(new_list)
return node
# 0 合并二叉树
class Solution:
def mergeTrees(self, root1: TreeNode, root2: TreeNode) -> TreeNode:
# 递归终止条件:
# 但凡有一个节点为空, 就立刻返回另外一个. 如果另外一个也为None就直接返回None.
if not root1:
return root2
if not root2:
return root1
# 上面的递归终止条件保证了代码执行到这里root1, root2都非空.
root1.val += root2.val # 中
root1.left = self.mergeTrees(root1.left, root2.left) #左
root1.right = self.mergeTrees(root1.right, root2.right) # 右
return root1 # 注意: 本题我们重复使用了题目给出的节点而不是创建新节点. 节省时间, 空间.
# 700.二叉搜索树中的搜索
class Solution:
def searchBST(self, root: TreeNode, val: int) -> TreeNode:
# 为什么要有返回值:
# 因为搜索到目标节点就要立即return,
# 这样才是找到节点就返回(搜索某一条边),如果不加return,就是遍历整棵树了。
if not root or root.val == val:
return root
if root.val > val:
return self.searchBST(root.left, val)
if root.val < val:
return self.searchBST(root.right, val)
# 0 验证二叉搜索树
class Solution:
def __init__(self):
self.vec = []
def traversal(self, root):
if root is None:
return
self.traversal(root.left)
self.vec.append(root.val) # 将二叉搜索树转换为有序数组
self.traversal(root.right)
def isValidBST(self, root):
self.vec = [] # 清空数组
self.traversal(root)
for i in range(1, len(self.vec)):
# 注意要小于等于,搜索树里不能有相同元素
if self.vec[i] <= self.vec[i - 1]:
return False
return True
# 0 二叉搜索树的最小绝对差
非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
class Solution:
def __init__(self):
self.result = float('inf')
self.pre = None
def traversal(self, cur):
if cur is None:
return
self.traversal(cur.left) # 左
if self.pre is not None: # 中
self.result = min(self.result, cur.val - self.pre.val)
self.pre = cur # 记录前一个
self.traversal(cur.right) # 右
def getMinimumDifference(self, root):
self.traversal(root)
return self.result
# 501.二叉搜索树中的众数
class Solution:
def __init__(self):
self.maxCount = 0 # 最大频率
self.count = 0 # 统计频率
self.pre = None
self.result = []
def searchBST(self, cur):
if cur is None:
return
self.searchBST(cur.left) # 左
# 中
if self.pre is None: # 第一个节点
self.count = 1
elif self.pre.val == cur.val: # 与前一个节点数值相同
self.count += 1
else: # 与前一个节点数值不同
self.count = 1
self.pre = cur # 更新上一个节点
if self.count == self.maxCount: # 如果与最大值频率相同,放进result中
self.result.append(cur.val)
if self.count > self.maxCount: # 如果计数大于最大值频率
self.maxCount = self.count # 更新最大频率
self.result = [cur.val] # 很关键的一步,不要忘记清空result,之前result里的元素都失效了
self.searchBST(cur.right) # 右
return
def findMode(self, root):
self.count = 0
self.maxCount = 0
self.pre = None # 记录前一个节点
self.result = []
self.searchBST(root)
return self.result
# 0 把二叉搜索树转换为累加树
# 每个节点 node 的新值等于原树中大于或等于 node.val 的值之和
class Solution:
def convertBST(self, root: TreeNode) -> TreeNode:
self.pre = 0 # 记录前一个节点的数值
self.traversal(root)
return root
def traversal(self, cur):
if cur is None:
return
self.traversal(cur.right)
cur.val += self.pre
self.pre = cur.val
self.traversal(cur.left)
# 236. 二叉树的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root, p, q):
if root == q or root == p or root is None:
return root
left = self.lowestCommonAncestor(root.left, p, q)
right = self.lowestCommonAncestor(root.right, p, q)
if left is not None and right is not None:
return root
if left is None and right is not None:
return right
elif left is not None and right is None:
return left
else:
return None
# 235. 二叉搜索树的最近公共祖先
class Solution:
def lowestCommonAncestor(self, root, p, q):
if root.val < p.val and root.val < q.val:
return self.lowestCommonAncestor(root.right, p, q)
if root.val > p.val and root.val > q.val:
return self.lowestCommonAncestor(root.left, p, q)
return root
# 0 二叉搜索树中的插入操作
class Solution:
def insertIntoBST(self, root, val):
if root is None:
node = TreeNode(val)
return node
if root.val > val:
root.left = self.insertIntoBST(root.left, val)
if root.val < val:
root.right = self.insertIntoBST(root.right, val)
return root
# 0 删除二叉搜索树中的节点
# 第一种情况:没找到删除的节点,遍历到空节点直接返回了
# 找到删除的节点
# 第二种情况:左右孩子都为空(叶子节点),直接删除节点, 返回NULL为根节点
# 第三种情况:删除节点的左孩子为空,右孩子不为空,删除节点,右孩子补位,返回右孩子为根节点
# 第四种情况:删除节点的右孩子为空,左孩子不为空,删除节点,左孩子补位,返回左孩子为根节点
# 第五种情况:左右孩子节点都不为空,则将删除节点的左子树头结点(左孩子)放到删除节点的右子树的最左面节点的左孩子上,返回删除节点右孩子为新的根节点。
class Solution:
def deleteNode(self, root, key):
if root is None:
return root
if root.val == key:
if root.left is None and root.right is None:
return None
elif root.left is None:
return root.right
elif root.right is None:
return root.left
else:
cur = root.right
while cur.left is not None:
cur = cur.left
cur.left = root.left
return root.right
if root.val > key:
root.left = self.deleteNode(root.left, key)
if root.val < key:
root.right = self.deleteNode(root.right, key)
return root
669. 修剪二叉搜索树
通过修剪二叉搜索树,使得所有节点的值在[L, R]
class Solution:
def trimBST(self, root: TreeNode, low: int, high: int) -> TreeNode:
if root is None:
return None
if root.val < low:
# 寻找符合区间 [low, high] 的节点
return self.trimBST(root.right, low, high)
if root.val > high:
# 寻找符合区间 [low, high] 的节点
return self.trimBST(root.left, low, high)
root.left = self.trimBST(root.left, low, high) # root.left 接入符合条件的左孩子
root.right = self.trimBST(root.right, low, high) # root.right 接入符合条件的右孩子
return root
# 108.将有序数组转换为二叉搜索树
class Solution:
def traversal(self, nums: List[int], left: int, right: int) -> TreeNode:
if left > right:
return None
mid = left + (right - left) // 2
root = TreeNode(nums[mid])
root.left = self.traversal(nums, left, mid - 1)
root.right = self.traversal(nums, mid + 1, right)
return root
def sortedArrayToBST(self, nums: List[int]) -> TreeNode:
root = self.traversal(nums, 0, len(nums) - 1)
return root
#R 671. 二叉树中第二小的节点。
# 给定一个非空特殊的二叉树,每个节点都是正数,并且每个节点的子节点数量只能为 2 或 0。如果一个节点有两个子节点的话,那么该节点的值等于两个子节点中较小的一个。
class Solution:
def findSecondMinimumValue(self, root: TreeNode) -> int:
if not root or not root.left:
return -1
# 我们知道root.val是最小值,那么
# 第二小的值存在于 更小的子节点那一边的子树的第二小的值 或 更大的子节点 之中
left = root.left.val if root.left.val != root.val else self.findSecondMinimumValue(root.left)
right = root.right.val if root.right.val != root.val else self.findSecondMinimumValue(root.right)
return min(left, right) if left != -1 and right != -1 else max(left, right)
662. Maximum Width of Binary Tree
from collections import deque
class TreeNode:
def __init__(self, val=0, left=None, right=None):
self.val = val
self.left = left
self.right = right
def widthOfBinaryTree(root):
if not root:
return 0
queue = deque([(root, 1)])
res = 0
while queue:
n = len(queue)
if n == 1:
queue[0] = (queue[0][0], 1) # 如果这一层只有一个节点,将左索引重置为1
l, r = queue[0][1], queue[-1][1]
for i in range(n):
top, idx = queue.popleft()
if top.left:
queue.append((top.left, idx * 2))
if top.right:
queue.append((top.right, idx * 2 + 1))
res = max(res, r - l + 1)
return res
#R 208 前缀树是一种特殊的多叉树,它的 TrieNode 中 chidren 是一个大小为 26 的一维数组,分别对应了26个英文字符 void insert(String word) 向前缀树中插入字符串 word,search(String word) 如果字符串 word 在前缀树中,返回 true(即,在检索之前已经插入);否则,返回 false 。 startsWith 如果之前已经插入的字符串 word 的前缀之一为 prefix ,返回 true ;否则,返回 false 。
class Trie:
def __init__(self):
self.children = [None] * 26
self.isEnd = False
def searchPrefix(self, prefix: str) -> "Trie":
node = self
for ch in prefix:
ch = ord(ch) - ord("a")
if not node.children[ch]:
return None
node = node.children[ch]
return node
def insert(self, word: str) -> None:
node = self
for ch in word:
ch = ord(ch) - ord("a")
if not node.children[ch]:
node.children[ch] = Trie()
node = node.children[ch]
node.isEnd = True
def search(self, word: str) -> bool:
node = self.searchPrefix(word)
return node is not None and node.isEnd
def startsWith(self, prefix: str) -> bool:
return self.searchPrefix(prefix) is not None
#R 1032. Stream of Characters 接收一个字符流,并检查这些字符的后缀是否是字符串数组 words 中的一个字符串。
class Trie:
def __init__(self):
self.children = [None] * 26
self.is_end = False
def insert(self, w: str):
node = self
for c in w[::-1]:
idx = ord(c) - ord('a')
if node.children[idx] is None:
node.children[idx] = Trie()
node = node.children[idx]
node.is_end = True
def search(self, w: List[str]) -> bool:
node = self
for c in w[::-1]:
idx = ord(c) - ord('a')
if node.children[idx] is None:
return False
node = node.children[idx]
if node.is_end:
return True
return False
class StreamChecker:
def __init__(self, words: List[str]):
self.trie = Trie()
self.cs = []
self.limit = 201
for w in words:
self.trie.insert(w)
def query(self, letter: str) -> bool:
self.cs.append(letter)
return self.trie.search(self.cs[-self.limit:])
# Your StreamChecker object will be instantiated and called as such:
# obj = StreamChecker(words)
# param_1 = obj.query(letter)
# 95 Unique Binary Search Trees II/不同的二叉搜索树 II (Medium)
class Solution:
def generateTrees(self, n: int) -> List[Optional[TreeNode]]:
def dfs(l, r):
if l > r:
return [None]
ans = []
for i in range(l, r + 1):
for x in dfs(l, i - 1):
for y in dfs(i + 1, r):
root = TreeNode(i)
root.left, root.right = x, y
ans.append(root)
return ans
return dfs(1, n)
# 100 Same Tree/相同的树 (Easy)
class Solution:
def isSameTree(self, p: Optional[TreeNode], q: Optional[TreeNode]) -> bool:
if p is None or q is None:
return p is q # 必须都是 None
return p.val == q.val and self.isSameTree(p.left, q.left) and self.isSameTree(p.right, q.right)
# 109 Convert Sorted List to Binary Search Tree/有序链表转换二叉搜索树 (Medium)
class Solution:
def sortedListToBST(self, head: ListNode) -> TreeNode:
def findmid(head, tail):
slow = head
fast = head
while fast != tail and fast.next!= tail :
slow = slow.next
fast = fast.next.next
return slow
def helper(head, tail):
if head == tail: return
node = findmid(head, tail)
root = TreeNode(node.val)
root.left = helper(head, node)
root.right = helper(node.next, tail)
return root
return helper(head, None)
# 112.99 Path Sum II
class Solution:
def pathSum(self, root: Optional[TreeNode], targetSum: int) -> List[List[int]]:
res, path = [], []
def recur(root, tar):
if not root: return
path.append(root.val)
tar -= root.val
if tar == 0 and not root.left and not root.right:
res.append(list(path))
recur(root.left, tar)
recur(root.right, tar)
path.pop()
recur(root, targetSum)
return res
#R 124 Binary Tree Maximum Path Sum/二叉树中的最大路径和 (Hard)
# 输入:root = [1,2,3], 最优路径是 2 -> 1 -> 3
# 链:从下面的某个节点(不一定是叶子)到当前节点的路径。把这条链的节点值之和,作为 dfs 的返回值。如果节点值之和是负数,则返回 0。
# 直径:等价于由两条(或者一条)链拼成的路径。我们枚举每个 node,假设直径在这里「拐弯」,也就是计算由左右两条从下面的某个节点(不一定是叶子)到 node 的链的节点值之和,去更新答案的最大值。
# dfs 返回的是链的节点值之和,不是直径的节点值之和。
class Solution:
def maxPathSum(self, root: Optional[TreeNode]) -> int:
ans = -inf
def dfs(node: Optional[TreeNode]) -> int:
if node is None:
return 0 # 没有节点,和为 0
l_val = dfs(node.left) # 左子树最大链和
r_val = dfs(node.right) # 右子树最大链和
nonlocal ans
ans = max(ans, l_val + r_val + node.val) # 两条链拼成路径
return max(max(l_val, r_val) + node.val, 0) # 当前子树最大链和(注意这里和 0 取最大值了)
dfs(root)
return ans
#R 297 Serialize and Deserialize Binary Tree/二叉树的序列化与反序列化 (Medium)
# 序列化 使用层序遍历实现。反序列化通过递推公式反推各节点在序列中的索引
class Codec:
def serialize(self, root):
if not root: return "[]"
queue = collections.deque()
queue.append(root)
res = []
while queue:
node = queue.popleft()
if node:
res.append(str(node.val))
queue.append(node.left)
queue.append(node.right)
else: res.append("null")
return '[' + ','.join(res) + ']'
def deserialize(self, data):
if data == "[]": return
vals, i = data[1:-1].split(','), 1
root = TreeNode(int(vals[0]))
queue = collections.deque()
queue.append(root)
while queue:
node = queue.popleft()
if vals[i] != "null":
node.left = TreeNode(int(vals[i]))
queue.append(node.left)
i += 1
if vals[i] != "null":
node.right = TreeNode(int(vals[i]))
queue.append(node.right)
i += 1
return root
#R 366 Find Leaves of Binary Tree $/寻找二叉树的叶子节点 (Medium)
# 收集所有的叶子节点;移除所有的叶子节点;重复以上步骤,直到树为空。 高度代表倒数第几个叶子节点
class Solution:
def findLeaves(self, root: TreeNode) -> List[List[int]]:
# 自底向上递归
def dfs(root):
if not root:return 0
l,r=dfs(root.left),dfs(root.right)
depth=max(l,r)+1
res[depth].append(root.val)
return depth
res=collections.defaultdict(list)
dfs(root)
return [v for v in res.values()]
# 606 Construct String from Binary Tree/根据二叉树创建字符串 (中等)
# 输入:root = [1,2,3,null,4] 输出:"1(2()(4))(3)"
class Solution:
def tree2str(self, root: Optional[TreeNode]) -> str:
if root is None:
return ""
if root.left is None and root.right is None:
return str(root.val)
if root.right is None:
return f"{root.val}({self.tree2str(root.left)})"
return f"{root.val}({self.tree2str(root.left)})({self.tree2str(root.right)})"
#R 652 Find Duplicate Subtrees/寻找重复的子树 (Medium)
# 递归的方法进行序列化
class Solution:
def findDuplicateSubtrees(self, root: Optional[TreeNode]) -> List[Optional[TreeNode]]:
def dfs(node: Optional[TreeNode]) -> str:
if not node:
return ""
serial = "".join([str(node.val), "(", dfs(node.left), ")(", dfs(node.right), ")"])
if (tree := seen.get(serial, None)):
repeat.add(tree)
else:
seen[serial] = node
return serial
seen = dict()
repeat = set()
dfs(root)
return list(repeat)
# 663 Equal Tree Partition $/均匀树划分 (Medium)
# 是否可以通过去掉原始树上的一条边将树分成两棵节点值之和相等的子树
# 记录下每个子树的和。我们可以使用深度优先搜索递归地完成此操作。然后,我们应检查整棵树的一半的和是否出现在我们的记录中
class Solution(object):
def checkEqualTree(self, root):
seen = []
def sum_(node):
if not node: return 0
seen.append(sum_(node.left) + sum_(node.right) + node.val)
return seen[-1]
total = sum_(root)
seen.pop()
return total / 2.0 in seen
#R 0 My Calendar II/我的日程安排表 II (Medium)
# 如果要添加的时间内不会导致三重预订时,则可以存储这个新的日程安排。
class MyCalendarTwo:
def __init__(self):
self.booked = []
self.overlaps = []
def book(self, start: int, end: int) -> bool:
if any(s < end and start < e for s, e in self.overlaps):
return False
# 注意两个区间不相交的补=s < end and start < e
for s, e in self.booked:
if s < end and start < e:
self.overlaps.append((max(s, start), min(e, end)))
self.booked.append((start, end))
return True
#R 987 Vertical Order Traversal of a Binary Tree/二叉树的垂序遍历 (Medium)
class Solution:
def verticalTraversal(self, root: Optional[TreeNode]) -> List[List[int]]:
groups = defaultdict(list)
def dfs(node, row, col): #二叉树先序遍历
if node is None:
return
groups[col].append((row, node.val)) # col 相同的分到同一组
dfs(node.left, row + 1, col - 1)
dfs(node.right, row + 1, col + 1)
dfs(root, 0, 0)
ans = []
for _, g in sorted(groups.items()):
g.sort() # 按照 row 排序,row 相同按照 val 排序
ans.append([val for _, val in g])
return ans
回溯算法
组合
# 第77题. 组合
# 返回 1 ... n 中所有可能的 k 个数的组合。
class Solution:
def combine(self, n: int, k: int) -> List[List[int]]:
result = [] # 存放结果集
self.backtracking(n, k, 1, [], result)
return result
def backtracking(self, n, k, startIndex, path, result):
if len(path) == k:
result.append(path[:])
return
for i in range(startIndex, n - (k - len(path)) + 2): # 优化的地方,优化前 range(startIndex, n + 1)
path.append(i) # 处理节点
self.backtracking(n, k, i + 1, path, result)
path.pop() # 回溯,撤销处理的节点
#R 216.组合总和III
# [1,2,3,4,5,6,7,8,9]这个集合中找到和为n的k个数的组合
class Solution:
def combinationSum3(self, k: int, n: int) -> List[List[int]]:
result = [] # 存放结果集
self.backtracking(n, k, 0, 1, [], result)
return result
def backtracking(self, targetSum, k, currentSum, startIndex, path, result):
if currentSum > targetSum: # 剪枝操作
return # 如果path的长度等于k但currentSum不等于targetSum,则直接返回
if len(path) == k:
if currentSum == targetSum:
result.append(path[:])
return
for i in range(startIndex, 9 - (k - len(path)) + 2): # 剪枝
currentSum += i # 处理
path.append(i) # 处理
self.backtracking(targetSum, k, currentSum, i + 1, path, result) # 注意i+1调整startIndex
currentSum -= i # 回溯
path.pop() # 回溯
# 0 电话号码的字母组合
class Solution:
def __init__(self):
self.letterMap = [
"", # 0
"", # 1
"abc", # 2
"def", # 3
"ghi", # 4
"jkl", # 5
"mno", # 6
"pqrs", # 7
"tuv", # 8
"wxyz" # 9
]
self.result = []
self.s = ""
def backtracking(self, digits, index):
if index == len(digits):
self.result.append(self.s)
return
digit = int(digits[index]) # 将索引处的数字转换为整数
letters = self.letterMap[digit] # 获取对应的字符集
for i in range(len(letters)):
self.s += letters[i] # 处理字符
self.backtracking(digits, index + 1) # 递归调用,注意索引加1,处理下一个数字
self.s = self.s[:-1] # 回溯,删除最后添加的字符
def letterCombinations(self, digits):
if len(digits) == 0:
return self.result
self.backtracking(digits, 0)
return self.result
# 39. 组合总和
# 无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
# candidates 中的数字可以无限制重复被选取。
class Solution:
def backtracking(self, candidates, target, total, startIndex, path, result):
if total > target:
return
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
total += candidates[i]
path.append(candidates[i])
self.backtracking(candidates, target, total, i, path, result) # 不用i+1了,表示可以重复读取当前的数
total -= candidates[i]
path.pop()
def combinationSum(self, candidates, target):
result = []
self.backtracking(candidates, target, 0, 0, [], result)
return result
#R 40.组合总和II
# 数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
class Solution:
def backtracking(self, candidates, target, total, startIndex, used, path, result):
if total == target:
result.append(path[:])
return
for i in range(startIndex, len(candidates)):
# 对于相同的数字,只选择第一个未被使用的数字,跳过其他相同数字
if i > startIndex and candidates[i] == candidates[i - 1] and not used[i - 1]:
continue
if total + candidates[i] > target:
break
total += candidates[i]
path.append(candidates[i])
used[i] = True
self.backtracking(candidates, target, total, i + 1, used, path, result)
used[i] = False
total -= candidates[i]
path.pop()
def combinationSum2(self, candidates, target):
used = [False] * len(candidates)
result = []
candidates.sort()
self.backtracking(candidates, target, 0, 0, used, [], result)
return result
分割
#R 131.分割回文串
# 将 s 分割成一些子串,使每个子串都是回文串
class Solution:
def partition(self, s: str) -> List[List[str]]:
# 递归用于纵向遍历; for循环用于横向遍历; 当切割线迭代至字符串末尾,说明找到一种方法; 类似组合问题,为了不重复切割同一位置,需要start_index来做标记下一轮递归的起始位置(切割线)
result = []
self.backtracking(s, 0, [], result)
return result
def backtracking(self, s, start_index, path, result ):
# Base Case
if start_index == len(s):
result.append(path[:])
return
# 单层递归逻辑
for i in range(start_index, len(s)):
# 此次比其他组合题目多了一步判断:
# 判断被截取的这一段子串([start_index, i])是否为回文串
if self.is_palindrome(s, start_index, i):
path.append(s[start_index:i+1])
self.backtracking(s, i+1, path, result) # 递归纵向遍历:从下一处进行切割,判断其余是否仍为回文串
path.pop() # 回溯
def is_palindrome(self, s: str, start: int, end: int) -> bool:
i: int = start
j: int = end
while i < j:
if s[i] != s[j]:
return False
i += 1
j -= 1
return True
#R 93.复原IP地址
class Solution:
def restoreIpAddresses(self, s: str) -> List[str]:
result = []
self.backtracking(s, 0, 0, "", result)
return result
def backtracking(self, s, start_index, point_num, current, result):
if point_num == 3: # 逗点数量为3时,分隔结束
if self.is_valid(s, start_index, len(s) - 1): # 判断第四段子字符串是否合法
current += s[start_index:] # 添加最后一段子字符串
result.append(current)
return
for i in range(start_index, len(s)):
if self.is_valid(s, start_index, i): # 判断 [start_index, i] 这个区间的子串是否合法
sub = s[start_index:i + 1]
self.backtracking(s, i + 1, point_num + 1, current + sub + '.', result)
else:
break
def is_valid(self, s, start, end):
if start > end:
return False
if s[start] == '0' and start != end: # 0开头的数字不合法
return False
num = 0
for i in range(start, end + 1):
if not s[i].isdigit(): # 遇到非数字字符不合法
return False
num = num * 10 + int(s[i])
if num > 255: # 如果大于255了不合法
return False
return True
子集
# 0 子集
class Solution:
def subsets(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
result.append(path[:]) # 收集子集,要放在终止添加的上面,否则会漏掉自己
# if startIndex >= len(nums): # 终止条件可以不加
# return
for i in range(startIndex, len(nums)):
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
#R 90.子集II
# 可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。 解集不能包含重复的子集。
class Solution:
def subsetsWithDup(self, nums):
result = []
path = []
used = [False] * len(nums)
nums.sort() # 去重需要排序
self.backtracking(nums, 0, used, path, result)
return result
def backtracking(self, nums, startIndex, used, path, result):
result.append(path[:]) # 收集子集
for i in range(startIndex, len(nums)):
# used[i - 1] == True,说明同一树枝 nums[i - 1] 使用过
# used[i - 1] == False,说明同一树层 nums[i - 1] 使用过
# 而我们要对同一树层使用过的元素进行跳过
if i > 0 and nums[i] == nums[i - 1] and not used[i - 1]:
continue
path.append(nums[i])
used[i] = True
self.backtracking(nums, i + 1, used, path, result)
used[i] = False
path.pop()
#R 491.递增子序列
# 数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。 利用set去重
class Solution:
def findSubsequences(self, nums):
result = []
path = []
self.backtracking(nums, 0, path, result)
return result
def backtracking(self, nums, startIndex, path, result):
if len(path) > 1:
result.append(path[:]) # 注意要使用切片将当前路径的副本加入结果集
# 注意这里不要加return,要取树上的节点
uset = set() # 使用集合对本层元素进行去重
for i in range(startIndex, len(nums)):
if (path and nums[i] < path[-1]) or nums[i] in uset:
continue
uset.add(nums[i]) # 记录这个元素在本层用过了,本层后面不能再用了
path.append(nums[i])
self.backtracking(nums, i + 1, path, result)
path.pop()
排列
# 46.全排列
class Solution:
def permute(self, nums):
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
#R 47.全排列 II
# 可包含重复数字的序列 ,返回所有不重复的全排列
class Solution:
def permuteUnique(self, nums):
nums.sort() # 排序
result = []
self.backtracking(nums, [], [False] * len(nums), result)
return result
def backtracking(self, nums, path, used, result):
if len(path) == len(nums):
result.append(path[:])
return
for i in range(len(nums)):
if (i > 0 and nums[i] == nums[i - 1] and not used[i - 1]) or used[i]:
continue
used[i] = True
path.append(nums[i])
self.backtracking(nums, path, used, result)
path.pop()
used[i] = False
棋盘
#R 51. N皇后
class Solution:
def solveNQueens(self, n: int) -> List[List[str]]:
result = [] # 存储最终结果的二维字符串数组
chessboard = ['.' * n for _ in range(n)] # 初始化棋盘
self.backtracking(n, 0, chessboard, result) # 回溯求解
return [[''.join(row) for row in solution] for solution in result] # 返回结果集
def backtracking(self, n: int, row: int, chessboard: List[str], result: List[List[str]]) -> None:
if row == n:
result.append(chessboard[:]) # 棋盘填满,将当前解加入结果集
return
for col in range(n):
if self.isValid(row, col, chessboard):
chessboard[row] = chessboard[row][:col] + 'Q' + chessboard[row][col+1:] # 放置皇后
self.backtracking(n, row + 1, chessboard, result) # 递归到下一行
chessboard[row] = chessboard[row][:col] + '.' + chessboard[row][col+1:] # 回溯,撤销当前位置的皇后
def isValid(self, row: int, col: int, chessboard: List[str]) -> bool:
# 检查列
for i in range(row):
if chessboard[i][col] == 'Q':
return False # 当前列已经存在皇后,不合法
# 检查 45 度角是否有皇后
i, j = row - 1, col - 1
while i >= 0 and j >= 0:
if chessboard[i][j] == 'Q':
return False # 左上方向已经存在皇后,不合法
i -= 1
j -= 1
# 检查 135 度角是否有皇后
i, j = row - 1, col + 1
while i >= 0 and j < len(chessboard):
if chessboard[i][j] == 'Q':
return False # 右上方向已经存在皇后,不合法
i -= 1
j += 1
return True # 当前位置合法
#R 37. 解数独
class Solution:
def solveSudoku(self, board: List[List[str]]) -> None:
"""
Do not return anything, modify board in-place instead.
"""
self.backtracking(board)
def backtracking(self, board: List[List[str]]) -> bool:
# 若有解,返回True;若无解,返回False
for i in range(len(board)): # 遍历行
for j in range(len(board[0])): # 遍历列
# 若空格内已有数字,跳过
if board[i][j] != '.': continue
for k in range(1, 10):
if self.is_valid(i, j, k, board):
board[i][j] = str(k)
if self.backtracking(board): return True
board[i][j] = '.'
# 若数字1-9都不能成功填入空格,返回False无解
return False
return True # 有解
def is_valid(self, row: int, col: int, val: int, board: List[List[str]]) -> bool:
# 判断同一行是否冲突
for i in range(9):
if board[row][i] == str(val):
return False
# 判断同一列是否冲突
for j in range(9):
if board[j][col] == str(val):
return False
# 判断同一九宫格是否有冲突
start_row = (row // 3) * 3
start_col = (col // 3) * 3
for i in range(start_row, start_row + 3):
for j in range(start_col, start_col + 3):
if board[i][j] == str(val):
return False
return True
其他
#R 282 Expression Add Operators/给表达式添加运算符 (Hard)
# 输入: num = "123", target = 6 输出: ["1+2+3", "1*2*3"]
# 每个字符与字符实际上有四种连接方式,加减乘和拼接
class Solution:
def addOperators(self, num: str, target: int) -> List[str]:
def backtrace(idx: int, cursum: int, preadd: int) -> None:
nonlocal res
nonlocal path
if idx == n:
if cursum == target:
res.append(path[:])
return
pn = len(path)
for i in range(idx, n):
x_str = num[idx: i + 1]
x = int(x_str)
if idx == 0:
path += x_str
backtrace(i + 1, cursum + x, x)
path = path[ :pn]
else:
path += '+' + x_str
backtrace(i + 1, cursum + x, x)
path = path[ :pn]
path += '-' + x_str
backtrace(i + 1, cursum - x, -x)
path = path[ :pn]
path += '*' + x_str
backtrace(i + 1, cursum - preadd + preadd * x, preadd * x)
path = path[ :pn]
if x == 0:
return
n = len(num)
res = []
path = ""
backtrace(0, 0, 0)
return res
#R 698 Partition to K Equal Sum Subsets/划分为k个相等的子集 (Medium)
class Solution:
def canPartitionKSubsets(self, nums: List[int], k: int) -> bool:
def dfs(i):
if i == len(nums):
return True
for j in range(k):
if j and cur[j] == cur[j - 1]:
continue
cur[j] += nums[i]
if cur[j] <= s and dfs(i + 1):
return True
cur[j] -= nums[i]
return False
s, mod = divmod(sum(nums), k)
if mod:
return False
cur = [0] * k
nums.sort(reverse=True)
return dfs(0)
图Visited
# 连通图归类
def fun(fd):
visited = set()
res = []
def helper(p, group):
if p in visited:
return
visited.add(p)
group.append(p)
for f in fd[p]:
if f not in visited:
helper(f, group)
for key in fd:
if key not in visited:
group = []
helper(key, group)
res.append(group)
return res
# 构造一个图,返回结果:[[1, 2], [3]]
graph = {
1: [2],
2: [1],
3: [ ]
}
# 计算某节点所在的连通分量的成员个数
from typing import List, Dict, Set
def helper(friend_map: Dict[int, List[int]], p: int, visited: Set[int]) -> int:
# helper在求p所在类的成员个数
if p in visited:
return 0
visited.add(p)
res = 1
for f in friend_map[p]:
if f not in visited:
res += helper(friend_map, f, visited)
return res
visited_set = set()
result = helper(graph, 1, visited_set)
# 返回类的个数(0. Number of Provinces/省份数量)(Medium)
from typing import List
def findCircleNum(graph: List[List[int]]) -> int:
def dfs(node):
visited.add(node)
for neighbor, isConnected in enumerate(graph[node]):
if isConnected and neighbor not in visited:
dfs(neighbor)
n = len(graph)
provinces = 0
visited = set()
for city in range(n):
if city not in visited:
provinces += 1
dfs(city)
return provinces
# 示例
graph = [
[1, 1, 0],
[1, 1, 0],
[0, 0, 1]
]
#R Most Stones Removed with Same Row or Column/移除最多的同行或同列石头(Medium)
# 石头放在整数坐标点上, move操作移除某一块石头共享一列或一行的一块石头,最多能执行多少次 move
class Solution:
def removeStones(self, stones: List[List[int]]) -> int:
# 构建图
graph = collections.defaultdict(list)
for i, (x, y) in enumerate(stones):
for j, (xx, yy) in enumerate(stones):
if x == xx or y == yy:
graph[i].append(j)
# DFS计算连通分量数量
def dfs(node):
visited.add(node)
for neighbor in graph[node]:
if neighbor not in visited:
dfs(neighbor)
n = len(stones)
visited = set()
components = 0
for i in range(n):
if i not in visited:
components += 1
dfs(i)
return n - components
# n个点最少移动几根能使所有点都连通当且仅当线的个数>=n-1. 最少移动的数就是连通分支个数-1 (1319. Number of Operations to Make Network Connected)
class Solution:
def makeConnected(self, n: int, connections: List[List[int]]) -> int:
if len(connections) < n - 1:
return -1
edges = collections.defaultdict(list)
for x, y in connections:
edges[x].append(y)
edges[y].append(x)
seen = set()
def dfs(u: int):
seen.add(u)
for v in edges[u]:
if v not in seen:
dfs(v)
ans = 0
for i in range(n):
if i not in seen:
dfs(i)
ans += 1
return ans - 1
#无向图环路检测,可以使用深度优先搜索(DFS)来检测是否存在环路。一般的思路是从一个节点开始进行深度优先搜索,如果在搜索的过程中访问到了已经访问过的节点,那么就说明存在环。
#? Bellman-Ford,无向图环路检测,良序
#R 200. Number of Islands 给定一个由 '1'(陆地)和 '0'(水)组成的二维网格,计算岛屿的数量。岛屿是由相邻的陆地水平或垂直连接形成的(不包括对角线)。你可以假设网格的四个边都被水包围。
class Solution:
def numIslands(self, grid: List[List[str]]) -> int:
if not grid or not grid[0]:
return 0
rows, cols = len(grid), len(grid[0])
islands = 0
def dfs(row, col):
if 0 <= row < rows and 0 <= col < cols and grid[row][col] == '1':
grid[row][col] = '0' # 标记为已访问
# 递归搜索相邻的陆地
dfs(row - 1, col)
dfs(row + 1, col)
dfs(row, col - 1)
dfs(row, col + 1)
for row in range(rows):
for col in range(cols):
if grid[row][col] == '1':
islands += 1
dfs(row, col)
return islands
#类似的题目 Search treasury
# 皮卡公式:如果只有一个dect告诉你点在内部=1,边界=0,外部=-1,求多边形面积:从一个内部点开始探测出内部点i和边界点b个数,用皮卡公式:i + b/2.0 – 1
def help(pt, visited, i, b):
# 为了在递归调用中保持对 i 和 b 的引用,将它们封装为长度为 1 的列表([i] 和 [b]),通过修改列表的值实现引用传递的效果。
def dect(p):
# 实现 dext 函数的逻辑
# 注意:这里的 dext 函数需要根据实际情况替换为你的具体实现
pass
if dect(pt) == -1 or tuple(pt) in visited:
return
visited.add(tuple(pt))
if dect(pt) == 1:
i[0] += 1
if dect(pt) == 0:
b[0] += 1
help([pt[0] + 1, pt[1]], visited, i, b)
help([pt[0] - 1, pt[1]], visited, i, b)
help([pt[0], pt[1] + 1], visited, i, b)
help([pt[0], pt[1] - 1], visited, i, b)
# 797 回溯, 所有可能的路径,有向无环图(DAG),请你找出所有从节点 0 到节点 n-1 的路径
class Solution:
def __init__(self):
self.result = []
self.path = [0]
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
if not graph: return []
self.dfs(graph, 0)
return self.result
def dfs(self, graph, root: int):
if root == len(graph) - 1: # 成功找到一条路径时
# ***Python的list是mutable类型***
# ***回溯中必须使用Deep Copy***
self.result.append(self.path[:])
return
for node in graph[root]: # 遍历节点n的所有后序节点
self.path.append(node)
self.dfs(graph, node)
self.path.pop() # 回溯
#R (hard) 单词接龙,从单词 beginWord 和 endWord 的转换序列每次转换只能改变一个字母,wordList = ["hot","dot","dog"]
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordSet = set(wordList)
if len(wordSet)== 0 or endWord not in wordSet:
return 0
mapping = {beginWord:1}
queue = deque([beginWord])
while queue:
word = queue.popleft()
path = mapping[word]
for i in range(len(word)):
word_list = list(word)
for j in range(26):
word_list[i] = chr(ord('a')+j)
newWord = "".join(word_list)
if newWord == endWord:
return path+1
if newWord in wordSet and newWord not in mapping:
mapping[newWord] = path+1
queue.append(newWord)
return 0
#R 463. 岛屿的周长
class Solution:
def islandPerimeter(self, grid: List[List[int]]) -> int:
m = len(grid)
n = len(grid[0])
# 创建res二维素组记录答案
res = [[0] * n for j in range(m)]
for i in range(m):
for j in range(len(grid[i])):
# 如果当前位置为水域,不做修改或reset res[i][j] = 0
if grid[i][j] == 0:
res[i][j] = 0
# 如果当前位置为陆地,往四个方向判断,update res[i][j]
elif grid[i][j] == 1:
if i == 0 or (i > 0 and grid[i-1][j] == 0):
res[i][j] += 1
if j == 0 or (j >0 and grid[i][j-1] == 0):
res[i][j] += 1
if i == m-1 or (i < m-1 and grid[i+1][j] == 0):
res[i][j] += 1
if j == n-1 or (j < n-1 and grid[i][j+1] == 0):
res[i][j] += 1
# 最后求和res矩阵,这里其实不一定需要矩阵记录,可以设置一个variable res 记录边长,舍矩阵无非是更加形象而已
ans = sum([sum(row) for row in res])
return ans
# 1066. Campus Bikes II
# visited技巧题目非图论 workers, bikes代表工人和自行车的坐标。找到工人各自的自行车使得L1距离总和最近。 res[i] = work[i]对应的bikes, work和bike都没有访问过,才更新res
def assignBikes(workers, bikes):
def dist(a, b):
return abs(a[0] - b[0]) + abs(a[1] - b[1])
dict_list = []
for i in range(len(workers)):
for j in range(len(bikes)):
dict_list.append([dist(workers[i], bikes[j]), i, j])
dict_list.sort(key=lambda x: x[0])
res = [-1] * len(workers)
visited = set()
for it in dict_list:
if res[it[1]] == -1 and it[2] not in visited:
res[it[1]] = it[2]
visited.add(it[2])
return res
#R 1091. Shortest Path in Binary Matrix/二进制矩阵中的最短路径(Medium)
class Solution:
def shortestPathBinaryMatrix(self, grid: List[List[int]]) -> int:
if grid[0][0]:
return -1
n = len(grid)
grid[0][0] = 1
q = deque([(0, 0)])
ans = 1
while q:
for _ in range(len(q)):
i, j = q.popleft()
if i == j == n - 1:
return ans
for x in range(i - 1, i + 2):
for y in range(j - 1, j + 2):
if 0 <= x < n and 0 <= y < n and grid[x][y] == 0:
grid[x][y] = 1
q.append((x, y))
ans += 1
return -1
# 0 Course Schedule. prerequisites[i] = [a, b] 表示如果要学习课程 a 则必须先学习课程 b。判断是否可能完成所有课程学习。
统计课程安排图中每个节点的入度,生成 入度表 indegrees。
借助一个队列 queue,将所有入度为 0 的节点入队。
当 queue 非空时,依次将队首节点出队,在课程安排图中删除此节点 pre
在每次 pre 出队时,执行 numCourses--;
class Solution:
def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
indegrees = [0 for _ in range(numCourses)]
adjacency = [[] for _ in range(numCourses)]
queue = deque()
# Get the indegree and adjacency of every course.
for cur, pre in prerequisites:
indegrees[cur] += 1
adjacency[pre].append(cur)
# Get all the courses with the indegree of 0.
for i in range(len(indegrees)):
if not indegrees[i]: queue.append(i)
# BFS TopSort.
while queue:
pre = queue.popleft()
numCourses -= 1
for cur in adjacency[pre]:
indegrees[cur] -= 1
if not indegrees[cur]: queue.append(cur)
return not numCourses
#R 210 Course Schedule II/课程表 II (Medium)
# 返回你为了学完所有课程所安排的学习顺序,indeg 存储每个节点的入度
class Solution:
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
g = defaultdict(list)
indeg = [0] * numCourses
for a, b in prerequisites:
g[b].append(a)
indeg[a] += 1
ans = []
q = deque(i for i, x in enumerate(indeg) if x == 0)
while q:
i = q.popleft()
ans.append(i)
for j in g[i]:
indeg[j] -= 1
if indeg[j] == 0:
q.append(j)
return ans if len(ans) == numCourses else []
#R 329 Longest Increasing Path in a Matrix/矩阵中的最长递增路径 (Medium)
class Solution:
def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
m, n = len(matrix), len(matrix[0])
flag = [[-1] * n for _ in range(m)] #存储从(i,j)出发的最长递归路径
def dfs(i, j):
if flag[i][j] != -1: # 记忆化搜索,避免重复的计算
return flag[i][j]
else:
d = 1
for (x, y) in [[-1, 0], [1, 0], [0, 1], [0, -1]]:
x, y = i + x, j + y
if 0 <= x < m and 0 <= y < n and matrix[x][y] > matrix[i][j]:
d = max(d, dfs(x, y) + 1) # 取四个邻接点的最长
flag[i][j] = d
return d
res = 0
for i in range(m): # 遍历矩阵计算最长路径
for j in range(n):
if flag[i][j] == -1:
res = max(res, dfs(i, j))
return res
#R 743 Network Delay Time/网络延迟时间 (Medium)
# 从某个节点 K 发出一个信号。需要多久才能使所有节点都收到信号 Dijkstra 算法
class Solution:
def networkDelayTime(self, times: List[List[int]], n: int, k: int) -> int:
g = [[float('inf')] * n for _ in range(n)]
for x, y, time in times:
g[x - 1][y - 1] = time
dist = [float('inf')] * n
dist[k - 1] = 0
used = [False] * n
for _ in range(n):
x = -1
for y, u in enumerate(used):
if not u and (x == -1 or dist[y] < dist[x]):
x = y
used[x] = True
for y, time in enumerate(g[x]):
dist[y] = min(dist[y], dist[x] + time)
ans = max(dist)
return ans if ans < float('inf') else -1
# 752 Open the Lock/打开转盘锁 (Medium)
from queue import Queue
class Solution:
def openLock(self, deadends: List[str], target: str) -> int:
deadends = set(deadends) # in 操作在set中时间复杂度为O(1)
if '0000' in deadends:
return -1
# ----------------BFS 开始------------------
q = Queue()
q.put(('0000', 0)) # 初始化根节点 (当前节点值,转动步数)
while not q.empty():
node, step = q.get()
for i in range(4):
for add in (1, -1):
cur = node[:i] + str((int(node[i]) + add) % 10) + node[i+1:]
if cur == target:
return step + 1
if not cur in deadends:
q.put((cur, step + 1))
deadends.add(cur) # 避免重复搜索
return -1
#R 827 Making A Large Island/最大人工岛 (Hard)
# 最多 只能将一格 0 变成 1
class Solution:
def largestIsland(self, grid: List[List[int]]) -> int:
n = len(grid)
def dfs(i: int, j: int) -> int:
size = 1
grid[i][j] = len(area) + 2 # 记录 (i,j) 属于哪个岛
for x, y in (i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1):
if 0 <= x < n and 0 <= y < n and grid[x][y] == 1:
size += dfs(x, y)
return size
# DFS 每个岛,统计各个岛的面积,记录到 area 列表中
area = []
for i, row in enumerate(grid):
for j, x in enumerate(row):
if x == 1:
area.append(dfs(i, j))
# 加上这个特判,可以快很多
if not area: # 没有岛
return 1
ans = 0
for i, row in enumerate(grid):
for j, x in enumerate(row):
if x: continue
s = set()
for x, y in (i - 1, j), (i + 1, j), (i, j - 1), (i, j + 1):
if 0 <= x < n and 0 <= y < n and grid[x][y]:
s.add(grid[x][y]) # 记录上下左右格子所属岛屿编号
ans = max(ans, sum(area[idx - 2] for idx in s) + 1) # 累加面积
return ans if ans else n * n # 如果最后 ans 仍然为 0,说明所有格子都是 1,返回 n^2
#R 0 Get Watched Videos by Your Friends/获取你好友已观看的视频 (Medium)
# watchedVideos[i] 和 friends[i] 分别表示 id = i 的人观看过的视频列表和他的好友列表。找出所有指定 level 的视频
class Solution:
def watchedVideosByFriends(self, watchedVideos, friends, id, level):
n = len(friends)
used = [False] * n
q = collections.deque([id])
used[id] = True
for _ in range(level):
span = len(q)
for i in range(span):
u = q.popleft()
for v in friends[u]:
if not used[v]:
q.append(v)
used[v] = True
freq = collections.Counter()
for _ in range(len(q)):
u = q.pop()
for watched in watchedVideos[u]:
freq[watched] += 1
videos = list(freq.items())
videos.sort(key=lambda x: (x[1], x[0]))
ans = [video[0] for video in videos]
return ans
#R 1761 Minimum Degree of a Connected Trio in a Graph/一个图中连通三元组的最小度数 (困难)
# 连通三元组的度数 是所有满足此条件的边的数目:一个顶点在这个三元组内,而另一个顶点不在这个三元组内。返回所有连通三元组中度数的 最小值
class Solution:
def minTrioDegree(self, n: int, edges: List[List[int]]) -> int:
g = [[False] * n for _ in range(n)]
deg = [0] * n
for u, v in edges:
u, v = u - 1, v - 1
g[u][v] = g[v][u] = True
deg[u] += 1
deg[v] += 1
ans = inf
for i in range(n):
for j in range(i + 1, n):
if g[i][j]:
for k in range(j + 1, n):
if g[i][k] and g[j][k]:
ans = min(ans, deg[i] + deg[j] + deg[k] - 6)
return -1 if ans == inf else ans
动态规划
简单
# 1. 斐波那契数
class Solution:
def fib(self, n: int) -> int:
# 排除 Corner Case
if n == 0:
return 0
# 创建 dp table
dp = [0] * (n + 1)
# 初始化 dp 数组
dp[0] = 0
dp[1] = 1
# 遍历顺序: 由前向后。因为后面要用到前面的状态
for i in range(2, n + 1):
# 确定递归公式/状态转移公式
dp[i] = dp[i - 1] + dp[i - 2]
# 返回答案
return dp[n]
# 1. 爬楼梯
# 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶
# 空间复杂度为O(n)版本
class Solution:
def climbStairs(self, n: int) -> int:
if n <= 1:
return n
dp = [0] * (n + 1)
dp[1] = 1
dp[2] = 2
for i in range(3, n + 1):
dp[i] = dp[i - 1] + dp[i - 2]
return dp[n]
# 1. 使用最小花费爬楼梯
class Solution:
def minCostClimbingStairs(self, cost: List[int]) -> int:
dp = [0] * (len(cost) + 1)
dp[0] = 0 # 初始值,表示从起点开始不需要花费体力
dp[1] = 0 # 初始值,表示经过第一步不需要花费体力
for i in range(2, len(cost) + 1):
# 在第i步,可以选择从前一步(i-1)花费体力到达当前步,或者从前两步(i-2)花费体力到达当前步
# 选择其中花费体力较小的路径,加上当前步的花费,更新dp数组
dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2])
return dp[len(cost)] # 返回到达楼顶的最小花费
# 62.不同路径
# 一个机器人位于一个 m x n 网格的左上角,机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角。总共有多少条不同的路径
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
# 创建一个二维列表用于存储唯一路径数
dp = [[0] * n for _ in range(m)]
# 设置第一行和第一列的基本情况
for i in range(m):
dp[i][0] = 1
for j in range(n):
dp[0][j] = 1
# 计算每个单元格的唯一路径数
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
# 返回右下角单元格的唯一路径数
return dp[m - 1][n - 1]
0. 子矩阵和:一般用动态规划。dp 行列各加一行0方便计算
# 1. 不同路径 II 网格中有障碍物
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid):
m = len(obstacleGrid)
n = len(obstacleGrid[0])
if obstacleGrid[m - 1][n - 1] == 1 or obstacleGrid[0][0] == 1:
return 0
dp = [[0] * n for _ in range(m)]
for i in range(m):
if obstacleGrid[i][0] == 0: # 遇到障碍物时,直接退出循环,后面默认都是0
dp[i][0] = 1
else:
break
for j in range(n):
if obstacleGrid[0][j] == 0:
dp[0][j] = 1
else:
break
for i in range(1, m):
for j in range(1, n):
if obstacleGrid[i][j] == 1:
continue
dp[i][j] = dp[i - 1][j] + dp[i][j - 1]
return dp[m - 1][n - 1]
# 96.不同的二叉搜索树
class Solution:
def numTrees(self, n: int) -> int:
dp = [0] * (n + 1) # 创建一个长度为n+1的数组,初始化为0
dp[0] = 1 # 当n为0时,只有一种情况,即空树,所以dp[0] = 1
for i in range(1, n + 1): # 遍历从1到n的每个数字
for j in range(1, i + 1): # 对于每个数字i,计算以i为根节点的二叉搜索树的数量
dp[i] += dp[j - 1] * dp[i - j] # 利用动态规划的思想,累加左子树和右子树的组合数量
return dp[n] # 返回以1到n为节点的二叉搜索树的总数量
# 139.单词拆分
# s 是否可以被空格拆分为一个或多个在字典中出现的单词
class Solution:
def wordBreak(self, s: str, wordDict: List[str]) -> bool:
wordSet = set(wordDict)
n = len(s)
dp = [False] * (n + 1) # dp[i] 表示字符串的前 i 个字符是否可以被拆分成单词
dp[0] = True # 初始状态,空字符串可以被拆分成单词
for i in range(1, n + 1): # 遍历背包
for j in range(i): # 遍历单词
if dp[j] and s[j:i] in wordSet:
dp[i] = True # 如果 s[0:j] 可以被拆分成单词,并且 s[j:i] 在单词集合中存在,则 s[0:i] 可以被拆分成单词
break
return dp[n]
背包问题
#R 背包问题
def test_2_ei_bag_problem1(weight, value, bagweight):
# 二维数组
dp = [[0] * (bagweight + 1) for _ in range(len(weight))]
# 初始化
for j in range(weight[0], bagweight + 1):
dp[0][j] = value[0]
# weight数组的大小就是物品个数
for i in range(1, len(weight)): # 遍历物品
for j in range(bagweight + 1): # 遍历背包容量
if j < weight[i]:
dp[i][j] = dp[i - 1][j]
else:
dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i])
return dp[len(weight) - 1][bagweight]
# 0 分割等和子集
# 将这个数组分割成两个子集,使得两个子集的元素和相等
class Solution:
def canPartition(self, nums: List[int]) -> bool:
total_sum = sum(nums)
if total_sum % 2 != 0:
return False
target_sum = total_sum // 2
dp = [[False] * (target_sum + 1) for _ in range(len(nums) + 1)]
# 初始化第一行(空子集可以得到和为0)
for i in range(len(nums) + 1):
dp[i][0] = True
for i in range(1, len(nums) + 1):
for j in range(1, target_sum + 1):
if j < nums[i - 1]:
# 当前数字大于目标和时,无法使用该数字
dp[i][j] = dp[i - 1][j]
else:
# 当前数字小于等于目标和时,可以选择使用或不使用该数字
dp[i][j] = dp[i - 1][j] or dp[i - 1][j - nums[i - 1]]
return dp[len(nums)][target_sum]
# 494.目标和
# 有两个符号 + 和 - 使最终数组和为目标数 S 的所有添加符号的方法数
class Solution:
def findTargetSumWays(self, nums: List[int], target: int) -> int:
total_sum = sum(nums) # 计算nums的总和
if abs(target) > total_sum:
return 0 # 此时没有方案
if (target + total_sum) % 2 == 1:
return 0 # 此时没有方案
target_sum = (target + total_sum) // 2 # 目标和
# 创建二维动态规划数组,行表示选取的元素数量,列表示累加和
dp = [[0] * (target_sum + 1) for _ in range(len(nums) + 1)]
# 初始化状态
dp[0][0] = 1
# 动态规划过程
for i in range(1, len(nums) + 1):
for j in range(target_sum + 1):
dp[i][j] = dp[i - 1][j] # 不选取当前元素
if j >= nums[i - 1]:
dp[i][j] += dp[i - 1][j - nums[i - 1]] # 选取当前元素
return dp[len(nums)][target_sum] # 返回达到目标和的方案数
# 474.一和零
# 找出并返回 strs 的最大子集的大小,该子集中 最多 有 m 个 0 和 n 个 1
# dp[i][j]:最多有i个0和j个1的strs的最大子集的大小为dp[i][j]。
# 518.零钱兑换II
# 函数来计算可以凑成总金额的硬币组合数
# 爬楼梯(进阶版)每次你可以爬至多m (1 <= m < n)个台阶。
#R 零钱兑换 计算可以凑成总金额所需的最少的硬币个数。
class Solution:
def coinChange(self, coins: List[int], amount: int) -> int:
n = len(coins)
dp = [[amount+1] * (amount+1) for _ in range(n+1)] # 初始化为一个较大的值,如 +inf 或 amount+1
# 合法的初始化
dp[0][0] = 0 # 其他 dp[0][j]均不合法
# 完全背包:优化后的状态转移
for i in range(1, n+1): # 第一层循环:遍历硬币
for j in range(amount+1): # 第二层循环:遍历背包
if j < coins[i-1]: # 容量有限,无法选择第i种硬币
dp[i][j] = dp[i-1][j]
else: # 可选择第i种硬币
dp[i][j] = min( dp[i-1][j], dp[i][j-coins[i-1]] + 1 )
ans = dp[n][amount]
return ans if ans != amount+1 else -1
# 279.完全平方数
# 若干个完全平方数(比如 1, 4, 9, 16, ...)使得它们的和等于 n。你需要让组成和的完全平方数的个数最少。
class Solution:
def numSquares(self, n: int) -> int:
# 预处理出 <=sqrt(n) 的完全平方数
nums = []
i = 1
while i*i <= n:
nums.append(i*i)
i += 1
# 转化为完全背包问题【套用「322. 零钱兑换」】
target = n
length = len(nums)
dp = [[target+1] * (target+1) for _ in range(length+1)] # 初始化为一个较大的值,如 +inf 或 target+1
dp[0][0] = 0 # 合法的初始化;其他 dp[0][j]均不合法
# 完全背包:优化后的状态转移
for i in range(1, length+1): # 第一层循环:遍历nums
for j in range(target+1): # 第二层循环:遍历背包
if j < nums[i-1]: # 容量有限,无法选择第i个数字
dp[i][j] = dp[i-1][j]
else: # 可选择第i个数字
dp[i][j] = min( dp[i-1][j], dp[i][j-nums[i-1]] + 1 )
return dp[length][target]
股票系列
#R 1. 买卖股票的最佳时机
class Solution:
def maxProfit(self, prices: List[int]) -> int:
length = len(prices)
if len == 0:
return 0
dp = [[0] * 2 for _ in range(length)]
dp[0][0] = -prices[0]
dp[0][1] = 0
for i in range(1, length):
dp[i][0] = max(dp[i-1][0], -prices[i])
dp[i][1] = max(dp[i-1][1], prices[i] + dp[i-1][0])
return dp[-1][1]
#R 122.买卖股票的最佳时机II
# 可以尽可能地完成更多的交易,不能同时参与多笔交易
class Solution:
def maxProfit(self, prices: List[int]) -> int:
length = len(prices)
dp = [[0] * 2 for _ in range(length)]
dp[0][0] = -prices[0]
dp[0][1] = 0
for i in range(1, length):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i]) #注意这里是和121. 买卖股票的最佳时机唯一不同的地方
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i])
return dp[-1][1]
#R 0 买卖股票的最佳时机含手续费
class Solution:
def maxProfit(self, prices: List[int], fee: int) -> int:
n = len(prices)
dp = [[0] * 2 for _ in range(n)]
dp[0][0] = -prices[0] #持股票
for i in range(1, n):
dp[i][0] = max(dp[i-1][0], dp[i-1][1] - prices[i])
dp[i][1] = max(dp[i-1][1], dp[i-1][0] + prices[i] - fee)
return max(dp[-1][0], dp[-1][1])
#R 123.买卖股票的最佳时机III 最多可以完成 两笔 交易。
# 0 没有操作 (其实我们也可以不设置这个状态); 1 第一次持有股票; 2 第一次不持有股票
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if len(prices) == 0:
return 0
dp = [[0] * 5 for _ in range(len(prices))]
dp[0][1] = -prices[0]
dp[0][3] = -prices[0]
for i in range(1, len(prices)):
dp[i][0] = dp[i-1][0]
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i])
dp[i][2] = max(dp[i-1][2], dp[i-1][1] + prices[i])
dp[i][3] = max(dp[i-1][3], dp[i-1][2] - prices[i])
dp[i][4] = max(dp[i-1][4], dp[i-1][3] + prices[i])
return dp[-1][4]
#R 188.买卖股票的最佳时机IV
最多可以完成 k 笔交易
class Solution:
def maxProfit(self, k: int, prices: List[int]) -> int:
if len(prices) == 0:
return 0
dp = [[0] * (2*k+1) for _ in range(len(prices))]
for j in range(1, 2*k, 2):
dp[0][j] = -prices[0]
for i in range(1, len(prices)):
for j in range(0, 2*k-1, 2):
dp[i][j+1] = max(dp[i-1][j+1], dp[i-1][j] - prices[i])
dp[i][j+2] = max(dp[i-1][j+2], dp[i-1][j+1] + prices[i])
return dp[-1][2*k]
#R 1. 股票最佳买卖股票时机含冷冻期
class Solution:
def maxProfit(self, prices: List[int]) -> int:
if not prices:
return 0
n = len(prices)
# f[i][0]: 手上持有股票的最大收益
# f[i][1]: 手上不持有股票,并且处于冷冻期中的累计最大收益
# f[i][2]: 手上不持有股票,并且不在冷冻期中的累计最大收益
f = [[-prices[0], 0, 0]] + [[0] * 3 for _ in range(n - 1)]
for i in range(1, n):
f[i][0] = max(f[i - 1][0], f[i - 1][2] - prices[i])
f[i][1] = f[i - 1][0] + prices[i]
f[i][2] = max(f[i - 1][1], f[i - 1][2])
return max(f[n - 1][1], f[n - 1][2])
子序列系列
# 300.最长递增子序列
# 找到其中最长严格递增子序列的长度
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if len(nums) <= 1:
return len(nums)
dp = [1] * len(nums)
result = 1
for i in range(1, len(nums)):
for j in range(0, i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
result = max(result, dp[i]) #取长的子序列
return result
# 1. 最长连续递增序列
class Solution:
def findLengthOfLCIS(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
result = 1
dp = [1] * len(nums)
for i in range(len(nums)-1):
if nums[i+1] > nums[i]: #连续记录
dp[i+1] = dp[i] + 1
result = max(result, dp[i+1])
return result
# 1. 最长重复子数组
# 数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度==连续子序列
class Solution:
def findLength(self, nums1: List[int], nums2: List[int]) -> int:
# 创建一个二维数组 dp,用于存储最长公共子数组的长度
dp = [[0] * (len(nums2) + 1) for _ in range(len(nums1) + 1)]
# 记录最长公共子数组的长度
result = 0
# 遍历数组 nums1
for i in range(1, len(nums1) + 1):
# 遍历数组 nums2
for j in range(1, len(nums2) + 1):
# 如果 nums1[i-1] 和 nums2[j-1] 相等
if nums1[i - 1] == nums2[j - 1]:
# 在当前位置上的最长公共子数组长度为前一个位置上的长度加一
dp[i][j] = dp[i - 1][j - 1] + 1
# 更新最长公共子数组的长度
if dp[i][j] > result:
result = dp[i][j]
# 返回最长公共子数组的长度
return result
# 1143.最长公共子序列
# 两个字符串的最长公共子序列的长度
class Solution:
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# 创建一个二维数组 dp,用于存储最长公共子序列的长度
dp = [[0] * (len(text2) + 1) for _ in range(len(text1) + 1)]
# 遍历 text1 和 text2,填充 dp 数组
for i in range(1, len(text1) + 1):
for j in range(1, len(text2) + 1):
if text1[i - 1] == text2[j - 1]:
# 如果 text1[i-1] 和 text2[j-1] 相等,则当前位置的最长公共子序列长度为左上角位置的值加一
dp[i][j] = dp[i - 1][j - 1] + 1
else:
# 如果 text1[i-1] 和 text2[j-1] 不相等,则当前位置的最长公共子序列长度为上方或左方的较大值
dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
# 返回最长公共子序列的长度
return dp[len(text1)][len(text2)]
# 1035.不相交的线
# 连接两个数字 A[i] 和 B[j] 的直线,只要 A[i] == B[j],且我们绘制的直线不与任何其他连线(非水平线)相交。
class Solution:
def maxUncrossedLines(self, A: List[int], B: List[int]) -> int:
dp = [[0] * (len(B)+1) for _ in range(len(A)+1)]
for i in range(1, len(A)+1):
for j in range(1, len(B)+1):
if A[i-1] == B[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
return dp[-1][-1]
# 1. 最大子序和
# 最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
class Solution:
def maxSubArray(self, nums: List[int]) -> int:
dp = [0] * len(nums)
dp[0] = nums[0]
result = dp[0]
for i in range(1, len(nums)):
dp[i] = max(dp[i-1] + nums[i], nums[i]) #状态转移公式
result = max(result, dp[i]) #result 保存dp[i]的最大值
return result
# 392.判断子序列
# s 是否为 t 的子序列
class Solution:
def isSubsequence(self, s: str, t: str) -> bool:
dp = [[0] * (len(t)+1) for _ in range(len(s)+1)]
for i in range(1, len(s)+1):
for j in range(1, len(t)+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = dp[i][j-1]
if dp[-1][-1] == len(s):
return True
return False
#R 0 不同的子序列
# s 的子序列中 t 出现的个数
class Solution:
def numDistinct(self, s: str, t: str) -> int:
dp = [[0] * (len(t)+1) for _ in range(len(s)+1)]
for i in range(len(s)):
dp[i][0] = 1
for j in range(1, len(t)):
dp[0][j] = 0
for i in range(1, len(s)+1):
for j in range(1, len(t)+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + dp[i-1][j]
else:
dp[i][j] = dp[i-1][j]
return dp[-1][-1]
# 1. 两个字符串的删除操作
# 每步可以删除任意一个字符串中的一个字符
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1)]
for i in range(len(word1)+1):
dp[i][0] = i
for j in range(len(word2)+1):
dp[0][j] = j
for i in range(1, len(word1)+1):
for j in range(1, len(word2)+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1] + 2, dp[i-1][j] + 1, dp[i][j-1] + 1)
return dp[-1][-1]
# 1. 编辑距离
class Solution:
def minDistance(self, word1: str, word2: str) -> int:
dp = [[0] * (len(word2)+1) for _ in range(len(word1)+1)]
for i in range(len(word1)+1):
dp[i][0] = i
for j in range(len(word2)+1):
dp[0][j] = j
for i in range(1, len(word1)+1):
for j in range(1, len(word2)+1):
if word1[i-1] == word2[j-1]:
dp[i][j] = dp[i-1][j-1]
else:
dp[i][j] = min(dp[i-1][j-1], dp[i-1][j], dp[i][j-1]) + 1
return dp[-1][-1]
# 1. 回文子串
字符串中有多少个回文子串,dp[i][j] 表示子串s[i⋯j]是否是回文子串
class Solution:
def countSubstrings(self, s: str) -> int:
n = len(s)
dp = [[False] * n for _ in range(n)]
result = 0
for i in range(n):
dp[i][i] = True
result += 1
for i in range(n - 1, -1, -1):
for j in range(i + 1, n):
dp[i][j] = (s[i] == s[j]) and (j - i <= 1 or dp[i + 1][j - 1])
if dp[i][j]:
result += 1
return result
#R 0 最长回文子序列
class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
dp = [[0] * len(s) for _ in range(len(s))]
for i in range(len(s)):
dp[i][i] = 1
for i in range(len(s)-1, -1, -1):
for j in range(i+1, len(s)):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return dp[0][-1]
#R 33.9 Wildcard Matching/通配符匹配 (Hard)
# '?' 可以匹配任何单个字符。'*' 可以匹配任意字符序列(包括空字符序列)。
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
f = [[False] * (n + 1) for _ in range(m + 1)]
# 初始条件:翻译自递归边界
# 对应边界 i<0 and j < 0 return True
f[0][0] = True
# 对应边界 i<0 j>=0的处理
for j in range(n):
f[0][j + 1] = p[j] == '*' and f[0][j]
# 对应边界 j<0 (f初始化已经赋值了False, 不用写)
for i in range(m):
for j in range(n):
if s[i] == p[j] or p[j] == '?':
f[i + 1][j + 1] = f[i][j]
elif p[j] == '*':
f[i + 1][j + 1] = f[i][j + 1] or f[i + 1][j]
return f[m][n]
# 64 Minimum Path Sum/最小路径和 (Medium)
# dp[i][j] 的值代表直到走到 (i,j) 的最小路径和。
class Solution:
def minPathSum(self, grid: [[int]]) -> int:
for i in range(len(grid)):
for j in range(len(grid[0])):
if i == j == 0: continue
elif i == 0: grid[i][j] = grid[i][j - 1] + grid[i][j]
elif j == 0: grid[i][j] = grid[i - 1][j] + grid[i][j]
else: grid[i][j] = min(grid[i - 1][j], grid[i][j - 1]) + grid[i][j]
return grid[-1][-1]
#R 91 Decode Ways/解码方法 (Medium)
# "12" 可以解码为 "AB"(1 2)或者 "L"(12) 时间复杂度O(n),空间复杂度O(n)
class Solution:
def numDecodings(self, s: str) -> int:
size = len(s)
#特判
if size == 0:
return 0
dp = [0]*(size+1)
dp[0] = 1
for i in range(1,size+1):
t = int(s[i-1])
if t>=1 and t<=9:
dp[i] += dp[i-1] #最后一个数字解密成一个字母
if i >=2:#下面这种情况至少要有两个字符
t = int(s[i-2])*10 + int(s[i-1])
if t>=10 and t<=26:
dp[i] += dp[i-2]#最后两个数字解密成一个一个字母
return dp[-1]
# 97 Interleaving String/交错字符串 (Medium)
# dp[i][j] 表示 s1 的前 i 个字符和 s2的前 j 个字符是否能构成s3 的前 i+j 个字符
class Solution:
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
len1=len(s1)
len2=len(s2)
len3=len(s3)
if(len1+len2!=len3):
return False
dp=[[False]*(len2+1) for i in range(len1+1)]
dp[0][0]=True
for i in range(1,len1+1):
dp[i][0]=(dp[i-1][0] and s1[i-1]==s3[i-1])
for i in range(1,len2+1):
dp[0][i]=(dp[0][i-1] and s2[i-1]==s3[i-1])
for i in range(1,len1+1):
for j in range(1,len2+1):
dp[i][j]=(dp[i][j-1] and s2[j-1]==s3[i+j-1]) or (dp[i-1][j] and s1[i-1]==s3[i+j-1])
return dp[-1][-1]
# 152 Maximum Product Subarray/乘积最大子数组 (Medium)
# 右端点下标为 i 的子数组的最大/小乘积
class Solution:
def maxProduct(self, nums: List[int]) -> int:
n = len(nums)
f_max = [0] * n
f_min = [0] * n
f_max[0] = f_min[0] = nums[0]
for i in range(1, n):
x = nums[i]
# 把 x 加到右端点为 i-1 的(乘积最大/最小)子数组后面,
# 或者单独组成一个子数组,只有 x 一个元素
f_max[i] = max(f_max[i - 1] * x, f_min[i - 1] * x, x)
f_min[i] = min(f_max[i - 1] * x, f_min[i - 1] * x, x)
return max(f_max)
# 198 House Robber (Easy)
# 每个房屋存放金额的非负整数数组,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。
class Solution:
def rob(self, nums: List[int]) -> int:
f = [0] * (len(nums) + 2)
for i, x in enumerate(nums):
f[i + 2] = max(f[i + 1], f[i] + x)
return f[-1]
# 221 Maximal Square/最大正方形 (Medium)
# f[i][j]代表在matrix[i-1][j-1]处能构成的最大正方形
class Solution:
def maximalSquare(self, matrix: List[List[str]]) -> int:
m, n = len(matrix), len(matrix[0])
f = [[0 for _ in range(n + 1)] for _ in range(m + 1)]
max_len = 0
for i in range(1, m + 1):
for j in range(1, n + 1):
if matrix[i - 1][j - 1] == "1":
f[i][j] = (
min(
f[i - 1][j],
f[i][j - 1],
f[i - 1][j - 1],
)
+ 1
)
max_len = max(max_len, f[i][j])
return max_len**2
#R 311.66 Burst Balloons/戳气球 (Medium)
# nums = [3,1,5,8] --> [3,5,8] --> [3,8] --> [8] --> []
# 定义 f[i][j] 表示戳破区间 [i,j] 内的所有气球能得到的最多硬币数
class Solution:
def maxCoins(self, nums: List[int]) -> int:
n = len(nums)
arr = [1] + nums + [1]
f = [[0] * (n + 2) for _ in range(n + 2)]
for i in range(n - 1, -1, -1):
for j in range(i + 2, n + 2):
for k in range(i + 1, j):
f[i][j] = max(f[i][j], f[i][k] + f[k][j] + arr[i] * arr[k] * arr[j])
return f[0][-1]
#R 377 Combination Sum IV/组合总和 Ⅳ (Medium)
# 与39题不同之处,顺序不同的序列被视作不同的组合。
class Solution(object):
def combinationSum4(self, nums, target):
dp = [0] * (target + 1)
dp[0] = 1
res = 0
for i in range(target + 1):
for num in nums:
if i >= num:
dp[i] += dp[i - num]
return dp[target]
#H 0 Arithmetic Slices II - Subsequence/等差数列划分 II - 子序列 (Hard)
# 所有等差子序列的数目 dp_ik=\sum_j^i-1 (dp_jk+1) 第一维用哈希表提高查询效率、节省空间
class Solution:
def numberOfArithmeticSlices(self, nums: List[int]) -> int:
n,ans=len(nums),0
dp=[defaultdict(int) for _ in range(n)]
for i,num in enumerate(nums):
for j in range(i):
k=num-nums[j]
dp[i][k]+=dp[j][k]+1
ans+=dp[j][k]
return ans
#H 0 Longest Arithmetic Subsequence/最长等差数列 (Medium)
# 以 a[i] 结尾的最长等差子序列公差及其长度,存到一个哈希表dp(i)={d:L}
class Solution:
def longestArithSeqLength(self, a: List[int]) -> int:
f = [{} for _ in range(len(a))]
for i, x in enumerate(a):
for j in range(i - 1, -1, -1):
d = x - a[j] # 公差
if d not in f[i]:
f[i][d] = f[j].get(d, 1) + 1
return max(max(d.values()) for d in f[1:])
#H 873 Length of Longest Fibonacci Subsequence/最长的斐波那契子序列的长度 (Medium)
# 这f[x][y]代表了以数字x和y结尾的最大斐波那契序列长度。f[x][y]=f[y−x][x]+1 由于数据范围很大,用哈希表嵌套哈希表实现。
class Solution:
def lenLongestFibSubseq(self, A: List[int]) -> int:
dp = {}
res = 0
tempA = set(A)
for i in range(1,len(A)):
for j in range(i):
diff = A[i]-A[j]
if diff <A[j] and diff in tempA:
dp[(A[j],A[i])] = dp.get((diff,A[j]),2)+1
res = max(res,dp[(A[j],A[i])])
return res
#R 467 Unique Substrings in Wraparound String/环绕字符串中唯一的子字符串 (Medium) 字符串 s = "zab" 有六个子字符串 ("z", "a", "b", "za", "ab", and "zab") 在 base 中
# dp[i] 表示以 s[i] 结尾的最长连续有效子串的长度。
class Solution:
def findSubstringInWraproundString(self, s: str) -> int:
if not s:
return 0
dp = {} # 记录以某个字符结尾的最长连续子串长度
max_len = 0 # 当前连续子串长度
for i in range(len(s)):
if i > 0 and (ord(s[i]) - ord(s[i-1]) == 1 or (s[i-1] == 'z' and s[i] == 'a')):
max_len += 1
else:
max_len = 1
dp[s[i]] = max(dp.get(s[i], 0), max_len)
return sum(dp.values())
#R 688 Knight Probability in Chessboard/骑士在棋盘上的概率 (Medium)
# dp[step][i][j]= 1/8 sum_di,dj dp[step−1][i+di][j+dj]
class Solution:
def knightProbability(self, n: int, k: int, row: int, column: int) -> float:
dp = [[[0] * n for _ in range(n)] for _ in range(k + 1)]
for step in range(k + 1):
for i in range(n):
for j in range(n):
if step == 0:
dp[step][i][j] = 1
else:
for di, dj in ((-2, -1), (-2, 1), (2, -1), (2, 1), (-1, -2), (-1, 2), (1, -2), (1, 2)):
ni, nj = i + di, j + dj
if 0 <= ni < n and 0 <= nj < n:
dp[step][i][j] += dp[step - 1][ni][nj] / 8
return dp[k][row][column]
# 740 Delete and Earn/删除并获得点数 (Medium)
# 必须删除 所有 等于 nums[i] - 1 和 nums[i] + 1 的元素。返回你能通过这些操作获得的最大点数。
# 数组 sum 记录数组 nums 中所有相同元素之和,即 sum[x]=x⋅c_x
class Solution:
def deleteAndEarn(self, nums: List[int]) -> int:
maxVal = max(nums)
total = [0] * (maxVal + 1)
for val in nums:
total[val] += val
def rob(nums: List[int]) -> int:
size = len(nums)
first, second = nums[0], max(nums[0], nums[1])
for i in range(2, size):
first, second = second, max(first + nums[i], second)
return second
return rob(total)
#R 877 Stone Game/石子游戏 (Medium)
class Solution:
def stoneGame(self, piles: List[int]) -> bool:
N = len(piles)
f = [[0]*(N+1) for _ in range(N+1)] # 防止出界
for l in range(N):
for i in range(N-l):
j = i+l
f[i][j] = max(piles[i]-f[i+1][j],
piles[j]-f[i][j-1])
return f[0][N-1]>0
# 0 Maximum Sum Circular Subarray/环形子数组的最大和 (Medium)
# 分类讨论一:子数组没有跨过边界(在nums中间)。最大非空子数组和(力扣第53题)
# 分类讨论二:子数组跨过边界(在nums两端)。其余元素和越小,子数组和越大。
class Solution:
def maxSubarraySumCircular(self, nums: List[int]) -> int:
max_s = -inf # 最大子数组和,不能为空
min_s = 0 # 最小子数组和,可以为空
max_f = min_f = 0
for x in nums:
# 以 nums[i-1] 结尾的子数组选或不选(取 max)+ x = 以 x 结尾的最大子数组和
max_f = max(max_f, 0) + x
max_s = max(max_s, max_f) # 最大遍历每个结尾
# 以 nums[i-1] 结尾的子数组选或不选(取 min)+ x = 以 x 结尾的最小子数组和
min_f = min(min_f, 0) + x
min_s = min(min_s, min_f)
if sum(nums) == min_s:
return max_s
return max(max_s, sum(nums) - min_s)
#R 1000 Minimum Cost to Merge Stones/合并石头的最低成本 (Hard)
# 每次移动需要将连续的k堆石头合并为一堆,成本为这k堆中石头的总数。
# 定义 f[i][j][k] 表示将 [i,j] 合并成k堆的最小成本 dp[i][j][k] = min(dp[i][m][1] + dp[m+1][j][k-1]) i≤m<j
def mergeStones(self, stones: List[int], K: int) -> int:
n = len(stones)
if (n - 1) % (K-1): return -1
def get(i, j):
return sums[j+1] - sums[i]
dp = [[[float("inf")] * (K+1) for _ in range(n)] for _ in range(n)]
sums = [0] * (1+n)
for i in range(1, n+1): sums[i] = sums[i-1] + stones[i-1]
for i in range(n): dp[i][i][1] = 0
for l in range(2, n+1):
for i in range(n - l + 1):
j = i + l - 1
for k in range(2, K+1):
for m in range(i, j):
dp[i][j][k] = min(dp[i][j][k], dp[i][m][1] + dp[m+1][j][k-1])
dp[i][j][1] = dp[i][j][K] + get(i, j) #合并成一堆特殊情况不可以从合并成0获得
return dp[0][n-1][1]
# 0 Longest String Chain/? (Medium)
# 返回 前身单词链的 最长可能长度
class Solution:
def longestStrChain(self, words: List[str]) -> int:
words.sort(key=len)
f = {} # 不要用 defaultdict,这会在字符串不存在的时候插入字符串
for s in words:
res = 0
for i in range(len(s)): # 枚举去掉 s[i]
res = max(res, f.get(s[:i] + s[i + 1:], 0))
f[s] = res + 1
return max(f.values())
# 1186 Maximum Subarray Sum with One Deletion/删除一次得到子数组最大和 (Medium)
# f[i][j] 表示子数组的右端点下标是i,j=不能/必须删除数字的情况下,子数组元素和的最大值。
class Solution:
def maximumSum(self, arr: List[int]) -> int:
f = [[-inf] * 2] + [[0, 0] for _ in arr]
for i, x in enumerate(arr):
f[i + 1][0] = max(f[i][0], 0) + x
f[i + 1][1] = max(f[i][1] + x, f[i][0])
return max(max(r) for r in f)
#R 1395 Count Number of Teams/统计作战单位数 (中等)
# 3个士兵组成一个作战单位单调的作战单位的方案数。
# dp[i]记录的是第i个数之前比其值小的数的个数
class Solution:
def numTeams(self, rating: List[int]) -> int:
def func(nums):
dp = [0] * len_
res = 0
for i in range(1, len_):
idx = i - 1
while idx >= 0:
if nums[i] > nums[idx]:
dp[i] += 1
if dp[idx] > 0:
res += dp[idx]
idx -= 1
return res
len_ = len(rating)
return func(rating[::-1]) + func(rating)
其他
#最大公约数
def gcd(a, b):
while b:
a, b = b, a % b
return a
#质因数分解
def prime_factors(n):
factors = []
divisor = 2
while n > 1:
while n % divisor == 0:
factors.append(divisor)
n //= divisor
divisor += 1
return factors
29.除法
def divide(dividend, divisor):
INT_MAX = 2**31 - 1
INT_MIN = -2**31
# 处理特殊情况
if divisor == 0:
return INT_MAX
if dividend == 0:
return 0
# 判断结果的正负性
sign = 1
if (dividend < 0) ^ (divisor < 0):
sign = -1
# 将被除数和除数都转为正数,以防止整数溢出
dividend, divisor = abs(dividend), abs(divisor)
# 使用位运算逐步减去除数的倍数
result = 0
while dividend >= divisor:
temp_divisor, multiple = divisor, 1
while dividend >= temp_divisor:
dividend -= temp_divisor
result += multiple
# 左移一位相当于将除数翻倍
temp_divisor <<= 1
multiple <<= 1
# 根据结果的正负性返回最终值
result *= sign
# 处理溢出情况
if result > INT_MAX:
return INT_MAX
elif result < INT_MIN:
return INT_MIN
else:
return result
311.稀疏矩阵乘法
def multiply(A, B):
# 确定 A 和 B 的行数和列数
rows_A, cols_A = len(A), len(A[0])
rows_B, cols_B = len(B), len(B[0])
# 初始化结果矩阵 C
C = [[0] * cols_B for _ in range(rows_A)]
# 遍历 A 的每个元素
for i in range(rows_A):
for j in range(cols_A):
# 如果 A 的元素不为 0,则遍历 B 对应列的元素进行乘法累加
if A[i][j] != 0:
for k in range(cols_B):
C[i][k] += A[i][j] * B[j][k]
return C
# ?a/b向0靠拢;a%b向-b或者b靠拢即a%b = a – (a/b)*b; 如果取余一定要 [0,n-1],可以: (a%n + n) % n;
# ?一堆string 转化成数字时,可以用map存储已经转化的string->number. 从左到右,先检验剩下的时候再map里,如果在 left*10^(right size)+right,else left = left*10 + 第i位,并存入map
# K个字母构成长度为n的字符,相邻最多两个一样:a[n]最后两个不一样;b[n]最后两个一样;一共c[n]=a[n]+b[n].则a[n]=c[n]*(K-1);b[n]=a[n-1]
# 0 ?/服务中心的最佳位置 (困难)
# 到所有客户的欧几里得距离的总和最小
class Solution:
def getMinDistSum(self, positions: List[List[int]]) -> float:
n = len(positions)
#### 梯度下降 Gradient Descent GD, 也可以尝试批量梯度下降,Batch Gradient Descent, BGD
epoches = 10 ** 5 #迭代次数
eps = 1e-7 #epsilon 浮点小相对误差限
lr = 1.11 #学习率 learning rate
decay = 0.003 #学习率的衰减率
def dist(xc, yc): #中心点[xc,yc]到所有点的距离之和
res = 0.0
for x, y in positions:
res += ((x-xc)**2 + (y-yc)**2) ** 0.5
return res
xc = sum(p[0] for p in positions) / n
yc = sum(p[1] for p in positions) / n
for epoc in range(epoches):
dx = 0.0
dy = 0.0
for x, y in positions:
dx += (xc-x) / ( ((xc-x)**2 + (yc-y)**2) ** 0.5 + eps )
dy += (yc-y) / ( ((xc-x)**2 + (yc-y)**2) ** 0.5 + eps )
xc_pre, yc_pre = xc, yc
xc -= dx * lr
yc -= dy * lr
lr *= (1.0 - decay)
delta = ((xc-xc_pre)**2 + (yc-yc_pre)**2) ** 0.5
if delta < eps:
break
return dist(xc, yc)
# 1779 ?/找到最近的有相同 X 或 Y 坐标的点 (简单)
# 返回距离你当前位置 曼哈顿距离 最近
class Solution:
def nearestValidPoint(self, x: int, y: int, points: List[List[int]]) -> int:
ans, mi = -1, inf
for i, (a, b) in enumerate(points):
if a == x or b == y:
d = abs(a - x) + abs(b - y)
if mi > d:
ans, mi = i, d
return ans
# 1049.9 Actors and Directors Who Cooperated At Least Three Times * $/? (Easy)
import pandas as pd
def actors_and_directors(actor_director: pd.DataFrame) -> pd.DataFrame:
# 使用 groupby 和 count 函数对 actor_id 和 director_id 进行分组计数
counts = actor_director.groupby(['actor_id', 'director_id']).size().reset_index(name='count')
# 从计数结果中筛选出合作次数大于等于3次的组合
result = counts[counts['count'] >= 3][['actor_id', 'director_id']]
return result
难题
# 218 The Skyline Problem/天际线问题 (Hard)
# 分治!时间复杂度为:nlog(n)
class Solution:
def getSkyline(self, buildings):
if not buildings: return []
if len(buildings) == 1:
return [[buildings[0][0], buildings[0][2]], [buildings[0][1], 0]]
mid = len(buildings) // 2
left = self.getSkyline(buildings[:mid])
right = self.getSkyline(buildings[mid:])
return self.merge(left, right)
def merge(self, left, right):
# 记录目前左右建筑物的高度
lheight = rheight = 0
# 位置
l = r = 0
# 输出结果
res = []
while l < len(left) and r < len(right):
if left[l][0] < right[r][0]:
# current point
cp = [left[l][0], max(left[l][1], rheight)]
lheight = left[l][1]
l += 1
elif left[l][0] > right[r][0]:
cp = [right[r][0], max(right[r][1], lheight)]
rheight = right[r][1]
r += 1
# 相等情况
else:
cp = [left[l][0], max(left[l][1], right[r][1])]
lheight = left[l][1]
rheight = right[r][1]
l += 1
r += 1
# 和前面高度比较,不一样才加入
if len(res) == 0 or res[-1][1] != cp[1]:
res.append(cp)
# 剩余部分添加进去
res.extend(left[l:] or right[r:])
return res
# 849.9 Rectangle Area II/矩形面积 II (Hard)
# 线段树 + 扫描线 太难了
class MyCalendarTwo: #线段树方法hard
def __init__(self):
self.tree = {}
def update(self, start: int, end: int, val: int, l: int, r: int, idx: int) -> None:
if r < start or end < l:
return
if start <= l and r <= end:
p = self.tree.get(idx, [0, 0])
p[0] += val
p[1] += val
self.tree[idx] = p
return
mid = (l + r) // 2
self.update(start, end, val, l, mid, 2 * idx)
self.update(start, end, val, mid + 1, r, 2 * idx + 1)
p = self.tree.get(idx, [0, 0])
p[0] = p[1] + max(self.tree.get(2 * idx, (0,))[0], self.tree.get(2 * idx + 1, (0,))[0])
self.tree[idx] = p
def book(self, start: int, end: int) -> bool:
self.update(start, end - 1, 1, 0, 10 ** 9, 1)
if self.tree[1][0] > 2:
self.update(start, end - 1, -1, 0, 10 ** 9, 1)
return False
return True
# 2663 Lexicographically Smallest Beautiful String/字典序最小的美丽字符串 (困难) hd
# 返回一个长度为n的美丽字符串,该字符串还满足:在字典序大于 s 的所有美丽字符串中字典序最小。
# 1 不能出现 s[i]=s[i−1] 以及 s[i]=s[i−2]; 2 小写字母表的前k个字母组成;3 修改的位置越靠右越好。s 视作一个 k 进制数,不停地末尾加一
class Solution:
def smallestBeautifulString(self, s: str, k: int) -> str:
a = ord('a')
k += a
s = list(map(ord, s)) #将字符串s转换为ASCII码列表
n = len(s)
i = n - 1 # 从最后一个字母开始
s[i] += 1 # 先加一
while i < n:
if s[i] == k: # 需要进位
if i == 0: # 无法进位
return ""
# 进位
s[i] = a
i -= 1
s[i] += 1
elif i and s[i] == s[i - 1] or i > 1 and s[i] == s[i - 2]:
s[i] += 1 # 如果 s[i] 和左侧的字符形成回文串,就继续增加 s[i]
else:
i += 1 # 反过来检查后面是否有回文串
return ''.join(map(chr, s))
# 0 Optimal Account Balancing $/最优账单平衡 (Hard)
# 输入: [[0,1,10],[2,0,5]],0 给 1 $10 ; 2 给 0 $5 。 输出:2
# 我们应该将这些人分成尽可能多的合计总金额为0的组。我们可以使用状态压缩动态规划,通过枚举子集的方式来进行求解。
# 令dp[state]表示state所对应的这组人所能够分成的最多的组数。注意我们只有在sum[state]=0的情况下才去枚举state的子集。
# 转移方程为dp[state]=max_{sub⫋state} (dp[sub])+1。最后的答案就是n−dp[2^n −1],其中n是总金额不为0的人数。
class Solution:
def minTransfers(self, transactions: List[List[int]]) -> int:
from collections import defaultdict
# 第一步:计算每个人的净余额
balance = defaultdict(int)
for sender, receiver, amount in transactions:
balance[sender] += amount # 发送者的余额增加
balance[receiver] -= amount # 接收者的余额减少
# 第二步:筛选出余额不为零的人
v = [account for account in balance.values() if account != 0]
n = len(v) # 非零余额的数量
# 第三步:计算所有子集的余额和
sum_ = [0] * (1 << n) # 初始化大小为 2^n 的 sum_ 数组
for i in range(1, 1 << n):
for j in range(n):
if i & (1 << j): # 如果子集中包含第 j 个人
sum_[i] = sum_[i ^ (1 << j)] + v[j] # 更新该子集的余额和
break
# 第四步:初始化动态规划数组
dp = [0] * (1 << n)
for i in range(1, 1 << n):
if sum_[i] != 0: # 如果子集的余额和不为零,跳过
continue
dp[i] = 1 # 初始至少需要一次转账
# 寻找子集的子掩码
si = (i - 1) & i # 使用 si 变量来存放当前子集的子掩码
while si: # 当 si 不为零时继续
if dp[si]: # 仅考虑有效的子掩码
dp[i] = max(dp[i], dp[si] + 1) # 更新当前子集的最大转账次数
si = (si - 1) & i # 获取下一个子掩码
# 最终结果是总人数减去能消除的转账数量
return n - dp[(1 << n) - 1] # dp[(1 << n) - 1] 表示所有人的组合
# 643.99 Maximum Average Subarray II $/子数组最大平均数 II (Hard)
# 找出长度大于等于 k 最大平均值的连续子数组的平均值。计算误差小于 10-5。
# 判断avg是大于还是小于答案,用累加和技巧计算长度大于等于 k的区间
class Solution:
def findMaxAverage(self, nums: List[int], k: int) -> float:
def check(avg):
# 如果是求长度等于 k 的区间的区间和:使用滑动窗口,维护首尾前缀和(见643.最大平均子段和)
# 这一题是大于等于 k 。 我们需要知道以 end 结尾,长度大于等于 k 的区间中最大的区间和
# 多维护一个 start_sum 的最小值即可。 end_sum - min_sum 即为所求区间和最大值
end_sum = sum(num - avg for num in nums[:k])
start_sum = min_start_sum = 0
for end in range(k, len(nums)):
if end_sum >= min_start_sum:
return True
end_sum += nums[end] - avg
start_sum += nums[end-k] - avg
min_start_sum = min(min_start_sum, start_sum)
return end_sum >= min_start_sum
# 二分法
l, r = min(nums), max(nums)
while r - l > 1e-5:
mid = (l+r) / 2
if check(mid): # 存在符合条件的区间,其平均值大于等于 mid,下界向上收缩
l = mid
else:
r = mid
return l