第01天 整数

剑指 Offer II 001. 整数除法

class Solution:# 时间复杂度:O(logn), 空间复杂度:O(1)def divideCore(self, dividend, divisor):result = 0while dividend <= divisor:   # 因为此时被除数和除数都成了负数value = divisorquotient = 1while (value >= -2**30 and dividend <= value + value):quotient += quotientvalue += valueresult += quotientdividend -= valuereturn resultdef divide(self, dividend: int, divisor: int) -> int:if dividend == -2**31 and divisor == -1:return 2**31 - 1  # 因为负数转化为正数会有溢出negative = 2if dividend > 0:negative -= 1dividend = -dividendif divisor > 0:negative -= 1divisor = -divisorresult = self.divideCore(dividend, divisor)if negative == 1:return  -resultelse:return result

剑指 Offer II 002. 二进制加法

class Solution:def addBinary(self, a: str, b: str) -> str:result = ""i = len(a) - 1j = len(b) - 1carry = 0while i >= 0 or j >= 0:digitA = int(a[i]) if i >= 0 else 0  # 使用id(i)可以看出i自增前后的在内存中的位置是变了的i -= 1digitB = int(b[j]) if j >= 0 else 0j -= 1sum = digitA + digitB + carrycarry = 1 if sum >= 2 else 0sum = sum - 2 if sum >= 2 else sumresult += str(sum)result += '1' if carry == 1 else ''return result[::-1]

剑指 Offer II 003. 前 n 个数字二进制中 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

第02天 整数

剑指 Offer II 004. 只出现一次的数字

class Solution:# 时间复杂度:O(n) 空间复杂度:O(1)def singleNumber(self, nums: List[int]) -> int:counts = [0] * 32for num in nums:for i in range(32):counts[i] += (num >> (31 - i)) & 1   # 表示从左边起num中的第i个位数中是否是1  # python中特殊的是对于负数,右移动补0 res = 0for 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位

剑指 Offer II 005. 单词长度的最大乘积

class Solution:# 时间复杂度:O(nk + n^2) 空间复杂度:O(n),# 当判断两个字符串是否包含相同字符时,该方法只需要一次位运算,而哈希表方法可能需要26次布尔运算def maxProduct(self, words: List[str]) -> int:n = len(words)flags = [0] * nfor i in range(n):for c in words[i]:flags[i] |= 1 << (ord(c) - ord('a')) # 将字符对应的位数标记为1,或运算res = 0for 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

剑指 Offer II 006. 排序数组中两个数字之和

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def twoSum(self, numbers: List[int], target: int) -> List[int]:i, j = 0, len(numbers) - 1while i < j and numbers[i] + numbers[j] != target:if numbers[i] + numbers[j] < target:i += 1else:j -= 1return [i, j]

第03天 数组

剑指 Offer II 007. 数组中和为 0 的三个数

class Solution:# 时间复杂度:O(nlogn + n^2), 排序+找 空间复杂度:O(1)def twosum(self, nums, i, res):j = i + 1k = len(nums) - 1while 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 += 1elif nums[i] + nums[j] + nums[k] < 0:j += 1else:k -= 1def threeSum(self, nums: List[int]) -> List[List[int]]:res = []if len(nums) >= 3:nums.sort()  # 先排序,变成有序的i = 0while i < len(nums) - 2:  self.twosum(nums, i, res)temp = nums[i]i += 1while nums[i] == temp and i < len(nums) - 1:  # 滤掉重复的数i += 1return res

剑指 Offer II 008. 和大于等于 target 的最短子数组

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)。 感觉是一种贪心算法的思想。#如果大于target,就左移P1(因为都是正数,所以相当于减少数字,减少和),如果大于target,就右移P2(相当于增加数字,增加和)def minSubArrayLen(self, target: int, nums: List[int]) -> int:left, sum = 0, 0res = 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 += 1return res if res != float('inf') else 0

剑指 Offer II 009. 乘积小于 K 的子数组

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def numSubarrayProductLessThanK(self, nums: List[int], k: int) -> int:product = 1left, res = 0, 0for right in range(len(nums)):product *= nums[right]while left <= right and product >= k:product /= nums[left]left += 1res += right - left + 1 if right >= left else 0return res

第04天 数组

剑指 Offer II 010. 和为 k 的子数组

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def subarraySum(self, nums: List[int], k: int) -> int:sumToCount = dict()sumToCount.setdefault(0, 1)sum, count = 0, 0for num in nums:sum += numcount += sumToCount.get(sum - k, 0)sumToCount[sum] = sumToCount.get(sum, 0) + 1return count

剑指 Offer II 011. 0 和 1 个数相同的子数组

哈希表,累加数组数字求子数组之和

class Solution:# 时间复杂度:O(n), 空间复杂度:O(n)def findMaxLength(self, nums: List[int]) -> int:sumToIndex = {0:-1}sum, maxLength = 0, 0for i in range(len(nums)):sum += -1 if nums[i] == 0 else 1if sum in sumToIndex.keys():maxLength = max(maxLength, i - sumToIndex.get(sum)) else: # 只有不存在时才添加,这样可以保证保留的是第一个累积到当前扫描的数字之和sumToIndex[sum] = i     return maxLength

剑指 Offer II 012. 左右两边子数组的和相等

累加数组数字求数字之和

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def pivotIndex(self, nums: List[int]) -> int:total = 0for i in range(len(nums)):total += nums[i]sum = 0for i in range(len(nums)):sum += nums[i]if sum - nums[i] == total - sum: # 当前数字中左边数组之和 是否等于 右边数组之和return ireturn -1

剑指 Offer II 013. 二维子矩阵的和

累加数组数字求子数组之和

class NumMatrix:# 时间复杂度:O(mn), 空间复杂度:O(mn)def __init__(self, matrix: List[List[int]]):if len(matrix) == 0 or len(matrix[0]) == 0:return 0self.sums = [[0] * (len(matrix[0]) + 1) for _ in range(len(matrix)+1)] # 都一个是为了防止求得本身是左上角的矩阵,以防出具ai出具aifor i in range(len(matrix)):rowSum = 0for 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]

第05天 字符串

剑指 Offer II 014. 字符串中的变位词

哈希表、双指针

class Solution:# 时间复杂度:O(m + n), 空间复杂度:O(1)def areAllZero(self,counts):for count in counts:if count != 0:return Falsereturn Truedef checkInclusion(self, s1: str, s2: str) -> bool:if len(s2) >= len(s1):counts = [0] * 26for i in range(len(s1)):counts[ord(s1[i]) - ord('a')] += 1counts[ord(s2[i]) - ord('a')] -= 1if self.areAllZero(counts):return Truefor j in range(len(s1), len(s2)):counts[ord(s2[j]) - ord('a')] -= 1counts[ord(s2[j - len(s1)]) - ord('a')] += 1if self.areAllZero(counts):return Truereturn False

剑指 Offer II 015. 字符串中的所有变位词

哈希表、双指针

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def areAllZero(self, counts):for count in counts:if count != 0:return Falsereturn Truedef findAnagrams(self, s1: str, s2: str) -> List[int]:indices = []if len(s1) >= len(s2):counts = [0] * 26for i in range(len(s2)):counts[ord(s2[i]) - ord('a')] += 1counts[ord(s1[i]) - ord('a')] -= 1if self.areAllZero(counts):indices.append(0)for j in range(len(s2), len(s1)):counts[ord(s1[j]) - ord('a')] -= 1counts[ord(s1[j - len(s2)]) - ord('a')] += 1if self.areAllZero(counts):indices.append(j - len(s2) + 1)return indices

