2223 lines
80 KiB
Markdown
2223 lines
80 KiB
Markdown
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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:]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
# 滑动窗口
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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 #返回最长窗口长度
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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 # 没有找到目标值
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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])
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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()
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
从左到右遍历元素。单调递增栈:栈顶最小。
|
|||
|
- 查找 「比当前元素大的元素」 就用 单调递增栈,查找 「比当前元素小的元素」 就用 单调递减栈。
|
|||
|
- 从 「左侧」 查找就看 「插入栈」 时的栈顶元素,从 「右侧」 查找就看 「弹出栈」 时即将插入的元素。
|
|||
|
|
|||
|
```python
|
|||
|
#H 模板,单调递增栈(递减改为小于等于)
|
|||
|
def monotoneIncreasingStack(nums):
|
|||
|
stack = []
|
|||
|
for num in nums:
|
|||
|
while stack and num >= stack[-1]:
|
|||
|
stack.pop()
|
|||
|
stack.append(num)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
# 堆
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
# 累加和
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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()]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
# 回溯算法
|
|||
|
|
|||
|
组合
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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() # 回溯
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
分割
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
子集
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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()
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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()
|
|||
|
```
|
|||
|
|
|||
|
排列
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
棋盘
|
|||
|
|
|||
|
```python
|
|||
|
#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 # 当前位置合法
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
其他
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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 []
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
# 动态规划
|
|||
|
|
|||
|
简单
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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])
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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])
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
子序列系列
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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:])
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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())
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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
|
|||
|
```
|
|||
|
|
|||
|
```python
|
|||
|
#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]
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
```python
|
|||
|
#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)
|
|||
|
```
|
|||
|
|
|||
|
|
|||
|
# 其他
|
|||
|
|
|||
|
|