LeetCode刷题 _「剑指 Offer]专项突破版

第01天 整数

剑指 Offer II 001. 整数除法

class Solution:
    # 时间复杂度:O(logn), 空间复杂度:O(1)
    def divideCore(self, dividend, divisor):
        result = 0
        while dividend <= divisor:   # 因为此时被除数和除数都成了负数
            value = divisor
            quotient = 1
            while (value >= -2**30 and dividend <= value + value):
                quotient += quotient
                value += value
            result += quotient
            dividend -= value
        return result

    def divide(self, dividend: int, divisor: int) -> int:
        if dividend == -2**31 and divisor == -1:
            return 2**31 - 1  # 因为负数转化为正数会有溢出
        negative = 2
        if dividend > 0:
            negative -= 1
            dividend = -dividend
        if divisor > 0:
            negative -= 1
            divisor = -divisor
        result = self.divideCore(dividend, divisor)
        if negative == 1:
            return  -result
        else:
            return result

剑指 Offer II 002. 二进制加法

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

剑指 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] * 32
        for num in nums:
            for i in range(32):
                counts[i] += (num >> (31 - i)) & 1   # 表示从左边起num中的第i个位数中是否是1  # python中特殊的是对于负数,右移动补0 
        res = 0
        for i in range(32):
            res  = (res << 1) + counts[i] % 3   
        # 负数,原码y 反码-y-1 补码-y 移码是补码的
        # python中的按位操作和计算机底层的操作是一样的,比如正数和负数都用补码保存,只不过正数的补码是自身,负数的补码是原码取反再加1
        # 而python中的函数却是经过处理的,便于我们自身理解的,这也是为什么称python是高级语言
        # res ^ 0xffffffff 表示对res的按位取反(截取出32位,把符号位的1全部反过来,但是数据位的也反了) 也就是此时是补码的反码(叫移码)
        # ~ res (此时再取反一次就行了叫,就表示真正的补码)
        return res if counts[0] % 3 == 0 else ~(res ^ 0xffffffff) # 最高位如果是1,证明是负数,此时保存的是补码。最高位如果是0,证明是正数,此时保存的是原码。因为Java中的int是32位

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

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

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

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

第03天 数组

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

class Solution:
    # 时间复杂度:O(nlogn + n^2), 排序+找 空间复杂度:O(1)
    def twosum(self, nums, i, res):
        j = i + 1
        k = len(nums) - 1
        while j < k:
            if nums[i] + nums[j] + nums[k] == 0:
                res.append([nums[i], nums[j], nums[k]])
                temp = nums[j]
                while (nums[j] == temp and j < k):  # twosum中滤掉重复的数,只需要考虑较小的数就行了
                    j += 1
            elif nums[i] + nums[j] + nums[k] < 0:
                j += 1
            else:
                k -= 1
        
    def threeSum(self, nums: List[int]) -> List[List[int]]:
        res = []
        if len(nums) >= 3:
            nums.sort()  # 先排序,变成有序的
            i = 0
            while i < len(nums) - 2:  
                self.twosum(nums, i, res)
                temp = nums[i]
                i += 1
                while nums[i] == temp and i < len(nums) - 1:  # 滤掉重复的数
                    i += 1
        return res

剑指 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, 0
        res = float('inf')
        for right in range(len(nums)):
            sum += nums[right]
            while left <= right and sum >= target:
                res = min(res, right - left + 1)
                sum -= nums[left]
                left += 1
        return res if res != float('inf') else 0

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

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

第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, 0
        for num in nums:
            sum += num
            count += sumToCount.get(sum - k, 0)
            sumToCount[sum] = sumToCount.get(sum, 0) + 1
        return 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, 0
        for i in range(len(nums)):
            sum += -1 if nums[i] == 0 else 1
            if sum in sumToIndex.keys():
                maxLength = max(maxLength, i - sumToIndex.get(sum)) 
            else: # 只有不存在时才添加,这样可以保证保留的是第一个累积到当前扫描的数字之和
                sumToIndex[sum] = i     
        return maxLength

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

累加数组数字求数字之和

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

剑指 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 0
        self.sums = [[0] * (len(matrix[0]) + 1) for _ in range(len(matrix)+1)] # 都一个是为了防止求得本身是左上角的矩阵,以防出具ai出具ai
        for i in range(len(matrix)):
            rowSum = 0
            for j in range(len(matrix[0])):
                rowSum += matrix[i][j]
                self.sums[i + 1][j + 1] = self.sums[i][j + 1] + rowSum
 
    # 时间复杂度:O(1)
    def sumRegion(self, row1: int, col1: int, row2: int, col2: int) -> int:
        return self.sums[row2 + 1][col2 + 1] - self.sums[row1][col2 + 1] - self.sums[row2 + 1][col1] + self.sums[row1][col1]

第05天 字符串

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

哈希表、双指针

class Solution:
    # 时间复杂度:O(m + n), 空间复杂度:O(1)
    def areAllZero(self,counts):
        for count in counts:
            if count != 0:
                return False
        return True

    def checkInclusion(self, s1: str, s2: str) -> bool:
        if len(s2) >= len(s1):
            counts = [0] * 26
            for i in range(len(s1)):
                counts[ord(s1[i]) - ord('a')] += 1
                counts[ord(s2[i]) - ord('a')] -= 1
            if self.areAllZero(counts):
                return True
            for j in range(len(s1), len(s2)):
                counts[ord(s2[j]) - ord('a')] -= 1
                counts[ord(s2[j - len(s1)]) - ord('a')] += 1
                if self.areAllZero(counts):
                    return True
        return False

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

哈希表、双指针

class Solution:
    # 时间复杂度:O(n), 空间复杂度:O(1)
    def areAllZero(self, counts):
        for count in counts:
            if count != 0:
                return False
        return True

    def findAnagrams(self, s1: str, s2: str) -> List[int]:
        indices = []
        if len(s1) >= len(s2):
            counts = [0] * 26
            for i in range(len(s2)):
                counts[ord(s2[i]) - ord('a')] += 1
                counts[ord(s1[i]) - ord('a')] -= 1
            if self.areAllZero(counts):
                indices.append(0)
            for j in range(len(s2), len(s1)):
                counts[ord(s1[j]) - ord('a')] -= 1
                counts[ord(s1[j - len(s2)]) - ord('a')] += 1
                if self.areAllZero(counts):
                    indices.append(j - len(s2) + 1)
        return indices

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

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

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

哈希表+字符串 需要多次遍历整个哈希表

class Solution:
    # 时间复杂度:O(n), 空间复杂度:O(1)。需要多次遍历整个哈希表
    def hasGreaterThan1(self, counts):
        for count in counts.values():
            if count > 1:
                return True
        return False

    def lengthOfLongestSubstring(self, s: str) -> int:
        counts = dict()
        longest = 1 if len(s) > 0 else 0
        j = -1 
        for i in range(len(s)):
            counts[s[i]] = counts.get(s[i], 0) + 1
            while self.hasGreaterThan1(counts):
                j += 1
                counts[s[j]] = counts.get(s[j], 0) - 1
            longest = max(i - j, longest)
        return longest

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

剑指 Offer II 018. 有效的回文

双指针

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

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

双指针