剑指 Offer II 016. 不含重复字符的最长子字符串

哈希表+双指针,避免多次遍历整个哈希表

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)。避免需要多次遍历整个哈希表def lengthOfLongestSubstring(self, s: str) -> int:counts = dict()longest = 1 if len(s) > 0 else 0countDup = 0j = -1 for i in range(len(s)):counts[s[i]] = counts.get(s[i], 0) + 1if counts[s[i]] == 2:countDup += 1while countDup > 0:j += 1counts[s[j]] -= 1if counts[s[j]] == 1:  # 用删除最右边一个字符后,该位置的值变成1判断是不是之前重复的字符,如果不是之前重复的,那会是-1countDup -= 1longest = 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 Truereturn Falsedef lengthOfLongestSubstring(self, s: str) -> int:counts = dict()longest = 1 if len(s) > 0 else 0j = -1 for i in range(len(s)):counts[s[i]] = counts.get(s[i], 0) + 1while self.hasGreaterThan1(counts):j += 1counts[s[j]] = counts.get(s[j], 0) - 1longest = max(i - j, longest)return longest

第06天 字符串

剑指 Offer II 017. 含有所有字符的最短字符串

哈希表+双指针

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) + 1count = len(charToCount)start = end = minStart = minEnd = 0minLength = 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] -= 1if charToCount[endCh] == 0:count -= 1end += 1else:if end - start < minLength:minLength = end - startminStart = startminEnd = endstartCh = s[start]if startCh in charToCount.keys():charToCount[startCh] += 1if charToCount.get(startCh) == 1:count += 1start += 1return s[minStart:minEnd] if minLength < float('inf') else ""

剑指 Offer II 018. 有效的回文

双指针

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def isPalindrome(self, s: str) -> bool:i, j = 0, len(s) - 1while i < j:ch1 = s[i]ch2 = s[j]if not ch1.isalnum():i += 1elif not ch2.isalnum():j -= 1else:if ch1.lower() != ch2.lower():return Falsei += 1j -= 1return True

剑指 Offer II 019. 最多删除一个字符得到回文

双指针

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def isPalindrome(self, s, start, end):while start < end:if s[start] != s[end]:breakstart += 1end -= 1return start >= enddef validPalindrome(self, s: str) -> bool:start = 0end = len(s) - 1for _ in range(len(s) // 2):if s[start] != s[end]:breakstart += 1end -= 1return start == len(s) // 2 or self.isPalindrome(s, start, end - 1) or self.isPalindrome(s, start + 1, end)

剑指 Offer II 020. 回文子字符串的个数

双指针,从里向外,考虑奇数和偶数

class Solution:# 时间复杂度:O(n^2), 空间复杂度:O(1)def countPalindrome(self, s, start, end):count = 0while start >= 0 and end < len(s) and s[start] == s[end]:count += 1start -= 1end += 1return countdef countSubstrings(self, s: str) -> int:count = 0for i in range(len(s)):count += self.countPalindrome(s, i, i)count += self.countPalindrome(s, i, i + 1)return count

第07天 链表

剑指 Offer II 021. 删除链表的倒数第 n 个结点

前后双指针

# 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 = headfront, back = head, dummyfor i in range(n):front = front.nextwhile front is not  None:front = front.nextback = back.nextback.next = back.next.nextreturn dummy.next

剑指 Offer II 022. 链表中环的入口节点

前后指针,不需要知道环中节点数目

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = Noneclass Solution:# 时间复杂度:O(n),空间复杂度:O(1),不需要知道环中节点数目def getNodeInloop(self, head):if head is None or head.next is None:return Noneslow = head.nextfast = slow.nextwhile (slow is not None and fast is not None):if slow == fast:return slowslow = slow.nextfast = fast.nextif fast is not None:fast = fast.nextreturn Nonedef detectCycle(self, head: ListNode) -> ListNode:inLoop = self.getNodeInloop(head) # 此时inloop的步数是环中节点数目的整数倍if (inLoop is None):return Nonenode = headwhile node != inLoop:  # 慢指针从头开始,快指针从inLoop开始,相遇时一定是环的入口节点node = node.nextinLoop = inLoop.nextreturn node

前后指针,需要知道环中节点数目

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = Noneclass Solution:# 时间复杂度:O(n),空间复杂度:O(1),需要知道环中节点数目def getNodeInloop(self, head):if head is None or head.next is None:return Noneslow = head.nextfast = slow.nextwhile (slow is not None and fast is not None):if slow == fast:return slowslow = slow.nextfast = fast.nextif fast is not None:fast = fast.nextreturn Nonedef detectCycle(self, head: ListNode) -> ListNode:inLoop = self.getNodeInloop(head)if (inLoop is None):return NoneloopCount = 1n = inLoopwhile n.next != inLoop:loopCount += 1n = n.nextfast = headfor i in range(loopCount):fast = fast.nextslow = headwhile fast!= slow:fast = fast.nextslow = slow.nextreturn slow

剑指 Offer II 023. 两个链表的第一个重合节点

快慢指针

# 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 = 0while head != None:count += 1head = head.nextreturn countdef 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 headBshorter = headB if count1 > count2 else headAnode1 = longerfor i in range(delta):node1 = node1.nextnode2 = shorterwhile node1 != node2:node2 = node2.nextnode1 = node1.nextreturn node1

第08天 链表

剑指 Offer II 024. 反转链表

三个指针

# 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 = Nonecur = headwhile cur is not None:next = cur.nextcur.next = prepre = curcur = nextreturn pre

剑指 Offer II 025. 链表中的两数相加

三个指针,反转链表的推广

# 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 = dummycarry = 0while 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) + carrycarry = 1 if sum >= 10 else 0sum = sum - 10 if sum >= 10 else sumnewNode = ListNode(sum)sumNode.next = newNodesumNode = sumNode.nexthead1 = None if head1 is None else head1.nexthead2 = None if head2 is None else head2.nextif carry > 0:sumNode.next = ListNode(carry)return dummy.nextdef reverseList(self, head):pre = Nonecur = headwhile cur is not None:next = cur.nextcur.next = prepre = curcur = nextreturn predef addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:head1 = self.reverseList(l1)head2 = self.reverseList(l2)reversedHead = self.addReversed(head1, head2)return self.reverseList(reversedHead)

剑指 Offer II 026. 重排链表

快慢指针找到中点,反转链表推广(反转链表的一半)

# 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 = headwhile node1 is not None and node2 is not None:temp = node1.nextpre.next = node1node1.next = node2pre = node2node1 = temp node2 = node2.nextif node1 is not None:pre.next = node1def reverseList(self, first):pre = Nonecur = firstwhile cur is not None:next = cur.nextcur.next = prepre = curcur = nextreturn predef reorderList(self, head: ListNode) -> None:dummy = ListNode()dummy.next = headfast = slow = dummywhile fast is not None and fast.next is not None:slow = slow.nextfast = fast.next.nexttemp = slow.nextslow.next = Noneself.link(head, self.reverseList(temp), dummy)

第09天 链表

剑指 Offer II 027. 回文链表

快慢指针,反转链表的推广(反转链表的前半段并且跟后半段做比较)

# 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 Trueslow = headfast = head.nextwhile fast.next is not None and fast.next.next is not None:fast = fast.next.nextslow = slow.nextsecondHalf = slow.nextif fast.next is not None:secondHalf = slow.next.nextslow.next = Nonereturn self.equals(secondHalf, self.reverseList(head))def reverseList(self, head):pre = Nonecur = headwhile cur is not None:next = cur.nextcur.next = prepre = curcur = nextreturn predef equals(self, l1, l2):while l1 is not None and l2 is not None:if l1.val != l2.val:return Falsel1 = l1.nextl2 = l2.nextreturn True

剑指 Offer II 028. 展平多级双向链表

双向链表

"""
# Definition for a Node.
class Node:def __init__(self, val, prev, next, child):self.val = valself.prev = prevself.next = nextself.child = child
"""
class Solution:# 时间复杂度:O(n),空间复杂度:O(k)。k是链表的层数def flatten(self, head: 'Node') -> 'Node':self.flattenGetTail(head)return headdef flattenGetTail(self, head):node = headtail = Nonewhile node is not None:next = node.nextif node.child is not None:child = node.childchildTail = self.flattenGetTail(node.child)node.child = Nonenode.next = childchild.prev = nodechildTail.next = nextif next is not None:next.prev = childTailtail = childTailelse:tail = nodenode = nextreturn tail

剑指 Offer II 029. 排序的循环链表

指针,循环链表

"""
# Definition for a Node.
class Node:def __init__(self, val=None, next=None):self.val = valself.next = next
"""class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def insert(self, head: 'Node', insertVal: int) -> 'Node':node = Node(insertVal)if head is None: # 特殊情况:链表本身为空head = nodenode.next = headelif head.next is None: # 特殊情况:链表本身长度为1head.next = nodenode.next = headelse:self.insertCore(head, node)return headdef insertCore(self, head, node):cur = headnext = head.nextbiggest = headwhile not(cur.val <= node.val and next.val >= node.val) and (next != head):cur = nextnext = next.nextif (cur.val >= biggest.val):biggest = curif cur.val <= node.val and next.val >= node.val:cur.next = nodenode.next = nextelse:node.next = biggest.nextbiggest.next = node

第10天 哈希表

剑指 Offer II 030. 插入、删除和随机访问都是 O(1) 的容器

哈希表、数组

class RandomizedSet:def __init__(self):self.numToLocation = dict()self.nums = []def insert(self, val: int) -> bool:if val in self.numToLocation.keys():return Falseself.numToLocation[val] = len(self.nums)self.nums.append(val)return Truedef remove(self, val: int) -> bool:if val not in self.numToLocation.keys():return Falselocation = self.numToLocation[val]self.numToLocation[self.nums[len(self.nums) - 1]] = locationdel self.numToLocation[val]self.nums[location] = self.nums[len(self.nums) - 1]del self.nums[len(self.nums) - 1]return Truedef getRandom(self) -> int:r = random.randint(0, len(self.nums) - 1)return self.nums[r]

剑指 Offer II 031. 最近最少使用缓存

哈希表+双向链表(键的值 = 结点)

class Node:def __init__(self, val=0, prev=None, next=None):self.value = valself.prev = prevself.next = nextclass LRUCache: # 时间复杂度:O(n)def __init__(self, capacity: int):self.head = Node(0)self.tail = Node(0)self.head.next = self.tailself.tail.prev = self.headself.map = dict()self.capacity = capacitydef get(self, key: int) -> int:if key not in self.map.keys():  # 键不在字典中return -1node = 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.nextself.deleteNode(toBeDeleted)for k,v in self.map.items():if v == toBeDeleted:del self.map[k]breaknode = Node(value) # 插入到尾部self.insertToTail(node)self.map[key] = nodedef moveToTail(self, node, newvalue):self.deleteNode(node)   node.value = newvalueself.insertToTail(node)def deleteNode(self, node):node.prev.next = node.nextnode.next.prev = node.prevdef insertToTail(self, node):self.tail.prev.next = nodenode.prev = self.tail.prevnode.next = self.tailself.tail.prev = node

剑指 Offer II 032. 有效的变位词

数组哈希
真正的哈希表

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 Falsecounts = dict()for ch in s:counts[ch] = counts.get(ch, 0) + 1for ch in t:if counts.get(ch, 0) == 0:return Falsecounts[ch] = counts.get(ch) - 1return True

第11天 哈希表

剑指 Offer II 033. 变位词组

字符映射到数字
将单词的字母排序

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] = ireturn list(groups.values())

