LeetCode刷题 _「剑指 Offer]第2版_按顺序

参考链接:
剑指Offer(上)01-35题实现 python版本
剑指Offer(下)36-75题实现 Python版本

剑指 Offer 03. 数组中重复的数字

数组、哈希表、排序

class Solution:
    def findRepeatNumber(self, nums: List[int]) -> int:
        res = collections.Counter(nums)
        for key in res:
            if res[key] > 1:
                return key

剑指 Offer 04. 二维数组中的查找

数组、二分查找、分治、矩阵

class Solution:
    # 二分查找,使得每一次都可以分出两个相反的选择。
    def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
        if not matrix:  # 考虑边界情况,特殊情况
            return False
        n, m = len(matrix), len(matrix[0]) # 从右上角开始,大的往下找,小的往左找
        i, j = 0, m - 1
        while 0 <= i < n and 0 <= j < m:
            if matrix[i][j] == target:
                return True
            elif matrix[i][j] < target:
                i += 1
            else:
                j -= 1
        return False

剑指 Offer 05. 替换空格

字符串

class Solution:
    def replaceSpace(self, s: str) -> str:
        return s.replace(' ', "%20")

剑指 Offer 06. 从尾到头打印链表

栈、递归、链表、双指针

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

class Solution:
    def reversePrint(self, head: ListNode) -> List[int]:
        res = []
        while head:
            res.append(head.val)
            head = head.next
        return res[::-1]

剑指 Offer 07. 重建二叉树

树、数组、哈希表、分治

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

class Solution:
    # 分治思想,从前序遍历中找到根节点,从中序遍历中定位根节点计算出子树的长度
    def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
        # 前序遍历:根左右, 中序遍历:左右根
        self.preRootDict = {} 
        self.pre = preorder 
        for i in range(len(inorder)):
            self.preRootDict[inorder[i]] = i # 记录中序结点的位置
        return self.getRoot(0, 0, len(inorder) - 1) # 根节点,中序遍历范围

    def getRoot(self, preRoot, inorderLeft, inorderRight):
        if inorderLeft > inorderRight: # 中序遍历为空
            return
        root = TreeNode(self.pre[preRoot]) # 建立根结点
        i = self.preRootDict[self.pre[preRoot]] # 找到前序遍历中根结点在中序中的位置
        
        root.left = self.getRoot(preRoot + 1, inorderLeft, i - 1)    
        # preRoot当前的根 左子树的长度 = 左子树的右边-左边 (i-1-inorderLeft+1) 。最后+1就是右子树的根了  
        root.right = self.getRoot(preRoot + i - inorderLeft + 1, i + 1, inorderRight) 

        return root

剑指 Offer 09. 用两个栈实现队列

栈、设计、队列

class CQueue:

    def __init__(self):
        self.stack1 = []
        self.stack2 = []

    def appendTail(self, value: int) -> None:
        self.stack1.append(value)

    def deleteHead(self) -> int:
        if self.stack2:
            return self.stack2.pop()
        else:
            if self.stack1:
                while self.stack1:
                    self.stack2.append(self.stack1.pop())
                return self.stack2.pop()
            return -1

剑指 Offer 10- I. 斐波那契数列

记忆化搜索、数学、动态规划

class Solution:
    # # 效率不太高的递归
    # def fib(self, n: int) -> int:
    #     if n <= 0:
    #         return 0
    #     if n == 1:
    #         return 1
    #     return self.fib(n - 1) + self.fib(n - 2)

    # # 效率提升的动态规划
    # def fib(self, n: int) -> int:
    #     if n <= 0:
    #         return 0
    #     if n == 1:
    #         return 1
    #     fibMinusOne = 0
    #     fibMinusTwo = 1
    #     fibN = 0
    #     for i in range(2, n + 1):
    #         fibN = fibMinusOne + fibMinusTwo
    #         fibMinusOne = fibMinusTwo
    #         fibMinusTwo = fibN
    #     return fibN % 1000000007

    # 效率提升的动态规划
    def fib(self, n: int) -> int:
        if n <= 0:
            return 0
        if n == 1:
            return 1
        arr = [0, 1]
        fibN = 0
        for i in range(2, n + 1):
            arr[i % 2] = arr[0] + arr[1]
        return arr[i % 2] % 1000000007

剑指 Offer 10- II. 青蛙跳台阶问题

记忆化搜索、数学、动态规划

class Solution:
    # 动态规划
    def numWays(self, n: int) -> int:
        if n == 1 or n == 0:
            return 1
        arr = [1, 1]
        for i in range(2, n + 1):
            arr[i % 2] = arr[0] + arr[1]
        return arr[i % 2] % 1000000007

剑指 Offer 11. 旋转数组的最小数字

数组、二分查找

class Solution:
    # 部分有序,二分查找。找出右排序数组中的第一个数字,也就是旋转点
    # 时间复杂度:O(log2N), 空间复杂度:O(1)
    def minArray(self, numbers: List[int]) -> int:
        i, j = 0 , len(numbers) - 1
        while i < j:  # 已知j一定在右排序数组中
            m = (i + j) // 2  # 当前的中心点
            if numbers[m] > numbers[j]: i = m + 1  # 若右侧的点小于中心位置的点,m一定在左排序区间内,则说明旋转点一定在中心位置的右侧
            elif numbers[m] < numbers[j]: j = m  # 若右侧的点大于中心位置的点,m一定在右排序区间内,则说明旋转点一定在中心位置的左侧
            else: j = j - 1   # 若相等,则需要进一步判断, 可参考某大佬评论
            '''
            若旋转点 x < j, 则上述肯定往左区间找
           若旋转点 x = j, 则容易错过旋转点,但可以证明,nums[i]=nums[x]=nums[中心点]=...=nums[j],则最终当i=j时返回的num[i]值等于旋转点的值
            还有不用nums[m] 和 nums[i]做比较,因为无法判断 m 在哪个排序数组中
            本质上,是因为 j 初始值肯定在右排序数组中; i 初始值无法确定在哪个排序数组中。
            对于以下两示例,当 i = 0, j = 4, m = 2 时,有 nums[m] > nums[i] ,而结果不同。
            [1, 2, 3, 4, 5] 旋转点 x = 0: m 在右排序数组(此示例只有右排序数组);
            [3, 4, 5, 1, 2] 旋转点 x = 3: m 在左排序数组。
            '''
        return numbers[i]

剑指 Offer 12. 矩阵中的路径

数组、回溯、矩阵

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def dfs(r, c, k):
            if not 0 <= r < len(board) or not 0 <= c < len(board[0]) or board[r][c] != word[k]:
                return False
            if k == len(word) - 1:
                return True
            temp, board[r][c] = board[r][c], "/"  # 用一个临时变量用于存储值,用于回溯
            res = dfs(r - 1, c, k + 1) or dfs(r + 1, c, k + 1) or dfs(r, c - 1, k + 1) or dfs(r, c + 1, k + 1)
            board[r][c] = temp 
            return res

        for row in range(len(board)):
            for col in range(len(board[0])):
                if dfs(row, col, 0):
                    return True
        return False

剑指 Offer 13. 机器人的运动范围

深度优先搜索、广度优先搜索

class Solution:
    # # 广度优先遍历, BFS
    # def movingCount(self, m: int, n: int, k: int) -> int:
    #     road = set()  # 用一个集合存储所有的路径, 防止后面重复添加
    #     queue = []
    #     queue.append((0, 0))
    #     while queue:
    #         i, j = queue.pop()
    #         if (i, j) not in road and self.getSum(i, j) <= k:
    #             road.add((i, j))
    #             for di, dj in [[1, 0], [0, 1]]:  # 仅考虑往右下方走就可以
    #                 if 0 <= (i + di) < m and 0 <= (j + dj) < n:
    #                     queue.append((i + di, j + dj))
    #     return len(road)
    
    # def getSum(self, row, col): #计算当前方格的各个位上的和 
    #     temp = 0
    #     while row > 0:
    #         temp += row % 10
    #         row = row // 10
    #     while col:
    #         temp += col % 10
    #         col = col // 10
    #     return temp
# 深度优先遍历, DFS 
class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def dfs(i, j):
            if i >= m or j >= n or (i, j) in road or self.getSum(i, j) > k:
                return
            road.add((i, j)) # 加入路径中
            dfs(i + 1, j) # 递归调用
            dfs(i, j + 1) # 递归调用
        road = set()
        dfs(0, 0)
        return len(road)

    def getSum(self, row, col): #计算当前方格的各个位上的和 
        temp = 0
        while row > 0:
            temp += row % 10
            row = row // 10
        while col:
            temp += col % 10
            col = col // 10
        return temp