class Solution:
    # 时间复杂度:O(n), 空间复杂度:O(1)
    def isPalindrome(self, s, start, end):
        while start < end:
            if s[start] != s[end]:
                break
            start += 1
            end -= 1
        return start >= end

    def validPalindrome(self, s: str) -> bool:
        start = 0
        end = len(s) - 1
        for _ in range(len(s) // 2):
            if s[start] != s[end]:
                break
            start += 1
            end -= 1
        return start == len(s) // 2 or self.isPalindrome(s, start, end - 1) or self.isPalindrome(s, start + 1, end)

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

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

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

    def countSubstrings(self, s: str) -> int:
        count = 0
        for i in range(len(s)):
            count += self.countPalindrome(s, i, i)
            count += self.countPalindrome(s, i, i + 1)
        return count

第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 = head
        front, back = head, dummy
        for i in range(n):
            front = front.next
        while front is not  None:
            front = front.next
            back = back.next
        back.next = back.next.next
        return dummy.next

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

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

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # 时间复杂度:O(n),空间复杂度:O(1),不需要知道环中节点数目
    def getNodeInloop(self, head):
        if head is None or head.next is None:
            return None
        slow = head.next
        fast = slow.next
        while (slow is not None and fast is not None):
            if slow == fast:
                return slow
            slow = slow.next
            fast = fast.next
            if fast is not None:
                fast = fast.next
        return None

    def detectCycle(self, head: ListNode) -> ListNode:
        inLoop = self.getNodeInloop(head) # 此时inloop的步数是环中节点数目的整数倍
        if (inLoop is None):
            return None
        node = head
        while node != inLoop:  # 慢指针从头开始,快指针从inLoop开始,相遇时一定是环的入口节点
            node = node.next
            inLoop = inLoop.next
        return node

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

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # 时间复杂度:O(n),空间复杂度:O(1),需要知道环中节点数目
    def getNodeInloop(self, head):
        if head is None or head.next is None:
            return None
        slow = head.next
        fast = slow.next
        while (slow is not None and fast is not None):
            if slow == fast:
                return slow
            slow = slow.next
            fast = fast.next
            if fast is not None:
                fast = fast.next
        return None

    def detectCycle(self, head: ListNode) -> ListNode:
        inLoop = self.getNodeInloop(head)
        if (inLoop is None):
            return None
        loopCount = 1
        n = inLoop
        while n.next != inLoop:
            loopCount += 1
            n = n.next
        fast = head
        for i in range(loopCount):
            fast = fast.next
        slow = head
        while fast!= slow:
            fast = fast.next
            slow = slow.next
        return slow

剑指 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 = 0
        while head != None:
            count += 1
            head = head.next
        return count

    def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        count1 = self.Qiulength(headA)
        count2 = self.Qiulength(headB)
        delta = abs(count1 - count2)
        longer = headA if count1 > count2 else headB
        shorter = headB if count1 > count2 else headA
        node1 = longer
        for i in range(delta):
            node1 = node1.next
        node2 = shorter
        while node1 != node2:
            node2 = node2.next
            node1 = node1.next
        return node1

第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 = None
        cur = head
        while cur is not None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return 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 = dummy
        carry = 0
        while head1 is not None or head2 is not None:
            sum = (0 if head1 is None else head1.val) + (0 if head2 is None else head2.val) + carry
            carry = 1 if sum >= 10 else 0
            sum = sum - 10 if sum >= 10 else sum
            newNode = ListNode(sum)
            sumNode.next = newNode
            sumNode = sumNode.next
            head1 = None if head1 is None else head1.next
            head2 = None if head2 is None else head2.next

        if carry > 0:
            sumNode.next = ListNode(carry)
        return dummy.next
            
    def reverseList(self, head):
        pre = None
        cur = head
        while cur is not None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

    def addTwoNumbers(self, l1: ListNode, l2: ListNode) -> ListNode:
        head1 = self.reverseList(l1)
        head2 = self.reverseList(l2)
        reversedHead = self.addReversed(head1, head2)
        return self.reverseList(reversedHead)

剑指 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 = head
        while node1 is not None and node2 is not None:
            temp = node1.next

            pre.next = node1
            node1.next = node2
            pre = node2

            node1 = temp 
            node2 = node2.next

        if node1 is not None:
            pre.next = node1
    
    def reverseList(self, first):
        pre = None
        cur = first
        while cur is not None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

    def reorderList(self, head: ListNode) -> None:
        dummy = ListNode()
        dummy.next = head
        fast = slow = dummy
        while fast is not None and fast.next is not None:
            slow = slow.next
            fast = fast.next.next
        temp = slow.next
        slow.next = None
        self.link(head, self.reverseList(temp), dummy)

第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 True
        slow = head
        fast = head.next
        while fast.next is not None and fast.next.next is not None:
            fast = fast.next.next
            slow = slow.next
        secondHalf = slow.next
        if fast.next is not None:
            secondHalf = slow.next.next
        slow.next = None
        return self.equals(secondHalf, self.reverseList(head))
    
    def reverseList(self, head):
        pre = None
        cur = head
        while cur is not None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

    def equals(self, l1, l2):
        while l1 is not None and l2 is not None:
            if l1.val != l2.val:
                return False
            l1 = l1.next
            l2 = l2.next
        return True

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

双向链表

"""
# Definition for a Node.
class Node:
    def __init__(self, val, prev, next, child):
        self.val = val
        self.prev = prev
        self.next = next
        self.child = child
"""
class Solution:
    # 时间复杂度:O(n),空间复杂度:O(k)。k是链表的层数
    def flatten(self, head: 'Node') -> 'Node':
        self.flattenGetTail(head)
        return head

    def flattenGetTail(self, head):
        node = head
        tail = None
        while node is not None:
            next = node.next
            if node.child is not None:
                child = node.child
                childTail = self.flattenGetTail(node.child)
                node.child = None
                node.next = child
                child.prev = node
                childTail.next = next
                if next is not None:
                    next.prev = childTail
                tail = childTail
            else:
                tail = node
            node = next
        return tail

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

指针,循环链表

"""
# Definition for a Node.
class Node:
    def __init__(self, val=None, next=None):
        self.val = val
        self.next = next
"""

class Solution:
    # 时间复杂度:O(n), 空间复杂度:O(1)
    def insert(self, head: 'Node', insertVal: int) -> 'Node':
        node = Node(insertVal)
        if head is None: # 特殊情况:链表本身为空
            head = node
            node.next = head
        elif head.next is None: # 特殊情况:链表本身长度为1
            head.next = node
            node.next = head
        else:
            self.insertCore(head, node)
        return head

    def insertCore(self, head, node):
        cur = head
        next = head.next
        biggest = head
        while not(cur.val <= node.val and next.val >= node.val) and (next != head):
            cur = next
            next = next.next
            if (cur.val >= biggest.val):
                biggest = cur
        if cur.val <= node.val and next.val >= node.val:
            cur.next = node
            node.next = next
        else:
            node.next = biggest.next
            biggest.next = node

第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 False
        self.numToLocation[val] = len(self.nums)
        self.nums.append(val)
        return True

    def remove(self, val: int) -> bool:
        if val not in self.numToLocation.keys():
            return False
        location = self.numToLocation[val]
        self.numToLocation[self.nums[len(self.nums) - 1]] = location
        del self.numToLocation[val]
        self.nums[location] = self.nums[len(self.nums) - 1]
        del self.nums[len(self.nums) - 1]
        return True

    def getRandom(self) -> int:
        r = random.randint(0, len(self.nums) - 1)
        return self.nums[r]

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

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

class Node:
    def __init__(self, val=0, prev=None, next=None):
        self.value = val
        self.prev = prev
        self.next = next

class LRUCache: 
    # 时间复杂度:O(n)
    def __init__(self, capacity: int):
        self.head = Node(0)
        self.tail = Node(0)
        self.head.next = self.tail
        self.tail.prev = self.head

        self.map = dict()
        self.capacity = capacity

    def get(self, key: int) -> int:
        if key not in self.map.keys():  # 键不在字典中
            return -1
        node = self.map[key]
        self.moveToTail(node, node.value)  # 键在字典中,直接修改到尾部
        return node.value  # 返回值

    def put(self, key: int, value: int) -> None:
        if key in self.map.keys(): # 键在字典中,直接修改到尾部
            self.moveToTail(self.map[key], value)
        else:  # 键不在字典中,需要考虑是否超了容量
            if len(self.map) == self.capacity: # 超过容量了,要删除最近最少使用的,同时修改链表和字典
                toBeDeleted = self.head.next
                self.deleteNode(toBeDeleted)
                for k,v in self.map.items():
                    if v == toBeDeleted:
                        del self.map[k]
                        break
                

            node = Node(value) # 插入到尾部
            self.insertToTail(node)
            self.map[key] = node

    def moveToTail(self, node, newvalue):
        self.deleteNode(node)   
        node.value = newvalue
        self.insertToTail(node)
    
    def deleteNode(self, node):
        node.prev.next = node.next
        node.next.prev = node.prev
    
    def insertToTail(self, node):
        self.tail.prev.next = node
        node.prev = self.tail.prev
        node.next = self.tail
        self.tail.prev = node

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

第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] = i
        return 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')] = i
        for j in range(len(words) - 1):
            if not self.isSorted(words[j], words[j + 1], orderArray):
                return False
        return True

    def isSorted(self, word1, word2, orderArray):
        i = 0
        while i < len(word1) and i < len(word2):
            ch1 = word1[i]
            ch2 = word2[i]
            if orderArray[ord(ch1) - ord('a')] < orderArray[ord(ch2) - ord('a')]:
                return True
            if orderArray[ord(ch1) - ord('a')] > orderArray[ord(ch2) - ord('a')]:
                return False
            i += 1
        return i == len(word1)