剑指 Offer II 034. 外星语言是否排序

哈希表
数组模拟哈希表

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')] = ifor j in range(len(words) - 1):if not self.isSorted(words[j], words[j + 1], orderArray):return Falsereturn Truedef isSorted(self, word1, word2, orderArray):i = 0while 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 Trueif orderArray[ord(ch1) - ord('a')] > orderArray[ord(ch2) - ord('a')]:return Falsei += 1return i == len(word1)

剑指 Offer II 035. 最小时间差

哈希表:布尔数组

class Solution:# 时间复杂度:O(n), 空间复杂度:O(1)def findMinDifference(self, timePoints: List[str]) -> int:if len(timePoints) > 1440:return 0minuteFlags = [False] * 1440for time in timePoints:a, b = time.split(":")minute = int(a) * 60 + int(b)if minuteFlags[minute]:return 0minuteFlags[minute] = Truereturn self.helper(minuteFlags)def helper(self, minuteFlags):minDiff = len(minuteFlags) - 1prev = -1first = len(minuteFlags) - 1last = -1for i in range(len(minuteFlags)):if minuteFlags[i]:if prev >= 0:minDiff = min(i - prev, minDiff)prev = ifirst = min(i, first)last = max(i, last)minDiff = min(first + len(minuteFlags) - last, minDiff)return minDiff

第12天 栈