剑指 Offer 14- I. 剪绳子

数学、动态规划

class Solution:
    # 动态规划
    def cuttingRope(self, n: int) -> int:
        dp = [0 for _ in range(n + 1)]
        dp[2] = 1 # 初始化
        res = -1
        for i in range(3, n + 1):
            for j in range(1, i):
                dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j])) # 三种情况, 1.不剪;2.从j处剪下来,剩下的i-j不再剪了;3.从j处剪下来,剩下的i-j继续剪
        return dp[n]

剑指 Offer 14- II. 剪绳子 II

数学、动态规划

class Solution:
    # 应尽可能的拆成多个3,当 n 小于等于 4 时,要特殊考虑
    def cuttingRope(self, n: int) -> int:
        if n < 4: return n - 1
        a, b, p = n // 3, n % 3, 1000000007     # 把绳子尽可能切为多个长度为 3 的片段,留下的最后一段绳子的长度可能为 0,1,2 三种情况。
        if b == 0: return 3 ** a % p            # 0. 若最后一段绳子长度为 0
        if b == 1: return 3 ** (a - 1) * 4 % p  # 1. 若最后一段绳子长度为 2 ;则保留,不再拆为 1+1
        return 3 ** a * 2 % p                   # 2. 若最后一段绳子长度为 1 ;则应把一份 3 + 1 替换为 2 + 2

剑指 Offer 15. 二进制中1的个数

位运算

class Solution:
    # n & (n - 1)的运算会把最后一个1消去
    def hammingWeight(self, n: int) -> int:
        res = 0
        while n != 0:
            res += 1
            n = n & (n - 1)
        return res 

剑指 Offer 16. 数值的整数次方

递归、数学

class Solution:
    # 分治法
    def myPow(self, x: float, n: int) -> float:
        if n < 0: # 当 n < 0时,把问题转化至 n >= 0的范围内
            x = 1 / x
            n = -n
        res = 1 # 初始化res = 1
        while n:
            if n & 1:
                res *= x # 奇数位先乘以多出来的1
            x *= x   # 执行 x = x ^ 2
            n >>= 1  # 执行n右移一位,缩小1半
        return res

剑指 Offer 17. 打印从1到最大的n位数

数组、数学

class Solution:
    # # 1.python自带的函数
    # def printNumbers(self, n: int) -> List[int]:
    #     return list(range(1, 10**n))

    # # 2.
    # def printNumbers(self, n: int) -> List[int]:
    #     res = []
    #     for i in range(1, 10 ** n):
    #         res.append(i)
    #     return res

    # 3.为正确表示大数
    def printNumbers(self, n: int) -> List[int]:
        def dfs(x):
            if x == n:  # 终止条件:已固定完所有位
                s = ''.join(num[self.start:]) # 拼接 num
                if s != '0': res.append(int(s))  # 不为 0 ,则添加至 res 尾部
                if n - self.start == self.nine: self.start -= 1  # 如果此时除了 0 就是 9, 则下一次递归 0 的个数减少 1
                return
            for i in range(10):  # 遍历 0 - 9
                if i == 9: self.nine += 1  # 9 的个数加 1
                num[x] = str(i)  # 固定第 x 位为 i
                dfs(x + 1)  # 开启固定第 x + 1 位
            self.nine -= 1 
        num, res = ['0'] * n, []
        self.nine = 0  # 表示 9 的个数
        self.start = n - 1  # 表示开始的 0 的个数
        dfs(0)
        return res

剑指 Offer 18. 删除链表的节点

链表

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def deleteNode(self, head: ListNode, val: int) -> ListNode:
        if head.val == val:  # 判断若头节点就是所找的数值
            head = head.next
        front = head
        p = head.next
        while p:
            if p.val == val:
                front.next = p.next
            front = front.next
            p = p.next
        return head

剑指 Offer 19. 正则表达式匹配

递归、字符串、动态规划

class Solution:
    # # 动态规划, 时间复杂度:O(mn), 空间复杂度:O(mn)
    # def isMatch(self, s: str, p: str) -> bool:
    #     n, m = len(s), len(p)
    #     res = [[False] * (m + 1) for _ in range(n + 1)]  # 状态转移的初始化
    #     for i in range(n + 1):
    #         for j in range(m + 1):
    #             if j == 0:  # 第一种情况:空正则
    #                 res[i][j] = (i == 0)
    #             else:  # 第二种情况:非空正则
    #                 if p[j - 1] != '*':  # 非空正则情况之一: 非*
    #                     if i > 0 and (s[i - 1] == p[j - 1] or p[j - 1] == '.'):  
    #                         res[i][j] = res[i - 1][j - 1]
    #                 else: # 非空正则情况之一: *
    #                     if j >= 2: # 不看a*的组合
    #                         res[i][j] |= res[i][j - 2] 
    #                     if i >= 1 and j >= 2 and (s[i - 1] == p[j - 2] or p[j - 2] == '.'):  # 看a*的组合
    #                         res[i][j] |= res[i - 1][j] # 正则串不动, 主串前移一个
    #     return res[n][m]

    # 递归, 时间复杂度:O(mn), 空间复杂度:O(mn)
    def isMatch(self, s: str, p: str) -> bool:
        # 如果字符串长度为0,需要检测下正则串
        if len(s) == 0:
            if len(p) % 2 != 0:
                return False  # 如果正则串长度为奇数,必定不匹配,比如 "."、"ab*",必须是 a*b*这种形式,*在奇数位上
            i = 1
            while i < len(p):
                if p[i] != '*': return False
                i += 2
            return True
        # 如果字符串长度不为0,但是正则串没了,return false
        if len(p) == 0: return False
        c1, c2, c3 = s[0], p[0], 'a'  # c1 和 c2 分别是两个串的当前位,c3是正则串当前位的后一位,如果存在的话,就更新一下
        if len(p) > 1:
            c3 = p[1]
        # 和dp一样,后一位分为是 '*' 和不是 '*' 两种情况
        if c3 != '*':  # 不是'*'
            if c1 == c2 or c2 == '.':
                return self.isMatch(s[1:], p[1:])
            else: # 否则不匹配
                return False 
        else:  # 是'*'
            if c1 == c2 or c2 == '.':  # 如果该位字符一样,或是正则串该位是 '.',和dp一样,有看和不看两种情况
                return self.isMatch(s[1:], p) or self.isMatch(s, p[2:])
            else:
                return self.isMatch(s, p[2:])  # 不一样,那么正则串这两位就废了,直接往后走

剑指 Offer 20. 表示数值的字符串

字符串

class Solution:
    # 状态转移,有限状态自动机
    def isNumber(self, s: str) -> bool:
        states = [
            {' ': 0, 's': 1, 'd': 2, '.': 4},    # 0. start with blank
            {'d': 2, '.': 4},                    # 1.sign before e
            {'d': 2, '.': 3, 'e': 5, ' ': 8},    # 2.digit before dot
            {'d': 3, 'e': 5, ' ': 8},            # 3.digit after dot
            {'d': 3},                            # 4.digit after dot (is blank before dot)
            {'s': 6, 'd': 7},                    # 5.e
            {'d': 7},                            # 6.sign after e
            {'d': 7, ' ': 8},                    # 7.digit after e
            {' ': 8},                            # 8.end with blank
        ]
        p = 0  # 以状态0开始
        for c in s:
            if '0' <= c <= '9': t = 'd'  # digit
            elif c in "+-": t = 's'      # sign
            elif c in "eE": t = 'e'      # e or E
            elif c in ". ": t = c        # dot or blank
            else: t = "?"                # unkown
            if t not in states[p]: return False
            p = states[p][t]
        return p in (2,3,7,8)

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

数组、双指针、排序

class Solution:
    def exchange(self, nums: List[int]) -> List[int]:
        pBegin, pEnd = 0, len(nums) - 1
        while pBegin < pEnd:
            # 当前pBegin指向奇数,向后移动pBegin
            while pBegin < pEnd and (nums[pBegin] & 0x1) != 0:
                pBegin += 1
            # 当前pBegin指向偶数,向前移动pEnd
            while pBegin < pEnd and (nums[pEnd] & 0x1) == 0:
                pEnd -= 1
            nums[pBegin], nums[pEnd] = nums[pEnd], nums[pBegin]
        return num

剑指 Offer 22. 链表中倒数第k个节点

链表、双指针

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def getKthFromEnd(self, head: ListNode, k: int) -> ListNode:
        if not head:
            return None
        front = second = head
        for i in range(k): # 前面的指针先走k步
            if not front:  # 链表长度小于k,则返回None
                return None
            else:
                front = front.next
        while front:  # 链表未到达末尾,所以前后指针分别前进
            front = front.next
            second = second.next
        return second