剑指 Offer II 035. 最小时间差

哈希表:布尔数组

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

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

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

剑指 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 0
        heights = [0] * len(matrix[0])
        maxArea = 0
        for row in range(len(matrix)):  # 转换成row个直方图
            for col in range(len(matrix[0])): # 确定每个直方图的高度
                if matrix[row][col] == '0':
                    heights[col] = 0
                else:
                    heights[col] += 1
            maxArea = max(maxArea, self.largestRectangleArea(heights)) # 每个直方图的最大矩形面积用单调栈的方法确定
        return maxArea
    
    def largestRectangleArea(self, heights):
        stack = []
        stack.append(-1)
        maxArea = 0
        for i in range(len(heights)):
            while stack[-1] != -1 and heights[stack[-1]] >= heights[i]:
                height = heights[stack.pop()]
                width = i - stack[-1] - 1
                maxArea = max(maxArea, height * width)
            stack.append(i)
        while stack[-1] != -1:
            height = heights[stack.pop()]
            width = len(heights) - stack[-1] - 1
            maxArea = max(maxArea, height * width)
        return maxArea

第14天 队列

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

队列

class MovingAverage:
    # 时间复杂度:O(1),空间复杂度:O(1),队列
    from collections import deque
    def __init__(self, size: int):
        self.capacity = size
        self.nums = deque()
        self.sum = 0

    def next(self, val: int) -> float:
        self.nums.append(val)
        self.sum += val
        if len(self.nums) > self.capacity:
            self.sum -= self.nums.popleft()
        return self.sum / len(self.nums)

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

队列

class RecentCounter:
    # 时间复杂度:O(1) 3000ms, 空间复杂度:O(1) 3000ms 队列
    def __init__(self):
        self.times = []
        self.windowSize = 3000

    def ping(self, t: int) -> int:
        self.times.append(t)
        while self.times[0] + self.windowSize < t:
            self.times.pop(0)
        return len(self.times)

剑指 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 = root
        self.queue = []
        self.queue.append(self.root)
        while self.queue[0].left != None and self.queue[0].right != None:
            node = self.queue.pop(0)
            self.queue.append(node.left)
            self.queue.append(node.right) 

    def insert(self, v: int) -> int:
        parent = self.queue[0]
        node = TreeNode(v)
        if parent.left == None:
            parent.left = node
        else:
            parent.right = node
            self.queue.pop(0)
            self.queue.append(parent.left)
            self.queue.append(parent.right)
        return parent.val

    def get_root(self) -> TreeNode:
        return self.root

第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 = queue2
                queue2 = []
        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.val
        while len(queue1) != 0:
            node = queue1.pop(0)
            if node.left != None:
                queue2.append(node.left)
            if node.right != None:
                queue2.append(node.right)
            if len(queue1) == 0:
                queue1 = queue2
                queue2 = []
                if len(queue1) != 0:
                    bottomLeft = queue1[0].val
        return bottomLeft

剑指 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 view
        queue1 = []
        queue2 = []
        queue1.append(root)
        while len(queue1) != 0:
            node = queue1.pop(0)
            if node.left != None:
                queue2.append(node.left)
            if node.right != None:
                queue2.append(node.right)
            if len(queue1) == 0:
                view.append(node.val)
                queue1 = queue2
                queue2 = []
        return view

第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 root
        root.left = self.pruneTree(root.left)
        root.right = self.pruneTree(root.right)
        if root.left is None and root.right is None and root.val == 0:
            return None
        return root

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

前序遍历

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Codec:
    # 时间复杂度:O(n), 空间复杂度:O(n), 前序遍历
    def serialize(self, root):
        result = []
        self.dfs(root, result)
        return result
    
    def dfs(self, node, r):
        if node == None:
            r.append(None)
        else:
            r.append(node.val)
            self.dfs(node.left, r)
            self.dfs(node.right, r)
        return r
        
    def deserialize(self, data):
        i = [0]  # 采用数组是因为调用者也需要知道下标增加1了????就是不太懂
        return self.df2s(data, i)

    def df2s(self, strs, i):
        s = strs[i[0]]
        i[0] += 1
        if s is None:
            return None
        node = TreeNode(s)
        node.left = self.df2s(strs, i)
        node.right = self.df2s(strs, i)
        return node

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

第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] = 1
        return self.dfs(root, targetSum, map, 0)

    def dfs(self, root, targetSum, map, path):
        if root == None:
            return 0
        path += root.val
        count = map.get(path - targetSum , 0)
        map[path] = map.get(path, 0) + 1

        count += self.dfs(root.left, targetSum, map, path)
        count += self.dfs(root.right, targetSum, map, path)

        map[path] -= 1 # 函数结束时,程序将回到节点的父节点,也就是说结束之前需要将当前节点从路径中删除
        return count