剑指 Offer II 036. 后缀表达式

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 + num2elif operator == '-':return num1 - num2elif operator == '*':return num1 * num2elif operator == '/':return ceil(num1 // num2) if num1 // num2 >= 0 else -floor(abs(num1 / num2))else:return 0

剑指 Offer II 037. 小行星碰撞

列表模拟栈

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

剑指 Offer II 038. 每日温度

数组模拟栈

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 - prevstack.append(i)return result

第13天 栈

剑指 Offer II 039. 直方图最大矩形面积

蛮力法
分治法,递归栈
单调栈

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 = 0for i in range(len(heights)):while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:  # 维护一个单调递增的栈height = heights[stack.pop()]width = i - stack[-1] - 1maxArea = max(maxArea, height * width)stack.append(i)while stack[-1] != -1:height = heights[stack.pop()]width = len(heights) - stack[-1] - 1maxArea = max(maxArea, height * width)return maxArea

剑指 Offer II 040. 矩阵中最大的矩形

直方图最大矩形面积的拓展(单调栈)

class Solution:# 时间复杂度:O(mn),空间复杂度:O(n)def maximalRectangle(self, matrix: List[str]) -> int:if len(matrix) == 0 or len(matrix[0]) == 0:return 0heights = [0] * len(matrix[0])maxArea = 0for row in range(len(matrix)):  # 转换成row个直方图for col in range(len(matrix[0])): # 确定每个直方图的高度if matrix[row][col] == '0':heights[col] = 0else:heights[col] += 1maxArea = max(maxArea, self.largestRectangleArea(heights)) # 每个直方图的最大矩形面积用单调栈的方法确定return maxAreadef largestRectangleArea(self, heights):stack = []stack.append(-1)maxArea = 0for i in range(len(heights)):while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:height = heights[stack.pop()]width = i - stack[-1] - 1maxArea = max(maxArea, height * width)stack.append(i)while stack[-1] != -1:height = heights[stack.pop()]width = len(heights) - stack[-1] - 1maxArea = max(maxArea, height * width)return maxArea

第14天 队列

剑指 Offer II 041. 滑动窗口的平均值

队列

class MovingAverage:# 时间复杂度:O(1),空间复杂度:O(1),队列from collections import dequedef __init__(self, size: int):self.capacity = sizeself.nums = deque()self.sum = 0def next(self, val: int) -> float:self.nums.append(val)self.sum += valif len(self.nums) > self.capacity:self.sum -= self.nums.popleft()return self.sum / len(self.nums)

剑指 Offer II 042. 最近请求次数

队列

class RecentCounter:# 时间复杂度:O(1) 3000ms, 空间复杂度:O(1) 3000ms 队列def __init__(self):self.times = []self.windowSize = 3000def ping(self, t: int) -> int:self.times.append(t)while self.times[0] + self.windowSize < t:self.times.pop(0)return len(self.times)

剑指 Offer II 043. 往完全二叉树添加节点

队列(二叉树的广度优先遍历)

# 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 = rootself.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 = nodeelse:parent.right = nodeself.queue.pop(0)self.queue.append(parent.left)self.queue.append(parent.right)return parent.valdef get_root(self) -> TreeNode:return self.root

第15天 队列

剑指 Offer II 044. 二叉树每层的最大值

一个队列
两个队列

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 = queue2queue2 = []return result

剑指 Offer II 045. 二叉树最底层最左边的值

队列

# 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.valwhile 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 = queue2queue2 = []if len(queue1) != 0:bottomLeft = queue1[0].valreturn bottomLeft

剑指 Offer II 046. 二叉树的右侧视图

两个队列

# 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 viewqueue1 = []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 = queue2queue2 = []return view

第16天 树

剑指 Offer II 047. 二叉树剪枝

后续遍历

# 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 rootroot.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 Nonereturn root

剑指 Offer II 048. 序列化与反序列化二叉树

前序遍历

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = Noneclass Codec:# 时间复杂度:O(n), 空间复杂度:O(n), 前序遍历def serialize(self, root):result = []self.dfs(root, result)return resultdef 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 rdef deserialize(self, data):i = [0]  # 采用数组是因为调用者也需要知道下标增加1了????就是不太懂return self.df2s(data, i)def df2s(self, strs, i):s = strs[i[0]]i[0] += 1if s is None:return Nonenode = TreeNode(s)node.left = self.df2s(strs, i)node.right = self.df2s(strs, i)return node

剑指 Offer II 049. 从根节点到叶节点的路径数字之和

深度优先遍历(前序遍历)

# 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:  # 如果在遇到叶节点之前就结束的路径,由于不符合要求,因此应该返回0return 0path = path * 10 + root.valif root.left == None and root.right == None:return pathreturn self.dfs(root.left, path) + self.dfs(root.right, path)

第17天 树

剑指 Offer II 050. 向下的路径节点之和

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] = 1return self.dfs(root, targetSum, map, 0)def dfs(self, root, targetSum, map, path):if root == None:return 0path += root.valcount = map.get(path - targetSum , 0)map[path] = map.get(path, 0) + 1count += self.dfs(root.left, targetSum, map, path)count += self.dfs(root.right, targetSum, map, path)map[path] -= 1 # 函数结束时,程序将回到节点的父节点,也就是说结束之前需要将当前节点从路径中删除return count

剑指 Offer II 051. 节点之和最大的路径

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 0maxSumLeft = [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) # 最后返回经过根节点路径的节点值之和

剑指 Offer II 052. 展平二叉搜索树

利用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 = rootprev = Nonefirst = Nonewhile cur != None or len(stack) != 0:while (cur != None):stack.append(cur)cur = cur.leftcur = stack.pop()if prev != None:prev.right = curelse:first = curprev = curcur.left = Nonecur = cur.rightreturn first

第18天 树

剑指 Offer II 053. 二叉搜索树中的中序后继

(二叉树的中序遍历的拓展)

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = Noneclass 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 = rootresult = Nonewhile cur != None:if cur.val > p.val:result = curcur = cur.leftelse:cur = cur.rightreturn result

剑指 Offer II 054. 所有大于等于节点的值之和

(中序遍历)

# 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 = rootsum = 0while cur != None or len(stack) != 0:while cur != None:stack.append(cur)cur = cur.rightcur = stack.pop()sum += cur.valcur.val = sumcur = cur.leftreturn root

剑指 Offer II 055. 二叉搜索树迭代器

(中序遍历)

# 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 = rootself.stack = []def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)while self.cur != None:self.stack.append(self.cur)self.cur = self.cur.leftself.cur = self.stack.pop()val = self.cur.valself.cur = self.cur.rightreturn valdef hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)return self.cur != None or len(self.stack) != 0

第19天 树

剑指 Offer II 056. 二叉搜索树中两个节点之和

(中序遍历)

# 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 FalseiterNext = BSTIterator(root)iterPrev = BSTIteratorReversed(root)next = iterNext.next()prev = iterPrev.prev()while next != prev:if next + prev == k:return Trueif next + prev < k:next = iterNext.next()else:prev = iterPrev.prev()return Falseclass BSTIterator:  # 中序遍历def __init__(self, root: TreeNode):self.cur = rootself.stack = []def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)while self.cur != None:self.stack.append(self.cur)self.cur = self.cur.leftself.cur = self.stack.pop()val = self.cur.valself.cur = self.cur.rightreturn valdef hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)return self.cur != None or len(self.stack) != 0class BSTIteratorReversed:  # 中序遍历def __init__(self, root: TreeNode):self.cur = rootself.stack = []def prev(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)while self.cur != None:self.stack.append(self.cur)self.cur = self.cur.rightself.cur = self.stack.pop()val = self.cur.valself.cur = self.cur.leftreturn valdef hasPrev(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)return self.cur != None or len(self.stack) != 0

剑指 Offer II 057. 值和下标之差都在给定的范围内

哈希表、有序集合

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 Falseif k == 0: return Falsen=len(nums)k = min(n-1,k)sst = SortedSet()for i in range(k+1):a = nums[i]l,r = a-t,a+tpl,pr=sst.bisect_left(l),sst.bisect_left(r)if pl < pr: return Trueif pr<len(sst) and sst[pr]==r: return Truesst.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+tpl,pr=sst.bisect_left(l),sst.bisect_left(r)if pl < pr: return Trueif pr<len(sst) and sst[pr]==r: return Truesst.add(a)return False

剑指 Offer II 058. 日程表

(有序字典)

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 Falseself.end_start[end] = startreturn True