剑指 Offer 24. 反转链表

递归、链表

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

class Solution:
    # 用两个指针辅助,原链表逆转
    # 时间复杂度:O(n), 空间复杂度:O(1)
    def reverseList(self, head: ListNode) -> ListNode:
        if not head: # 边缘情况
            return None
        pre = None
        cur = head
        while cur is not None:
            next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        return pre

剑指 Offer 25. 合并两个排序的链表

递归、链表

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

class Solution:
    # 迭代实现
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        new_node  = cur = ListNode(0)
        while l1 and l2:
            if l1.val < l2.val:
                cur.next = l1
                l1 = l1.next
            else:
                cur.next = l2
                l2 = l2.next
            cur = cur.next
        cur.next = l1 if l1 else l2
        return new_node.next

    # # 递归实现
    # def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
    #     if not l1:
    #         return l2
    #     elif not l2:
    #         return l1
    #     pMergedHead = ListNode(0)
    #     if l1.val < l2.val:
    #         pMergedHead.next = l1
    #         pMergedHead.next.next = self.mergeTwoLists(l1.next, l2)
    #     else:
    #         pMergedHead.next = l2
    #         pMergedHead.next.next = self.mergeTwoLists(l1, l2.next) 
    #     return pMergedHead.next

剑指 Offer 26. 树的子结构

树、深度优先搜索、二叉树

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

class Solution:
    # 深度优先遍历,递归
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        if A == None or B == None:
            return False
        return self.dfs(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B)
    
    def dfs(self, A, B):
        if B == None:
            return True
        if A == None:
            return False
        return A.val == B.val and self.dfs(A.left, B.left) and self.dfs(A.right, B.right)

剑指 Offer 27. 二叉树的镜像

树、深度优先搜索、广度优先搜索

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

class Solution:
    # # 递归实现
    # def mirrorTree(self, root: TreeNode) -> TreeNode:
    #     if root == None:
    #         return None
    #     if root.left != None or root.right != None: # 交换左右子树
    #         root.left, root.right = root.right, root.left
    #         self.mirrorTree(root.left)
    #         self.mirrorTree(root.right)
    #     return root

    # 迭代实现, 用栈实现
    def mirrorTree(self, root: TreeNode) -> TreeNode:
        stack = []
        if not root:
            return 
        stack.append(root)
        while stack:  # 将结点出栈,然后将该结点的左右子树入栈,交换左右子树。直到栈为空时结束。
            cur_node = stack.pop()
            if cur_node.left:
                stack.append(cur_node.left)
            if cur_node.right:
                stack.append(cur_node.right)
            cur_node.left, cur_node.right = cur_node.right, cur_node.left
        return root

剑指 Offer 28. 对称的二叉树

树、深度优先搜索、广度优先搜索

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

class Solution:
    # # 递归
    # def isSymmetric(self, root: TreeNode) -> bool:
    #     return self.help(root.left, root.right) if root else True
    
    # def help(self, left, right):
    #     if not left and not right:  # 如果左右两边都没有结点了,跳出条件之一
    #         return True
    #     if not left or not right or left.val != right.val:  # #左没有结点或者右没有结点或者两个值不相等,跳出条件之二
    #         return False
    #     return self.help(left.left, right.right) and self.help(left.right, right.left)

    # 迭代,利用树的层次遍历, 双端队列
    def isSymmetric(self, root: TreeNode) -> bool:
        if not root:
            return True
        queue = collections.deque()
        queue.appendleft(root.left)
        queue.append(root.right)
        while queue:
            left_node = queue.popleft()
            right_node = queue.pop()
            if not left_node and not right_node:
                continue
            if not left_node or not right_node or left_node.val != right_node.val:
                return False
            queue.appendleft(left_node.right)
            queue.appendleft(left_node.left)
            queue.append(right_node.left)
            queue.append(right_node.right)
        return True

剑指 Offer 29. 顺时针打印矩阵

数组、矩阵、模拟

class Solution:
    # 矩阵的遍历
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        if not matrix:
            return matrix
        top, bottom, left, right = 0, len(matrix) - 1, 0, len(matrix[0]) - 1
        res = []
        while True:
            for i in range(left, right + 1): # 从左到右遍历
                res.append(matrix[top][i])
            top += 1 # 上边界向下收敛
            if top > bottom: # 是否打印完毕
                break
            for j in range(top, bottom + 1): # 从上到下遍历
                res.append(matrix[j][right])
            right -= 1 # 右边界收敛
            if right < left: # 是否打印完毕
                break
            for m in range(right, left - 1, -1): # 从右到左遍历
                res.append(matrix[bottom][m])
            bottom -= 1 # 下边界收敛
            if top > bottom:  # 是否打印完毕
                break
            for n in range(bottom, top - 1, -1): # 从下到上遍历
                res.append(matrix[n][left])
            left += 1  # 左边界收敛
            if left > right:   # 是否打印完毕
                break
        return res

剑指 Offer 30. 包含min函数的栈

栈、设计

class MinStack:

    def __init__(self):
        """
        initialize your data structure here.
        """
        self.stackA = []
        self.stackB = []

    def push(self, x: int) -> None:
        self.stackA.append(x)
        if not self.stackB or self.stackA[-1] <= self.stackB[-1]:
            self.stackB.append(x)

    def pop(self) -> None:
        if self.stackA.pop() == self.stackB[-1]:
            self.stackB.pop()

    def top(self) -> int:
        return self.stackA[-1]

    def min(self) -> int:
        return self.stackB[-1]

剑指 Offer 31. 栈的压入、弹出序列

栈、数组、模拟

class Solution:
    # 模拟栈弹出,压入
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        stack = [] # 辅助栈
        j = 0
        for num in pushed:
            stack.append(num) # 按照压入栈的顺序压元素进入辅助栈
            while stack and stack[-1] == popped[j]: # 辅助栈不为空,并且栈顶元素等于弹出栈中元素,则弹出
                stack.pop()
                j += 1 # 弹出栈中元素后移
        if not stack: # 最终如果辅助栈为空,说明可以按照新相应的压入顺序得到弹出栈顺序
            return True
        return False # 否则不可以

剑指 Offer 32 - I. 从上到下打印二叉树

树、广度优先搜索、二叉树

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

class Solution:
    # # 二叉树的层次遍历,用队列
    # def levelOrder(self, root: TreeNode) -> List[int]:
    #     if root is None:
    #         return []
    #     queue1, queue2, res = [], [], []
    #     queue1.append(root)
    #     while queue1:
    #         node = queue1.pop(0)
    #         res.append(node.val)
    #         if node.left:
    #             queue2.append(node.left)
    #         if node.right:
    #             queue2.append(node.right)
    #         if len(queue1) == 0:
    #             queue1 = queue2
    #             queue2 = []
    #     return res

     # 二叉树的层次遍历,用队列
    def levelOrder(self, root: TreeNode) -> List[int]:
        if root is None:
            return []
        queue, res = [], []
        queue.append(root)
        while queue:
            node = queue.pop(0)
            res.append(node.val)
            if node.left:
                queue.append(node.left)
            if node.right:
                queue.append(node.right)
        return res

剑指 Offer 32 - II. 从上到下打印二叉树

树、广度优先搜索、二叉树

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

class Solution:
    # 层次遍历,队列
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if root is None:
            return []
        queue1, queue2, res, temp = [], [], [], []
        queue1.append(root)
        while queue1:
            node = queue1.pop(0)
            temp.append(node.val)
            if node.left:
                queue2.append(node.left)
            if node.right:
                queue2.append(node.right)
            if len(queue1) == 0:
                res.append(temp)
                temp = []
                queue1 = queue2
                queue2 = []
        return res

剑指 Offer 32 - III. 从上到下打印二叉树

树、广度优先搜索、二叉树

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

class Solution:
    def levelOrder(self, root: TreeNode) -> List[List[int]]:
        if root is None:
            return []
        queue1, queue2, res, temp, count = [], [], [], [], 0
        queue1.append(root)
        while queue1:
            node = queue1.pop(0)
            temp.append(node.val)
            if node.left:
                queue2.append(node.left)
            if node.right:
                queue2.append(node.right)
            if len(queue1) == 0:
                if count % 2 == 0:
                    res.append(temp)
                    temp = []
                else:
                    res.append(temp[::-1])
                    temp = []
                count += 1
                queue1 = queue2
                queue2 = []
        return res

剑指 Offer 33. 二叉搜索树的后序遍历序列

栈、树、二叉搜索树、递归