剑指 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 0
        maxSumLeft = [float('-inf')]
        left = max(0, self.dfs(root.left, maxSumLeft))  # 左子树路径节点值节点值之和的最大值

        maxSumRight = [float('-inf')]
        right = max(0, self.dfs(root.right, maxSumRight)) # 右子树路径节点值节点值之和的最大值

        maxSum[0] = max(maxSumLeft[0], maxSumRight[0])
        maxSum[0] = max(maxSum[0], left + right + root.val)  # 三者比较取其大
        return root.val + max(left, right) # 最后返回经过根节点路径的节点值之和

剑指 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 = root
        prev = None
        first = None
        while cur != None or len(stack) != 0:
            while (cur != None):
                stack.append(cur)
                cur = cur.left
            cur = stack.pop()
            if prev != None:
                prev.right = cur
            else:
                first = cur
            prev = cur
            cur.left = None
            cur = cur.right
        return first

第18天 树

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

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

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution:
    # # 解法一、时间复杂度:O(n), 空间复杂度:O(h)
    # def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
    #     stack = []
    #     cur = root
    #     found = False
    #     while cur != None or len(stack) != 0:
    #         while cur != None:
    #             stack.append(cur)
    #             cur = cur.left
    #         cur = stack.pop()
    #         if found:
    #             break
    #         elif p == cur:
    #             found = True
    #         cur = cur.right
    #     return cur

    # 解法一、时间复杂度:O(h), 空间复杂度:O(1) 利用二叉搜索树下一个结点一定比自身大的特点
    def inorderSuccessor(self, root: 'TreeNode', p: 'TreeNode') -> 'TreeNode':
        cur = root
        result = None
        while cur != None:
            if cur.val > p.val:
                result = cur
                cur = cur.left
            else:
                cur = cur.right
        return result

剑指 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 = root
        sum = 0
        while cur != None or len(stack) != 0:
            while cur != None:
                stack.append(cur)
                cur = cur.right
            cur = stack.pop()
            sum += cur.val
            cur.val = sum
            cur = cur.left
        return root

剑指 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 = root
        self.stack = []

    def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
            while self.cur != None:
                self.stack.append(self.cur)
                self.cur = self.cur.left
            self.cur = self.stack.pop()
            val = self.cur.val
            self.cur = self.cur.right
            return val

    def hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
        return self.cur != None or len(self.stack) != 0

第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 False
        iterNext = BSTIterator(root)
        iterPrev = BSTIteratorReversed(root)
        next = iterNext.next()
        prev = iterPrev.prev()
        while next != prev:
            if next + prev == k:
                return True
            if next + prev < k:
                next = iterNext.next()
            else:
                prev = iterPrev.prev()
        return False

class BSTIterator:  
    # 中序遍历
    def __init__(self, root: TreeNode):
        self.cur = root
        self.stack = []

    def next(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
        while self.cur != None:
            self.stack.append(self.cur)
            self.cur = self.cur.left
        self.cur = self.stack.pop()
        val = self.cur.val
        self.cur = self.cur.right
        return val

    def hasNext(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
        return self.cur != None or len(self.stack) != 0

class BSTIteratorReversed:  
    # 中序遍历
    def __init__(self, root: TreeNode):
        self.cur = root
        self.stack = []

    def prev(self) -> int: # 时间复杂度:O(h),空间复杂度:O(1)
        while self.cur != None:
            self.stack.append(self.cur)
            self.cur = self.cur.right
        self.cur = self.stack.pop()
        val = self.cur.val
        self.cur = self.cur.left
        return val
            
    def hasPrev(self) -> bool: # 时间复杂度:O(h),空间复杂度:O(1)
        return self.cur != None or len(self.stack) != 0

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

剑指 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 False
        self.end_start[end] = start
        return 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 = k
        self.heap = []
        for num in nums:
            self.add(num)

    def add(self, val: int) -> int:
        heapq.heappush(self.heap, val)
        if len(self.heap) > self.size:
            heapq.heappop(self.heap)
        return self.heap[0]

剑指 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) + 1
        minHeap = []
        for ke, fre in numToCount.items(): 
            heapq.heappush(minHeap, (fre, ke)) # 因为要对频率排序,所以频率放在前面
            if len(minHeap) > k:
                    heapq.heappop(minHeap)
        res = []
        while minHeap:
            fre, ke = heapq.heappop(minHeap)
            res.append(ke)
        return res

剑指 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] * 26
        self.isEnd = False

    def searchPrefix(self, prefix):
        node = self
        for ch in prefix:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                return None
            node = node.children[ch]
        return node

    def insert(self, word: str) -> None: # 时间复杂度:O(n),空间复杂度:O(1)
        node = self
        for ch in word:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                node.children[ch] = Trie()
            node = node.children[ch]
        node.isEnd = True

    def search(self, word: str) -> bool: # 时间复杂度:O(n),空间复杂度:O(1)
        node = self.searchPrefix(word)
        return node is not None and node.isEnd

    def startsWith(self, prefix: str) -> bool: # 时间复杂度:O(n),空间复杂度:O(1)
        return self.searchPrefix(prefix) is not None

剑指 Offer II 063. 替换单词

class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.isword = False

class Solution:
    # 时间复杂度:O(n),空间复杂度:O(n)
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        root = self.buildTrie(dictionary)
        words = sentence.split(' ')
        for i in range(len(words)):
            prefix = self.findPrefix(root, words[i])
            if len(prefix) != 0:
                words[i] = prefix
        return " ".join(words)

    def buildTrie(self, diction):
        root = TrieNode()
        for word in diction:
            node = root
            for ch in word:
                if node.children[ord(ch) - ord('a')] == None:
                    node.children[ord(ch) - ord('a')] = TrieNode()
                node = node.children[ord(ch) - ord('a')]
            node.isword = True
        return root
    
    def findPrefix(self, root, word):
        node = root
        builder = ""
        for ch in word:
            if node.isword or node.children[ord(ch)-ord('a')] == None:
                break
            builder += ch
            node = node.children[ord(ch)-ord('a')]
        return builder if node.isword else ""

剑指 Offer II 064. 神奇的字典

class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.isword = False

class MagicDictionary:
    # 时间复杂度:O(n),空间复杂度:O(n)
    def __init__(self):
        self.root = TrieNode()

    def buildDict(self, dictionary: List[str]) -> None:
        for word in dictionary:
            node = self.root
            for ch in word:
                if node.children[ord(ch)-ord('a')] == None:
                    node.children[ord(ch)-ord('a')] = TrieNode()
                node = node.children[ord(ch)-ord('a')]
            node.isword = True

    def search(self, searchWord: str) -> bool:
        root = self.root
        return self.dfs(root, searchWord, 0, 0)
    
    def dfs(self, root, word, i, edit):
        if root is None:
            return False
        if root.isword and i == len(word) and edit == 1:
            return True
        if i < len(word) and edit <= 1:
            found = False
            for j in range(26):
                if not found:
                    next = edit if j == (ord(word[i])-ord('a')) else edit + 1
                    found = self.dfs(root.children[j], word, (i + 1), next)
                else:break
            return found
        return False

第22天 前缀树

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

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

class TrieNode:
    def __init__(self):
        self.children = [None] * 26