第20天 堆

剑指 Offer II 059. 数据流的第 K 大数值

(最小堆)

import heapq
class KthLargest:# 时间复杂度:O(logk), 空间复杂度中;O(k) 最小堆,适用于n远大于k的情况def __init__(self, k: int, nums: List[int]):self.size = kself.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]

剑指 Offer II 060. 出现频率最高的 k 个数字

(最小堆+哈希表)

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) + 1minHeap = []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

剑指 Offer II 061. 和最小的 k 个数对

(最小堆)

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

第21天 前缀树

剑指 Offer II 062. 实现前缀树

class Trie:def __init__(self):self.children = [None] * 26self.isEnd = Falsedef searchPrefix(self, prefix):node = selffor ch in prefix:ch = ord(ch) - ord('a')if not node.children[ch]:return Nonenode = node.children[ch]return nodedef insert(self, word: str) -> None: # 时间复杂度:O(n),空间复杂度:O(1)node = selffor ch in word:ch = ord(ch) - ord('a')if not node.children[ch]:node.children[ch] = Trie()node = node.children[ch]node.isEnd = Truedef search(self, word: str) -> bool: # 时间复杂度:O(n),空间复杂度:O(1)node = self.searchPrefix(word)return node is not None and node.isEnddef startsWith(self, prefix: str) -> bool: # 时间复杂度:O(n),空间复杂度:O(1)return self.searchPrefix(prefix) is not None

剑指 Offer II 063. 替换单词

class TrieNode:def __init__(self):self.children = [None] * 26self.isword = Falseclass 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] = prefixreturn " ".join(words)def buildTrie(self, diction):root = TrieNode()for word in diction:node = rootfor 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 = Truereturn rootdef findPrefix(self, root, word):node = rootbuilder = ""for ch in word:if node.isword or node.children[ord(ch)-ord('a')] == None:breakbuilder += chnode = node.children[ord(ch)-ord('a')]return builder if node.isword else ""

剑指 Offer II 064. 神奇的字典

class TrieNode:def __init__(self):self.children = [None] * 26self.isword = Falseclass MagicDictionary:# 时间复杂度:O(n),空间复杂度:O(n)def __init__(self):self.root = TrieNode()def buildDict(self, dictionary: List[str]) -> None:for word in dictionary:node = self.rootfor 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 = Truedef search(self, searchWord: str) -> bool:root = self.rootreturn self.dfs(root, searchWord, 0, 0)def dfs(self, root, word, i, edit):if root is None:return Falseif root.isword and i == len(word) and edit == 1:return Trueif i < len(word) and edit <= 1:found = Falsefor j in range(26):if not found:next = edit if j == (ord(word[i])-ord('a')) else edit + 1found = self.dfs(root.children[j], word, (i + 1), next)else:breakreturn foundreturn False

第22天 前缀树

剑指 Offer II 065. 最短的单词编码

(后缀变前缀+深度优先遍历)

class TrieNode:def __init__(self):self.children = [None] * 26class 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 = rootfor 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 rootdef dfs(self, root, length, total):isLeaf = Truefor child in root.children:if child is not None:isLeaf = Falseself.dfs(child, length + 1, total)if isLeaf:total[0] += length

剑指 Offer II 066. 单词之和

(前缀树+递归)

class TrieNode:def __init__(self):self.children = [None] * 26self.value = 0class MapSum:# 时间复杂度:O(n),空间复杂度:O(n), 前缀树+递归def __init__(self):self.root = TrieNode()def insert(self, key: str, val: int) -> None:node = self.rootfor 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 = valdef sum(self, prefix: str) -> int:node = self.rootfor i in range(len(prefix)):ch = prefix[i]if node.children[ord(ch)-ord('a')] is None:return 0node = node.children[ord(ch)-ord('a')]return self.getSum(node)def getSum(self, node):if node is None:return 0result = node.valuefor child in node.children:result += self.getSum(child)return result

剑指 Offer II 067. 最大的异或

(前缀树)

class TrieNode:def __init__(self):self.children = [None] * 2class Solution:# 时间复杂度:O(n), 空间复杂度:O(n).前缀树+def findMaximumXOR(self, nums: List[int]) -> int:root = self.buildTrie(nums)maxV = 0for num in nums:node = rootxor = 0for i in range(31, -1, -1):  # 每个数字的深度都是32bit = (num >> i) & 1 if node.children[1 - bit] is not None:  # 每一次都尽量找最高位异或为1的靠近xor = (xor << 1) + 1node = node.children[1 - bit]else:xor = xor << 1node = node.children[bit]maxV = max(maxV, xor)return maxVdef buildTrie(self, nums):root = TrieNode()  # 构造深度是32,每个结点的子节点都是2的前缀树for num in nums:node = rootfor i in range(31, -1, -1):bit = (num >> i) & 1if node.children[bit] is None:node.children[bit] = TrieNode()node = node.children[bit]return root

第23天 二分查找

剑指 Offer II 068. 查找插入位置

(二分查找)

class Solution:# 时间复杂度:O(logn),空间复杂度:O(l)def searchInsert(self, nums: List[int], target: int) -> int:left, right = 0, len(nums) - 1while left <= right:mid = (left + right) // 2if nums[mid] >= target:if mid == 0 or nums[mid - 1] < target:  # 退出的边界情况或者情况return midright = mid - 1else:left = mid + 1return len(nums)

剑指 Offer II 069. 山峰数组的顶部

(二分查找)

class Solution:# 时间复杂度:O(logn),空间复杂度:O(1), 数组部分有序,二分查找def peakIndexInMountainArray(self, arr: List[int]) -> int:left = 1right = len(arr) - 2while left <= right:mid = (left + right) // 2if arr[mid] > arr[mid + 1] and arr[mid] > arr[mid - 1]: # 满足条件return midif arr[mid] > arr[mid - 1]:  # 位于数组上升部分,前半部分left = mid + 1else:  # 位于数组下降部分,后半部分right = mid - 1

剑指 Offer II 070. 排序数组中只出现一次的数字

(二分查找)

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) // 2i = mid * 2if 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 - 1else:left = mid + 1return nums[len(nums) - 1]

第24天 二分查找

剑指 Offer II 071. 按权重生成随机数

class Solution:# 时间复杂度:O(logn), 空间复杂度:O(n) 二分查找def __init__(self, w: List[int]):self.total = sum(w)self.sums, isum = [0], 0for i in w:isum += iself.sums.append(isum)   # 前缀和def pickIndex(self) -> int:arr = random.randint(1, self.total)left = 1right = len(self.sums) - 1while left <= right:mid = (left + right) // 2if self.sums[mid - 1] < arr <= self.sums[mid]:  # 找到第一个大于arr的数字的下标  left = mid       breakelif self.sums[mid - 1] >= arr:  # 左边界都大于等于arr,说明区间太靠右了right = mid - 1else:left = mid + 1  # 否则是因为区间太靠左return left - 1

剑指 Offer II 072. 求平方根

(二分查找)

class Solution:# 时间复杂度:O(logn), 空间复杂度:O(n) 二分查找def mySqrt(self, x: int) -> int:left = 1right = xwhile left <= right:mid = (left + right) // 2if mid <= x / mid  and (mid + 1) > x / (mid + 1) :return midelif mid + 1 <= x / (mid + 1):left = mid + 1else:right = mid - 1return 0

