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

本文涵盖了剑指 Offer 系列题目,包括数组、链表、树、栈、队列等多种数据结构与算法的练习,涉及二叉树、动态规划、字符串、位运算等主题,旨在提升编程能力。

参考链接:
剑指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
            
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值