class Solution:
    # 时间复杂度;O(n),空间复杂度:O(n) 后缀树,深度优先遍历
    def minimumLengthEncoding(self, words: List[str]) -> int:
        root = self.buildTries(words)
        total = [0]
        self.dfs(root, 1, total)
        return total[0]

    def buildTries(self, words):
        root = TrieNode()
        for word in words:
            node = root
            for i in range(len(word)-1, -1, -1):
                ch = word[i]
                if node.children[ord(ch)-ord('a')] is None:
                    node.children[ord(ch)-ord('a')] = TrieNode()
                node = node.children[ord(ch) - ord('a')]
        return root
    
    def dfs(self, root, length, total):
        isLeaf = True
        for child in root.children:
            if child is not None:
                isLeaf = False
                self.dfs(child, length + 1, total)
        if isLeaf:
            total[0] += length

剑指 Offer II 066. 单词之和

(前缀树+递归)

class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.value = 0

class MapSum:
    # 时间复杂度:O(n),空间复杂度:O(n), 前缀树+递归
    def __init__(self):
        self.root = TrieNode()

    def insert(self, key: str, val: int) -> None:
        node = self.root
        for i in range(len(key)):
            ch = key[i]
            if node.children[ord(ch)-ord('a')] is None:
                node.children[ord(ch)-ord('a')] = TrieNode()
            node = node.children[ord(ch)-ord('a')]
        node.value = val

    def sum(self, prefix: str) -> int:
        node = self.root
        for i in range(len(prefix)):
            ch = prefix[i]
            if node.children[ord(ch)-ord('a')] is None:
                return 0
            node = node.children[ord(ch)-ord('a')]
        return self.getSum(node)

    def getSum(self, node):
        if node is None:
            return 0
        result = node.value
        for child in node.children:
            result += self.getSum(child)
        return result

剑指 Offer II 067. 最大的异或

(前缀树)

class TrieNode:
    def __init__(self):
        self.children = [None] * 2

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

    def buildTrie(self, nums):
        root = TrieNode()  # 构造深度是32,每个结点的子节点都是2的前缀树
        for num in nums:
            node = root
            for i in range(31, -1, -1):
                bit = (num >> i) & 1
                if node.children[bit] is None:
                    node.children[bit] = TrieNode()
                node = node.children[bit]
        return root

第23天 二分查找

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

(二分查找)

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

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

(二分查找)

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

剑指 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) // 2
            i = mid * 2
            if i < len(nums) - 1 and nums[i] != nums[i + 1]:
                if mid == 0 or nums[i - 2] == nums[i - 1]:
                    return nums[i]
                right = mid - 1
            else:
                left = mid + 1
        return nums[len(nums) - 1]

第24天 二分查找

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

class Solution:
    # 时间复杂度:O(logn), 空间复杂度:O(n) 二分查找
    def __init__(self, w: List[int]):
        self.total = sum(w)
        self.sums, isum = [0], 0
        for i in w:
            isum += i
            self.sums.append(isum)   # 前缀和

    def pickIndex(self) -> int:
        arr = random.randint(1, self.total)
        left = 1
        right = len(self.sums) - 1
        while left <= right:
            mid = (left + right) // 2
            if self.sums[mid - 1] < arr <= self.sums[mid]:  # 找到第一个大于arr的数字的下标  
                left = mid       
                break
            elif self.sums[mid - 1] >= arr:  # 左边界都大于等于arr,说明区间太靠右了
                right = mid - 1
            else:
                left = mid + 1  # 否则是因为区间太靠左
        return left - 1

剑指 Offer II 072. 求平方根

(二分查找)

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

剑指 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, maxV
        while left <= right:
            mid = (left + right) // 2
            hours1 = self.getHours(piles, mid)
            hours2 = self.getHours(piles, mid - 1)
            if hours1 <= h and hours2 > h:
                return mid
            elif hours1 <= h and hours2 <= h:
                right = mid - 1
            elif hours1 > h:
                left = mid + 1
        return 1

    def getHours(self, piles, speed):
        hours = 0
        for pile in piles:
            hours += (pile + speed - 1) // speed
        return hours

第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] * 1001
        for num in arr1:  # 得到数组arr1的计数排序
            counts[num] += 1
        i = 0
        for num in arr2:  # 按照arr2的顺序输出数字
            while counts[num] > 0:
                arr1[i] = num
                i += 1
                counts[num] -= 1
        for j in range(len(counts)): # 对数组中剩余的数进行排序
            while counts[j] > 0:
                arr1[i] = j
                i += 1
                counts[j] -= 1
        return arr1

第26天 排序

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

(快速排序)

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

    def partition(self, nums, start, end):
        randomV = random.randint(start, end)
        nums[randomV], nums[end] = nums[end], nums[randomV]
        small = start - 1  # 相当于第一个指针
        for i in range(start, end):
            if nums[i] < nums[end]:
                small += 1
                nums[i], nums[small] = nums[small], nums[i]
        small += 1
        nums[end], nums[small] = nums[small], nums[end]
        return small

剑指 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 head
        head1 = head
        head2 = self.split(head)

        head1 = self.sortList(head1)
        head2 = self.sortList(head2)

        return self.merge(head1, head2)
    
    def split(self, head):  # 快慢指针
        slow = head
        fast = head.next
        while fast != None and fast.next != None:
            slow = slow.next
            fast = fast.next.next
        second = slow.next
        slow.next = None
        return second
    
    def merge(self, head1, head2):
        dummy = ListNode()
        cur = dummy
        while head1 != None and head2 != None:
            if head1.val < head2.val:
                cur.next = head1
                head1 = head1.next
            else:
                cur.next = head2
                head2 = head2.next
            cur = cur.next
        cur.next = head2 if

剑指 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 None
        return self.mergeLists(lists, 0, len(lists)) # 调用函数合并两个链表的

    def mergeLists(self, lists, start, end):  # 分治法,多个链表通过分治法不断递归转换成两个链表
        if start + 1 == end:
            return lists[start]
        mid = (start + end) // 2
        head1 = self.mergeLists(lists, start, mid)
        head2 = self.mergeLists(lists, mid, end)
        return self.merge(head1, head2)
    
    def merge(self, head1, head2):  # 合并两个链表
        dummy = ListNode()
        cur = dummy
        while head1 != None and head2 != None:
            if head1.val < head2.val:
                cur.next = head1
                head1 = head1.next
            else:
                cur.next = head2
                head2 = head2.next
            cur = cur.next
        cur.next = head2 if head1 == None else head1
        return dummy.next

第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 result
        self.helper(nums, 0, [], result)  # 递归
        return result
   
    def helper(self, nums, index, subset, result):
        if index == len(nums):
            subset1 = subset[:]  # 注意这里需要将subset的拷贝添加到result中,可以避免之后修改subset,而修改result
            result.append(subset1)
        elif index < len(nums):
            self.helper(nums, index + 1, subset, result)  # 选择不加入当前元素

            subset.append(nums[index]) # 选择加入上前元素
            self.helper(nums, index + 1, subset, result)
            subset.pop()  # 把刚才加入的剔除

剑指 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 result
    
    def helper(self, n, k, i, combination, result):
        if len(combination) == k:  # 添加到结果中的子集的条件
            com1 = combination[:]
            result.append(com1)
        elif i <= n:
            self.helper(n, k, i + 1, combination, result) # 选择当前数字不添加到子集中

            combination.append(i) # 选择当前数字添加到子集中
            self.helper(n, k, i + 1, combination, result)
            combination.pop()  # 为了不影响被调用函数,需要将数字从当前子集中剔除