剑指 Offer II 073. 狒狒吃香蕉

(二分查找)

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, maxVwhile left <= right:mid = (left + right) // 2hours1 = self.getHours(piles, mid)hours2 = self.getHours(piles, mid - 1)if hours1 <= h and hours2 > h:return midelif hours1 <= h and hours2 <= h:right = mid - 1elif hours1 > h:left = mid + 1return 1def getHours(self, piles, speed):hours = 0for pile in piles:hours += (pile + speed - 1) // speedreturn hours

第25天 排序

剑指 Offer II 074. 合并区间

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

剑指 Offer II 075. 数组相对排序

(计数排序)

class Solution:# 时间复杂度:O(m+n), 空间复杂度:O(1)因为范围是1000,计数排序def relativeSortArray(self, arr1: List[int], arr2: List[int]) -> List[int]:counts = [0] * 1001for num in arr1:  # 得到数组arr1的计数排序counts[num] += 1i = 0for num in arr2:  # 按照arr2的顺序输出数字while counts[num] > 0:arr1[i] = numi += 1counts[num] -= 1for j in range(len(counts)): # 对数组中剩余的数进行排序while counts[j] > 0:arr1[i] = ji += 1counts[j] -= 1return arr1

第26天 排序

剑指 Offer II 076. 数组中的第 k 大的数字

(快速排序)

class Solution:# 时间复杂度:O(n) , 空间复杂度;O(1)快速排序,是一种不断划分左侧小于当前数,右侧大于当前数的过程def findKthLargest(self, nums: List[int], k: int) -> int:target = len(nums) - kstart, end = 0, len(nums) - 1index = self.partition(nums, start, end)while index != target:if index > target:end = index - 1else:start = index + 1index = 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 += 1nums[i], nums[small] = nums[small], nums[i]small += 1nums[end], nums[small] = nums[small], nums[end]return small

剑指 Offer II 077. 链表排序

(归并排序)

# 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 headhead1 = headhead2 = self.split(head)head1 = self.sortList(head1)head2 = self.sortList(head2)return self.merge(head1, head2)def split(self, head):  # 快慢指针slow = headfast = head.nextwhile fast != None and fast.next != None:slow = slow.nextfast = fast.next.nextsecond = slow.nextslow.next = Nonereturn seconddef merge(self, head1, head2):dummy = ListNode()cur = dummywhile head1 != None and head2 != None:if head1.val < head2.val:cur.next = head1head1 = head1.nextelse:cur.next = head2head2 = head2.nextcur = cur.nextcur.next = head2 if

剑指 Offer II 078. 合并排序链表

# 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 Nonereturn self.mergeLists(lists, 0, len(lists)) # 调用函数合并两个链表的def mergeLists(self, lists, start, end):  # 分治法,多个链表通过分治法不断递归转换成两个链表if start + 1 == end:return lists[start]mid = (start + end) // 2head1 = self.mergeLists(lists, start, mid)head2 = self.mergeLists(lists, mid, end)return self.merge(head1, head2)def merge(self, head1, head2):  # 合并两个链表dummy = ListNode()cur = dummywhile head1 != None and head2 != None:if head1.val < head2.val:cur.next = head1head1 = head1.nextelse:cur.next = head2head2 = head2.nextcur = cur.nextcur.next = head2 if head1 == None else head1return dummy.next

第27天 回溯法

剑指 Offer II 079. 所有子集

(回溯法+递归)

class Solution:# 时间复杂度:O(2^n), 空间复杂度:O(2^n)  递归def subsets(self, nums: List[int]) -> List[List[int]]:result = []if len(nums) == 0:return resultself.helper(nums, 0, [], result)  # 递归return resultdef helper(self, nums, index, subset, result):if index == len(nums):subset1 = subset[:]  # 注意这里需要将subset的拷贝添加到result中,可以避免之后修改subset,而修改resultresult.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()  # 把刚才加入的剔除

剑指 Offer II 080. 含有 k 个元素的组合

(回溯法+递归)

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 resultdef 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()  # 为了不影响被调用函数,需要将数字从当前子集中剔除

剑指 Offer II 081. 允许重复选择元素的组合

(回溯法+递归)

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 resultdef 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()

第28天 回溯法

剑指 Offer II 082. 含有重复元素集合的组合

(回溯法 + 递归)

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 resultdef 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 = indexwhile next < len(nums) and nums[next] == nums[index]:next += 1return next

剑指 Offer II 083. 没有重复元素集合

(回溯法 + 递归)

class Solution:# 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归def permute(self, nums: List[int]) -> List[List[int]]:result = []self.helper(nums, 0, result)return resultdef 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]  # 清楚对排列状态的修改,再次交换之前交换的两个数字

剑指 Offer II 084. 含有重复元素集

(回溯法 + 递归)

class Solution:# 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归def permuteUnique(self, nums: List[int]) -> List[List[int]]:result = []self.helper(nums, 0, result)return resultdef 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]) # 集合中添加元素是addnums[j], nums[i] = nums[i], nums[j]self.helper(nums, i + 1, result)nums[j], nums[i] = nums[i], nums[j]

第29天 回溯法

剑指 Offer II 085. 生成匹配的括号

(回溯法 + 递归)

class Solution:# 时间复杂度:O(2^2n),空间复杂度:O() 回溯法 + 递归def generateParenthesis(self, n: int) -> List[str]:result  = []self.helper(n, n, "", result)return resultdef 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)  # 添加右括号

剑指 Offer II 086. 分割回文子字符串

(回溯法 + 递归)

class Solution:# 回溯法 + 递归def partition(self, s: str) -> List[List[str]]:result = []self.helper(s, 0, [], result)return resultdef 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 Falsestart += 1end -= 1return True

剑指 Offer II 087. 复原 IP

(回溯法 + 递归)

    # 回溯法 + 递归 IP地址的特点是:1、三个.分隔字符为4段,每段是从0-255之间,2、除0外,其他数字不能以0开头def restoreIpAddresses(self, s: str) -> List[str]:result = []self.helper(s, 0, 0, "", "", result)return resultdef 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')

第30天 动态规划

剑指 Offer II 088. 爬楼梯的最少成本

(单序列问题 // 动态规划)

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])

剑指 Offer II 089. 房屋偷盗

(单序列问题 // 动态规划)

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 0dp = [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])

剑指 Offer II 090. 环形房屋偷盗

(单序列问题、动态规划)

class Solution:# 单序列问题,动态规划。时间复杂度:O(n), 空间复杂度:O(1)def rob(self, nums: List[int]) -> int:if len(nums) == 0:return 0if 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] * 2dp[0] = nums[start]if start < end:dp[1] = max(nums[start], nums[start + 1])for i in range(start + 2, end + 1):j = i - startdp[j % 2] = max(dp[(j - 1) % 2], dp[(j - 2) % 2] + nums[i])return dp[(end - start) % 2]

第31天 动态规划

剑指 Offer II 091. 粉刷房子

(单序列问题、动态规划、自下往上)

class Solution:# 单序列问题,动态规划,自下向上,时间复杂度:O(n), 空间复杂度:O(1)def minCost(self, costs: List[List[int]]) -> int:if len(costs) == 0:return 0dp = [[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) % 2temp = min(dp[0][last], dp[1][last])return min(temp, dp[2][last])

