class Solution:
# 时间复杂度:O(logn), 空间复杂度:O(1)
def divideCore(self, dividend, divisor):
result = 0
while dividend <= divisor: # 因为此时被除数和除数都成了负数
value = divisor
quotient = 1
while (value >= -2**30 and dividend <= value + value):
quotient += quotient
value += value
result += quotient
dividend -= value
return result
def divide(self, dividend: int, divisor: int) -> int:
if dividend == -2**31 and divisor == -1:
return 2**31 - 1 # 因为负数转化为正数会有溢出
negative = 2
if dividend > 0:
negative -= 1
dividend = -dividend
if divisor > 0:
negative -= 1
divisor = -divisor
result = self.divideCore(dividend, divisor)
if negative == 1:
return -result
else:
return result
class Solution:
def addBinary(self, a: str, b: str) -> str:
result = ""
i = len(a) - 1
j = len(b) - 1
carry = 0
while i >= 0 or j >= 0:
digitA = int(a[i]) if i >= 0 else 0 # 使用id(i)可以看出i自增前后的在内存中的位置是变了的
i -= 1
digitB = int(b[j]) if j >= 0 else 0
j -= 1
sum = digitA + digitB + carry
carry = 1 if sum >= 2 else 0
sum = sum - 2 if sum >= 2 else sum
result += str(sum)
result += '1' if carry == 1 else ''
return result[::-1]
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def countBits(self, n: int) -> List[int]:
result = [0] * (n + 1) # 利用好i/2计算1中的个数,
for i in range(1, n + 1):
result[i] = result[i >> 1] + (1 & i) # i&(i-1)中1的个数仅比i中1的个数少1个
return result
class Solution:
# 时间复杂度:O(n) 空间复杂度:O(1)
def singleNumber(self, nums: List[int]) -> int:
counts = [0] * 32
for num in nums:
for i in range(32):
counts[i] += (num >> (31 - i)) & 1 # 表示从左边起num中的第i个位数中是否是1 # python中特殊的是对于负数,右移动补0
res = 0
for i in range(32):
res = (res << 1) + counts[i] % 3
# 负数,原码y 反码-y-1 补码-y 移码是补码的
# python中的按位操作和计算机底层的操作是一样的,比如正数和负数都用补码保存,只不过正数的补码是自身,负数的补码是原码取反再加1
# 而python中的函数却是经过处理的,便于我们自身理解的,这也是为什么称python是高级语言
# res ^ 0xffffffff 表示对res的按位取反(截取出32位,把符号位的1全部反过来,但是数据位的也反了) 也就是此时是补码的反码(叫移码)
# ~ res (此时再取反一次就行了叫,就表示真正的补码)
return res if counts[0] % 3 == 0 else ~(res ^ 0xffffffff) # 最高位如果是1,证明是负数,此时保存的是补码。最高位如果是0,证明是正数,此时保存的是原码。因为Java中的int是32位
class Solution:
# 时间复杂度:O(nk + n^2) 空间复杂度:O(n),
# 当判断两个字符串是否包含相同字符时,该方法只需要一次位运算,而哈希表方法可能需要26次布尔运算
def maxProduct(self, words: List[str]) -> int:
n = len(words)
flags = [0] * n
for i in range(n):
for c in words[i]:
flags[i] |= 1 << (ord(c) - ord('a')) # 将字符对应的位数标记为1,或运算
res = 0
for i in range(n):
for j in range(i+1, n):
if flags[i] & flags[j] == 0: # 位运算判断是否有相同字符
prod = len(words[i]) * len(words[j])
res = max(res, prod)
return res
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def twoSum(self, numbers: List[int], target: int) -> List[int]:
i, j = 0, len(numbers) - 1
while i < j and numbers[i] + numbers[j] != target:
if numbers[i] + numbers[j] < target:
i += 1
else:
j -= 1
return [i, j]
class Solution:
# 时间复杂度:O(nlogn + n^2), 排序+找 空间复杂度:O(1)
def twosum(self, nums, i, res):
j = i + 1
k = len(nums) - 1
while j < k:
if nums[i] + nums[j] + nums[k] == 0:
res.append([nums[i], nums[j], nums[k]])
temp = nums[j]
while (nums[j] == temp and j < k): # twosum中滤掉重复的数,只需要考虑较小的数就行了
j += 1
elif nums[i] + nums[j] + nums[k] < 0:
j += 1
else:
k -= 1
def threeSum(self, nums: List[int]) -> List[List[int]]:
res = []
if len(nums) >= 3:
nums.sort() # 先排序,变成有序的
i = 0
while i < len(nums) - 2:
self.twosum(nums, i, res)
temp = nums[i]
i += 1
while nums[i] == temp and i < len(nums) - 1: # 滤掉重复的数
i += 1
return res
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。 感觉是一种贪心算法的思想。
#如果大于target,就左移P1(因为都是正数,所以相当于减少数字,减少和),如果大于target,就右移P2(相当于增加数字,增加和)
def minSubArrayLen(self, target: int, nums: List[int]) -> int:
left, sum = 0, 0
res = float('inf')
for right in range(len(nums)):
sum += nums[right]
while left <= right and sum >= target:
res = min(res, right - left + 1)
sum -= nums[left]
left += 1
return res if res != float('inf') else 0
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:
product = 1
left, res = 0, 0
for right in range(len(nums)):
product *= nums[right]
while left <= right and product >= k:
product /= nums[left]
left += 1
res += right - left + 1 if right >= left else 0
return res
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def subarraySum(self, nums: List[int], k: int) -> int:
sumToCount = dict()
sumToCount.setdefault(0, 1)
sum, count = 0, 0
for num in nums:
sum += num
count += sumToCount.get(sum - k, 0)
sumToCount[sum] = sumToCount.get(sum, 0) + 1
return count
哈希表,累加数组数字求子数组之和
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(n)
def findMaxLength(self, nums: List[int]) -> int:
sumToIndex = {0:-1}
sum, maxLength = 0, 0
for i in range(len(nums)):
sum += -1 if nums[i] == 0 else 1
if sum in sumToIndex.keys():
maxLength = max(maxLength, i - sumToIndex.get(sum))
else: # 只有不存在时才添加,这样可以保证保留的是第一个累积到当前扫描的数字之和
sumToIndex[sum] = i
return maxLength
累加数组数字求数字之和
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def pivotIndex(self, nums: List[int]) -> int:
total = 0
for i in range(len(nums)):
total += nums[i]
sum = 0
for i in range(len(nums)):
sum += nums[i]
if sum - nums[i] == total - sum: # 当前数字中左边数组之和 是否等于 右边数组之和
return i
return -1
累加数组数字求子数组之和
class NumMatrix:
# 时间复杂度:O(mn), 空间复杂度:O(mn)
def __init__(self, matrix: List[List[int]]):
if len(matrix) == 0 or len(matrix[0]) == 0:
return 0
self.sums = [[0] * (len(matrix[0]) + 1) for _ in range(len(matrix)+1)] # 都一个是为了防止求得本身是左上角的矩阵,以防出具ai出具ai
for i in range(len(matrix)):
rowSum = 0
for j in range(len(matrix[0])):
rowSum += matrix[i][j]
self.sums[i + 1][j + 1] = self.sums[i][j + 1] + rowSum
# 时间复杂度:O(1)
def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
return self.sums[row2 + 1][col2 + 1] - self.sums[row1][col2 + 1] - self.sums[row2 + 1][col1] + self.sums[row1][col1]
哈希表、双指针
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def areAllZero(self,counts):
for count in counts:
if count != 0:
return False
return True
def checkInclusion(self, s1: str, s2: str) -> bool:
if len(s2) >= len(s1):
counts = [0] * 26
for i in range(len(s1)):
counts[ord(s1[i]) - ord('a')] += 1
counts[ord(s2[i]) - ord('a')] -= 1
if self.areAllZero(counts):
return True
for j in range(len(s1), len(s2)):
counts[ord(s2[j]) - ord('a')] -= 1
counts[ord(s2[j - len(s1)]) - ord('a')] += 1
if self.areAllZero(counts):
return True
return False
哈希表、双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def areAllZero(self, counts):
for count in counts:
if count != 0:
return False
return True
def findAnagrams(self, s1: str, s2: str) -> List[int]:
indices = []
if len(s1) >= len(s2):
counts = [0] * 26
for i in range(len(s2)):
counts[ord(s2[i]) - ord('a')] += 1
counts[ord(s1[i]) - ord('a')] -= 1
if self.areAllZero(counts):
indices.append(0)
for j in range(len(s2), len(s1)):
counts[ord(s1[j]) - ord('a')] -= 1
counts[ord(s1[j - len(s2)]) - ord('a')] += 1
if self.areAllZero(counts):
indices.append(j - len(s2) + 1)
return indices
哈希表+双指针,避免多次遍历整个哈希表
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。避免需要多次遍历整个哈希表
def lengthOfLongestSubstring(self, s: str) -> int:
counts = dict()
longest = 1 if len(s) > 0 else 0
countDup = 0
j = -1
for i in range(len(s)):
counts[s[i]] = counts.get(s[i], 0) + 1
if counts[s[i]] == 2:
countDup += 1
while countDup > 0:
j += 1
counts[s[j]] -= 1
if counts[s[j]] == 1: # 用删除最右边一个字符后,该位置的值变成1判断是不是之前重复的字符,如果不是之前重复的,那会是-1
countDup -= 1
longest = max(i - j, longest)
return longest
哈希表+字符串 需要多次遍历整个哈希表
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)。需要多次遍历整个哈希表
def hasGreaterThan1(self, counts):
for count in counts.values():
if count > 1:
return True
return False
def lengthOfLongestSubstring(self, s: str) -> int:
counts = dict()
longest = 1 if len(s) > 0 else 0
j = -1
for i in range(len(s)):
counts[s[i]] = counts.get(s[i], 0) + 1
while self.hasGreaterThan1(counts):
j += 1
counts[s[j]] = counts.get(s[j], 0) - 1
longest = max(i - j, longest)
return longest
哈希表+双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def minWindow(self, s: str, t: str) -> str:
charToCount = dict()
for ch in t:
charToCount[ch] = charToCount.get(ch, 0) + 1
count = len(charToCount)
start = end = minStart = minEnd = 0
minLength = float('inf')
while end < len(s) or (count == 0 and end == len(s)):
if count > 0:
endCh = s[end]
if endCh in charToCount.keys():
charToCount[endCh] -= 1
if charToCount[endCh] == 0:
count -= 1
end += 1
else:
if end - start < minLength:
minLength = end - start
minStart = start
minEnd = end
startCh = s[start]
if startCh in charToCount.keys():
charToCount[startCh] += 1
if charToCount.get(startCh) == 1:
count += 1
start += 1
return s[minStart:minEnd] if minLength < float('inf') else ""
双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def isPalindrome(self, s: str) -> bool:
i, j = 0, len(s) - 1
while i < j:
ch1 = s[i]
ch2 = s[j]
if not ch1.isalnum():
i += 1
elif not ch2.isalnum():
j -= 1
else:
if ch1.lower() != ch2.lower():
return False
i += 1
j -= 1
return True
双指针
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def isPalindrome(self, s, start, end):
while start < end:
if s[start] != s[end]:
break
start += 1
end -= 1
return start >= end
def validPalindrome(self, s: str) -> bool:
start = 0
end = len(s) - 1
for _ in range(len(s) // 2):
if s[start] != s[end]:
break
start += 1
end -= 1
return start == len(s) // 2 or self.isPalindrome(s, start, end - 1) or self.isPalindrome(s, start + 1, end)
双指针,从里向外,考虑奇数和偶数
class Solution:
# 时间复杂度:O(n^2), 空间复杂度:O(1)
def countPalindrome(self, s, start, end):
count = 0
while start >= 0 and end < len(s) and s[start] == s[end]:
count += 1
start -= 1
end += 1
return count
def countSubstrings(self, s: str) -> int:
count = 0
for i in range(len(s)):
count += self.countPalindrome(s, i, i)
count += self.countPalindrome(s, i, i + 1)
return count
前后双指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode:
dummy = ListNode()
dummy.next = head
front, back = head, dummy
for i in range(n):
front = front.next
while front is not None:
front = front.next
back = back.next
back.next = back.next.next
return dummy.next
前后指针,不需要知道环中节点数目
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1),不需要知道环中节点数目
def getNodeInloop(self, head):
if head is None or head.next is None:
return None
slow = head.next
fast = slow.next
while (slow is not None and fast is not None):
if slow == fast:
return slow
slow = slow.next
fast = fast.next
if fast is not None:
fast = fast.next
return None
def detectCycle(self, head: ListNode) -> ListNode:
inLoop = self.getNodeInloop(head) # 此时inloop的步数是环中节点数目的整数倍
if (inLoop is None):
return None
node = head
while node != inLoop: # 慢指针从头开始,快指针从inLoop开始,相遇时一定是环的入口节点
node = node.next
inLoop = inLoop.next
return node
前后指针,需要知道环中节点数目
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1),需要知道环中节点数目
def getNodeInloop(self, head):
if head is None or head.next is None:
return None
slow = head.next
fast = slow.next
while (slow is not None and fast is not None):
if slow == fast:
return slow
slow = slow.next
fast = fast.next
if fast is not None:
fast = fast.next
return None
def detectCycle(self, head: ListNode) -> ListNode:
inLoop = self.getNodeInloop(head)
if (inLoop is None):
return None
loopCount = 1
n = inLoop
while n.next != inLoop:
loopCount += 1
n = n.next
fast = head
for i in range(loopCount):
fast = fast.next
slow = head
while fast!= slow:
fast = fast.next
slow = slow.next
return slow
快慢指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def Qiulength(self, head):
count = 0
while head != None:
count += 1
head = head.next
return count
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
count1 = self.Qiulength(headA)
count2 = self.Qiulength(headB)
delta = abs(count1 - count2)
longer = headA if count1 > count2 else headB
shorter = headB if count1 > count2 else headA
node1 = longer
for i in range(delta):
node1 = node1.next
node2 = shorter
while node1 != node2:
node2 = node2.next
node1 = node1.next
return node1
三个指针
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
三个指针,反转链表的推广
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(m + n), 空间复杂度:O(1)
def addReversed(self, head1, head2):
dummy = ListNode(0)
sumNode = dummy
carry = 0
while head1 is not None or head2 is not None:
sum = (0 if head1 is None else head1.val) + (0 if head2 is None else head2.val) + carry
carry = 1 if sum >= 10 else 0
sum = sum - 10 if sum >= 10 else sum
newNode = ListNode(sum)
sumNode.next = newNode
sumNode = sumNode.next
head1 = None if head1 is None else head1.next
head2 = None if head2 is None else head2.next
if carry > 0:
sumNode.next = ListNode(carry)
return dummy.next
def reverseList(self, head):
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
head1 = self.reverseList(l1)
head2 = self.reverseList(l2)
reversedHead = self.addReversed(head1, head2)
return self.reverseList(reversedHead)
快慢指针找到中点,反转链表推广(反转链表的一半)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def link(self, node1, node2, head):
pre = head
while node1 is not None and node2 is not None:
temp = node1.next
pre.next = node1
node1.next = node2
pre = node2
node1 = temp
node2 = node2.next
if node1 is not None:
pre.next = node1
def reverseList(self, first):
pre = None
cur = first
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def reorderList(self, head: ListNode) -> None:
dummy = ListNode()
dummy.next = head
fast = slow = dummy
while fast is not None and fast.next is not None:
slow = slow.next
fast = fast.next.next
temp = slow.next
slow.next = None
self.link(head, self.reverseList(temp), dummy)
快慢指针,反转链表的推广(反转链表的前半段并且跟后半段做比较)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def isPalindrome(self, head: ListNode) -> bool:
if head is None or head.next is None:
return True
slow = head
fast = head.next
while fast.next is not None and fast.next.next is not None:
fast = fast.next.next
slow = slow.next
secondHalf = slow.next
if fast.next is not None:
secondHalf = slow.next.next
slow.next = None
return self.equals(secondHalf, self.reverseList(head))
def reverseList(self, head):
pre = None
cur = head
while cur is not None:
next = cur.next
cur.next = pre
pre = cur
cur = next
return pre
def equals(self, l1, l2):
while l1 is not None and l2 is not None:
if l1.val != l2.val:
return False
l1 = l1.next
l2 = l2.next
return True
双向链表
"""
# Definition for a Node.
class Node:
def __init__(self, val, prev, next, child):
self.val = val
self.prev = prev
self.next = next
self.child = child
"""
class Solution:
# 时间复杂度:O(n),空间复杂度:O(k)。k是链表的层数
def flatten(self, head: 'Node') -> 'Node':
self.flattenGetTail(head)
return head
def flattenGetTail(self, head):
node = head
tail = None
while node is not None:
next = node.next
if node.child is not None:
child = node.child
childTail = self.flattenGetTail(node.child)
node.child = None
node.next = child
child.prev = node
childTail.next = next
if next is not None:
next.prev = childTail
tail = childTail
else:
tail = node
node = next
return tail
指针,循环链表
"""
# Definition for a Node.
class Node:
def __init__(self, val=None, next=None):
self.val = val
self.next = next
"""
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def insert(self, head: 'Node', insertVal: int) -> 'Node':
node = Node(insertVal)
if head is None: # 特殊情况:链表本身为空
head = node
node.next = head
elif head.next is None: # 特殊情况:链表本身长度为1
head.next = node
node.next = head
else:
self.insertCore(head, node)
return head
def insertCore(self, head, node):
cur = head
next = head.next
biggest = head
while not(cur.val <= node.val and next.val >= node.val) and (next != head):
cur = next
next = next.next
if (cur.val >= biggest.val):
biggest = cur
if cur.val <= node.val and next.val >= node.val:
cur.next = node
node.next = next
else:
node.next = biggest.next
biggest.next = node
哈希表、数组
class RandomizedSet:
def __init__(self):
self.numToLocation = dict()
self.nums = []
def insert(self, val: int) -> bool:
if val in self.numToLocation.keys():
return False
self.numToLocation[val] = len(self.nums)
self.nums.append(val)
return True
def remove(self, val: int) -> bool:
if val not in self.numToLocation.keys():
return False
location = self.numToLocation[val]
self.numToLocation[self.nums[len(self.nums) - 1]] = location
del self.numToLocation[val]
self.nums[location] = self.nums[len(self.nums) - 1]
del self.nums[len(self.nums) - 1]
return True
def getRandom(self) -> int:
r = random.randint(0, len(self.nums) - 1)
return self.nums[r]
哈希表+双向链表(键的值 = 结点)
class Node:
def __init__(self, val=0, prev=None, next=None):
self.value = val
self.prev = prev
self.next = next
class LRUCache:
# 时间复杂度:O(n)
def __init__(self, capacity: int):
self.head = Node(0)
self.tail = Node(0)
self.head.next = self.tail
self.tail.prev = self.head
self.map = dict()
self.capacity = capacity
def get(self, key: int) -> int:
if key not in self.map.keys(): # 键不在字典中
return -1
node = self.map[key]
self.moveToTail(node, node.value) # 键在字典中,直接修改到尾部
return node.value # 返回值
def put(self, key: int, value: int) -> None:
if key in self.map.keys(): # 键在字典中,直接修改到尾部
self.moveToTail(self.map[key], value)
else: # 键不在字典中,需要考虑是否超了容量
if len(self.map) == self.capacity: # 超过容量了,要删除最近最少使用的,同时修改链表和字典
toBeDeleted = self.head.next
self.deleteNode(toBeDeleted)
for k,v in self.map.items():
if v == toBeDeleted:
del self.map[k]
break
node = Node(value) # 插入到尾部
self.insertToTail(node)
self.map[key] = node
def moveToTail(self, node, newvalue):
self.deleteNode(node)
node.value = newvalue
self.insertToTail(node)
def deleteNode(self, node):
node.prev.next = node.next
node.next.prev = node.prev
def insertToTail(self, node):
self.tail.prev.next = node
node.prev = self.tail.prev
node.next = self.tail
self.tail.prev = node
数组哈希
真正的哈希表
class Solution:
# # 时间复杂度:O(n), 空间复杂度:O(1) 只考虑英文字母,则用数组模拟哈希表
# def isAnagram(self, s: str, t: str) -> bool:
# if len(s) != len(t) or s == t:
# return False
# counts = [0] * 26
# for ch in s:
# counts[ord(ch)- ord('a')] += 1
# for ch in t:
# if counts[ord(ch)- ord('a')] == 0:
# return False
# counts[ord(ch) - ord('a')] -= 1
# return True
# # 时间复杂度:O(n), 空间复杂度:O(n) 考虑非英文字母,用真正的哈希表
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t) or s == t:
return False
counts = dict()
for ch in s:
counts[ch] = counts.get(ch, 0) + 1
for ch in t:
if counts.get(ch, 0) == 0:
return False
counts[ch] = counts.get(ch) - 1
return True
字符映射到数字
将单词的字母排序
class Solution:
# # 时间复杂度:O(mn),空间复杂度:O(1) 将单词映射到数字,乘法可能会有溢出
# def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
# hash = [2,3,5,7,11,13,17,19,23,29,31,37,41,43,47,53,59,61,67,71,73,79,83,89,97,101]
# groups = dict()
# i = []
# for str in strs:
# wordHash = 1
# for ch in str:
# wordHash *= hash[ord(ch) - ord('a')]
# i = groups.get(wordHash, [])
# i.append(str)
# groups[wordHash] = i
# return list(groups.values())
# 时间复杂度:O(nmlogm),空间复杂度:O(1) 将单词的字母排序,这样不会出现乘法溢出问题
def groupAnagrams(self, strs: List[str]) -> List[List[str]]:
groups = dict()
i = []
for str in strs:
key = ''.join(sorted(str))
i = groups.get(key, [])
i.append(str)
groups[key] = i
return list(groups.values())
哈希表
数组模拟哈希表
class Solution:
# # 时间复杂度:O(n), 空间复杂度:O(1)(哈希表) 真正的哈希表
# def isAlienSorted(self, words: List[str], order: str) -> bool:
# orderArray = dict()
# for i in range(len(order)):
# orderArray[order[i]] = i
# for j in range(len(words) - 1):
# if not self.isSorted(words[j], words[j + 1], orderArray):
# return False
# return True
# def isSorted(self, word1, word2, orderArray):
# i = 0
# while i < len(word1) and i < len(word2):
# ch1 = word1[i]
# ch2 = word2[i]
# if orderArray[ch1] < orderArray[ch2]:
# return True
# if orderArray[ch1] > orderArray[ch2]:
# return False
# i += 1
# return i == len(word1)、
# 时间复杂度:O(n), 空间复杂度:O(1)(哈希表) 数组模拟哈希表
def isAlienSorted(self, words: List[str], order: str) -> bool:
orderArray = [0] * len(order)
for i in range(len(order)):
orderArray[ord(order[i]) - ord('a')] = i
for j in range(len(words) - 1):
if not self.isSorted(words[j], words[j + 1], orderArray):
return False
return True
def isSorted(self, word1, word2, orderArray):
i = 0
while i < len(word1) and i < len(word2):
ch1 = word1[i]
ch2 = word2[i]
if orderArray[ord(ch1) - ord('a')] < orderArray[ord(ch2) - ord('a')]:
return True
if orderArray[ord(ch1) - ord('a')] > orderArray[ord(ch2) - ord('a')]:
return False
i += 1
return i == len(word1)
哈希表:布尔数组
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(1)
def findMinDifference(self, timePoints: List[str]) -> int:
if len(timePoints) > 1440:
return 0
minuteFlags = [False] * 1440
for time in timePoints:
a, b = time.split(":")
minute = int(a) * 60 + int(b)
if minuteFlags[minute]:
return 0
minuteFlags[minute] = True
return self.helper(minuteFlags)
def helper(self, minuteFlags):
minDiff = len(minuteFlags) - 1
prev = -1
first = len(minuteFlags) - 1
last = -1
for i in range(len(minuteFlags)):
if minuteFlags[i]:
if prev >= 0:
minDiff = min(i - prev, minDiff)
prev = i
first = min(i, first)
last = max(i, last)
minDiff = min(first + len(minuteFlags) - last, minDiff)
return minDiff
栈
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def evalRPN(self, tokens: List[str]) -> int:
stack = []
for token in tokens:
if token == '+' or token == '-' or token == '*' or token == '/' :
num1 = stack.pop()
num2 = stack.pop()
stack.append(self.calculate(num2, num1, token))
else:
stack.append(int(token))
return stack.pop()
def calculate(self, num1, num2, operator):
if operator == '+':
return num1 + num2
elif operator == '-':
return num1 - num2
elif operator == '*':
return num1 * num2
elif operator == '/':
return ceil(num1 // num2) if num1 // num2 >= 0 else -floor(abs(num1 / num2))
else:
return 0
列表模拟栈
class Solution:
# 时间复杂度:O(n),空间复杂度:O(1)
def asteroidCollision(self, asteroids: List[int]) -> List[int]:
stack = []
for aster in asteroids:
while len(stack) != 0 and stack[-1] > 0 and stack[-1] < -aster:
stack.pop()
if len(stack) != 0 and aster < 0 and stack[-1] == - aster:
stack.pop()
elif aster > 0 or len(stack) == 0 or stack[-1] < 0:
stack.append(aster)
return stack
数组模拟栈
class Solution:
# 时间复杂度:O(n),空间复杂度:O(n)
def dailyTemperatures(self, temperatures: List[int]) -> List[int]:
result = [0] * len(temperatures)
stack = []
for i in range(len(temperatures)):
while len(stack) != 0 and temperatures[i] > temperatures[stack[-1]]:
prev = stack.pop()
result[prev] = i - prev
stack.append(i)
return result
蛮力法
分治法,递归栈
单调栈
class Solution:
# # 解法一、蛮力法。时间复杂度:O(n), 空间复杂度:O(1)
# def largestRectangleArea(self, heights: List[int]) -> int:
# maxArea = 0
# for i in range(len(heights)):
# minL = heights[i]
# for j in range(i, len(heights)):
# minL = min(minL, heights[j])
# area = minL * (j - i + 1)
# maxArea = max(area, maxArea)
# return maxArea
# # 解法二、分治法,递归。时间复杂度:O(nlogn), 空间复杂度:O(logn)
# def largestRectangleArea(self, heights: List[int]) -> int:
# if len(heights) == 0:
# return 0
# return self.helper(heights, 0, len(heights))
# def helper(self, heights, start, end):
# if start == end:
# return 0
# if start + 1 == end:
# return heights[start]
# minIndex = start
# for i in range(start + 1, end):
# if heights[i] < heights[minIndex]:
# minIndex = i
# area = (end - start) * heights[minIndex] # 第一种情况:矩形通过最矮的柱子
# left = self.helper(heights, start, minIndex) # 第二种情况:矩形最矮的柱子的左侧
# right = self.helper(heights, minIndex + 1, end) # 第二种情况:矩形最矮的柱子的右侧
# area = max(area, left)
# return max(area, right)
# 解法三、单调栈法。时间复杂度:O(n), 空间复杂度:O(logn)
def largestRectangleArea(self, heights: List[int]) -> int:
stack = []
stack.append(-1)
maxArea = 0
for i in range(len(heights)):
while stack[-1] != -1 and heights[stack[-1]] >= heights[i]: # 维护一个单调递增的栈
height = heights[stack.pop()]
width = i - stack[-1] - 1
maxArea = max(maxArea, height * width)
stack.append(i)
while stack[-1] != -1:
height = heights[stack.pop()]
width = len(heights) - stack[-1] - 1
maxArea = max(maxArea, height * width)
return maxArea
直方图最大矩形面积的拓展(单调栈)
class Solution:
# 时间复杂度:O(mn),空间复杂度:O(n)
def maximalRectangle(self, matrix: List[str]) -> int:
if len(matrix) == 0 or len(matrix[0]) == 0:
return 0
heights = [0] * len(matrix[0])
maxArea = 0
for row in range(len(matrix)): # 转换成row个直方图
for col in range(len(matrix[0])): # 确定每个直方图的高度
if matrix[row][col] == '0':
heights[col] = 0
else:
heights[col] += 1
maxArea = max(maxArea, self.largestRectangleArea(heights)) # 每个直方图的最大矩形面积用单调栈的方法确定
return maxArea
def largestRectangleArea(self, heights):
stack = []
stack.append(-1)
maxArea = 0
for i in range(len(heights)):
while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:
height = heights[stack.pop()]
width = i - stack[-1] - 1
maxArea = max(maxArea, height * width)
stack.append(i)
while stack[-1] != -1:
height = heights[stack.pop()]
width = len(heights) - stack[-1] - 1
maxArea = max(maxArea, height * width)
return maxArea
队列
class MovingAverage:
# 时间复杂度:O(1),空间复杂度:O(1),队列
from collections import deque
def __init__(self, size: int):
self.capacity = size
self.nums = deque()
self.sum = 0
def next(self, val: int) -> float:
self.nums.append(val)
self.sum += val
if len(self.nums) > self.capacity:
self.sum -= self.nums.popleft()
return self.sum / len(self.nums)
队列
class RecentCounter:
# 时间复杂度:O(1) 3000ms, 空间复杂度:O(1) 3000ms 队列
def __init__(self):
self.times = []
self.windowSize = 3000
def ping(self, t: int) -> int:
self.times.append(t)
while self.times[0] + self.windowSize < t:
self.times.pop(0)
return len(self.times)
队列(二叉树的广度优先遍历)
# 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 CBTInserter:
# 时间复杂度:O(n), 空间复杂度:O(n)
def __init__(self, root: TreeNode):
self.root = root
self.queue = []
self.queue.append(self.root)
while self.queue[0].left != None and self.queue[0].right != None:
node = self.queue.pop(0)
self.queue.append(node.left)
self.queue.append(node.right)
def insert(self, v: int) -> int:
parent = self.queue[0]
node = TreeNode(v)
if parent.left == None:
parent.left = node
else:
parent.right = node
self.queue.pop(0)
self.queue.append(parent.left)
self.queue.append(parent.right)
return parent.val
def get_root(self) -> TreeNode:
return self.root
一个队列
两个队列
class Solution:
# # 由于要知道每层的开始和结束,因此可能需要两个队列
# # 时间复杂度:O(n),空间复杂度:O(n)), 用一个队列,两个变量记录
# def largestValues(self, root: TreeNode) -> List[int]:
# current, next = 0, 0
# queue = []
# if root != None:
# queue.append(root)
# current = 1
# result = []
# maxValue = float('-inf')
# while len(queue) != 0:
# node = queue.pop(0)
# current -= 1
# maxValue = max(maxValue, node.val)
# if node.left != None:
# queue.append(node.left)
# next += 1
# if node.right != None:
# queue.append(node.right)
# next += 1
# if current == 0:
# result.append(maxValue)
# maxValue = float('-inf')
# current = next
# next = 0
# return result
# 时间复杂度:O(n), 空间复杂度:O(n), 用两个队列
def largestValues(self, root: TreeNode) -> List[int]:
queue1 = []
queue2 = []
if root != None:
queue1.append(root)
result = []
maxValue = float('-inf')
while len(queue1) != 0:
node = queue1.pop(0)
maxValue = max(maxValue, node.val)
if node.left != None:
queue2.append(node.left)
if node.right != None:
queue2.append(node.right)
if len(queue1) == 0:
result.append(maxValue)
maxValue = float('-inf')
queue1 = queue2
queue2 = []
return result
队列
# 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:
# 时间复杂度:O(n), 空间复杂度:O(1) 队列
def findBottomLeftValue(self, root: TreeNode) -> int:
queue1 = []
queue2 = []
if root != None:
queue1.append(root)
bottomLeft = root.val
while len(queue1) != 0:
node = queue1.pop(0)
if node.left != None:
queue2.append(node.left)
if node.right != None:
queue2.append(node.right)
if len(queue1) == 0:
queue1 = queue2
queue2 = []
if len(queue1) != 0:
bottomLeft = queue1[0].val
return bottomLeft
两个队列
# 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:
# 时间复杂度:O(n), 空间复杂度:O(n) ;队列
def rightSideView(self, root: TreeNode) -> List[int]:
view = []
if root is None:
return view
queue1 = []
queue2 = []
queue1.append(root)
while len(queue1) != 0:
node = queue1.pop(0)
if node.left != None:
queue2.append(node.left)
if node.right != None:
queue2.append(node.right)
if len(queue1) == 0:
view.append(node.val)
queue1 = queue2
queue2 = []
return view
后续遍历
# 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:
# 时间复杂度:O(n),空间复杂度:O(n),后续遍历
def pruneTree(self, root: TreeNode) -> TreeNode:
if root is None:
return root
root.left = self.pruneTree(root.left)
root.right = self.pruneTree(root.right)
if root.left is None and root.right is None and root.val == 0:
return None
return root
前序遍历
# Definition for a binary tree node.
# class TreeNode(object):
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Codec:
# 时间复杂度:O(n), 空间复杂度:O(n), 前序遍历
def serialize(self, root):
result = []
self.dfs(root, result)
return result
def dfs(self, node, r):
if node == None:
r.append(None)
else:
r.append(node.val)
self.dfs(node.left, r)
self.dfs(node.right, r)
return r
def deserialize(self, data):
i = [0] # 采用数组是因为调用者也需要知道下标增加1了????就是不太懂
return self.df2s(data, i)
def df2s(self, strs, i):
s = strs[i[0]]
i[0] += 1
if s is None:
return None
node = TreeNode(s)
node.left = self.df2s(strs, i)
node.right = self.df2s(strs, i)
return node
深度优先遍历(前序遍历)
# 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:
# 时间复杂度:O(n),空间复杂度:O(n) 深度优先遍历
def sumNumbers(self, root: TreeNode) -> int:
return self.dfs(root, 0)
def dfs(self, root, path):
if root == None: # 如果在遇到叶节点之前就结束的路径,由于不符合要求,因此应该返回0
return 0
path = path * 10 + root.val
if root.left == None and root.right == None:
return path
return self.dfs(root.left, path) + self.dfs(root.right, path)
dfs,哈希,前序遍历
# 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:
# 注意该题不包括经过孩子结点,而不经过父亲节点的路径
# # 时间复杂度:O(n),空间复杂度:O(h),深度优先遍历,前序遍历,哈希表
def pathSum(self, root: TreeNode, targetSum: int) -> int:
map = dict()
map[0] = 1
return self.dfs(root, targetSum, map, 0)
def dfs(self, root, targetSum, map, path):
if root == None:
return 0
path += root.val
count = map.get(path - targetSum , 0)
map[path] = map.get(path, 0) + 1
count += self.dfs(root.left, targetSum, map, path)
count += self.dfs(root.right, targetSum, map, path)
map[path] -= 1 # 函数结束时,程序将回到节点的父节点,也就是说结束之前需要将当前节点从路径中删除
return count
dfs,后序遍历
# 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:
# # 时间复杂度:O(n),空间复杂度:O(h). 深度优先遍历,后序遍历
def maxPathSum(self, root: TreeNode) -> int:
maxSum = [float('-inf')]
self.dfs(root, maxSum)
return maxSum[0]
def dfs(self, root, maxSum):
if root is None:
return 0
maxSumLeft = [float('-inf')]
left = max(0, self.dfs(root.left, maxSumLeft)) # 左子树路径节点值节点值之和的最大值
maxSumRight = [float('-inf')]
right = max(0, self.dfs(root.right, maxSumRight)) # 右子树路径节点值节点值之和的最大值
maxSum[0] = max(maxSumLeft[0], maxSumRight[0])
maxSum[0] = max(maxSum[0], left + right + root.val) # 三者比较取其大
return root.val + max(left, right) # 最后返回经过根节点路径的节点值之和
利用BTS的特点
# 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:
# 时间复杂度:O(n),空间复杂度:O(h) 中序遍历.二叉搜索树
def increasingBST(self, root: TreeNode) -> TreeNode:
stack = []
cur = root
prev = None
first = None
while cur != None or len(stack) != 0:
while (cur != None):
stack.append(cur)
cur = cur.left
cur = stack.pop()
if prev != None:
prev.right = cur
else:
first = cur
prev = cur
cur.left = None
cur = cur.right
return first
(二叉树的中序遍历的拓展)
# Definition for a binary tree node.
# class TreeNode:
# def __init__(self, x):
# self.val = x
# self.left = None
# self.right = None
class Solution:
# # 解法一、时间复杂度:O(n), 空间复杂度:O(h)
# def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
# stack = []
# cur = root
# found = False
# while cur != None or len(stack) != 0:
# while cur != None:
# stack.append(cur)
# cur = cur.left
# cur = stack.pop()
# if found:
# break
# elif p == cur:
# found = True
# cur = cur.right
# return cur
# 解法一、时间复杂度:O(h), 空间复杂度:O(1) 利用二叉搜索树下一个结点一定比自身大的特点
def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
cur = root
result = None
while cur != None:
if cur.val > p.val:
result = cur
cur = cur.left
else:
cur = cur.right
return result
(中序遍历)
# 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:
# 时间复杂度:O(n),空间复杂度:O(h),中序遍历按照根->右->左的顺序遍历,使得节点按照从大到小顺序排列
def convertBST(self, root: TreeNode) -> TreeNode:
stack = []
cur = root
sum = 0
while cur != None or len(stack) != 0:
while cur != None:
stack.append(cur)
cur = cur.right
cur = stack.pop()
sum += cur.val
cur.val = sum
cur = cur.left
return root
(中序遍历)
# 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 BSTIterator:
# 中序遍历
def __init__(self, root: TreeNode):
self.cur = root
self.stack = []
def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
while self.cur != None:
self.stack.append(self.cur)
self.cur = self.cur.left
self.cur = self.stack.pop()
val = self.cur.val
self.cur = self.cur.right
return val
def hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
return self.cur != None or len(self.stack) != 0
(中序遍历)
# 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:
# # 解法一哈希表、时间复杂度:O(n),空间复杂度:O(n) 哈希表,中序遍历
# def findTarget(self, root: TreeNode, k: int) -> bool:
# res = set()
# stack = []
# cur = root
# while cur != None or len(stack) != 0:
# while cur != None:
# stack.append(cur)
# cur = cur.left
# cur = stack.pop()
# if k - cur.val in res:
# return True
# res.add(cur.val)
# cur = cur.right
# return False
# 解法二应用双指针、时间复杂度:O(h),空间复杂度:O(1) 哈希表,中序遍历
def findTarget(self, root: TreeNode, k: int) -> bool:
if root == None:
return False
iterNext = BSTIterator(root)
iterPrev = BSTIteratorReversed(root)
next = iterNext.next()
prev = iterPrev.prev()
while next != prev:
if next + prev == k:
return True
if next + prev < k:
next = iterNext.next()
else:
prev = iterPrev.prev()
return False
class BSTIterator:
# 中序遍历
def __init__(self, root: TreeNode):
self.cur = root
self.stack = []
def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
while self.cur != None:
self.stack.append(self.cur)
self.cur = self.cur.left
self.cur = self.stack.pop()
val = self.cur.val
self.cur = self.cur.right
return val
def hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
return self.cur != None or len(self.stack) != 0
class BSTIteratorReversed:
# 中序遍历
def __init__(self, root: TreeNode):
self.cur = root
self.stack = []
def prev(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
while self.cur != None:
self.stack.append(self.cur)
self.cur = self.cur.right
self.cur = self.stack.pop()
val = self.cur.val
self.cur = self.cur.left
return val
def hasPrev(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
return self.cur != None or len(self.stack) != 0
哈希表、有序集合
from sortedcontainers import SortedSet
class Solution:
# # 时间复杂度:O(n), 空间复杂度:O(k)。哈希表
# def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
# buckets = dict()
# bucketSize = t + 1
# for i in range(len(nums)):
# num = nums[i]
# id = self.getBucketID(num, bucketSize)
# if id in buckets.keys() or (id-1 in buckets.keys() and buckets[id-1]+t>=num) or (id+1 in buckets.keys() and buckets[id+1]-t<=num):
# return True
# buckets[id] = num
# if i >= k:
# del buckets[self.getBucketID(nums[i-k], bucketSize)]
# return False
# def getBucketID(self, num, bucketSize):
# return num // bucketSize if num >= 0 else (num + 1) // bucketSize - 1
# 时间复杂度:O(n*logk), 空间复杂度:O(n)。有序集合
def containsNearbyAlmostDuplicate(self, nums: List[int], k: int, t: int) -> bool:
if not nums: return False
if k == 0: return False
n=len(nums)
k = min(n-1,k)
sst = SortedSet()
for i in range(k+1):
a = nums[i]
l,r = a-t,a+t
pl,pr=sst.bisect_left(l),sst.bisect_left(r)
if pl < pr: return True
if pr<len(sst) and sst[pr]==r: return True
sst.add(a)
for i in range(1,n-k):
b,a = nums[i-1],nums[i+k]
sst.discard(b)
l,r = a-t,a+t
pl,pr=sst.bisect_left(l),sst.bisect_left(r)
if pl < pr: return True
if pr<len(sst) and sst[pr]==r: return True
sst.add(a)
return False
(有序字典)
from sortedcontainers import SortedDict as SD
class MyCalendar:
# 时间复杂度:O(logn),空间复杂度;O(logn) 有序字典
def __init__(self):
self.end_start = SD()
def book(self, start: int, end: int) -> bool:
ID = self.end_start.bisect_right(start)
if ID >= 0 and ID < len(self.end_start): # 此时当前事件开始时间位于某个编号为ID的事件结束之前
if self.end_start.values()[ID] < end: # 编号为ID的事件开始时间却在当前事件结束时间之前。因此重叠
return False
self.end_start[end] = start
return True
(最小堆)
import heapq
class KthLargest:
# 时间复杂度:O(logk), 空间复杂度中;O(k) 最小堆,适用于n远大于k的情况
def __init__(self, k: int, nums: List[int]):
self.size = k
self.heap = []
for num in nums:
self.add(num)
def add(self, val: int) -> int:
heapq.heappush(self.heap, val)
if len(self.heap) > self.size:
heapq.heappop(self.heap)
return self.heap[0]
(最小堆+哈希表)
class Solution:
# 时间复杂度:O(logk), 空间复杂度:O(n + k) 最小堆 + 哈希表
def topKFrequent(self, nums: List[int], k: int) -> List[int]:
numToCount = dict()
for num in nums:
numToCount[num] = numToCount.get(num, 0) + 1
minHeap = []
for ke, fre in numToCount.items():
heapq.heappush(minHeap, (fre, ke)) # 因为要对频率排序,所以频率放在前面
if len(minHeap) > k:
heapq.heappop(minHeap)
res = []
while minHeap:
fre, ke = heapq.heappop(minHeap)
res.append(ke)
return res
(最小堆)
class Solution:
# # 解法一、时间复杂度:O(k^2logk), 空间复杂度:O(k) 最大堆问题,加符号变成最小堆问题
# def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
# maxHeap = []
# for i in range(min(k, len(nums1))):
# for j in range(min(k, len(nums2))):
# heapq.heappush(maxHeap, (-nums1[i]-nums2[j], [nums1[i], nums2[j]]))
# if len(maxHeap) > k:
# heapq.heappop(maxHeap)
# res = []
# while maxHeap:
# vals = heapq.heappop(maxHeap)
# res.append(vals[1])
# return res
# 解法二、时间复杂度:O(klogk), 空间复杂度:O(k) 真正的最小堆
def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]:
minHeap = []
if len(nums2) > 0:
for i in range(min(k, len(nums1))):
heapq.heappush(minHeap, (nums1[i] + nums2[0], [i, 0]))
res = []
while k > 0 and len(minHeap) != 0:
k -= 1
_,ids = heapq.heappop(minHeap)
res.append([nums1[ids[0]], nums2[ids[1]]])
if ids[1] < len(nums2) - 1:
heapq.heappush(minHeap, (nums1[ids[0]] + nums2[ids[1] + 1], [ids[0], ids[1] + 1]))
return res
class Trie:
def __init__(self):
self.children = [None] * 26
self.isEnd = False
def searchPrefix(self, prefix):
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: # 时间复杂度:O(n),空间复杂度:O(1)
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: # 时间复杂度:O(n),空间复杂度:O(1)
node = self.searchPrefix(word)
return node is not None and node.isEnd
def startsWith(self, prefix: str) -> bool: # 时间复杂度:O(n),空间复杂度:O(1)
return self.searchPrefix(prefix) is not None
class TrieNode:
def __init__(self):
self.children = [None] * 26
self.isword = False
class Solution:
# 时间复杂度:O(n),空间复杂度:O(n)
def replaceWords(self, dictionary: List[str], sentence: str) -> str:
root = self.buildTrie(dictionary)
words = sentence.split(' ')
for i in range(len(words)):
prefix = self.findPrefix(root, words[i])
if len(prefix) != 0:
words[i] = prefix
return " ".join(words)
def buildTrie(self, diction):
root = TrieNode()
for word in diction:
node = root
for ch in word:
if node.children[ord(ch) - ord('a')] == None:
node.children[ord(ch) - ord('a')] = TrieNode()
node = node.children[ord(ch) - ord('a')]
node.isword = True
return root
def findPrefix(self, root, word):
node = root
builder = ""
for ch in word:
if node.isword or node.children[ord(ch)-ord('a')] == None:
break
builder += ch
node = node.children[ord(ch)-ord('a')]
return builder if node.isword else ""
class TrieNode:
def __init__(self):
self.children = [None] * 26
self.isword = False
class MagicDictionary:
# 时间复杂度:O(n),空间复杂度:O(n)
def __init__(self):
self.root = TrieNode()
def buildDict(self, dictionary: List[str]) -> None:
for word in dictionary:
node = self.root
for ch in word:
if node.children[ord(ch)-ord('a')] == None:
node.children[ord(ch)-ord('a')] = TrieNode()
node = node.children[ord(ch)-ord('a')]
node.isword = True
def search(self, searchWord: str) -> bool:
root = self.root
return self.dfs(root, searchWord, 0, 0)
def dfs(self, root, word, i, edit):
if root is None:
return False
if root.isword and i == len(word) and edit == 1:
return True
if i < len(word) and edit <= 1:
found = False
for j in range(26):
if not found:
next = edit if j == (ord(word[i])-ord('a')) else edit + 1
found = self.dfs(root.children[j], word, (i + 1), next)
else:break
return found
return False
(后缀变前缀+深度优先遍历)
class TrieNode:
def __init__(self):
self.children = [None] * 26
class Solution:
# 时间复杂度;O(n),空间复杂度:O(n) 后缀树,深度优先遍历
def minimumLengthEncoding(self, words: List[str]) -> int:
root = self.buildTries(words)
total = [0]
self.dfs(root, 1, total)
return total[0]
def buildTries(self, words):
root = TrieNode()
for word in words:
node = root
for i in range(len(word)-1, -1, -1):
ch = word[i]
if node.children[ord(ch)-ord('a')] is None:
node.children[ord(ch)-ord('a')] = TrieNode()
node = node.children[ord(ch) - ord('a')]
return root
def dfs(self, root, length, total):
isLeaf = True
for child in root.children:
if child is not None:
isLeaf = False
self.dfs(child, length + 1, total)
if isLeaf:
total[0] += length
(前缀树+递归)
class TrieNode:
def __init__(self):
self.children = [None] * 26
self.value = 0
class MapSum:
# 时间复杂度:O(n),空间复杂度:O(n), 前缀树+递归
def __init__(self):
self.root = TrieNode()
def insert(self, key: str, val: int) -> None:
node = self.root
for i in range(len(key)):
ch = key[i]
if node.children[ord(ch)-ord('a')] is None:
node.children[ord(ch)-ord('a')] = TrieNode()
node = node.children[ord(ch)-ord('a')]
node.value = val
def sum(self, prefix: str) -> int:
node = self.root
for i in range(len(prefix)):
ch = prefix[i]
if node.children[ord(ch)-ord('a')] is None:
return 0
node = node.children[ord(ch)-ord('a')]
return self.getSum(node)
def getSum(self, node):
if node is None:
return 0
result = node.value
for child in node.children:
result += self.getSum(child)
return result
(前缀树)
class TrieNode:
def __init__(self):
self.children = [None] * 2
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(n).前缀树+
def findMaximumXOR(self, nums: List[int]) -> int:
root = self.buildTrie(nums)
maxV = 0
for num in nums:
node = root
xor = 0
for i in range(31, -1, -1): # 每个数字的深度都是32
bit = (num >> i) & 1
if node.children[1 - bit] is not None: # 每一次都尽量找最高位异或为1的靠近
xor = (xor << 1) + 1
node = node.children[1 - bit]
else:
xor = xor << 1
node = node.children[bit]
maxV = max(maxV, xor)
return maxV
def buildTrie(self, nums):
root = TrieNode() # 构造深度是32,每个结点的子节点都是2的前缀树
for num in nums:
node = root
for i in range(31, -1, -1):
bit = (num >> i) & 1
if node.children[bit] is None:
node.children[bit] = TrieNode()
node = node.children[bit]
return root
(二分查找)
class Solution:
# 时间复杂度:O(logn),空间复杂度:O(l)
def searchInsert(self, nums: List[int], target: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
mid = (left + right) // 2
if nums[mid] >= target:
if mid == 0 or nums[mid - 1] < target: # 退出的边界情况或者情况
return mid
right = mid - 1
else:
left = mid + 1
return len(nums)
(二分查找)
class Solution:
# 时间复杂度:O(logn),空间复杂度:O(1), 数组部分有序,二分查找
def peakIndexInMountainArray(self, arr: List[int]) -> int:
left = 1
right = len(arr) - 2
while left <= right:
mid = (left + right) // 2
if arr[mid] > arr[mid + 1] and arr[mid] > arr[mid - 1]: # 满足条件
return mid
if arr[mid] > arr[mid - 1]: # 位于数组上升部分,前半部分
left = mid + 1
else: # 位于数组下降部分,后半部分
right = mid - 1
(二分查找)
class Solution:
# 时间复杂度:O(logn), 空间复杂度;O(1)。二分查找,排序数组
def singleNonDuplicate(self, nums: List[int]) -> int:
left = 0
right = len(nums) // 2 # 注意这里应该是向下取整
while left <= right:
mid = (left + right) // 2
i = mid * 2
if i < len(nums) - 1 and nums[i] != nums[i + 1]:
if mid == 0 or nums[i - 2] == nums[i - 1]:
return nums[i]
right = mid - 1
else:
left = mid + 1
return nums[len(nums) - 1]
class Solution:
# 时间复杂度:O(logn), 空间复杂度:O(n) 二分查找
def __init__(self, w: List[int]):
self.total = sum(w)
self.sums, isum = [0], 0
for i in w:
isum += i
self.sums.append(isum) # 前缀和
def pickIndex(self) -> int:
arr = random.randint(1, self.total)
left = 1
right = len(self.sums) - 1
while left <= right:
mid = (left + right) // 2
if self.sums[mid - 1] < arr <= self.sums[mid]: # 找到第一个大于arr的数字的下标
left = mid
break
elif self.sums[mid - 1] >= arr: # 左边界都大于等于arr,说明区间太靠右了
right = mid - 1
else:
left = mid + 1 # 否则是因为区间太靠左
return left - 1
(二分查找)
class Solution:
# 时间复杂度:O(logn), 空间复杂度:O(n) 二分查找
def mySqrt(self, x: int) -> int:
left = 1
right = x
while left <= right:
mid = (left + right) // 2
if mid <= x / mid and (mid + 1) > x / (mid + 1) :
return mid
elif mid + 1 <= x / (mid + 1):
left = mid + 1
else:
right = mid - 1
return 0
(二分查找)
class Solution:
# 时间复杂度:O(mlogn),空间复杂度:O(n)二分查找 m是香蕉的堆数,n是最大堆的数目
def minEatingSpeed(self, piles: List[int], h: int) -> int:
maxV = float('-inf')
for pile in piles:
maxV = max(maxV, pile)
left, right = 2, maxV
while left <= right:
mid = (left + right) // 2
hours1 = self.getHours(piles, mid)
hours2 = self.getHours(piles, mid - 1)
if hours1 <= h and hours2 > h:
return mid
elif hours1 <= h and hours2 <= h:
right = mid - 1
elif hours1 > h:
left = mid + 1
return 1
def getHours(self, piles, speed):
hours = 0
for pile in piles:
hours += (pile + speed - 1) // speed
return hours
class Solution:
# 时间复杂度:O(nlogn)[排序] + O(n)[一次扫描], 空间复杂度:O(logn)[排序的开销] 排序
def merge(self, intervals: List[List[int]]) -> List[List[int]]:
intervals.sort(key = lambda x: x[0]) # 先对数组的首元素排序
merged = []
for interval in intervals:
if not merged or merged[-1][1] < interval[0]: # 如果列表为空,或者当前区间与区间不重合,则直接添加
merged.append(interval)
else:
merged[-1][1] = max(merged[-1][1], interval[1]) # 否则的话,我们就可以与上一区间进行合并
return merged
(计数排序)
class Solution:
# 时间复杂度:O(m+n), 空间复杂度:O(1)因为范围是1000,计数排序
def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:
counts = [0] * 1001
for num in arr1: # 得到数组arr1的计数排序
counts[num] += 1
i = 0
for num in arr2: # 按照arr2的顺序输出数字
while counts[num] > 0:
arr1[i] = num
i += 1
counts[num] -= 1
for j in range(len(counts)): # 对数组中剩余的数进行排序
while counts[j] > 0:
arr1[i] = j
i += 1
counts[j] -= 1
return arr1
(快速排序)
class Solution:
# 时间复杂度:O(n) , 空间复杂度;O(1)快速排序,是一种不断划分左侧小于当前数,右侧大于当前数的过程
def findKthLargest(self, nums: List[int], k: int) -> int:
target = len(nums) - k
start, end = 0, len(nums) - 1
index = self.partition(nums, start, end)
while index != target:
if index > target:
end = index - 1
else:
start = index + 1
index = self.partition(nums, start, end)
return nums[index]
def partition(self, nums, start, end):
randomV = random.randint(start, end)
nums[randomV], nums[end] = nums[end], nums[randomV]
small = start - 1 # 相当于第一个指针
for i in range(start, end):
if nums[i] < nums[end]:
small += 1
nums[i], nums[small] = nums[small], nums[i]
small += 1
nums[end], nums[small] = nums[small], nums[end]
return small
(归并排序)
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# 时间复杂度:O(nlogn), 空间复杂度:O(logn) 递归+归并排序
def sortList(self, head: ListNode) -> ListNode:
if head is None or head.next is None:
return head
head1 = head
head2 = self.split(head)
head1 = self.sortList(head1)
head2 = self.sortList(head2)
return self.merge(head1, head2)
def split(self, head): # 快慢指针
slow = head
fast = head.next
while fast != None and fast.next != None:
slow = slow.next
fast = fast.next.next
second = slow.next
slow.next = None
return second
def merge(self, head1, head2):
dummy = ListNode()
cur = dummy
while head1 != None and head2 != None:
if head1.val < head2.val:
cur.next = head1
head1 = head1.next
else:
cur.next = head2
head2 = head2.next
cur = cur.next
cur.next = head2 if
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
# # 时间复杂度:O(nlogk)k个链表,共n个结点。空间复杂度:O(k) 利用最小堆选取值为最小的节点
# def mergeKLists(self, lists: List[ListNode]) -> ListNode:
# dummy = ListNode()
# cur = dummy
# minHeap = []
# for num, li in enumerate(lists):
# if li != None:
# heapq.heappush(minHeap, (li.val, num, li))
# while minHeap:
# val, num, least = heapq.heappop(minHeap)
# cur.next = least
# cur = cur.next
# least = least.next
# if least != None:
# heapq.heappush(minHeap, (least.val, num, least))
# return dummy.next
# 时间复杂度:O(nlogk)k个链表,共n个结点。空间复杂度:O(logk) 递归+归并排序
def mergeKLists(self, lists: List[ListNode]) -> ListNode:
if len(lists) == 0:
return None
return self.mergeLists(lists, 0, len(lists)) # 调用函数合并两个链表的
def mergeLists(self, lists, start, end): # 分治法,多个链表通过分治法不断递归转换成两个链表
if start + 1 == end:
return lists[start]
mid = (start + end) // 2
head1 = self.mergeLists(lists, start, mid)
head2 = self.mergeLists(lists, mid, end)
return self.merge(head1, head2)
def merge(self, head1, head2): # 合并两个链表
dummy = ListNode()
cur = dummy
while head1 != None and head2 != None:
if head1.val < head2.val:
cur.next = head1
head1 = head1.next
else:
cur.next = head2
head2 = head2.next
cur = cur.next
cur.next = head2 if head1 == None else head1
return dummy.next
(回溯法+递归)
class Solution:
# 时间复杂度:O(2^n), 空间复杂度:O(2^n) 递归
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
if len(nums) == 0:
return result
self.helper(nums, 0, [], result) # 递归
return result
def helper(self, nums, index, subset, result):
if index == len(nums):
subset1 = subset[:] # 注意这里需要将subset的拷贝添加到result中,可以避免之后修改subset,而修改result
result.append(subset1)
elif index < len(nums):
self.helper(nums, index + 1, subset, result) # 选择不加入当前元素
subset.append(nums[index]) # 选择加入上前元素
self.helper(nums, index + 1, subset, result)
subset.pop() # 把刚才加入的剔除
(回溯法+递归)
class Solution:
# 时间复杂度:O(2^n), 空间复杂度:O() 回溯法 + 递归
def combine(self, n: int, k: int) -> List[List[int]]:
result = []
combination = []
self.helper(n, k, 1, combination, result)
return result
def helper(self, n, k, i, combination, result):
if len(combination) == k: # 添加到结果中的子集的条件
com1 = combination[:]
result.append(com1)
elif i <= n:
self.helper(n, k, i + 1, combination, result) # 选择当前数字不添加到子集中
combination.append(i) # 选择当前数字添加到子集中
self.helper(n, k, i + 1, combination, result)
combination.pop() # 为了不影响被调用函数,需要将数字从当前子集中剔除
(回溯法+递归)
class Solution:
# 时间复杂度:O(2^n), 空间复杂度:O() 回溯法+递归
def combinationSum(self, candidates: List[int], target: int) -> List[List[int]]:
result = []
combination = []
self.helper(candidates, target, 0, combination, result)
return result
def helper(self, nums, target, i, combination, result):
if target == 0:
com1 = combination[:]
result.append(com1)
elif target > 0 and i < len(nums): # 增加一个target条件是因为如果小于0没必要再递归,用于剪枝
self.helper(nums, target, i + 1, combination, result) # 选择不添加
combination.append(nums[i]) # 选择添加
self.helper(nums, target - nums[i], i, combination, result)
combination.pop()
(回溯法 + 递归)
class Solution:
# 时间复杂度:O(2^n), 空间复杂度:O(), 排序 + 回溯法
def combinationSum2(self, candidates: List[int], target: int) -> List[List[int]]:
candidates.sort()
result = []
combination = []
self.helper(candidates, target, 0, combination, result)
return result
def helper(self, nums, target, i, combination, result):
if target == 0:
com1 = combination[:]
result.append(com1)
elif target > 0 and i < len(nums):
self.helper(nums, target, self.getNext(nums, i), combination, result) # 不选择当前数,当决定跳过某个值为m的数字时,跳过所有值为m的数字
combination.append(nums[i]) # 选择当前数
self.helper(nums, target - nums[i], i + 1, combination, result)
combination.pop()
def getNext(self, nums, index): # 辅助跳过与当前相同的数
next = index
while next < len(nums) and nums[next] == nums[index]:
next += 1
return next
(回溯法 + 递归)
class Solution:
# 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归
def permute(self, nums: List[int]) -> List[List[int]]:
result = []
self.helper(nums, 0, result)
return result
def helper(self, nums, i, result):
if i == len(nums):
permutation = nums[:]
result.append(permutation)
else:
for j in range(i, len(nums)): # 遍历当前位置和之后的位置
nums[j], nums[i] = nums[i], nums[j] # 从当前下标开始交换位置,选定当前位置的元素
self.helper(nums, i + 1, result) # 回溯选择下一位置的元素
nums[j], nums[i] = nums[i], nums[j] # 清楚对排列状态的修改,再次交换之前交换的两个数字
(回溯法 + 递归)
class Solution:
# 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归
def permuteUnique(self, nums: List[int]) -> List[List[int]]:
result = []
self.helper(nums, 0, result)
return result
def helper(self, nums, i, result):
if i == len(nums):
permutation = nums[:]
result.append(permutation)
else:
HashSet = set() # 哈希集合用来保存已经交换到排列下标为i的位置的所有值
for j in range(i, len(nums)):
if nums[j] not in HashSet:
HashSet.add(nums[j]) # 集合中添加元素是add
nums[j], nums[i] = nums[i], nums[j]
self.helper(nums, i + 1, result)
nums[j], nums[i] = nums[i], nums[j]
(回溯法 + 递归)
class Solution:
# 时间复杂度:O(2^2n),空间复杂度:O() 回溯法 + 递归
def generateParenthesis(self, n: int) -> List[str]:
result = []
self.helper(n, n, "", result)
return result
def helper(self, left, right, parenthesis, result):
if left == 0 and right == 0:
result.append(parenthesis) # 字符串是不可变序列,因此不需要创建新的数据类型来保存
if left > 0:
self.helper(left - 1, right, parenthesis + "(", result) # 添加左括号
if left < right:
self.helper(left, right - 1, parenthesis + ")", result) # 添加右括号
(回溯法 + 递归)
class Solution:
# 回溯法 + 递归
def partition(self, s: str) -> List[List[str]]:
result = []
self.helper(s, 0, [], result)
return result
def helper(self, str1, start, substring, result):
if start == len(str1):
sub1 = substring[:] # 列表是可变序列
result.append(sub1)
for i in range(start, len(str1)): # for循环依次判断后面的回文子串
if self.isPalindrome(str1, start, i): # 是回文子串就添加
substring.append(str1[start: i + 1]) # 注意字符串切片的中间的冒号
self.helper(str1, i + 1, substring, result) # 递归调用
substring.pop() # 恢复状态
def isPalindrome(self, str1, start, end): # 判断改子串是否是回文
while start < end:
if str1[start] != str1[end]:
return False
start += 1
end -= 1
return True
(回溯法 + 递归)
# 回溯法 + 递归 IP地址的特点是:1、三个.分隔字符为4段,每段是从0-255之间,2、除0外,其他数字不能以0开头
def restoreIpAddresses(self, s: str) -> List[str]:
result = []
self.helper(s, 0, 0, "", "", result)
return result
def helper(self, s, index, segIndex, seg, ip, result):
if index == len(s) and segIndex == 3 and self.isValisdSeg(seg):
result.append(ip + seg) # 字符串是不可变序列
elif index < len(s) and segIndex <= 3: # 整个字符串索引和分段索引都符合要求
ch = s[index]
if self.isValisdSeg(seg + ch): # 把当前字符加到当前段
self.helper(s, index + 1, segIndex, seg + ch, ip, result)
if len(seg) > 0 and segIndex < 3: # 把当前字符加到下一段,,此时还没有完全加,所以index不加1,只是开辟出一个新的段,等待下一次判断是否是有效的加入
self.helper(s, index, segIndex + 1, "", ip + seg + ".", result)
def isValisdSeg(self, seg): # 只有当前段在0-255之间,并且本身是0或者不以0开头的非0值
return int(seg) <= 255 and ((int(seg) == 0 and len(seg) == 1) or seg[0] != '0')
(单序列问题 // 动态规划)
class Solution:
# # 递归代码,存在大量的重复计算,因为子问题之间有重叠
# def minCostClimbingStairs(self, cost: List[int]) -> int:
# n = len(cost)
# return min(self.helper(cost, n - 2), self.helper(cost, n - 1))
# def helper(self, cost, i):
# if i < 2:
# return cost[i]
# return min(self.helper(cost, i - 2), self.helper(cost, i - 1)) + cost[i]
# # 使用缓存的递归代码,自上而下将已经求解过的问题的结果保存下来。 时间复杂度:O(n), 空间复杂度:O(n)
# def minCostClimbingStairs(self, cost: List[int]) -> int:
# n = len(cost)
# dp = [0] * n
# self.helper(cost, n - 1, dp)
# return min(dp[n - 2], dp[n - 1])
# def helper(self, cost, i, dp):
# if i < 2:
# dp[1] = cost[1]
# dp[0] = cost[0]
# elif dp[i] == 0:
# self.helper(cost, i - 2, dp)
# self.helper(cost, i - 1, dp)
# dp[i] = min(dp[i - 2], dp[i - 1]) + cost[i]
# # 迭代代码, 自下往上。 时间复杂度:O(n), 空间复杂度:O(n)
# def minCostClimbingStairs(self, cost: List[int]) -> int:
# n = len(cost)
# dp = [0] * n
# dp[0], dp[1] = cost[0], cost[1]
# for i in range(2, n):
# dp[i] = min(dp[i - 2], dp[i - 1]) + cost[i]
# return min(dp[n - 2], dp[n - 1])
# 动态规划,自下往上。 时间复杂度:O(n), 空间复杂度:O(1)
def minCostClimbingStairs(self, cost: List[int]) -> int:
n = len(cost)
dp = [cost[0], cost[1]]
for i in range(2, n):
dp[i % 2] = min(dp[0], dp[1]) + cost[i]
return min(dp[0], dp[1])
(单序列问题 // 动态规划)
class Solution:
# # 单序列问题,带缓存的递归代码,自上往下。时间复杂度;O(n), 空间复杂度;O(n)
# def rob(self, nums: List[int]) -> int:
# if len(nums) == 0:
# return 0
# dp = [-1] * len(nums)
# self.helper(nums, len(nums) - 1, dp)
# return dp[len(nums) - 1]
# def helper(self, nums, i, dp):
# if i == 0:
# dp[i] = nums[0]
# elif i == 1:
# dp[i] = max(nums[0], nums[1])
# elif dp[i] < 0:
# self.helper(nums, i - 2, dp)
# self.helper(nums, i - 1, dp)
# dp[i] = max(dp[i - 2] + nums[i], dp[i - 1])
# 单序列问题,迭代代码,自下往上。时间复杂度;O(n), 空间复杂度;O(n)
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
dp = [0] * len(nums)
dp[0] = nums[0]
if len(nums) > 1:
dp[1] = max(nums[1], nums[0])
for i in range(2, len(nums)):
dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
return dp[len(nums) - 1]
# # 单序列问题,迭代代码,自下往上。时间复杂度;O(n), 空间复杂度;O(1)
# def rob(self, nums: List[int]) -> int:
# if len(nums) == 0:
# return 0
# dp = [0] * 2
# dp[0] = nums[0]
# if len(nums) > 1:
# dp[1] = max(nums[1], nums[0])
# for i in range(2, len(nums)):
# dp[i % 2] = max(dp[(i - 1) % 2], dp[(i - 2) % 2] + nums[i])
# return dp[(len(nums) - 1) % 2]
# # 单序列问题,动态规划,自下往上,两个状态转移方程。时间复杂度;O(n), 空间复杂度;O(1)
# def rob(self, nums: List[int]) -> int:
# n = len(nums)
# if n == 0:
# return 0
# dp = [[0] * 2 for i in range(2)]
# dp[0][0] = 0
# dp[1][0] = nums[0]
# for i in range(1, n):
# dp[0][i % 2] = max(dp[0][(i - 1) % 2], dp[1][(i - 1) % 2])
# dp[1][i % 2] = nums[i] + dp[0][(i - 1) % 2]
# return max(dp[0][(n - 1) % 2], dp[1][(n - 1) % 2])
(单序列问题、动态规划)
class Solution:
# 单序列问题,动态规划。时间复杂度:O(n), 空间复杂度:O(1)
def rob(self, nums: List[int]) -> int:
if len(nums) == 0:
return 0
if len(nums) == 1:
return nums[0]
result1 = self.helper(nums, 0, len(nums) - 2) # 这个是编号从0到n-2的房屋偷盗最大值
result2 = self.helper(nums, 1, len(nums) - 1) # 这个是编号从1到n-1的房屋偷盗最大值
return max(result1, result2)
def helper(self, nums, start, end):
dp = [0] * 2
dp[0] = nums[start]
if start < end:
dp[1] = max(nums[start], nums[start + 1])
for i in range(start + 2, end + 1):
j = i - start
dp[j % 2] = max(dp[(j - 1) % 2], dp[(j - 2) % 2] + nums[i])
return dp[(end - start) % 2]
(单序列问题、动态规划、自下往上)
class Solution:
# 单序列问题,动态规划,自下向上,时间复杂度:O(n), 空间复杂度:O(1)
def minCost(self, costs: List[List[int]]) -> int:
if len(costs) == 0:
return 0
dp = [[0] * 2 for i in range(3)]
for j in range(3):
dp[j][0] = costs[0][j]
for i in range(1, len(costs)):
for j in range(3):
prev1 = dp[(j + 2) % 3][(i - 1) % 2]
prev2 = dp[(j + 1) % 3][(i - 1) % 2]
dp[j][i % 2] = min(prev1, prev2) + costs[i][j]
last = (len(costs) - 1) % 2
temp = min(dp[0][last], dp[1][last])
return min(temp, dp[2][last])
(单序列问题、动态规划、自下往上)
class Solution:
# 单序列问题、动态规划、自下往上、时间复杂度:O(n), 空间复杂度:O(1)
def minFlipsMonoIncr(self, s: str) -> int:
n = len(s)
if n == 0:
return 0
dp = [[0] * 2 for i in range(2)]
ch = s[0]
dp[0][0] = 0 if ch == '0' else 1 # 表示第一行当前字符串翻转后结果是0
dp[0][1] = 0 if ch == '1' else 1 # 表示第二行当前字符串翻转后结果是1
for i in range(1, n):
ch = s[i]
prev0 = dp[0][(i - 1) % 2]
prev1 = dp[1][(i - 1) % 2]
dp[0][i % 2] = prev0 + (0 if ch == "0" else 1)
dp[1][i % 2] = min(prev0, prev1) + (0 if ch == "1" else 1)
return min(dp[0][(n - 1) % 2], dp[1][(n - 1) % 2])
(单序列问题,动态规划,哈希表)
class Solution:
# 时间复杂度:O(n^2), 空间复杂度:O(n^2) 单序列问题、动态规划、自下往上
# 使用哈希表之后, 时间复杂度:O(n^2 logn), 空间复杂度:O(1), 哈希表是时间换空间
def lenLongestFibSubseq(self, arr: List[int]) -> int:
map = dict()
for i in range(len(arr)):
map[arr[i]] = i
dp = [[0] * len(arr) for i in range(len(arr))]
result = 2
for i in range(len(arr)):
for j in range(i):
k = map.get(arr[i] - arr[j], -1)
dp[i][j] = dp[j][k] + 1 if (k >= 0 and k < j) else 2
result = max(result, dp[i][j])
return result if result > 2 else 0
(动态规划,单序列问题,自下往上)
class Solution:
# 时间复杂度:O(n^2), 空间复杂度:O(n^2) 动态规划,单序列问题,自下往上
def minCut(self, s: str) -> int:
n = len(s)
isPal = [[False] * n for _ in range(n)] # 判断每个子字符串是不是回文
for i in range(n):
for j in range(i + 1):
ch1 = s[i]
ch2 = s[j]
if ch1 == ch2 and (i <= j + 1 or isPal[j + 1][i - 1]):
isPal[j][i] = True
dp = [0] * n # 状态转移方程
for i in range(n):
if isPal[0][i]:
dp[i] = 0
else:
dp[i] = i
for j in range(1, i + 1):
if isPal[j][i]:
dp[i] = min(dp[i], dp[j - 1] + 1)
return dp[n - 1]
(动态规划,双序列问题)
class Solution:
# # 时间复杂度:O(mn), 空间复杂度:O(mn) 动态规划, 双序列
# def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# n1, n2 = len(text1), len(text2)
# dp = [[0] * (n2 + 1) for _ in range(n1 + 1)]
# for i in range(n1):
# for j in range(n2):
# if text1[i] == text2[j]:
# dp[i + 1][j + 1] = dp[i][j] + 1
# else:
# dp[i + 1][j + 1] = max(dp[i][j + 1], dp[i + 1][j])
# return dp[n1][n2]
# # 时间复杂度:O(mn), 空间复杂度:O(min(m, n)) 动态规划, 双序列, 空间复杂度缩减到两行
# def longestCommonSubsequence(self, text1: str, text2: str) -> int:
# n1, n2 = len(text1), len(text2)
# if n1 < n2:
# return self.longestCommonSubsequence(text2, text1)
# dp = [[0] * (n2 + 1) for _ in range(2)]
# for i in range(n1):
# for j in range(n2):
# if text1[i] == text2[j]:
# dp[(i + 1) % 2][j + 1] = dp[i % 2][j] + 1
# else:
# dp[(i + 1) % 2][j + 1] = max(dp[i % 2][j + 1], dp[(i + 1) % 2][j])
# return dp[n1 % 2][n2]
# 时间复杂度:O(mn), 空间复杂度:O(min(m, n)) 动态规划, 双序列, 空间复杂度缩减到一行
def longestCommonSubsequence(self, text1: str, text2: str) -> int:
n1, n2 = len(text1), len(text2)
if n1 < n2:
return self.longestCommonSubsequence(text2, text1)
dp = [0] * (n2 + 1)
for i in range(n1):
prev = dp[0]
for j in range(n2):
if text1[i] == text2[j]:
cur = prev + 1
else:
cur = max(dp[j], dp[j + 1])
prev = dp[j + 1]
dp[j + 1] = cur
return dp[n2]
(动态规划、双序列问题)
class Solution:
# # 时间复杂度:O(mn), 空间复杂度:O(mn) 动态规划, 双序列问题
# def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
# if len(s1) + len(s2) != len(s3):
# return False
# dp = [[False] * (len(s2) + 1) for _ in range(len(s1) + 1)]
# dp[0][0] = True
# for i in range(len(s1)):
# dp[i + 1][0] = (s1[i] == s3[i] and dp[i][0])
# for j in range(len(s2)):
# dp[0][j + 1] = (s2[j] == s3[j] and dp[0][j])
# for i in range(len(s1)):
# for j in range(len(s2)):
# ch1, ch2 = s1[i], s2[j]
# ch3 = s3[i + j + 1]
# dp[i + 1][j + 1] = ((ch1 == ch3 and dp[i][j + 1]) or (ch2 == ch3 and dp[i + 1][j]))
# return dp[len(s1)][len(s2)]
# 时间复杂度:O(mn), 空间复杂度:O(min(m, n)) 动态规划, 双序列问题, 空间优化
def isInterleave(self, s1: str, s2: str, s3: str) -> bool:
if len(s1) + len(s2) != len(s3):
return False
if len(s1) < len(s2):
return self.isInterleave(s2, s1, s3)
dp = [False] * (len(s2) + 1)
dp[0] = True
for j in range(len(s2)):
dp[j + 1] = (s2[j] == s3[j] and dp[j])
for i in range(len(s1)):
dp[0] = dp[0] and s1[i] == s3[i]
for j in range(len(s2)):
ch1, ch2 = s1[i], s2[j]
ch3 = s3[i + j + 1]
dp[j + 1] = (ch1 == ch3 and dp[j + 1]) or (ch2 == ch3 and dp[j])
return dp[len(s2)]
(动态规划, 双序列问题, 自下往上)
class Solution:
# # 时间复杂度:O(mn), 空间复杂度:O(mn) 动态规划, 双序列问题, 自下往上
# def numDistinct(self, s: str, t: str) -> int:
# dp = [[0] * (len(t) + 1) for _ in range(len(s) + 1)]
# dp[0][0] = 1
# for i in range(len(s)):
# dp[i + 1][0] = 1
# for j in range(i + 1):
# if j < len(t):
# if s[i] == t[j]:
# dp[i + 1][j + 1] = dp[i][j] + dp[i][j + 1]
# else:
# dp[i + 1][j + 1] = dp[i][j + 1]
# return dp[len(s)][len(t)]
# 时间复杂度:O(mn), 空间复杂度:O(n) 动态规划, 双序列问题, 自下往上, 空间效率优化到两行
def numDistinct(self, s: str, t: str) -> int:
dp = [0] * (len(t) + 1)
if len(s) > 0:
dp[0] = 1
for i in range(len(s)):
for j in range(min(i, len(t) - 1), -1, -1): # 从右往左防止覆盖
if s[i] == t[j]:
dp[j + 1] += dp[j]
return dp[len(t)]
(矩阵路径问题, 动态规划)
class Solution:
# 矩阵路径问题
# # 时间复杂度;O(mn), 空间复杂度:O(mn), 递归
# def uniquePaths(self, m: int, n: int) -> int:
# dp = [[0] * n for _ in range(m)]
# return self.helper(m - 1, n - 1, dp)
# def helper(self, i, j, dp):
# if dp[i][j] == 0:
# if i == 0 or j == 0:
# dp[i][j] = 1
# else:
# dp[i][j] = self.helper(i - 1, j, dp) + self.helper(i, j - 1, dp)
# return dp[i][j]
# # 时间复杂度;O(mn), 空间复杂度:O(mn), 迭代
# 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][j - 1] + dp[i -1][j]
# return dp[m - 1][n - 1]
# 时间复杂度;O(mn), 空间复杂度:O(mn), 迭代, 进一步优化时间效率为1行
def uniquePaths(self, m: int, n: int) -> int:
dp = [1] * n
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j - 1]
return dp[n - 1]
(矩阵路径问题,动态规划)
class Solution:
# 矩阵路径问题, 动态规划
# # 时间复杂度:O(mn),空间复杂度:O(mn)
# def minPathSum(self, grid: List[List[int]]) -> int:
# dp = [[0] * len(grid[0]) for _ in range(len(grid))]
# dp[0][0] = grid[0][0]
# for j in range(1, len(grid[0])):
# dp[0][j] = grid[0][j] + dp[0][j - 1]
# for i in range(1, len(grid)):
# dp[i][0] = grid[i][0] + dp[i - 1][0]
# for j in range(1, len(grid[0])):
# prev = min(dp[i - 1][j], dp[i][j - 1])
# dp[i][j] = grid[i][j] + prev
# return dp[len(grid) - 1][len(grid[0]) - 1]
# 时间复杂度:O(mn),空间复杂度:O(n), 优化空间效率
def minPathSum(self, grid: List[List[int]]) -> int:
dp = [0] * len(grid[0])
dp[0] = grid[0][0]
for j in range(1, len(grid[0])):
dp[j] = grid[0][j] + dp[j - 1]
for i in range(1, len(grid)):
dp[0] += grid[i][0]
for j in range(1, len(grid[0])):
dp[j] = min(dp[j - 1], dp[j]) + grid[i][j]
return dp[len(grid[0]) - 1]
(矩阵路径问题, 动态规划)
class Solution:
# # 时间复杂度:O(n^2), 空间复杂度:O(n^2), 动态规划,矩阵路径问题
# def minimumTotal(self, triangle: List[List[int]]) -> int:
# size = len(triangle)
# dp = [[0] * size for _ in range(size)]
# for i in range(size):
# for j in range(i + 1):
# dp[i][j] = triangle[i][j]
# if i > 0 and j == 0:
# dp[i][j] += dp[i - 1][j]
# elif i > 0 and i == j:
# dp[i][j] += dp[i - 1][j - 1]
# elif i > 0:
# dp[i][j] += min(dp[i - 1][j], dp[i - 1][j - 1])
# res = float('inf')
# for num in dp[size - 1] :
# res = min(res, num)
# return res
# 时间复杂度:O(n^2), 空间复杂度:O(n), 动态规划,矩阵路径问题, 优化空间效率为1行
def minimumTotal(self, triangle: List[List[int]]) -> int:
dp = [0] * len(triangle)
for i in range(len(triangle)):
for j in range(i, -1, -1): # 从右往左,防止被覆盖
if j == 0:
dp[j] += triangle[i][j]
elif j == i:
dp[j] += dp[j - 1] + triangle[i][j]
else:
dp[j] = min(dp[j], dp[j - 1]) + triangle[i][j]
res = float('inf')
for num in dp :
res = min(res, num)
return res
(动态规划、0-1背包问题)
class Solution:
# 动态规划,0 - 1背包问题
# # 时间复杂度:O(nt), 空间复杂度:O(nt) n是数组长度, t是目标值, 递归
# def canPartition(self, nums: List[int]) -> bool:
# sum = 0
# for num in nums:
# sum += num
# if sum % 2 == 1: # 如果是奇数肯定不满足条件
# return False
# return self.subsetSum(nums, sum // 2)
# def subsetSum(self, nums, target):
# dp = [[None] * (target + 1) for _ in range(len(nums) + 1)]
# return self.helper(nums, dp, len(nums), target) # 利用递归
# def helper(self, nums, dp, i, j):
# if dp[i][j] == None:
# if j == 0: # 递归出口,背包容量为0,不论有多少个物品,只要什么物品都不选择,就满足
# dp[i][j] = True
# elif i == 0: # 物品的数量为0, 肯定无法用0个物品来放满容量大于0的背包
# dp[i][j] = False
# else:
# dp[i][j] = self.helper(nums, dp, i - 1, j) # 不将标号为i - 1的物品放入背包
# if not dp[i][j] and j >= nums[i - 1]: # 满足条件,将标号为i - 1的物品放入背包
# dp[i][j] = self.helper(nums, dp, i - 1, j - nums[i - 1])
# return dp[i][j]
# # 时间复杂度:O(nt), 空间复杂度:O(nt) n是数组长度, t是目标值, 迭代
# def canPartition(self, nums: List[int]) -> bool:
# sum = 0
# for num in nums:
# sum += num
# if sum % 2 == 1: # 如果是奇数肯定不满足条件
# return False
# return self.subsetSum(nums, sum // 2)
# def subsetSum(self, nums, target):
# dp = [[False] * (target + 1) for _ in range(len(nums) + 1)]
# 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 + 1):
# dp[i][j] = dp[i - 1][j]
# if not dp[i][j] and j >= nums[i - 1]:
# dp[i][j] = dp[i - 1][j - nums[i - 1]]
# return dp[len(nums)][target]
# 时间复杂度:O(nt), 空间复杂度:O(t) n是数组长度, t是目标值, 迭代, 优化空间复杂度到一行
def canPartition(self, nums: List[int]) -> bool:
sum = 0
for num in nums:
sum += num
if sum % 2 == 1: # 如果是奇数肯定不满足条件
return False
return self.subsetSum(nums, sum // 2)
def subsetSum(self, nums, target):
dp = [False] * (target + 1)
dp[0] = True
for i in range(1, len(nums) + 1):
for j in range(target, 0, -1):
if not dp[j] and j >= nums[i - 1]:
dp[j] = dp[j - nums[i - 1]]
return dp[target]
(动态规划、0-1背包问题)
class Solution:
# 动态规划,0-1背包问题,找出和为p数字,其中p+q=sum, p-q=target -> p=(target+sum)//2
# 时间复杂度:O(nt), 空间复杂度:O(t), n为数组的长度,t是目标值p
def findTargetSumWays(self, nums: List[int], target: int) -> int:
sum = 0
for num in nums:
sum += num
if (sum + target) % 2 == 1 or sum < target:
return 0
return self.subsetSum(nums, (sum + target) // 2)
def subsetSum(self, nums, target):
dp = [0] * (target + 1)
dp[0] = 1
for num in nums:
for j in range(target, num - 1, -1):
dp[j] += dp[j - num] # dp[j]将当前的num不放入背包 + dp[j - num]将当前的num放入背包
return dp[target]
(动态规划,完全背包问题,无界背包问题)
class Solution:
# 动态规划, 完全背包问题, 无界背包问题
# # 时间复杂度:O(ntk), 空间复杂度:O(t), n是币种种类, t是目标总额, k是某种币种凑出总额t的硬币数
# def coinChange(self, coins: List[int], amount: int) -> int:
# dp = [amount + 1] * (amount + 1)
# dp[0] = 0
# for coin in coins:
# for j in range(amount, 0, -1):
# res = j // coin + 1
# for k in range(1, res):
# dp[j] = min(dp[j], dp[j - k * coin] + k)
# return -1 if dp[amount] > amount else dp[amount]
# 时间复杂度:O(nt), 空间复杂度:O(t), n是币种种类, t是目标总额, 空间优化, 类似于贪心算法
def coinChange(self, coins: List[int], amount: int) -> int:
dp = [amount + 1] * (amount + 1)
dp[0] = 0
for i in range(1, amount + 1):
for coin in coins:
if i >= coin:
dp[i] = min(dp[i], dp[i - coin] + 1)
return -1 if dp[amount] > amount else dp[amount]
(动态规划, 完全背包问题, 无界背包问题)
class Solution:
# 动态规划, 完全背包问题, 无界背包问题
# 时间复杂度:O(nt), 空间复杂度:O(t) n是输入数组的长度, t是排列的目标数
def combinationSum4(self, nums: List[int], target: int) -> int:
dp = [0] * (target + 1)
dp[0] = 1
for i in range(1, target + 1):
for num in nums:
if i >= num:
dp[i] += dp[i - num]
return dp[target]
(图的遍历)
class Solution:
# # 时间复杂度:O(mn), 空间复杂度:O(mn) 广度优先搜索, 队列
# def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
# rows, cols = len(grid), len(grid[0])
# visited = [[False] * cols for _ in range(rows)]
# maxArea = 0
# for i in range(rows):
# for j in range(cols):
# if grid[i][j] == 1 and not visited[i][j]:
# area = self.getArea(grid, visited, i, j)
# maxArea = max(maxArea, area)
# return maxArea
# 广度优先搜索的时间复杂度:O(v + e) v节点数目, e是边的数目
# def getArea(self, grid, visited, i, j):
# queue = []
# queue.append((i, j))
# visited[i][j] = True
# dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
# area = 0
# while len(queue) != 0:
# pos = queue.pop(0)
# area += 1
# for dir in dirs:
# r = pos[0] + dir[0]
# c = pos[1] + dir[1]
# if r >= 0 and r < len(grid) and c >= 0 and c < len(grid[0]) and not visited[r][c] and grid[r][c] == 1:
# queue.append((r, c))
# visited[r][c] = True
# return area
# # 时间复杂度:O(mn), 空间复杂度:O(mn) 深度优先搜索, 栈
# def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
# rows, cols = len(grid), len(grid[0])
# visited = [[False] * cols for _ in range(rows)]
# maxArea = 0
# for i in range(rows):
# for j in range(cols):
# if grid[i][j] == 1 and not visited[i][j]:
# area = self.getArea(grid, visited, i, j)
# maxArea = max(maxArea, area)
# return maxArea
# # 深度优先搜索的时间复杂度:O(v + e) v节点数目, e是边的数目
# def getArea(self, grid, visited, i, j):
# stack = []
# stack.append((i, j))
# visited[i][j] = True
# dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
# area = 0
# while len(stack) != 0:
# pos = stack.pop()
# area += 1
# for dir in dirs:
# r = pos[0] + dir[0]
# c = pos[1] + dir[1]
# if r >= 0 and r < len(grid) and c >= 0 and c < len(grid[0]) and not visited[r][c] and grid[r][c] == 1:
# stack.append((r, c))
# visited[r][c] = True
# return area
# 时间复杂度:O(mn), 空间复杂度:O(mn) 递归
def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
rows, cols = len(grid), len(grid[0])
visited = [[False] * cols for _ in range(rows)]
maxArea = 0
for i in range(rows):
for j in range(cols):
if grid[i][j] == 1 and not visited[i][j]:
area = self.getArea(grid, visited, i, j)
maxArea = max(maxArea, area)
return maxArea
# 深度优先搜索的时间复杂度:O(v + e) v节点数目, e是边的数目
def getArea(self, grid, visited, i, j):
area = 1
visited[i][j] = True
dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
for dir in dirs:
r = i + dir[0]
c = j + dir[1]
if r >= 0 and r < len(grid) and c >= 0 and c < len(grid[0]) and not visited[r][c] and grid[r][c] == 1:
area += self.getArea(grid, visited, r, c)
return area
(图的遍历)
class Solution:
# # 时间复杂度:O(v + e), 空间复杂度:O(v + e), v是节点的个数[len(graph)], e是边的个数[len(graph[j])之和], 广度优先遍历
# def isBipartite(self, graph: List[List[int]]) -> bool:
# size = len(graph)
# colors = [-1] * size
# for i in range(size):
# if colors[i] == -1:
# if not self.setColor(graph, colors, i, 0):
# return False
# return True
# def setColor(self, graph, colors, i, color):
# queue = []
# queue.append(i)
# colors[i] = color
# while len(queue) != 0:
# v = queue.pop(0)
# for neighbor in graph[v]:
# if colors[neighbor] >= 0:
# if colors[neighbor] == colors[v]:
# return False
# else:
# queue.append(neighbor)
# colors[neighbor] = 1 - colors[v]
# return True
# 时间复杂度:O(v + e), 空间复杂度:O(v + e), v是节点的个数[len(graph)], e是边的个数[len(graph[j])之和], 深度优先遍历, 递归
def isBipartite(self, graph: List[List[int]]) -> bool:
size = len(graph)
colors = [-1] * size
for i in range(size):
if colors[i] == -1:
if not self.setColor(graph, colors, i, 0):
return False
return True
def setColor(self, graph, colors, i, color):
if colors[i] >= 0:
return colors[i] == color
colors[i] = color
for neighbor in graph[i]:
if not self.setColor(graph, colors, neighbor, 1 - color):
return False
return True
(图,广度优先遍历,最短距离)
class Solution:
# 最近距离,广度优先遍历
# 时间复杂度:O(mn), 空间复杂度:O(mn)
def updateMatrix(self, mat: List[List[int]]) -> List[List[int]]:
rows, cols = len(mat), len(mat[0])
dists = [[0] * cols for _ in range(rows)]
queue = []
for i in range(rows): # 先维护一个同样大小的数组,找到为0和非0的位置,并且维护一个该位置全是0的队列
for j in range(cols):
if mat[i][j] == 0:
queue.append((i, j))
dists[i][j] = 0
else:
dists[i][j] = float('inf')
dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
while len(queue) != 0:
pos = queue.pop(0)
dist = dists[pos[0]][pos[1]]
for dir in dirs:
r = pos[0] + dir[0]
c = pos[1] + dir[1]
if r >= 0 and c >= 0 and r < rows and c < cols:
if dists[r][c] > dist + 1: # 如果为True,表示该格子仍然是最大的整数值,之前没有到达过, 如果是False, 表示之前到达过该节点,并且第一次到达时一定记录的是最短距离,那肯定不可能大于dist+1,这样可以避免重复访问某个格子
dists[r][c] = dist + 1
queue.append((r, c))
return dists
(广度优先搜索,哈希表)
class Solution:
# # 时间复杂度:O(从单词beginWord往下建树,深度是m, 空间复杂度:O(从单词beginWord往下建树,深度是m, m=len(beginWord)单向广度优先搜索, 哈希表
# def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
# queue1, queue2 = [], []
# notVisited = set(wordList) # 创建一个关于列表的集合
# length = 1
# queue1.append(beginWord)
# while len(queue1) != 0:
# word = queue1.pop(0)
# if word == endWord:
# return length
# neighbors = self.getNeibors(word) # 得到当前word所有一步可以变换到的单词,此时经历的长度是length
# for neighbor in neighbors:
# if neighbor in notVisited: # 如果一个节点不在notVisited中, 要么它不在单词列表中,要么之前已经访问过
# queue2.append(neighbor)
# notVisited.remove(neighbor)
# if len(queue1) == 0:
# length += 1
# queue1 = queue2
# queue2 = []
# return 0
# def getNeibors(self, word):
# neighbors = []
# chars = list(word)
# for i in range(len(word)):
# old = word[i]
# for j in range(ord('a'), ord('z') + 1):
# if old != chr(j):
# chars[i] = chr(j)
# neighbors.append("".join(chars))
# chars[i] = old # 最后在word中把当前字母改变回来
# return neighbors
# 时间复杂度:O(从单词beginWord往下建树,深度是m, 空间复杂度:O(从单词beginWord往下建树,深度是m, m=len(beginWord)双向广度优先搜索, 哈希表
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
notVisited = set(wordList) # 创建一个关于列表的集合
if endWord not in notVisited:
return 0
set1, set2 = set(), set() # 用集合不用队列是因为后边判断从一个方向搜索到的节点在另一个方向是否已经访问过,只需要O(1)
length = 2
set1.add(beginWord)
set2.add(endWord)
notVisited.remove(endWord)
while len(set1) != 0 and len(set2) != 0:
if len(set2) < len(set1): # 确保每次都是从需要访问少的方向开始搜索
temp = set1
set1 = set2
set2 = temp
set3 = set()
for word in set1:
neighbors = self.getNeighbors(word) # 找到当前单词的所有相邻单词
for neighbor in neighbors:
if neighbor in set2: # 说明两个方向的搜索相遇,已经找到了一条从起始节点和目标节点之间的最短距离
return length
if neighbor in notVisited: # 否则将节点添加都set3中
set3.add(neighbor)
notVisited.remove(neighbor)
length += 1 # 当set1中的所有节点都访问完毕,接下来可能访问set1的相邻节点,即set3中的所有节点
set1 = set3
return 0
def getNeighbors(self, word):
neighbors = []
chars = list(word)
for i in range(len(word)):
old = word[i]
for j in range(ord('a'), ord('z') + 1):
if old != chr(j):
chars[i] = chr(j)
neighbors.append("".join(chars))
chars[i] = old # 最后在word中把当前字母改变回来
return neighbors
(广度优先遍历,哈希集合,队列)
class Solution:
# 单向广度优先搜索, 哈希表, 队列
def openLock(self, deadends: List[str], target: str) -> int:
dead = set(deadends)
visited = set()
init = "0000"
if init in dead or target in dead:
return -1
queue1, queue2 = [], []
steps = 0
queue1.append(init)
visited.add(init)
while len(queue1) != 0:
cur = queue1.pop(0)
if cur == target:
return steps
nexts = self.getNeighbors(cur)
for next in nexts:
if next not in dead and next not in visited: # 如果在visited中, 说明走这条路线会出现死锁状态要避免。
visited.add(next)
queue2.append(next)
if len(queue1) == 0:
steps += 1
queue1 = queue2
queue2 = []
return -1
def getNeighbors(self,cur):
nexts = []
for i in range(len(cur)):
ch = cur[i]
newCh = '9' if ch == '0' else str(int(ch) - 1)
builder = list(cur)
builder[i] = newCh
nexts.append("".join(builder))
newCh = '0' if ch == '9' else str(int(ch) + 1)
builder = list(cur)
builder[i] = newCh
nexts.append("".join(builder))
return nexts
(图,深度优先遍历)
class Solution:
# 深度优先遍历, 本质上来说,回溯法就是深度优先遍历
# 时间复杂度:O(v + e), 空间复杂度:O()。 v是节点的数目, e是边的数目
def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
result = []
path = []
self.dfs(0, graph, path, result)
return result
def dfs(self, source, graph, path, result):
path.append(source)
if source == len(graph) - 1:
path1 = path[:] # 需要更改到另一个数组中, 否则后续会改变
result.append(path1)
else:
for next in graph[source]:
self.dfs(next, graph, path, result)
path.pop()
(图的遍历,深度优先遍历,哈希表)
class Solution:
# 被除数和除数是节点,商是边的权重,
# 有向图,深度优先遍历, 因为涉及到记录一个节点到另一个节点的路径
def calcEquation(self, equations: List[List[str]], values: List[float], queries: List[List[str]]) -> List[float]:
graph = self.buildGraph(equations, values) # 构建有向图
results = [0.0] * len(queries)
for i in range(len(queries)):
fro = queries[i][0]
too = queries[i][1]
if fro not in graph or too not in graph: # 结果不存在的情况
results[i] = -1
else:
visited = set()
results[i] = self.dfs(graph, fro, too, visited)
return results
def buildGraph(self, equations, values):
graph = dict() # 哈希表作为邻接表
for i in range(len(equations)):
var1 = equations[i][0]
var2 = equations[i][1]
if var1 not in graph.keys():
graph[var1] = {var2: values[i]} # 键是图中一条有向边的起始节点,值是与该节点相连的其他节点(也用哈希表表示)\
else:
graph[var1][var2] = values[i]
if var2 not in graph.keys():
graph[var2] = {var1: 1.0/values[i]} # 反转上述的起始节点和终止节点,有向嘛
else:
graph[var2][var1] = 1.0/values[i]
return graph
def dfs(self, graph, fro, too, visited):
if fro == too:
return 1.0
visited.add(fro)
for k in graph[fro].keys():
v = graph[fro][k]
if k not in visited:
nextVal = self.dfs(graph, k, too, visited)
if nextVal > 0:
return v * nextVal
visited.remove(fro)
return -1.0
(图的遍历,深度优先遍历,递归)
class Solution:
# 图的遍历,深度优先遍历。
# 从较小的数指向较大的数,有向无环图
def longestIncreasingPath(self, matrix: List[List[int]]) -> int:
m, n = len(matrix), len(matrix[0])
if m == 0 or n == 0:
return 0
lengths = [[0] * n for _ in range(m)]
longest = 0
for i in range(m):
for j in range(n):
length = self.dfs(matrix, lengths, i, j)
longest = max(longest, length)
return longest
def dfs(self, matrix, lengths, i, j):
if lengths[i][j] != 0: # 如果值不为0,说明之前已经计算过以坐标(i, j)为起点的最长递增路径的长度,矩阵lengths起到缓存的作用,确保以任意坐标为起点的最长递增路径的长度只需计算一次。
return lengths[i][j]
rows, cols = len(matrix), len(matrix[0])
dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)]
length = 1
for dir in dirs:
r = i + dir[0]
c = j + dir[1]
if r >= 0 and r < rows and c >= 0 and c < cols and matrix[r][c] > matrix[i][j]:
path = self.dfs(matrix, lengths, r, c)
length = max(path + 1, length)
lengths[i][j] = length
return length
(图、拓扑排序、队列、数组)
class Solution:
# 图,拓扑排序
# 时间复杂度:O(m + n), 空间复杂度:O(m + n) m是课程的数目,n是边的数目
def findOrder(self, numCourses: int, prerequisites: List[List[int]]) -> List[int]:
graph = dict() # 哈希表存储邻近表
for i in range(numCourses):
graph[i] = [] # 哈希表的键是课程数目,值是必须在该门课之后学习的课程,为一个数组
inDegrees = [0] * numCourses # 数组保存每门课的入度
for prereq in prerequisites:
graph[prereq[1]].append(prereq[0])
inDegrees[prereq[0]] += 1 # 该门课的入度加1
queue = [] # 用队列的迭代方法实现
for i in range(numCourses): # 找到入度为0的课程
if inDegrees[i] == 0:
queue.append(i)
order = []
while len(queue) != 0:
course = queue.pop(0)
order.append(course)
for next in graph[course]:
inDegrees[next] -= 1
if inDegrees[next] == 0:
queue.append(next)
return order if len(order) == numCourses else []
(图,拓扑排序,哈希表,队列)
class Solution:
# 图,拓扑排序
# 时间复杂度:O(mn),空间复杂度:O(mn)
def alienOrder(self, words: List[str]) -> str:
graph = dict() # 建立有向图的数据结构
inDegrees = dict()
for word in words: # 找到单词中所有的字母并做初始化
for ch in word:
graph[ch] = set() # 排在该字母后面的值是一个集合
inDegrees[ch] = 0 # 所有字母入度初始化为0
for i in range(1, len(words)): # 初始化有向图
w1 = words[i - 1]
w2 = words[i]
if w1.startswith(w2) and w1 != w2: # 排除特殊情况
return ""
j = 0
while j < len(w1) and j < len(w2):
ch1 = w1[j]
ch2 = w2[j]
if ch1 != ch2:
if ch2 not in graph[ch1]:
graph[ch1].add(ch2)
inDegrees[ch2] = inDegrees.get(ch2, 0) + 1
break
j += 1
queue = []
for ch in inDegrees.keys(): # 找到入度为0的节点,添加到队列中
if inDegrees[ch] == 0:
queue.append(ch)
result = ""
while len(queue) != 0:
ch = queue.pop(0)
result += ch
for next in graph[ch]:
inDegrees[next] -= 1
if inDegrees[next] == 0:
queue.append(next)
return result if len(result) == len(graph) else ""
(图,拓扑排序,字典,队列)
class Solution:
# 判断有向图的拓扑排序序列是否唯一
def sequenceReconstruction(self, org: List[int], seqs: List[List[int]]) -> bool:
graph = dict()
inDegrees = dict()
for seq in seqs: # 构建有向图
for num in seq: # 初始化字典的键
if num < 1 or num > len(org):
return False
graph[num] = set() # 初始化字典中每个键的值为集合,之后存储排序在该数字之后的num
inDegrees[num] = 0 # 初始化字典中每个键的入度
for seq in seqs: # 构建有向图
for i in range(len(seq) - 1): # 初始化字典的值
num1 = seq[i]
num2 = seq[i + 1]
if num2 not in graph[num1]:
graph[num1].add(num2)
inDegrees[num2] += 1
queue = []
for num in inDegrees.keys(): # 找到入度为0的节点
if inDegrees[num] == 0:
queue.append(num)
built = []
while len(queue) == 1:
num = queue.pop(0)
built.append(num)
for next in graph[num]:
inDegrees[next] -= 1
if inDegrees[next] == 0:
queue.append(next)
return built == org
(图,广度优先遍历,并查集, 邻接矩阵)
class Solution:
# # 时间复杂度:O(n^2), 空间复杂度:O(n^2) 广度优先搜索
# def findCircleNum(self, isConnected: List[List[int]]) -> int:
# visited = [False] * len(isConnected)
# result = 0
# for i in range(len(isConnected)):
# if not visited[i]:
# self.findCircle(isConnected, visited, i)
# result += 1
# return result
# def findCircle(self, isConnected, visited, i):
# queue = []
# queue.append(i)
# visited[i] = True
# while len(queue) != 0:
# t = queue.pop(0)
# for friend in range(len(isConnected)):
# if isConnected[t][friend] == 1 and not visited[friend]:
# queue.append(friend)
# visited[friend] = True
# 时间复杂度:O(a(n)), 空间复杂度:O(n^2) 并查集,邻接矩阵
def findCircleNum(self, isConnected: List[List[int]]) -> int:
fathers = [False] * len(isConnected)
for i in range(len(fathers)):
fathers[i] = i
count = len(isConnected)
for i in range(len(isConnected)):
for j in range(i+1, len(isConnected)):
if isConnected[i][j] == 1 and self.union(fathers, i, j): # 前者条件满足时,才会有后者合并(并且只有union判断i和j属于不同的祖先,返回True,count才减少1,当判断i和j属于同一祖先时,本身就是一个子集,所以返回False,不减少1)
count -= 1
return count
def findFather(self, fathers, i): # 根节点的查找, 相当于压缩路径
if fathers[i] != i:
fathers[i] = self.findFather(fathers, fathers[i])
return fathers[i]
def union(self, fathers, i, j): # 两棵树的合并
fathersOfI = self.findFather(fathers, i)
fathersOfJ = self.findFather(fathers, j)
if fathersOfI != fathersOfJ: # 每当两个子集合并成一个子集, 集体数目应该减少1, 返回Ture
fathers[fathersOfI] = fathersOfJ
return True
return False
(并查集)
class Solution:
# 并查集,邻接矩阵,由于题目中搜友字符串的长度相同并且两两互为变位词,所以两个字符串之间的对应位置不同字符的字符的个数不超过两个,那么它们一定相似
# # 时间复杂度:O(n^2*a(n)), 空间复杂度:O(n)
def numSimilarGroups(self, strs: List[str]) -> int:
fathers = [0] * len(strs)
for i in range(len(fathers)): # 初始化每个节点的祖先节点为自己
fathers[i] = i
groups = len(strs)
for i in range(len(strs)):
for j in range(i+1, len(strs)):
if self.similar(strs[i], strs[j]) and self.union(fathers, i, j):
groups -= 1
return groups
def similar(self, str1, str2):
diffCount = 0
for i in range(len(str1)):
if str1[i] != str2[i]:
diffCount += 1
return diffCount <= 2
def findFather(self, fathers, i):
if fathers[i] != i:
fathers[i] = self.findFather(fathers, fathers[i])
return fathers[i]
def union(self, fathers, i, j):
fatherOfI = self.findFather(fathers, i)
fatherOfJ = self.findFather(fathers, j)
if fatherOfI != fatherOfJ:
fathers[fatherOfI] = fatherOfJ
return True
return False
(并查集)
class Solution:
# 时间复杂度:O(n), 空间复杂度:O(n), 并查集,查找和合并的时间接近O(1)
def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
maxVertx = 0
for edge in edges: # 找到最大的节点,就是一共有多少个节点
maxVertx = max(maxVertx, edge[0])
maxVertx = max(maxVertx, edge[1])
fathers = [0] * (maxVertx + 1) # n个节点,n条边,肯定有一条边是多余的
for i in range(1, maxVertx + 1):
fathers[i] = i
for edge in edges:
if not self.union(fathers, edge[0], edge[1]): # union返回True,表示两个节点的祖先节点不是一个,没有环,如果返回False,表示有环
return edge
return [0] * 2
def findFather(self, fathers, i):
if fathers[i] != i:
fathers[i] = self.findFather(fathers, fathers[i])
return fathers[i]
def union(self, fathers, i, j):
fatherOfI = self.findFather(fathers, i)
fatherOfJ = self.findFather(fathers, j)
if fatherOfI != fatherOfJ: # 祖先节点不一样,没有环,连接两条边,并且更新前者的祖先节点跟后者的祖先一样
fathers[fatherOfI] = fatherOfJ
return True
return False
(图,广度优先搜索,并查集)
class Solution:
# # 时间复杂度:O(n), 空间复杂度:O(n), 图,广度优先搜索
# def longestConsecutive(self, nums: List[int]) -> int:
# hashSet = set(nums) # 集合消除列表中的重复元素
# longest = 0
# while len(hashSet) != 0: # 遍历这个集合
# myiter = iter(hashSet) # 迭代器的巧用
# longest = max(longest, self.dfs(hashSet, next(myiter))) # 每次都记录最长的连续数组
# return longest
# def dfs(self, set1, num):
# queue = []
# queue.append(num) # 队列
# set1.remove(num)
# length = 1
# while len(queue) != 0:
# i = queue.pop(0)
# neighbors = [i - 1, i + 1]
# for neighbor in neighbors:
# if neighbor in set1:
# queue.append(neighbor) # 添加到队列中,就要从原来集合中去除
# set1.remove(neighbor)
# length += 1
# return length
# 时间复杂度:O(n), 空间复杂度:O(n), 图,并查集
def longestConsecutive(self, nums: List[int]) -> int:
fathers = dict()
counts = dict()
all = set(nums)
for num in nums: # 初始化每个节点的祖先节点
fathers[num] = num
counts[num] = 1
for num in nums:
if num + 1 in all:
self.union(fathers, counts, num, num + 1)
if num - 1 in all:
self.union(fathers, counts, num, num - 1)
longest = 0
for length in counts.values():
longest = max(longest, length)
return longest
def findFather(self, fathers, i): # 查找祖先节点,并且压缩路径,使得查找操作的时间复杂度近似是O(1)
if fathers[i] != i:
fathers[i] = self.findFather(fathers, fathers[i])
return fathers[i]
def union(self, fathers, counts, i, j): # 合并祖先,并且更新counts数目
fatherOfI = self.findFather(fathers, i)
fatherOfJ = self.findFather(fathers, j)
if fatherOfI != fatherOfJ:
fathers[fatherOfI] = fatherOfJ
countOfI = counts[fatherOfI]
countOfJ = counts[fatherOfJ]
counts[fatherOfJ] = countOfI + countOfJ