剑指 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 result

    def helper(self, nums, target, i, combination, result):
        if target == 0:
            com1 = combination[:]
            result.append(com1)
        elif target > 0 and i < len(nums): # 增加一个target条件是因为如果小于0没必要再递归,用于剪枝
            self.helper(nums, target, i + 1, combination, result) # 选择不添加

            combination.append(nums[i])  # 选择添加
            self.helper(nums, target - nums[i], i, combination, result)
            combination.pop()

第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 result

    def helper(self, nums, target, i, combination, result):
        if target == 0:
            com1 = combination[:]
            result.append(com1)
        elif target > 0 and i < len(nums):
            self.helper(nums, target, self.getNext(nums, i), combination, result) # 不选择当前数,当决定跳过某个值为m的数字时,跳过所有值为m的数字

            combination.append(nums[i]) # 选择当前数
            self.helper(nums, target - nums[i], i + 1, combination, result)
            combination.pop()
    
    def getNext(self, nums, index):  # 辅助跳过与当前相同的数
        next = index
        while next < len(nums) and nums[next] == nums[index]:
            next += 1
        return next

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

(回溯法 + 递归)

class Solution:
    # 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归
    def permute(self, nums: List[int]) -> List[List[int]]:
        result = []
        self.helper(nums, 0, result)
        return result
    
    def helper(self, nums, i, result):
        if i == len(nums):
            permutation = nums[:]
            result.append(permutation)
        else:
            for j in range(i, len(nums)):  # 遍历当前位置和之后的位置
                nums[j], nums[i] = nums[i], nums[j]  # 从当前下标开始交换位置,选定当前位置的元素
                self.helper(nums, i + 1, result)  # 回溯选择下一位置的元素
                nums[j], nums[i] = nums[i], nums[j]  # 清楚对排列状态的修改,再次交换之前交换的两个数字

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

(回溯法 + 递归)

class Solution:
    # 时间复杂度:O(n!), 空间复杂度:O() 回溯法 + 递归
    def permuteUnique(self, nums: List[int]) -> List[List[int]]:
        result = []
        self.helper(nums, 0, result)
        return result

    def helper(self, nums, i, result):
        if i == len(nums):
            permutation = nums[:]
            result.append(permutation)
        else:
            HashSet = set()  # 哈希集合用来保存已经交换到排列下标为i的位置的所有值
            for j in range(i, len(nums)):
                if nums[j] not in HashSet:
                    HashSet.add(nums[j]) # 集合中添加元素是add
                    nums[j], nums[i] = nums[i], nums[j]
                    self.helper(nums, i + 1, result)
                    nums[j], nums[i] = nums[i], nums[j]

第29天 回溯法

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

(回溯法 + 递归)

class Solution:
    # 时间复杂度:O(2^2n),空间复杂度:O() 回溯法 + 递归
    def generateParenthesis(self, n: int) -> List[str]:
        result  = []
        self.helper(n, n, "", result)
        return result

    def helper(self, left, right, parenthesis, result):
        if left == 0 and right == 0:
            result.append(parenthesis)  # 字符串是不可变序列,因此不需要创建新的数据类型来保存
        if left > 0:
            self.helper(left - 1, right, parenthesis + "(", result)  # 添加左括号
        if left < right:
            self.helper(left, right - 1, parenthesis + ")", result)  # 添加右括号

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

(回溯法 + 递归)

class Solution:
    # 回溯法 + 递归
    def partition(self, s: str) -> List[List[str]]:
        result = []
        self.helper(s, 0, [], result)
        return result
    def helper(self, str1, start, substring, result):
        if start == len(str1):
            sub1 = substring[:] # 列表是可变序列
            result.append(sub1)
        for i in range(start, len(str1)):  # for循环依次判断后面的回文子串
            if self.isPalindrome(str1, start, i):  # 是回文子串就添加
                substring.append(str1[start: i + 1]) # 注意字符串切片的中间的冒号
                self.helper(str1, i + 1, substring, result)  # 递归调用
                substring.pop()  # 恢复状态
        
    def isPalindrome(self, str1, start, end):  # 判断改子串是否是回文
        while start < end:
            if str1[start] != str1[end]:
                return False
            start += 1
            end -= 1
        return True

剑指 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 result
    
    def helper(self, s, index, segIndex, seg, ip, result):
        if index == len(s) and segIndex == 3 and self.isValisdSeg(seg):
            result.append(ip + seg) # 字符串是不可变序列
        elif index < len(s) and segIndex <= 3:  # 整个字符串索引和分段索引都符合要求
            ch = s[index]
            if self.isValisdSeg(seg + ch):  # 把当前字符加到当前段
                self.helper(s, index + 1, segIndex, seg + ch, ip, result)
            if len(seg) > 0 and segIndex < 3: # 把当前字符加到下一段,,此时还没有完全加,所以index不加1,只是开辟出一个新的段,等待下一次判断是否是有效的加入
                self.helper(s, index, segIndex + 1, "", ip + seg + ".", result)
            
    def isValisdSeg(self, seg):  # 只有当前段在0-255之间,并且本身是0或者不以0开头的非0值
        return int(seg) <= 255 and ((int(seg) == 0 and len(seg) == 1) or seg[0] != '0')

第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 0
        dp = [0] * len(nums)
        dp[0] = nums[0]
        if len(nums) > 1:
            dp[1] = max(nums[1], nums[0])
        for i in range(2, len(nums)):
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i])
        return dp[len(nums) - 1]

    # # 单序列问题,迭代代码,自下往上。时间复杂度;O(n), 空间复杂度;O(1)
    # def rob(self, nums: List[int]) -> int:
    #     if len(nums) == 0:
    #         return 0
    #     dp = [0] * 2
    #     dp[0] = nums[0]
    #     if len(nums) > 1:
    #         dp[1] = max(nums[1], nums[0])
    #     for i in range(2, len(nums)):
    #         dp[i % 2] = max(dp[(i - 1) % 2], dp[(i - 2) % 2] + nums[i])
    #     return dp[(len(nums) - 1) % 2]

    # # 单序列问题,动态规划,自下往上,两个状态转移方程。时间复杂度;O(n), 空间复杂度;O(1)
    # def rob(self, nums: List[int]) -> int:
    #     n = len(nums)
    #     if n == 0:
    #         return 0
    #     dp = [[0] * 2 for i in range(2)]
    #     dp[0][0] = 0
    #     dp[1][0] = nums[0]
    #     for i in range(1, n):
    #         dp[0][i % 2] = max(dp[0][(i - 1) % 2], dp[1][(i - 1) % 2])
    #         dp[1][i % 2] = nums[i] + dp[0][(i - 1) % 2]
    #     return max(dp[0][(n - 1) % 2], dp[1][(n - 1) % 2])

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

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

class Solution:
    # 单序列问题,动态规划。时间复杂度:O(n), 空间复杂度:O(1)
    def rob(self, nums: List[int]) -> int:
        if len(nums) == 0:
            return 0
        if len(nums) == 1:
            return nums[0]
        result1 = self.helper(nums, 0, len(nums) - 2) # 这个是编号从0到n-2的房屋偷盗最大值
        result2 = self.helper(nums, 1, len(nums) - 1) # 这个是编号从1到n-1的房屋偷盗最大值
        return max(result1, result2)

    def helper(self, nums, start, end):
        dp = [0] * 2
        dp[0] = nums[start]
        if start < end:
            dp[1] = max(nums[start], nums[start + 1])
        for i in range(start + 2, end + 1):
            j = i - start
            dp[j % 2] = max(dp[(j - 1) % 2], dp[(j - 2) % 2] + nums[i])
        return dp[(end - start) % 2]