class Solution:
    # 分治法,遍历左右根,运用递归精神
    def verifyPostorder(self, postorder: List[int]) -> bool:
        i = 0
        j = len(postorder) - 1
        return self.veriftyRes(postorder, i, j)

    def veriftyRes(self, porder, i, j):
        if i >= j:
            return True
        left = i
        while porder[left] < porder[j]: # 左子树上的结点值应该小于根节点的值
            left += 1
        right = left # 划分左右子树,right位置为右子树起点

        while porder[left] > porder[j]: # 右子树上的结点值应该大于根节点的值
            left += 1
        
        return left == j and self.veriftyRes(porder, i, right - 1) and self.veriftyRes(porder, right, j - 1)  

剑指 Offer 34. 二叉树中和为某一值的路径

树、深度优先搜索、回溯、二叉树

# Definition for a binary tree node.
# class TreeNode:
#     def __init__(self, val=0, left=None, right=None):
#         self.val = val
#         self.left = left
#         self.right = right
class Solution:
    # 先序遍历
    def pathSum(self, root: TreeNode, target: int) -> List[List[int]]:
        res = [] # 最后的结果
        path = [] # 遍历的路径
        def preOrder(node, cur_Sum):
            if not node: # 递归出口
                return 
            path.append(node.val)
            cur_Sum -= node.val  # 算出之后遍历要得到的数字之和
            if cur_Sum == 0 and not node.left and not node.right: # 保证此时数字之和就是所求,且是叶子节点
                res.append(list(path))  # 此处因为list是可改变的格式, 需要复制保存
            preOrder(node.left, cur_Sum) # 遍历左子树
            preOrder(node.right, cur_Sum) # 遍历右子树
            path.pop() # 恢复到上一层

        preOrder(root, target)
        return res

剑指 Offer 35. 复杂链表的复制

哈希表、链表

"""
# Definition for a Node.
class Node:
    def __init__(self, x: int, next: 'Node' = None, random: 'Node' = None):
        self.val = int(x)
        self.next = next
        self.random = random
"""
class Solution:
    # # 深拷贝,图的深度优先搜索
    # def copyRandomList(self, head: 'Node') -> 'Node':
    #     visited = {}  # 哈希表,字典
    #     return self.dfs(head, visited)
    # def dfs(self, head, visited):
    #     if not head:
    #         return None
    #     if head in visited:
    #         return visited[head]
    #     copy = Node(head.val, None, None)
    #     visited[head] = copy
    #     copy.next = self.dfs(head.next, visited) # 先复制next结点
    #     copy.random = self.dfs(head.random, visited) # 再复制random结点
    #     return copy

    # # 深拷贝,图的广度优先搜索
    # def copyRandomList(self, head: 'Node') -> 'Node':
    #     visited = {}  # 哈希表,字典
    #     return self.bfs(head, visited)
    # def bfs(self, head, visited):
    #     if not head:
    #         return None
    #     copy = Node(head.val, None, None) # 创建新的结点
    #     queue = []  # 利用队列记录
    #     queue.append(head)
    #     visited[head] = copy
    #     while queue:
    #         node = queue.pop(0)
    #         if node.next and node.next not in visited:
    #             visited[node.next] = Node(node.next.val, None, None)
    #             queue.append(node.next)
    #         if node.random and node.random not in visited:
    #             visited[node.random] = Node(node.random.val, None, None)
    #             queue.append(node.random)
    #         visited[node].next = visited.get(node.next, None)
    #         visited[node].random = visited.get(node.random, None)
    #     return copy
    
    # 迭代这种方法比较直接,就是分别拷贝next结点和random结点。
    def copyRandomList(self, head: 'Node') -> 'Node':
        visited = {}  #构建一个链表来记录
        def getCloneNode(node):
            if node:
                if node not in visited:
                    visited[node] = Node(node.val, None, None)
                    return visited[node]                
                else:
                    return visited[node]
          
        if not head:
            return head
        old_node = head
        new_node = Node(old_node.val, None, None)
        visited[old_node] = new_node
        
        while old_node:
            new_node.next = getCloneNode(old_node.next)
            new_node.random = getCloneNode(old_node.random)
            old_node = old_node.next
            new_node = new_node.next
            
        return visited[head]

剑指 Offer 36. 二叉搜索树与双向链表

栈、树、深度优先搜索

"""
# Definition for a Node.
class Node:
    def __init__(self, val, left=None, right=None):
        self.val = val
        self.left = left
        self.right = right
"""
class Solution:
    # 对二叉搜索树进行排序,相当于中序遍历二叉树,然后放入链表
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        def dfs(cur):
            if not cur: # 递归出口
                return
            dfs(cur.left) # 中序遍历,左右根
            if self.pre: # pre不为None, 说明找到了头结点
                self.pre.right = cur
                cur.left = self.pre
            else:
                self.head = cur # 此时找到头结点
            self.pre = cur
            dfs(cur.right)

        if not root:  # 特殊输入的处理
            return None
        self.pre = None  # 保存前一结点,最终是尾结点
        dfs(root)
        self.head.left = self.pre # 头结点的左孩子是尾结点
        self.pre.right = self.head  # 尾结点的右孩子是头结点
        return self.head

剑指 Offer 37. 序列化二叉树

树、深度优先搜索、广度优先搜索

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
import collections
class Codec:
    # # 树的层次遍历, 广度优先遍历
    # def serialize(self, root):
    #     if not root:
    #         return "[]"
    #     queue = collections.deque()
    #     queue.append(root)
    #     res = []
    #     while queue:
    #         node = queue.popleft()
    #         if node:
    #             res.append(str(node.val))
    #             queue.append(node.left)
    #             queue.append(node.right)
    #         else:
    #             res.append("null")
    #     return '[' + ','.join(res) + ']'

    # def deserialize(self, data):
    #     if data == "[]":
    #         return
    #     vals, i = data[1: -1].split(','), 1
    #     root = TreeNode(int(vals[0]))
    #     queue = collections.deque()
    #     queue.append(root)
    #     while queue:
    #         node = queue.popleft()
    #         if vals[i] != 'null':
    #             node.left = TreeNode(int(vals[i]))
    #             queue.append(node.left)
    #         i += 1
    #         if vals[i] != 'null':
    #             node.right = TreeNode(int(vals[i]))
    #             queue.append(node.right)
    #         i += 1
    #     return root
    
    # 前序遍历,深度优先搜索
    def serialize(self, root):
        return self.rserialize(root, "")

    def rserialize(self, root, strs):
        if root == None:
            strs += "None,"
        else:
            strs += str(root.val) + ","
            strs = self.rserialize(root.left, strs)
            strs = self.rserialize(root.right, strs)
        return strs

    def deserialize(self, data):
        dataArray = data.split(",")
        dataList = list(dataArray)
        return self.rdeseralize(dataList)
    
    def rdeseralize(self, dataList):
        if dataList[0] == "None":
            dataList.pop(0)
            return None
        root = TreeNode(int(dataList[0]))
        dataList.pop(0)
        root.left = self.rdeseralize(dataList)
        root.right = self.rdeseralize(dataList)
        return root

剑指 Offer 38. 字符串的排列

字符串、回溯

class Solution:
    # # 方法一:递归 + 回溯
    # def permutation(self, s: str) -> List[str]:
    #     strs, n, res = list(s), len(s), []
    #     def backtrace(num):
    #         if num == n: # 跳出条件
    #             res.append("".join(strs))
    #             return
    #         jihe = set()
    #         for i in range(num, n):
    #             if strs[i] in jihe:
    #                 continue
    #             jihe.add(strs[i])
    #             strs[i], strs[num] = strs[num], strs[i] # 固定num的位置
    #             backtrace(num + 1)  # 开始固定num + 1的位置
    #             strs[i], strs[num] = strs[num], strs[i] # 恢复
    #     backtrace(0)
    #     return res

    # 方法二:利用回溯模板
    def permutation(self, s: str) -> List[str]:
        if not s:
            return
        strs, res = list(sorted(s)), []
        def backtrace(strs, tmp):
            if not strs: 
                res.append(''.join(tmp))
            for i in range(len(strs)):
                if i > 0 and strs[i] == strs[i - 1]: # 去重
                    continue
                backtrace(strs[:i] + strs[i + 1:], tmp + [strs[i]])
        backtrace(strs, [])
        return res

剑指 Offer 39. 数组中出现次数超过一半的数字

数组、哈希表、分治、计数、排序