剑指 Offer II 092. 翻转字符

(单序列问题、动态规划、自下往上)

class Solution:# 单序列问题、动态规划、自下往上、时间复杂度:O(n), 空间复杂度:O(1)def minFlipsMonoIncr(self, s: str) -> int:n = len(s)if n == 0:return 0dp = [[0] * 2 for i in range(2)]ch = s[0]dp[0][0] = 0 if ch == '0' else 1  # 表示第一行当前字符串翻转后结果是0dp[0][1] = 0 if ch == '1' else 1  # 表示第二行当前字符串翻转后结果是1for 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])

剑指 Offer II 093. 最长斐波那契数列

(单序列问题,动态规划,哈希表)

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]] = idp = [[0] * len(arr) for i in range(len(arr))]result = 2for 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 2result = max(result, dp[i][j])return result if result > 2 else 0

第32天 动态规划

剑指 Offer II 094. 最少回文分割

(动态规划,单序列问题,自下往上)

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] = Truedp = [0] * n  # 状态转移方程for i in range(n):if isPal[0][i]:dp[i] = 0else:dp[i] = ifor j in range(1, i + 1):if isPal[j][i]:dp[i] = min(dp[i], dp[j - 1] + 1)return dp[n - 1]

剑指 Offer II 095. 最长公共子序列

(动态规划,双序列问题)

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 + 1else:cur = max(dp[j], dp[j + 1])prev = dp[j + 1]dp[j + 1] = curreturn dp[n2]

剑指 Offer II 096. 字符串交织

(动态规划、双序列问题)

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 Falseif len(s1) < len(s2):return self.isInterleave(s2, s1, s3)dp = [False] * (len(s2) + 1)dp[0] = Truefor 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)]

第33天 动态规划

剑指 Offer II 097. 子序列的数目

(动态规划, 双序列问题, 自下往上)

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] = 1for 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)]

剑指 Offer II 098. 路径的数目

(矩阵路径问题, 动态规划)

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] * nfor i in range(1, m):for j in range(1, n):dp[j] += dp[j - 1]return dp[n - 1]

剑指 Offer II 099. 最小路径之和

(矩阵路径问题,动态规划)

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]

第34天 动态规划

剑指 Offer II 100. 三角形中最小路径之和

(矩阵路径问题, 动态规划)

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

剑指offer101、分割等和子集

(动态规划、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 = 0for num in nums:sum += numif sum % 2 == 1: # 如果是奇数肯定不满足条件return Falsereturn self.subsetSum(nums, sum // 2)def subsetSum(self, nums, target):dp = [False] * (target + 1)dp[0] = Truefor 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]

剑指offer102、加减的目标值

(动态规划、0-1背包问题)

class Solution:# 动态规划,0-1背包问题,找出和为p数字,其中p+q=sum, p-q=target -> p=(target+sum)//2# 时间复杂度:O(nt), 空间复杂度:O(t), n为数组的长度,t是目标值pdef findTargetSumWays(self, nums: List[int], target: int) -> int:sum = 0for num in nums:sum += numif (sum + target) % 2 == 1 or sum < target:return 0return self.subsetSum(nums, (sum + target) // 2)def subsetSum(self, nums, target):dp = [0] * (target + 1)dp[0] = 1for 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]

第35天 动态规划

剑指offer103、最少的硬币数目

(动态规划,完全背包问题,无界背包问题)

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] = 0for 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]

剑指offer104、排列的数目

(动态规划, 完全背包问题, 无界背包问题)

class Solution:# 动态规划, 完全背包问题, 无界背包问题# 时间复杂度:O(nt), 空间复杂度:O(t) n是输入数组的长度, t是排列的目标数def combinationSum4(self, nums: List[int], target: int) -> int:dp = [0] * (target + 1)dp[0] = 1for i in range(1, target + 1):for num in nums:if i >= num:dp[i] += dp[i - num]return dp[target]

第36天 图

剑指offer105、最大岛屿

(图的遍历)

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 = 0for 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 = 1visited[i][j] = Truedirs = [(-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

剑指offer106、二分图

(图的遍历)

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] * sizefor i in range(size):if colors[i] == -1:if not self.setColor(graph, colors, i, 0):return Falsereturn Truedef setColor(self, graph, colors, i, color):if colors[i] >= 0:return colors[i] == colorcolors[i] = colorfor neighbor in graph[i]:if not self.setColor(graph, colors, neighbor, 1 - color):return Falsereturn True

剑指offer107、矩阵中的距离

(图,广度优先遍历,最短距离)

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] = 0else: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 + 1queue.append((r, c))return dists

第37天 图

剑指offer108、单词演变

(广度优先搜索,哈希表)

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 0set1, set2 = set(), set() # 用集合不用队列是因为后边判断从一个方向搜索到的节点在另一个方向是否已经访问过,只需要O(1)length = 2set1.add(beginWord)set2.add(endWord)notVisited.remove(endWord)while len(set1) != 0 and len(set2) != 0:if len(set2) < len(set1): # 确保每次都是从需要访问少的方向开始搜索temp = set1set1 = set2set2 = tempset3 = set()for word in set1:neighbors = self.getNeighbors(word)  # 找到当前单词的所有相邻单词for neighbor in neighbors:  if neighbor in set2:  # 说明两个方向的搜索相遇,已经找到了一条从起始节点和目标节点之间的最短距离return lengthif neighbor in notVisited:  # 否则将节点添加都set3中set3.add(neighbor)notVisited.remove(neighbor)length += 1  # 当set1中的所有节点都访问完毕,接下来可能访问set1的相邻节点,即set3中的所有节点set1 = set3return 0def 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

剑指offer109、开密码锁

(广度优先遍历,哈希集合,队列)

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 -1queue1, queue2 = [], []steps = 0queue1.append(init)visited.add(init)while len(queue1) != 0:cur = queue1.pop(0)if cur == target:return stepsnexts = 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 += 1queue1 = queue2queue2 = []return -1def 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] = newChnexts.append("".join(builder))newCh = '0' if ch == '9' else str(int(ch) + 1)builder = list(cur)builder[i] = newChnexts.append("".join(builder))return nexts

剑指offer110、所有路径

(图,深度优先遍历)

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 resultdef 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()

第38天 图

剑指offer111、计算除法

(图的遍历,深度优先遍历,哈希表)

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] = -1else:visited = set()results[i] = self.dfs(graph, fro, too, visited)return resultsdef 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 graphdef dfs(self, graph, fro, too, visited):if fro == too:return 1.0visited.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 * nextValvisited.remove(fro)return -1.0

剑指offer112、最长递增路径

(图的遍历,深度优先遍历,递归)

class Solution:# 图的遍历,深度优先遍历。# 从较小的数指向较大的数,有向无环图def longestIncreasingPath(self, matrix: List[List[int]]) -> int:m, n = len(matrix), len(matrix[0])if m == 0 or n == 0:return 0lengths = [[0] * n for _ in range(m)]longest = 0for i in range(m):for j in range(n):length = self.dfs(matrix, lengths, i, j)longest = max(longest, length)return longestdef 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 = 1for 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] = lengthreturn length

剑指offer113、课程排序