第31天 动态规划

剑指 Offer II 091. 粉刷房子

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

class Solution:
    # 单序列问题,动态规划,自下向上,时间复杂度:O(n), 空间复杂度:O(1)
    def minCost(self, costs: List[List[int]]) -> int:
        if len(costs) == 0:
            return 0
        dp = [[0] * 2 for i in range(3)]
        for j in range(3):
            dp[j][0] = costs[0][j]
        for i in range(1, len(costs)):
            for j in range(3):
                prev1 = dp[(j + 2) % 3][(i - 1) % 2]
                prev2 = dp[(j + 1) % 3][(i - 1) % 2]
                dp[j][i % 2] = min(prev1, prev2) + costs[i][j]
        last = (len(costs) - 1) % 2
        temp = min(dp[0][last], dp[1][last])
        return min(temp, dp[2][last])

剑指 Offer II 092. 翻转字符

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

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

剑指 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]] = i
        dp = [[0] * len(arr) for i in range(len(arr))]
        result = 2
        for i in range(len(arr)):
            for j in range(i):
                k = map.get(arr[i] - arr[j], -1)
                dp[i][j] = dp[j][k] + 1 if (k >= 0 and k < j) else 2
                result = max(result, dp[i][j])
        return result if result > 2 else 0

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

剑指 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 + 1
                else:
                    cur = max(dp[j], dp[j + 1])
                prev = dp[j + 1]
                dp[j + 1] = cur
        return 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 False
        if len(s1) < len(s2):
            return self.isInterleave(s2, s1, s3)
        dp = [False] * (len(s2) + 1)
        dp[0] = True
        for j in range(len(s2)):
            dp[j + 1] = (s2[j] == s3[j] and dp[j])
        for i in range(len(s1)):
            dp[0] = dp[0] and s1[i] == s3[i]
            for j in range(len(s2)):
                ch1, ch2  = s1[i], s2[j]
                ch3 = s3[i + j + 1]
                dp[j + 1] = (ch1 == ch3 and dp[j + 1]) or (ch2 == ch3 and dp[j])
        return dp[len(s2)]

第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] = 1
        for i in range(len(s)):
            for j in range(min(i, len(t) - 1), -1, -1):  # 从右往左防止覆盖
                if s[i] == t[j]:
                    dp[j + 1] += dp[j]
        return dp[len(t)]

剑指 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] * n
        for 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 = 0
        for num in nums:
            sum += num
        if sum % 2 == 1: # 如果是奇数肯定不满足条件
            return False
        return self.subsetSum(nums, sum // 2)

    def subsetSum(self, nums, target):
        dp = [False] * (target + 1)
        dp[0] = True
        for i in range(1, len(nums) + 1):
            for j in range(target, 0, -1):
                if not dp[j] and j >= nums[i - 1]:
                    dp[j] = dp[j - nums[i - 1]]
        return dp[target]

剑指offer102、加减的目标值

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

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

第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] = 0
        for i in range(1, amount + 1):
            for coin in coins:
                if i >= coin:
                    dp[i] = min(dp[i], dp[i - coin] + 1)
        return -1 if dp[amount] > amount else dp[amount]

剑指offer104、排列的数目

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

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

第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 = 0
        for i in range(rows):
            for j in range(cols):
                if grid[i][j] == 1 and not visited[i][j]:
                    area = self.getArea(grid, visited, i, j)
                    maxArea = max(maxArea, area)
        return maxArea
    # 深度优先搜索的时间复杂度:O(v + e) v节点数目, e是边的数目
    def getArea(self, grid, visited, i, j):
        area = 1
        visited[i][j] = True
        dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
        for dir in dirs:
            r = i + dir[0]
            c = j + dir[1]
            if r >= 0 and r < len(grid) and c >= 0 and c < len(grid[0]) and not visited[r][c] and grid[r][c] == 1:
                area += self.getArea(grid, visited, r, c)
        return area

剑指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] * size
        for i in range(size):
            if colors[i] == -1:
                if not self.setColor(graph, colors, i, 0):
                    return False
        return True

    def setColor(self, graph, colors, i, color):
        if colors[i] >= 0:
            return colors[i] == color
        colors[i] = color
        for neighbor in graph[i]:
            if not self.setColor(graph, colors, neighbor, 1 - color):
                return False
        return True

剑指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] = 0
                else:
                    dists[i][j] = float('inf')
        dirs = [(-1, 0), (1, 0), (0, -1), (0,1)]
        while len(queue) != 0:
            pos = queue.pop(0)
            dist = dists[pos[0]][pos[1]]
            for dir in dirs:
                r = pos[0] + dir[0]
                c = pos[1] + dir[1]
                if r >= 0 and c >= 0 and r < rows and c < cols:
                    if dists[r][c] > dist + 1:  # 如果为True,表示该格子仍然是最大的整数值,之前没有到达过, 如果是False, 表示之前到达过该节点,并且第一次到达时一定记录的是最短距离,那肯定不可能大于dist+1,这样可以避免重复访问某个格子
                        dists[r][c] = dist + 1
                        queue.append((r, c))
        return dists

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

    def getNeighbors(self, word):
        neighbors = []
        chars = list(word)
        for i in range(len(word)):  
            old = word[i]
            for j in range(ord('a'), ord('z') + 1):
                if old != chr(j):
                    chars[i] = chr(j)
                    neighbors.append("".join(chars))
            chars[i] = old # 最后在word中把当前字母改变回来
        return neighbors

剑指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 -1
        queue1, queue2 = [], []
        steps = 0
        queue1.append(init)
        visited.add(init)
        while len(queue1) != 0:
            cur = queue1.pop(0)
            if cur == target:
                return steps
            nexts = self.getNeighbors(cur)
            for next in nexts:
                if next not in dead and next not in visited: # 如果在visited中, 说明走这条路线会出现死锁状态要避免。
                    visited.add(next)
                    queue2.append(next)
            if len(queue1) == 0:
                steps += 1
                queue1 = queue2
                queue2 = []
        return -1
    
    def getNeighbors(self,cur):
        nexts = []
        for i in range(len(cur)):
            ch = cur[i]

            newCh = '9' if ch == '0' else str(int(ch) - 1)
            builder = list(cur)
            builder[i] = newCh
            nexts.append("".join(builder))

            newCh = '0' if ch == '9' else str(int(ch) + 1)
            builder = list(cur)
            builder[i] = newCh
            nexts.append("".join(builder))
        return nexts

剑指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 result
    
    def dfs(self, source, graph, path, result):
        path.append(source)
        if source == len(graph) - 1:
            path1 = path[:]  # 需要更改到另一个数组中, 否则后续会改变
            result.append(path1)
        else:
            for next in graph[source]:
                self.dfs(next, graph, path, result)
        path.pop()