class Solution:
    # # 摩尔投票法
    # def majorityElement(self, nums: List[int]) -> int:
    #     if not nums:  # 边界情况
    #         return 0 
    #     target = nums[0] 
    #     res = 1 # 控制一个计数量res
    #     for i in range(1, len(nums)):
    #         if nums[i] == target: # 如果跟前一个数相同,就加1
    #             res += 1
    #         else: # 如果跟前一个数不相同,则减1
    #             res -= 1 
    #         if res == 0:
    #             target = nums[i] # res为0时,重新赋值
    #             res = 1
    #     return target  # 最后剩下的数就是超过一半的数

    # 字典哈希
    def majorityElement(self, nums: List[int]) -> int:
        if not nums:  # 边界情况
            return 0 
        target = len(nums) // 2
        numDict = {} 
        for num in nums:
            if num in numDict:
                numDict[num] += 1
            else:
                numDict[num] = 1
            if numDict[num] > target:
                return num    

剑指 Offer 40. 最小的k个数

数组、分治、快速选择、排序

import heapq
class Solution:
    # # 排序方法:大根堆, 不改变数组中数的位置
    # # 时间复杂度:O(logK), 空间复杂度:O(k)
    # def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
    #     if k == 0:  # 处理边界情况
    #         return list()
    #     hp = [-x for x in arr[:k]] # 因为python语言中的堆为小根堆,所以需要对数组中所有的值取反,才能使用小根堆维护前k个数
    #     heapq.heapify(hp) # 将列表list转换为heap堆,在现行时间内,重新排列表
    #     for i in range(k, len(arr)): # 从第k+1个数开始遍历
    #         if -hp[0] > arr[i]: # 如果当前遍历到的数比大根堆的堆顶的数要小
    #             heapq.heappop(hp) # 弹出堆顶的数
    #             heapq.heappush(hp, -arr[i]) # 往堆中插入当前遍历到的数
    #     res = [-x for x in hp] # 最后将大根堆的数存入数组返回
    #     return res
  
    # 排序方法:快速排序, 改变数组中数的位置
    # 时间复杂度:O(logK), 空间复杂度:O(k)
    def getLeastNumbers(self, arr: List[int], k: int) -> List[int]:
        length = len(arr)
        if k <= 0 or k > length: # 边界情况
            return list()
        left, right = 0, length - 1 # 左右指针
        while left < right:
            res = self.partition(arr, left, right) # 确定left的数在arr中位置
            if res == k - 1: # 如果刚好是k - 1的位置,说明此时找到前k个数字
                break
            elif res < k - 1: # 如果位置偏左,则调整左指针
                left = res + 1
            else:
                right = res - 1 # 如果位置偏右,则调整右指针
        return arr[: k]
    
    def partition(self, arr, left, right):
        cur_res = arr[left]
        while left < right:
            while left < right and arr[right] >= cur_res:
                right -= 1 
            arr[left] = arr[right]
            while left < right and arr[left] <= cur_res:
                left += 1
            arr[right] = arr[left]
        arr[left] = cur_res
        return left

剑指 Offer 41. 数据流中的中位数

设计、双指针、数据流、排序

from heapq import *
class MedianFinder:
    # # 自带函数sort()的排序, 时间复杂度:O(nlogn)
    # def __init__(self):
    #     """
    #     initialize your data structure here.
    #     """
    #     self.store = []

    # def addNum(self, num: int) -> None:
    #     self.store.append(num)

    # def findMedian(self) -> float:
    #     self.store.sort()
    #     n = len(self.store)
    #     if n & 1:
    #         return self.store[n // 2]
    #     else:
    #         return (self.store[n // 2 - 1] + self.store[n // 2]) / 2

    # 堆排序, 时间复杂度:O(n)
    def __init__(self):
        """
        initialize your data structure here.
        """
        self.left = [] # 大顶堆,保存左半部分小于中位数的数据
        self.right = [] # 小顶堆,保存右半部分大于中位数的数据

    def addNum(self, num: int) -> None:
        if len(self.left) == len(self.right): # 当左右两顶堆长度相同时,插入左边的大顶堆
            heapq.heappush(self.right, num) # 此时数据大于右边小顶堆的最小值,需要插入右边的小顶堆
            heapq.heappush(self.left, -heapq.heappop(self.right)) # 并把右边小顶堆的最小值插入左边大顶堆
        else: # 当左右两顶堆长度不相同时,肯定是左边大顶堆数目多于右边小顶堆的数目,插入右边的小顶堆
            heapq.heappush(self.left, -num) # 此时数据小于左边大顶堆的最大值,需要插入大边的大顶堆
            heapq.heappush(self.right, -heapq.heappop(self.left)) # 并把左边大顶堆的最大值插入右边小顶堆

    def findMedian(self) -> float:
        if len(self.left) == 0:
            return 0
        if len(self.left) == len(self.right):
            mid = (self.right[0] - self.left[0]) / 2.0
        else:
            mid = -self.left[0]
        return mid

剑指 Offer 42. 连续子数组的最大和

数组、分治、动态规划

class Solution:
    # 动态规划,维护以当前数字为结尾的子数组的和
    def maxSubArray(self, nums: List[int]) -> int:
        if not nums:  # 边界情况
            return 0 
        nCurSum = 0
        nGreatestSum = nums[0]
        for num in nums:
            if nCurSum <= 0: # num之前的最大子数组的和 比0小就更新为 num --- f(i) = num 当i=0或者f(i - 1) <= 0
                nCurSum = num
            else:  # num之前的最大子数组的和 比0大就更新为 加上num --- f(i) = f(i-1) + num 当i!=0或者f(i - 1) > 0
                nCurSum += num
            if nCurSum > nGreatestSum:
                nGreatestSum = nCurSum
        return nGreatestSum

剑指 Offer 43. 1~n 整数中 1 出现的次数

递归、数学、动态规划