(图、拓扑排序、队列、数组)

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  # 该门课的入度加1queue = [] # 用队列的迭代方法实现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] -= 1if inDegrees[next] == 0:queue.append(next)return order if len(order) == numCourses else []

第39天 图

剑指offer114、外星文字典

(图,拓扑排序,哈希表,队列)

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  # 所有字母入度初始化为0for i in range(1, len(words)): # 初始化有向图w1 = words[i - 1]w2 = words[i]if w1.startswith(w2) and w1 != w2: # 排除特殊情况return ""j = 0while 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) + 1breakj += 1queue = []for ch in inDegrees.keys():  # 找到入度为0的节点,添加到队列中if inDegrees[ch] == 0:queue.append(ch)result = ""while len(queue) != 0:ch = queue.pop(0)result += chfor next in graph[ch]:inDegrees[next] -= 1if inDegrees[next] == 0:queue.append(next)return result if len(result) == len(graph) else ""

剑指offer115、重建序列

(图,拓扑排序,字典,队列)

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 Falsegraph[num] = set()  # 初始化字典中每个键的值为集合,之后存储排序在该数字之后的numinDegrees[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] += 1queue = []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] -= 1if inDegrees[next] == 0:queue.append(next)return built == org

剑指offer116、朋友圈

(图,广度优先遍历,并查集, 邻接矩阵)

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] = icount = 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 -= 1return countdef 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, 返回Turefathers[fathersOfI] = fathersOfJreturn Truereturn False

第40天 图

剑指offer117、相似的字符串

(并查集)

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] = igroups = 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 -= 1return groupsdef similar(self, str1, str2):diffCount = 0for i in range(len(str1)):if str1[i] != str2[i]:diffCount += 1return diffCount <= 2def 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] = fatherOfJreturn Truereturn False

剑指offer118、多余的边

(并查集)

class Solution:# 时间复杂度:O(n), 空间复杂度:O(n), 并查集,查找和合并的时间接近O(1)def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:maxVertx = 0for 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] = ifor edge in edges:if not self.union(fathers, edge[0], edge[1]):  # union返回True,表示两个节点的祖先节点不是一个,没有环,如果返回False,表示有环return edgereturn [0] * 2def 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] = fatherOfJreturn Truereturn False

剑指offer119、最长连续序列

(图,广度优先搜索,并查集)

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] = numcounts[num] = 1for 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 = 0for length in counts.values():longest = max(longest, length)return longestdef 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] = fatherOfJcountOfI = counts[fatherOfI]countOfJ = counts[fatherOfJ]counts[fatherOfJ] = countOfI + countOfJ

LeetCode刷题 _「剑指 Offer]专项突破版相关推荐

  1. 剑指Offer专项突破版(58)—— 日程表

    题目 剑指 Offer II 058. 日程表 思路 假设现在已经有一堆互不冲突的日程了,此时需要新增一个日程k,其开始时间为start,结束时间为end,怎么判断是否和已有的日程冲突呢? 先考虑所有 ...

  2. 剑指offer 专项突破版 78、合并排序链表

    题目链接 思路 最初的思路是上一题既然实现了归并两个链表,那么我们可以挨个归并~ 归并n-1次就可以,但是这样时间复杂度为O(k²n) class Solution {public ListNode ...

  3. 剑指offer 专项突破版 74、合并区间

    题目链接 思路 注意合并区间的规则是,对于有重合的区间直接合并(重合意味着某一个区间的头部在另一个区间之中) 所以我们可以先把区间按照区间头排序,然后再挨个判断是否重合~ 注意具体的写法 class ...

  4. 剑指offer 专项突破版 73、狒狒吃香蕉

    题目链接 思路 这个也是范围内的查找,一开始可以确定狒狒的速度区间应该是[1,maxVal],但是有两个细节需要注意 如何通过piles数组和speed计算时间 result += (pile + s ...

  5. 剑指offer专项突破版

    面试题1:整除除法 力扣连接:https://leetcode-cn.com/problems/xoh6Oh/submissions/ 题目描述 输入两个int型整数,它们进行除法计算并返回商,要求不 ...

  6. 剑指offer 专项突破版 79、所有子集

    题目链接 思路一:回溯 对于这种可以分为若干步,每一步有很多选择的题目非常适合用回溯法来做. 具体的方式为写一个helper函数,参数为当前的结果集合subset,当前选择的数字的索引index. 首 ...

  7. 对分查找的最多次数_「剑指offer题解」数组中出现次数超过一半的数字

    关注我--个人公众号:后端技术漫谈 我目前是一名后端开发工程师.主要关注后端开发,数据安全,网络爬虫,物联网,边缘计算等方向. 原创博客主要内容 Java知识点复习全手册 Leetcode算法题解析 ...

  8. java牛客排序算法题_《剑指offer》面试题28:字符串的排列(牛客网版本) java...

    输入一个字符串,按字典序打印出该字符串中字符的所有排列.例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab和cba. 输入描述: 输入一个字符 ...

  9. 剑指offer专项突击版第24天

    剑指 Offer II 071. 按权重生成随机数 二分+前缀和 看了几遍题目愣是没看明白,最后看的题解明白了! 题意就是按照 w[i]w[i]w[i] 的权重选取下标 iii,这个问题可以转化成:所 ...

最新文章

  1. MyBatis 源码解读-propertiesElement()
  2. iOS_9_scrollView分页
  3. 为什么Java 中1000==1000为false,而100==100为true?
  4. pythonopencv算法_opencv python 光流法
  5. Keras中长短期记忆网络LSTM的5步生命周期
  6. 智能手机射频前端架构初识: Phase 2/3/5/6/6L/7/7L/7LE
  7. C语言编写函数求字符串长度的几种实现方法
  8. 听风的插件-集成到设置中
  9. 北京化工大学计算机类分流,北京化工大学A类学科名单有哪些(含A、B、C类学科名单)...
  10. angular的传值,子传父,父传子
  11. Zabbix监控配置详解
  12. System.UnauthorizedAccessException: Access to the path 'D:/web/WebMicaps/TempImages/msc_cntr_0.txt'
  13. Windows10系统错误码0xc0000142怎么修复?
  14. python语法基础语法_Python基本语法[二],python入门到精通[四]
  15. Nginx教程(一) Nginx入门教程
  16. 解决word 2016中不能加载EndNote x7
  17. mysql去除全角空格_PHP输出全角空格,导致页面布局混乱
  18. 2014年TABLE投资收购业界布局
  19. 如何选择好公司和计算好价格
  20. 很想联网,家庭用的。

热门文章

  1. 一图看懂6G应用及用例研究
  2. c语言中的字符串拼接
  3. NVIDIA Thrust教程
  4. 编写一个程序,打印所有的“水仙花数”“水仙花数”是指一个三位数,其各位数字的立方和等于该数本身
  5. 输出1000以内的所有”水仙花数“,所谓”水仙花数“是指一个一个三位数,其各位数字立方和等于该数本身
  6. AsyncQueryHandler
  7. Anaconda中用pip安装本地包
  8. 【Unity】Unity3D控制Camera移动
  9. 网络分流器-网络分流器TAP网络流量分析
  10. 一个Servlet同一时刻只有一个实例。 当多个请求发送到同一个Servlet,服务器会为每个请求创建一个新线程来处理。