第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] = -1
            else:
                visited = set()
                results[i] = self.dfs(graph, fro, too, visited)
        return results

    def buildGraph(self, equations, values):
        graph = dict()  # 哈希表作为邻接表
        for i in range(len(equations)):
            var1 = equations[i][0]  
            var2 = equations[i][1]
            if var1 not in graph.keys():
                graph[var1] = {var2: values[i]} # 键是图中一条有向边的起始节点,值是与该节点相连的其他节点(也用哈希表表示)\
            else:
                graph[var1][var2] = values[i]
            if var2 not in graph.keys():
                graph[var2] = {var1: 1.0/values[i]}  # 反转上述的起始节点和终止节点,有向嘛
            else:
                graph[var2][var1] = 1.0/values[i]
        return graph
    
    def dfs(self, graph, fro, too, visited):
        if fro == too:
            return 1.0
        visited.add(fro)
        for k in graph[fro].keys():
            v = graph[fro][k]
            if k not in visited:
                nextVal = self.dfs(graph, k, too, visited)
                if nextVal > 0:
                    return v * nextVal
        visited.remove(fro)
        return -1.0

剑指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 0
        lengths = [[0] * n for _ in range(m)]
        longest = 0
        for i in range(m):
            for j in range(n):
                length = self.dfs(matrix, lengths, i, j)
                longest = max(longest, length)
        return longest

    def dfs(self, matrix, lengths, i, j):
        if lengths[i][j] != 0:  # 如果值不为0,说明之前已经计算过以坐标(i, j)为起点的最长递增路径的长度,矩阵lengths起到缓存的作用,确保以任意坐标为起点的最长递增路径的长度只需计算一次。
            return lengths[i][j]
        rows, cols = len(matrix), len(matrix[0])
        dirs = [(-1, 0), (0, -1), (1, 0), (0, 1)]
        length = 1
        for dir in dirs:
            r = i + dir[0]
            c = j + dir[1]
            if r >= 0 and r < rows and c >= 0 and c < cols and matrix[r][c] > matrix[i][j]:
                path = self.dfs(matrix, lengths, r, c)
                length = max(path + 1, length)
        lengths[i][j] = length
        return length

剑指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  # 该门课的入度加1
        queue = [] # 用队列的迭代方法实现
        for i in range(numCourses):  # 找到入度为0的课程
            if inDegrees[i] == 0:
                queue.append(i)
        order = []  
        while len(queue) != 0:
            course = queue.pop(0)
            order.append(course)
            for next in graph[course]:
                inDegrees[next] -= 1
                if inDegrees[next] == 0:
                    queue.append(next)
        return order if len(order) == numCourses else []

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

剑指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 False
                graph[num] = set()  # 初始化字典中每个键的值为集合,之后存储排序在该数字之后的num
                inDegrees[num] = 0  # 初始化字典中每个键的入度
        for seq in seqs:  # 构建有向图
            for i in range(len(seq) - 1): # 初始化字典的值
                num1 = seq[i]
                num2 = seq[i + 1]
                if num2 not in graph[num1]:
                    graph[num1].add(num2)
                    inDegrees[num2] += 1
        queue = []
        for num in inDegrees.keys():  # 找到入度为0的节点
            if inDegrees[num] == 0:
                queue.append(num)
        built = []
        while len(queue) == 1:
            num = queue.pop(0)
            built.append(num)
            for next in graph[num]:
                inDegrees[next] -= 1
                if inDegrees[next] == 0:
                    queue.append(next)
        return built == org

剑指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] = i
        count = len(isConnected)
        for i in range(len(isConnected)):
            for j in range(i+1, len(isConnected)):
                if isConnected[i][j] == 1 and self.union(fathers, i, j): # 前者条件满足时,才会有后者合并(并且只有union判断i和j属于不同的祖先,返回True,count才减少1,当判断i和j属于同一祖先时,本身就是一个子集,所以返回False,不减少1)
                    count -= 1
        return count

    def findFather(self, fathers, i):  # 根节点的查找, 相当于压缩路径
        if fathers[i] != i:
            fathers[i] = self.findFather(fathers, fathers[i])
        return fathers[i]
 
    def union(self, fathers, i, j):  # 两棵树的合并
        fathersOfI = self.findFather(fathers, i)
        fathersOfJ = self.findFather(fathers, j)
        if fathersOfI != fathersOfJ:  # 每当两个子集合并成一个子集, 集体数目应该减少1, 返回Ture
            fathers[fathersOfI] = fathersOfJ
            return True
        return False

第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] = i
        groups = len(strs)
        for i in range(len(strs)):
            for j in range(i+1, len(strs)):
                if self.similar(strs[i], strs[j]) and self.union(fathers, i, j):
                    groups -= 1
        return groups
    
    def similar(self, str1, str2):
        diffCount = 0
        for i in range(len(str1)):
            if str1[i] != str2[i]:
                diffCount += 1
        return diffCount <= 2
    
    def findFather(self, fathers, i):
        if fathers[i] != i:
            fathers[i] = self.findFather(fathers, fathers[i])
        return fathers[i]

    def union(self, fathers, i, j):
        fatherOfI = self.findFather(fathers, i)
        fatherOfJ = self.findFather(fathers, j)     
        if fatherOfI != fatherOfJ:
            fathers[fatherOfI] = fatherOfJ
            return True
        return False

剑指offer118、多余的边

(并查集)

class Solution:
    # 时间复杂度:O(n), 空间复杂度:O(n), 并查集,查找和合并的时间接近O(1)
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        maxVertx = 0
        for edge in edges:  # 找到最大的节点,就是一共有多少个节点
            maxVertx = max(maxVertx, edge[0])
            maxVertx = max(maxVertx, edge[1])
        fathers = [0] * (maxVertx + 1) # n个节点,n条边,肯定有一条边是多余的
        for i in range(1, maxVertx + 1):
            fathers[i] = i
        for edge in edges:
            if not self.union(fathers, edge[0], edge[1]):  # union返回True,表示两个节点的祖先节点不是一个,没有环,如果返回False,表示有环
                return edge
        return [0] * 2

    def findFather(self, fathers, i):
        if fathers[i] != i:
            fathers[i] = self.findFather(fathers, fathers[i])
        return fathers[i]
    
    def union(self, fathers, i, j):
        fatherOfI = self.findFather(fathers, i)
        fatherOfJ = self.findFather(fathers, j)
        if fatherOfI != fatherOfJ:  # 祖先节点不一样,没有环,连接两条边,并且更新前者的祖先节点跟后者的祖先一样
            fathers[fatherOfI] = fatherOfJ
            return True
        return False

剑指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] = num
            counts[num] = 1
        for num in nums:
            if num + 1 in all:
                self.union(fathers, counts, num, num + 1)
            if num - 1 in all:
                self.union(fathers, counts, num, num - 1)
        longest = 0
        for length in counts.values():
            longest = max(longest, length)
        return longest
   
    def findFather(self, fathers, i):  # 查找祖先节点,并且压缩路径,使得查找操作的时间复杂度近似是O(1)
        if fathers[i] != i:
            fathers[i] = self.findFather(fathers, fathers[i])
        return fathers[i]
    
    def union(self, fathers, counts, i, j):  # 合并祖先,并且更新counts数目
        fatherOfI = self.findFather(fathers, i)
        fatherOfJ = self.findFather(fathers, j)
        if fatherOfI != fatherOfJ:
            fathers[fatherOfI] = fatherOfJ
            countOfI = counts[fatherOfI]
            countOfJ = counts[fatherOfJ]
            counts[fatherOfJ] = countOfI + countOfJ

你可能感兴趣的:(力扣刷题,leetcode,算法,动态规划,数据结构,python)