class Solution:
    # 数学
    def countDigitOne(self, n: int) -> int:
        k, mulk = 0, 1  # mulk 表示 10 ** k
        res = 0
        while n >= mulk:
            res += (n // (mulk * 10)) * mulk + min(max(n % (mulk * 10) - mulk + 1, 0), mulk)  # 计算结果
            k += 1
            mulk *= 10
        return res

剑指 Offer 44. 数字序列中某一位的数字

数学、二分查找

class Solution:
    # 数学
    def findNthDigit(self, n: int) -> int:
        digit, start, count = 1, 1, 9  # 分别初始化 位数, 起始, 数位
        while n > count: # 1. 确定 n 所在数字的位数 digit
            n -= count  
            digit += 1   # 1, 2, 3, ...
            start *= 10  # 1, 10, 100, ...
            count = 9 * digit * start  # 9, 180, 2700, ...
        num = start + (n - 1) // digit  # 2. 确定所求数位所在的数字 num
        return int(str(num)[(n - 1) % digit])  # 3. 确定所求数位在num中的哪一位       

剑指 Offer 45. 把数组排成最小的数

贪心、字符串、排序

class Solution:
    # 类似于快速排序的的思想
    # 时间复杂度:O(nlogn), 最差为O(n^2). 空间复杂度:O(n)
    def minNumber(self, nums: List[int]) -> str:
        strs = [str(num) for num in nums]
        def quickSort(left, right):
            if left >= right: # 递归出口
                return
            i, j = left, right
            while i < j:
                # 字符串的比较是比较ASCII码值。
                # 从字符串的第一个字符开始比较,谁的ASCII码大谁就大。如果第一个相同,则比较第二个。以此类推。如果都相同则相等。
                while i < j and strs[j] + strs[left] >= strs[left] + strs[j]: # 保证位置j右边的数都应该在left位置右边
                    j -= 1
                while i < j and strs[i] + strs[left] <= strs[left] + strs[i]: # 保证位置i左边的数都应该在left位置左边
                    i += 1
                strs[i], strs[j] = strs[j], strs[i] # 调整此时的i,j位置的数,放在应该放在的位置
            strs[i], strs[left] = strs[left], strs[i] # 把left放在应该排序的位置
            quickSort(left, i - 1) # 递归排序left左边的数, 直到递归出口
            quickSort(i + 1, right) # 递归排序right右边的数, 直到递归出口
        quickSort(0, len(nums) - 1) # 递归开始
        return "".join(strs)

剑指 Offer 46. 把数字翻译成字符串

字符串、动态规划

class Solution:
    # 动态规划,时间复杂度: O(n) ,空间复杂度:O(1)
    # def translateNum(self, num: int) -> int:
    #     str_num = str(num)
    #     dp_i_1, dp_i_2 = 1, 1  # 当字符长度是0和1,都分别只有一种翻译方法
    #     for i in range(2, len(str_num) + 1):
    #         if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
    #             dp_i = dp_i_1 + dp_i_2  # dp[i] = dp[i - 1] + dp[i - 2]
    #         else:  # dp[i] = dp[i - 1]
    #             dp_i = dp_i_1
    #         dp_i_2 = dp_i_1  # 像车轮一样翻滚,更新它们
    #         dp_i_1 = dp_i
    #     return dp_i_1
 
    # 动态规划,时间复杂度: O(n) ,空间复杂度:O(n)
    # def translateNum(self, num: int) -> int:
    #     str_num = str(num)
    #     dp = [1] * (len(str_num) + 1)
    #     for i in range(2, len(str_num) + 1):
    #         if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
    #             dp[i] = dp[i - 1] + dp[i - 2]
    #         else:
    #             dp[i] = dp[i - 1]
    #     return dp[len(str_num)]
 
    # 动态规划,时间复杂度: O(n) ,空间复杂度:O(1) 优化上述的空间效率
    def translateNum(self, num: int) -> int:
        str_num = str(num)
        dp = [1] * 2
        for i in range(2, len(str_num) + 1):
            if "10" <= str_num[i - 2: i] <= "25": # 这个期间的数字可以拆成两种
                dp[i % 2] = dp[(i - 1) % 2] + dp[(i - 2) % 2]
            else:
                dp[i % 2] = dp[(i - 1) % 2]
        return dp[len(str_num) % 2]

剑指 Offer 47. 礼物的最大价值

数组、动态规划、矩阵路径

class Solution:
    # # 动态规划,时间复杂度O(mn), 空间复杂度O(mn)
    # def maxValue(self, grid: List[List[int]]) -> int:
    #     m, n = len(grid), len(grid[0])
    #     res = [[0] * n for _ in range(m)]
    #     res[0][0] = grid[0][0]
    #     for i in range(1, m):
    #         res[i][0] = res[i - 1][0] + grid[i][0]
    #     for j in range(1, n):
    #         res[0][j] = res[0][j - 1] + grid[0][j]
    #     for i in range(1, m):
    #         for j in range(1, n):
    #             res[i][j] = max(res[i - 1][j], res[i][j - 1]) + grid[i][j]
    #     return res[m - 1][n - 1]

    # # 动态规划,时间复杂度O(mn), 空间复杂度O(n) 两行
    # def maxValue(self, grid: List[List[int]]) -> int:
    #     m, n = len(grid), len(grid[0])
    #     res = [[0] * n for _ in range(2)]
    #     res[0][0] = grid[0][0]
    #     for j in range(1, n):
    #         res[0][j] = res[0][j - 1] + grid[0][j]
    #     for i in range(1, m):
    #         res[i % 2][0] = res[(i - 1)  % 2][0] + grid[i][0]
    #         for j in range(1, n):
    #             res[i % 2][j] = max(res[(i - 1) % 2][j], res[i % 2][j - 1]) + grid[i][j]
    #     return res[(m - 1) % 2][n - 1]

    # 动态规划,时间复杂度O(mn), 空间复杂度O(n) 一行
    def maxValue(self, grid: List[List[int]]) -> int:
        m, n = len(grid), len(grid[0])
        res = [0] * n
        res[0] = grid[0][0]
        for j in range(1, n):
            res[j] = res[j - 1] + grid[0][j]
        for i in range(1, m):
            res[0] = res[0] + grid[i][0]
            for j in range(1, n):
                res[j] = max(res[j], res[j - 1]) + grid[i][j]
        return res[n - 1]

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

哈希表、字符串、滑动窗口

class Solution:
    # # 蛮力法
    # def lengthOfLongestSubstring(self, s: str) -> int:
    #     if not s:
    #         return 0
    #     longest = 0
    #     for start in range(len(s)):
    #         for end in range(start, len(s)):
    #             count = end - start + 1
    #             substring = s[start: end + 1]
    #             position = dict()
    #             if not self.hasDuplication(substring, position):
    #                 if count > longest:
    #                     longest = count
    #             else:
    #                 break
    #     return longest
    
    # def hasDuplication(self, substring, position):
    #     for i in range(len(substring)):
    #         indexInposition = ord(substring[i]) - ord('a')
    #         if indexInposition in position:
    #             return True
    #         position[indexInposition] = indexInposition
    #     return False

    # 动态规划
    def lengthOfLongestSubstring(self, s: str) -> int:
        if not s: # 边界情况
            return 0 
        temp = dict() # 保存与当前字符一样的下标
        dp = [0] * len(s) # 用来记录以j为结尾的最长不重复子字符串
        for j in range(len(s)):
            i = temp.get(s[j], -1) #获取与当前字符s[j]一样的字符的下标
            temp[s[j]] = j  # 更新字典
            if dp[j - 1] < j - i : # 状态转移方程 i < 0 或者 dp[j - 1] < j - i
                dp[j] = dp[j - 1] + 1
            else:
                dp[j] = j - i
        return max(dp)

剑指 Offer 49. 丑数

哈希表、数学、动态规划

class Solution:
    # 动态规划
    def nthUglyNumber(self, n: int) -> int:
        dp, a, b, c = [1] * n, 0, 0, 0
        for i in range(1, n):
            n2, n3, n5 = dp[a] * 2, dp[b] * 3, dp[c] * 5
            dp[i] = min(n2, n3, n5)
            if dp[i] == n2: a += 1
            if dp[i] == n3: b += 1 # 必须要是if,如果既等于n2,又等于n3,则都跳过
            if dp[i] == n5: c += 1
        return dp[-1]

剑指 Offer 50. 第一个只出现一次的字符

队列、哈希表、字符串、计数

class Solution:
    # 利用哈希特征来进行高效查找
    def firstUniqChar(self, s: str) -> str:
        if not s:
            return " "
        haxi = dict()
        for num in s:
            if num in haxi: haxi[num] += 1
            else: haxi[num] = 1
        for num in haxi:
            if haxi[num] == 1:
                return num
        return " "

剑指 Offer 51. 数组中的逆序对

树状数组、线段树、数组

class Solution:
    # 归并排序, 左右指针, 时间复杂度:O(n), 空间复杂度:O(n)
    def mergeSort(self, nums, tmp, l, r):
        if l >= r:
            return 0
        mid = (l + r) // 2
        inv_count = self.mergeSort(nums, tmp, l, mid) + self.mergeSort(nums, tmp, mid + 1, r)
        i, j, pos = l, mid + 1, l
        while i <= mid and j <= r:
            if nums[i] <= nums[j]:
                tmp[pos] = nums[i]
                i += 1
                inv_count += (j - (mid + 1))  # 逆序对个数
            else:
                tmp[pos] = nums[j]
                j += 1
            pos += 1
        for k in range(i, mid + 1): # 左指针没有到达 mid, 则接下来比较
            tmp[pos] = nums[k]
            inv_count += (j - (mid + 1))
            pos += 1
        for k in range(j, r + 1):  # 右指针没有达到 r, 则接下来比较
            tmp[pos] = nums[k]
            pos += 1
        nums[l: r + 1] = tmp[l: r + 1] # 更新下一次归并的数组 
        return inv_count

    def reversePairs(self, nums: List[int]) -> int:
        n = len(nums)
        tmp = [0] * n # 辅助数组
        return self.mergeSort(nums, tmp, 0, n - 1)

剑指 Offer 52. 两个链表的第一个公共节点

哈希表、链表、双指针

# 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 getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
        nLength1 = self.GetListLength(headA) # 得到两个链表的长度
        nLength2 = self.GetListLength(headB)
        nLengthDif = nLength1 - nLength2

        pListHeadLong = headA
        pListHeadShort = headB
        if nLength2 > nLength1:
            pListHeadLong = headB
            pListHeadShort = headA
            nLengthDif = nLength2 - nLength1
        
        for i in range(nLengthDif): # 先在长链表上走几步,再同时在两个链表上遍历
            pListHeadLong = pListHeadLong.next

        while pListHeadLong != None and pListHeadShort != None and pListHeadShort != pListHeadLong:
            pListHeadLong = pListHeadLong.next
            pListHeadShort = pListHeadShort.next
        
        return pListHeadLong  # 得到第一个公共结点
    
    def GetListLength(self, pHead):
        nLength = 0
        pNode = pHead
        while pNode != None:
            nLength += 1
            pNode = pNode.next
        return nLength

剑指 Offer 53 - I. 在排序数组中查找数字 I

数组、二分查找

class Solution:
    # # 二分查找,找是target的值的位置
    # def search(self, nums: List[int], target: int) -> int:
    #     left, right = 0, len(nums) - 1
    #     res = 0
    #     while left <= right:
    #         m = (left + right) // 2
    #         if nums[m] == target: # 若找到目标值后,从中心扩散,寻找上下界
    #             res += 1
    #             i = m - 1
    #             j = m + 1
    #             while j <= len(nums) - 1 and nums[j] == target:
    #                 res += 1
    #                 j += 1
    #             while i >= 0 and nums[i] == target:
    #                 res += 1
    #                 i -= 1
    #             break
    #         elif nums[m] < target:
    #             left = m + 1
    #         else:
    #             right = m - 1
    #     return res

    # 二分查找,相当于找左右边界,找不是target的值的位置
    def search(self, nums: List[int], target: int) -> int:
        i, j = 0, len(nums) - 1
        while i <= j:
            m = (i + j) // 2
            if nums[m] <= target: # i想找的是第一个大于target的数
                i = m + 1
            else:
                j = m - 1
        right = i  # 找右边界 

        i, j = 0, len(nums) - 1
        while i <= j:
            m = (i + j) // 2
            if nums[m] < target: 
                i = m + 1  # 尝试查找比target小的更大的数
            else: # j想找的是第一个小于target的数
                j = m - 1  # 尝试查找大于等于target的更小的数
        left = j  # 找左边界
        
        return right - left - 1

剑指 Offer 53 - II. 0~n-1中缺失的数字

位运算、数组、哈希表、数学

class Solution:
    # 二分查找
    def missingNumber(self, nums: List[int]) -> int:
        i, j = 0, len(nums) - 1
        while i <= j:
            mid = (i + j) // 2
            if nums[mid] == mid:
                i = mid + 1
            else:
                j = mid - 1
        return i

剑指 Offer 54. 二叉搜索树的第k大节点

树、深度优先搜索、二叉搜索树

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

class Solution:
    # 二叉搜索树的中序遍历是从小到大排序,如果中序遍先右后左,每次k值减小1,当k值减小到0时, root.val即为所求
    def kthLargest(self, root: TreeNode, k: int) -> int:
        self.k = k
        def dfs(node):
            if not node:
                return
            dfs(node.right) # 遍历右子树
            self.k -= 1  # 减一
            if self.k == 0: # 保存结果
                self.res = node.val
                return
            dfs(node.left) # 遍历左子树

        dfs(root)
        return self.res

剑指 Offer 55 - I. 二叉树的深度

树、深度优先搜索、广度优先搜索

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

class Solution:
    # # 后序遍历,循环
    # def maxDepth(self, root: TreeNode) -> int:
    #     if not root:
    #         return 0
    #     return max(self.maxDepth(root.left), self.maxDepth(root.right)) + 1

    # 层次遍历,循环
    def maxDepth(self, root: TreeNode) -> int:
        if not root:
            return 0
        queue, res = [root], 0
        while queue:
            tmp = []
            for node in queue:
                if node.left: tmp.append(node.left)
                if node.right: tmp.append(node.right)
            queue = tmp
            res += 1
        return res

剑指 Offer 55 - II. 平衡二叉树

树、深度优先搜索、二叉树

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

class Solution:
    # # 递归,从顶开始,判断深度
    # def isBalanced(self, root: TreeNode) -> bool:
    #     if not root:
    #         return True
    #     return abs(self.depth(root.left) - self.depth(roo t.right)) <= 1 and self.isBalanced(root.left) and self.isBalanced(root.right)
    # def depth(self, root):
    #     if not root:
    #         return 0
    #     return max(self.depth(root.left), self.depth(root.right)) + 1

    # 迭代,后序遍历 + 剪枝
    def isBalanced(self, root: TreeNode) -> bool:
        def lastSort(root):
            if not root:
                return 0
            left = lastSort(root.left) # 判断左子树是否满足平衡二叉树, 满足的话返回左子树的深度
            if left == -1: # 左子树,不满足,返回-1
                return -1
            right = lastSort(root.right) # 判断右子树是否满足平衡二叉树, 满足的话返回右子树的深度
            if right == -1: # 右子树,不满足,返回-1
                return -1
            if abs(left - right) <= 1: # 当前左右子树的父节点是否满足平衡二叉树,满足的话返回其父节点的深度
                return max(left, right) + 1
            else:
                return -1
        return lastSort(root) != -1

剑指 Offer 56 - I. 数组中数字出现的次数

位运算、数组

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        res = 0  # 保留异或结果
        a, b = 0, 0  # 分组异或
        for num in nums:
            res ^= num  # 所有数字异或, 最终表示的是两个不同的数字的结果

        position = 1 # 开始找第一位不是0的数,也就是第一位两个数开始不同的位置
        while res & position == 0:
            position <<= 1
        for num in nums: # 开始分组
            if position & num == 0: # a记录的是第一位不为0的数
                a ^= num
            else:
                b ^= num
        return [a, b]

剑指 Offer 56 - II. 数组中数字出现的次数 II

位运算、数组

class Solution:
    # 应用位操作,若某一位上1出现的次数不是3的倍数,则唯一出现的数字该位上为1
    def singleNumber(self, nums: List[int]) -> int:
        res = 0
        for i in range(32):
            cnt = 0 # 记录该位置上1的个数
            bit = 1 << i # 记录当前要操作的bit
            for num in nums:
                if num & bit != 0:
                    cnt += 1  # 记录该位置上1出现的次数0
            if cnt % 3 != 0:
                res |= bit # 保留该位上的结果
        return res

剑指 Offer 57. 和为s的两个数字

数组、双指针、二分查找

class Solution:
    def twoSum(self, nums: List[int], target: int) -> List[int]:
        left, right = 0, len(nums) - 1 
        while left < right: # 二分查找
            if nums[left] + nums[right] > target: 
                right -= 1
            elif nums[left] + nums[right] < target:
                left += 1
            else:
                return [nums[left], nums[right]]
        return []

剑指 Offer 57 - II. 和为s的连续正数序列

数学、双指针、枚举

class Solution:
    # 滑动窗口解题, 双指针
    def findContinuousSequence(self, target: int) -> List[List[int]]:
        left, right = 1, 2
        res = []
        while right <= target // 2 + 1: # 最大也是target的二分之一
            cur_num = []
            s = (left + right) * (right - left + 1) // 2 # 求窗口中连续数的和
            if s < target:  # 如果小于目标值,右指针增长
                right += 1 
            elif s > target: # 如果大于目标值,左指针增长
                left += 1
            else: # 如果为目标值,保存结果
                for num in range(left, right + 1):
                    cur_num.append(num)
                res.append(cur_num)
                right += 1
        return res
        

剑指 Offer 58 - I. 翻转单词顺序

双指针、字符串

class Solution:
    def reverseWords(self, s: str) -> str:
        s = s.strip()  # 删除首位空格
        left = right = len(s) - 1 # 两个指针的初始位置
        res = []
        while left >= 0:  # 从尾部开始往前移动
            while left >= 0 and s[left] != ' ': # 搜索直到遇到空格
                left -= 1
            res.append(s[left + 1: right + 1]) # 把之前的单词加入到结果中
            while s[left] == " ":  # 当指针遇到空格,跳过
                left -= 1
            right = left # right指向下一个单词尾部
        return " ".join(res)

剑指 Offer 58 - II. 左旋转字符串

数学、双指针、字符串

class Solution:
    # 字符串切片
    def reverseLeftWords(self, s: str, n: int) -> str:
        s1 = s[:n]
        s2 = s[n:]
        return s2 + s1

    # # 旋转操作
    # def reverseLeftWords(self, s: str, n: int) -> str:
    #     s = list(s)  # 因为字符串是不可变序列,还有元组
    #     length = len(s) - 1
    #     self.reverse(s, 0, n - 1)
    #     self.reverse(s, n, length)
    #     self.reverse(s, 0, length)
    #     return "".join(s)

    # def reverse(self, s, start, end):
    #     while start < end:
    #         s[start], s[end] = s[end], s[start]
    #         start += 1
    #         end -= 1

剑指 Offer 59 - I. 滑动窗口的最大值

队列、滑动窗口、单调队列

import collections
import heapq
class Solution:
    # # 优先队列,时间复杂度: O(nlogn), 空间复杂度: O(n)
    # def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
    #     if not nums:
    #         return []
    #     n = len(nums) 
    #     q = [[-nums[i], i] for i in range(k)]  # 注意python默认的优先队列是最小堆
    #     heapq.heapify(q)
    #     res = [-q[0][0]] # 记录当前最大值
    #     for i in range(k, n):
    #         heapq.heappush(q, [-nums[i], i])
    #         while q[0][1] <= i - k: # 堆中的值不在当前窗口中
    #             heapq.heappop(q)
    #         res.append(-q[0][0])
    #     return res
    
    # 双端队列,时间复杂度: O(n), 空间复杂度: O(k)
    def maxSlidingWindow(self, nums: List[int], k: int) -> List[int]:
        if not nums or k == 0: # 边界情况
            return []
        deque = collections.deque()   # 构建队列记录当前最大值的序列
        for i in range(k):  # 在未形成窗口前
            while deque and deque[-1] < nums[i]:  # 队列为空或者队列尾部元素小于数组中的当前元素
                deque.pop() # 此时队尾元素不可能是当前窗口的最大值,弹出
            deque.append(nums[i])
        res = [deque[0]]  # 将第一个窗口中的最大值写入结果栈中
        for i in range(k, len(nums)):  # 已经形成窗口了
            if deque[0] == nums[i - k]: # 队列头部元素
                deque.popleft()  # 弹出队首元素,维护当前元素是在窗口中
            while deque and deque[-1] < nums[i]: # 维护双端队列是单调递减的,不断将新的元素与队尾元素相比较
                deque.pop() # 弹出队尾元素
            deque.append(nums[i])
            res.append(deque[0])
        return res

剑指 Offer 59 - II. 队列的最大值

设计、队列、单调队列

class MaxQueue:
    # 双队列
    def __init__(self):
        self.queue = []
        self.max_queue = []

    def max_value(self) -> int:
        if not self.queue: 
            return -1
        return self.max_queue[0] 

    def push_back(self, value: int) -> None:
        self.queue.append(value)
        while self.max_queue and value > self.max_queue[-1]:
            self.max_queue.pop(-1)
        self.max_queue.append(value)

    def pop_front(self) -> int:
        if not self.queue:
            return -1
        if self.queue[0] == self.max_queue[0]:
            self.max_queue.pop(0)
        return self.queue.pop(0)

剑指 Offer 60. n个骰子的点数

数学、动态规划、概率与统计

class Solution:
    # 动态规划,时间复杂度:O(n^2), 空间复杂度:O(n)
    def dicesProbability(self, n: int) -> List[float]:
        dp = [1/6] * 6  # 初始化
        for i in range(2, n + 1):
            tmp = [0] * (5 * i + 1)  # 本轮点数之和共有情况: 6*i - (i - 1) = 5i + 1 种
            for j in range(len(dp)):
                for k in range(6):
                    tmp[j + k] += dp[j] / 6
            dp = tmp
        return dp

剑指 Offer 61. 扑克牌中的顺子

数组、排序

class Solution:
    # # 方法一,不排序,如这五张牌连续,需要满足条件1:这五张牌没有重复,条件2:最大值-最小值<5。
    # def isStraight(self, nums: List[int]) -> bool:
    #     repeat = set()
    #     max_num, min_num = 0, 14 # 最大值初始化为最小值,最小值初始化为最大值
    #     for num in nums:
    #         if num == 0: continue
    #         max_num = max(max_num, num)  # 最大值
    #         min_num = min(min_num, num)  # 最小值
    #         if num in repeat:  # 保证repeat中没有重复的数
    #             return False
    #         repeat.add(num)  # 集合中添加num
    #     return max_num - min_num < 5
    
    # 方法二,先排序,判断有没有重复的,判断和最小值之间是不是相差值小于5,和前一种方法不同的点在于,判断重复时直接和上一个位置值判断是不是一样就OK
    def isStraight(self, nums: List[int]) -> bool:
        nums.sort()
        joker = 0  # 计算大小王数量
        for i in range(len(nums) - 1):
            if nums[i] == 0: # 记录大小王数量
                joker += 1 
            elif nums[i] == nums[i + 1]: # 如果有重复,则直接判断不成立
                return False
        return nums[4] - nums[joker] < 5 # 第joker数位代表的除0之外最小的数

剑指 Offer 62. 圆圈中最后剩下的数字

递归、数学

class Solution:
    # # 计算机模拟,暴力超时了
    # def lastRemaining(self, n: int, m: int) -> int:
    #     lists = list(range(n))
    #     i = 0 
    #     while len(lists) > 1:
    #         i = (i + m - 1) % len(lists) # 计算出当前要删除第一个元素
    #         lists.pop(i) # 然后从lists中弹出第几个元素
    #     return lists[0]

    # 数学方法,约瑟夫环问题
    def lastRemaining(self, n: int, m: int) -> int:
        num = 0
        for i in range(2, n + 1):
            num = (num + m) % i
        return num

剑指 Offer 63. 股票的最大利润

数学、动态规划

class Solution:
    # 动态规划,记录以当前数字结尾的子数组的最大利润
    def maxProfit(self, prices: List[int]) -> int:
        if not prices: # 边界情况
            return 0
        profit = 0
        min_price = prices[0]
        for price in prices:
            min_price = min(min_price, price)
            profit = max(price - min_price, profit)
        return profit

剑指 Offer 64. 求1+2+…+n

位运算、递归、脑筋急转弯

class Solution:
    def __init__(self):
        self.res = 0
        
    # 位运算。逻辑运算符
    def sumNums(self, n: int) -> int:
        n > 1 and self.sumNums(n - 1)
        self.res += n
        return self.res

剑指 Offer 65. 不用加减乘除做加法

位运算、数学

class Solution:
    def add(self, a: int, b: int) -> int:
        x = 0xffffffff   # 与该数相与,可以舍去数字32位以上的数字
        a, b = a & x, b & x   # 将a, b从无限长度变成一个32位整数
        while b != 0:
            
            a, b = (a ^ b), (a & b) << 1 & x # 异或结果得到非进位和; 相与得到进位和,左移1位是作为下一位,并且需要与x与截获前32位   
        # python中没有int,long等不同长度变量,既没有变量位数的概念
        # 获取负数的补码:与十六进制0xffffffff 相与,舍去此数字32位以上的数字,从无限长度变为一个32位整数
        # 返回前数字还原:若补码a为负数(最大的整数补码是0x7fffffff),需执行~(a ^ x), 将1至32位按位取反,再整个数字取反
        return a if a <= 0x7fffffff else ~(a ^ x) # 如果是正数,就直接返回

剑指 Offer 66. 构建乘积数组

数组、前缀和

class Solution:
    # 计算上三角和下三角
    def constructArr(self, a: List[int]) -> List[int]:
        b = [1] * len(a)
        tmp = 1
        for i in range(1, len(a)):
            b[i] = b[i - 1] * a[i - 1] # 从上到下计算下三角
        for i in range(len(a) - 2, - 1, -1):
            tmp *= a[i + 1] # 从下到上计算上三角
            b[i] *= tmp
        return b

剑指 Offer 67. 把字符串转换成整数

字符串

class Solution:
    def strToInt(self, strs: str) -> int:
        res, i, sign, length = 0, 0, 1, len(strs)
        int_max, int_min, bndry = 2 ** 31 - 1, - 2 ** 31, 2 ** 31 // 10
        if not strs: return 0  # 空字符串,返回0
        while strs[i] == " ":
            i += 1
            if i== length: return 0  # 字符串全为空格,提前返回
        if strs[i] == "-": sign = -1
        if strs[i] in "+-": i += 1
        for c in strs[i:]:
            if not '0' <= c <= '9': break
            if res > bndry or res == bndry and c > '7':
                return int_max if sign == 1 else int_min
            res = 10 * res + ord(c) - ord('0')
        return sign * res

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

树、深度优先搜索、二叉搜索树

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

class Solution:
    # 利用二叉搜索树的特点, 利用值来判断
    def lowestCommonAncestor(self, root: 'TreeNode', p: 'TreeNode', q: 'TreeNode') -> 'TreeNode':
        if p.val > q.val:
            p, q = q, p  # 保障 p.val < q.val, 减少比较次数
        while root:
            if root.val < p.val:
                root = root.right # 两个值均在右子树中
            if root.val > q.val:
                root = root.left # 两个值均在左子树中
            else:
                break
        return root
        

剑指 Offer 68 - II. 二叉树的最近公共祖先

树、深度优先搜索、二叉树

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

class Solution:
    # 后序遍历,递归
    def lowestCommonAncestor(self, root: TreeNode, p: TreeNode, q: TreeNode) -> TreeNode:
        if not root or root == p or root == q: # 递归出口,1,越过叶节点 2.当root等于p,q
            return root
        left = self.lowestCommonAncestor(root.left, p, q) # 递归工作,递归左子节点
        right = self.lowestCommonAncestor(root.right, p, q) # 递归工作,递归右子节点
        if not left: # left 为空,right 不为空
            return right 
        if not right: # right 为空,left 不为空
            return left
        return root  # left, right都不为空

你可能感兴趣的:(力扣刷题,leetcode,算法,链表)