参考链接:
剑指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

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

被折叠的 条评论
为什么被折叠?



