剑指offer刷题记录 python3 Java
- 剑指 Offer 09. 用两个栈实现队列
- 剑指 Offer 10- I. 斐波那契数列
- 剑指 Offer 03. 数组中重复的数字
- 【★】剑指 Offer 04. 二维数组中的查找
- 剑指 Offer 10- II. 青蛙跳台阶问题
- 剑指 Offer 11. 旋转数组的最小数字
- 剑指 Offer 12. 矩阵中的路径
- 剑指 Offer 05. 替换空格
- 剑指 Offer 13. 机器人的运动范围
- 剑指 Offer 06. 从尾到头打印链表
- 剑指 Offer 07. 重建二叉树
- 【★】剑指 Offer 14- I. 剪绳子
- 剑指 Offer 14- II. 剪绳子 II
- 剑指 Offer 25. 合并两个排序的链表
- 剑指 Offer 26. 树的子结构
- 剑指 Offer 27. 二叉树的镜像
- 剑指 Offer 28. 对称的二叉树
- 【★】剑指 Offer 20. 表示数值的字符串
- 剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
- 【★】剑指 Offer 15. 二进制中1的个数
- 【★】剑指 Offer 29. 顺时针打印矩阵
- 剑指 Offer 22. 链表中倒数第k个节点
- 【★】剑指 Offer 16. 数值的整数次方
- 【★】剑指 Offer 17. 打印从1到最大的n位数
- 【★】剑指 Offer 19. 正则表达式匹配
- 剑指 Offer 24. 反转链表
- 剑指 Offer 18. 删除链表的节点
- 剑指 Offer 35. 复杂链表的复制
- 剑指 Offer 40. 最小的k个数
剑指 Offer 09. 用两个栈实现队列
【思路】
使用两个栈q1、q2:
- q1用作存储,对于所有的appendTail操作增加的元素都放到q1.append()。
- q2用作弹出,对于多有的deleteHead操作删除的元素都是q2的pop操作,若q2为空,则将q1的所有元素pop再q2.append(),实现顺序反转,此时q2头部为最先进入的元素。
【代码】
class CQueue:
def __init__(self):
self.q1, self.q2 = [], []
def appendTail(self, value: int) -> None:
self.q1.append(value)
def deleteHead(self) -> int:
if not self.q2:
while self.q1:
self.q2.append(self.q1.pop())
return self.q2.pop() if self.q2 else -1
# Your CQueue object will be instantiated and called as such:
# obj = CQueue()
# obj.appendTail(value)
# param_2 = obj.deleteHead()
剑指 Offer 10- I. 斐波那契数列
【思路】
- 使用“递归法”会产生大量重复计算,本题使用“动态规划”。
- 对于取模操作,在加法过程中取模,而不是在结果返回前取模,防止数据越界。
【代码】
class Solution:
def fib(self, n: int) -> int:
if n == 0:
return 0
m = int(1e9+7)
a, b = 0, 1
for _ in range(n - 1):
c = b
b = (a + b) % m
a = c
return b
剑指 Offer 03. 数组中重复的数字
【思路】
采用交换的思想,对于nums = [2, 3, 1, 0, 2, 5, 3],第一次操作转换为nums = [1, 3, 2, 0, 2, 5, 3],即把nums[0] = 2放到对应位置nums[2]上(交换nums[0]和nums[2]),然后第二次操作再把nums[0] = 1放到对应位置nums[1]上,转换为nums = [3, 1, 2, 0, 2, 5, 3] … \dots … 重复操作,知道找到第一个nums[nums[i]] == nums[i]的数。
【代码】
class Solution:
def findRepeatNumber(self, nums: List[int]) -> int:
for i in range(len(nums)):
t = i
while t != nums[t]:
if nums[t] == nums[nums[t]]:
return nums[t]
nums[nums[t]], nums[t] = nums[t], nums[nums[t]] # python交换赋值为左优先,即先对nums[nums[t]]赋值。nums[nums[t]]和nums[t]位置互换会出错。参考https://stackoverflow.com/questions/21047524/how-does-swapping-of-members-in-tuples-a-b-b-a-work-internally
【★】剑指 Offer 04. 二维数组中的查找
【思路】
- 矩阵元素排列是有规律的(左至右、上至下为递增),那么共有下列4种搜索方式:
- 左上角开始,往右递增,往下递增。
- 右下角开始,往左递减,往上递减。
- 左下角开始,往右递增,往上递减。
- 右上角开始,往左递减,往下递增。
- 只能选取左下角开始和右上角开始两种思路。
【代码】
class Solution:
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
if len(matrix) == 0 or len(matrix[0]) == 0:
return False
rows, cols = len(matrix), len(matrix[0])
row, col = 0, cols - 1
while row < rows and col >= 0:
if target == matrix[row][col]:
return True
elif target < matrix[row][col]:
col -= 1
else:
row += 1
return False
剑指 Offer 10- II. 青蛙跳台阶问题
【思路】同【剑指 Offer 10- I. 斐波那契数列】
【代码】略
剑指 Offer 11. 旋转数组的最小数字
【思路】
- 这是一个递增数组的旋转,则有两种情况:
- 某两个相邻元素为递减(旋转长度 > 0),找到这个递减位置的元素即可;
- 整个数组为递增(旋转长度 = 0),返回第一个元素。
【代码】
class Solution:
def minArray(self, numbers: List[int]) -> int:
n = len(numbers)
if n < 2:
return numbers[0]
for i in range(1, len(numbers)):
if numbers[i - 1] > numbers[i]:
return numbers[i]
return numbers[0] # 执行完for循环,但是没有return,说明这是一个递增数组,第一个为最小
剑指 Offer 12. 矩阵中的路径
【思路】
- 判断是否存在的问题,利用DFS(全部解利用BFS)。
- 当遍历长度等于str长度时,返回True。
- 对所有位置遍历一次,防止漏掉正确答案。
- 当前层遍历完,需要记录遍历过当前位置,这里采用改变内容的方案(也可以使用dp数组)。
【代码】
class Solution:
def exist(self, board: List[List[str]], word: str) -> bool:
m, n, s = len(board), len(board[0]), len(word)
def dfs(i, j, k):
if not 0 <= i < m or not 0 <= j < n or board[i][j] != word[k]:
return False
if k == s - 1:
return True
board[i][j] = ''
res = dfs(i - 1, j, k + 1) or dfs(i + 1, j, k + 1) or dfs(i, j - 1, k + 1) or dfs(i, j + 1, k + 1)
board[i][j] = word[k]
return res
for i in range(m):
for j in range(n):
if dfs(i, j, 0):
return True
return False
剑指 Offer 05. 替换空格
【思路】无
【代码】
class Solution:
def replaceSpace(self, s: str) -> str:
return s.replace(' ', '%20')
剑指 Offer 13. 机器人的运动范围
【思路】
- 首先有个函数能够实现根据坐标计算和的功能。
- 利用dfs进行所有位置的遍历,然后利用dp数组保存遍历过的位置。
【代码】
# 求解
def digitsum(n):
ans = 0
while n:
ans += n % 10
n //= 10
return ans
class Solution:
def movingCount(self, m: int, n: int, k: int) -> int:
ans = 0
dp = [[False] * n for _ in range(m)]
def dfs(i, j):
if i < m and j < n and not dp[i][j]:
nonlocal ans
tmp = digitsum(i) + digitsum(j)
if tmp <= k:
dp[i][j] = True
ans += 1
dfs(i + 1, j)
dfs(i, j + 1)
dfs(0, 0)
return ans
剑指 Offer 06. 从尾到头打印链表
【思路】
- 思路1:遍历一次,放到数组中,然后reverse再return。
- 思路2:利用递归,到达最末层就向前递归,同时输出当前层的值。
【代码】
# 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]:
ans = []
def backtrack(node):
if not node:
return
backtrack(node.next)
ans.append(node.val)
backtrack(head)
return ans
剑指 Offer 07. 重建二叉树
【思路】
- preorder中第一个元素就是root。
- 再inorder中找到root对应的位置loc,这个位置左边就是左子树,右边就是右子树。
- 递归建树。
- 优化:可以利用索引坐标进行建树,这样的话减少了新建list的时间和内存。
【代码】
# 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:
if not preorder:
return
root = TreeNode(preorder[0])
loc = inorder.index(preorder[0])
root.left = self.buildTree(preorder[1: loc + 1], inorder[: loc])
root.right = self.buildTree(preorder[loc + 1: ], inorder[loc + 1: ])
return root
【★】剑指 Offer 14- I. 剪绳子
【思路】
- 当 n ≤ 3 n \leq 3 n≤3时,按照规则应不切分,但由于题目要求必须剪成m>1段,因此必须剪出一段长度为1的绳子,即返回 n − 1 n - 1 n−1。
- 当
n
>
3
n > 3
n>3时,求 n 除以 3 的 整数部分 a 和 余数部分 b(即
n
=
3
a
+
b
n = 3a + b
n=3a+b),并分为以下三种情况:
- 当 b = 0 b = 0 b=0时,直接返回 3 a 3^a 3a;
- 当 b = 1 b = 1 b=1时,要将一个 1 + 3 1 + 3 1+3转换为 2 + 2 2 + 2 2+2,因此返回 3 a − 1 × 4 3^{a-1} \times 4 3a−1×4;
- 当 b = 2 b = 2 b=2时,返回 3 a × 2 3^a \times 2 3a×2。
【代码】
class Solution:
def cuttingRope(self, n: int) -> int:
if n <= 3: return n - 1
a, b = n // 3, n % 3
if b == 0: return int(pow(3, a))
if b == 1: return int(pow(3, a - 1) * 4)
return int(pow(3, a) * 2)
剑指 Offer 14- II. 剪绳子 II
【思路】
同上。
- 优化的点:
- 可以用“快速幂”代替
pow()
- 可以用“快速幂”代替
# 求 (x^a) % p —— 快速幂求余
def remainder(x, a, p):
rem = 1
while a > 0:
if a % 2: rem = (rem * x) % p
x = x ** 2 % p
a //= 2
return rem
【代码】
class Solution:
def cuttingRope(self, n: int) -> int:
if n <= 3: return n - 1
a, b, m = n // 3, n % 3, int(1e9+7)
if b == 0: return int(pow(3, a, m))
if b == 1: return int(pow(3, a - 1, m) * 4 % m)
return int(pow(3, a, m) * 2 % m)
剑指 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:
dummy = ListNode(0)
p = dummy
while l1 and l2:
if l1.val < l2.val:
p.next = l1
l1 = l1.next
else:
p.next = l2
l2 = l2.next
p = p.next
if l1:
p.next = l1
if l2:
p.next = l2
return dummy.next
剑指 Offer 26. 树的子结构
【思路】
- (先序)遍历A,找到与B的根相同的节点,若没找到返回False。
- 从相同节点开始
dfs()
,递归地判断是否为同结构。
【代码】
# 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:
def dfs(n, m):
if not m:
return True
if not n or n.val != m.val:
return False
return dfs(n.left, m.left) and dfs(n.right, m.right)
return bool(A and B) and (dfs(A, B) or self.isSubStructure(A.left, B) or self.isSubStructure(A.right, B))
剑指 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:
root.left, root.right = root.right, root.left
self.mirrorTree(root.right)
self.mirrorTree(root.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:
def dfs(l: TreeNode, r: TreeNode):
if not l or not r:
if not l and not r:
return True
return False
return (l.val == r.val) and dfs(l.left, r.right) and dfs(l.right, r.left)
return dfs(root.left, root.right) if root else True
【★】剑指 Offer 20. 表示数值的字符串
【思路】
利用有限状态机解决这个问题,如图。
算法流程:
- 初始化:
- 状态转移表 s t a t e s states states:设 s t a t e s [ i ] states[i] states[i],其中 i i i为所处状态, s t a t e s [ i ] states[i] states[i]使用哈希表存储可转移至的状态。键值对 ( k e y , v a l u e ) (key, value) (key,value)含义:若输入 k e y key key,则可从状态 i i i转移至状态 v a l u e value value。
- 当前状态 p p p:起始状态初始化为 p = 0 p = 0 p=0。
- 状态转移循环:遍历字符串
s
s
s的每个字符
c
c
c。
- 记录字符类型
t
t
t:分为四种情况。
- 当 c c c为正负号时,执行 t = ′ s ′ t = 's' t=′s′;
- 当 c c c为数字时,执行 t = ′ d ′ t = 'd' t=′d′;
- 当 c c c为 e , E e , E e,E时,执行 t = ′ e ′ t = 'e' t=′e′;
- 当 c c c为 . . ., 空格时,执行 t = c t = c t=c(即用字符本身表示字符类型);
- 否则,执行 t = ′ ? ′ t = '?' t=′?′,代表为不属于判断范围的非法字符,后续直接返回 F a l s e False False 。
- 终止条件: 若字符类型 t t t不在哈希表 s t a t e s [ p ] states[p] states[p]中,说明无法转移至下一状态,因此直接返回 F a l s e False False。
- 状态转移: 状态 p p p转移至 s t a t e s [ p ] [ t ] states[p][t] states[p][t]。
- 记录字符类型
t
t
t:分为四种情况。
- 返回值: 跳出循环后,若状态 p ∈ 2 , 3 , 7 , 8 p \in {2, 3, 7, 8} p∈2,3,7,8,说明结尾合法,返回 T r u e True True,否则返回 F a l s e False False。
【代码】
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' (‘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 # start with state 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, blank
else: t = '?' # unknown
if t not in states[p]: return False
p = states[p][t]
return p in (2, 3, 7, 8)
# python特性
class Solution:
def isNumber(self, s: str) -> bool:
try:
float(s)
return True
except Exception:
return False
剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
【题目】剑指 Offer 21. 调整数组顺序使奇数位于偶数前面
【思路】
- 利用左右双指针 l 、 r l、r l、r。
- 将 l l l向右移动,找到偶数;将 r r r向左移动,找到奇数。
- 互换这两个位置的数。
- 最终,奇数靠前,偶数靠后。
【代码】
class Solution:
def exchange(self, nums: List[int]) -> List[int]:
if nums:
l, r = 0, len(nums) - 1
while l < r:
while l < r and nums[l] % 2 == 1:
l += 1
while l < r and nums[r] % 2 == 0:
r -= 1
nums[l], nums[r] = nums[r], nums[l]
return nums
【★】剑指 Offer 15. 二进制中1的个数
【思路】
- 利用位运算,每次计算
n &= n - 1
,这样只会将最后一个1
清除掉。 - 当
n = 0
时,说明1
都被清除掉了。
也利用Python特点,
bin()
取得二进制,再str.count()
统计'1'
的个数。
【代码】
class Solution:
# Python 特性
# def hammingWeight(self, n: int) -> int:
# return bin(n).count('1')
def hammingWeight(self, n: int) -> int:
ret = 0
while n:
n &= n - 1
ret += 1
return ret
【★】剑指 Offer 29. 顺时针打印矩阵
【思路】
- 可以将矩阵看成若干层,首先打印最外层的元素,其次打印次外层的元素,直到打印最内层的元素。
- 对于每层,从左上方开始以顺时针的顺序遍历所有元素。假设当前层的左上角位于
(
top
,
left
)
(\textit{top}, \textit{left})
(top,left),右下角位于
(
bottom
,
right
)
(\textit{bottom}, \textit{right})
(bottom,right),按照如下顺序遍历当前层的元素。
- 从左到右遍历上侧元素,依次为 ( top , left ) (\textit{top}, \textit{left}) (top,left)到 ( top , right ) (\textit{top}, \textit{right}) (top,right)。
- 从上到下遍历右侧元素,依次为 ( top + 1 , right ) (\textit{top} + 1, \textit{right}) (top+1,right)到 ( bottom , right ) (\textit{bottom}, \textit{right}) (bottom,right)。
- 如果 left < right \textit{left} < \textit{right} left<right且 top < bottom \textit{top} < \textit{bottom} top<bottom,则从右到左遍历下侧元素,依次为 ( bottom , right − 1 ) (\textit{bottom}, \textit{right} - 1) (bottom,right−1)到 ( bottom , left + 1 ) (\textit{bottom}, \textit{left} + 1) (bottom,left+1),以及从下到上遍历左侧元素,依次为 ( bottom , left ) (\textit{bottom}, \textit{left}) (bottom,left)到 ( top + 1 , left ) (\textit{top} + 1, \textit{left}) (top+1,left)。
- 遍历完当前层的元素之后,将 left \textit{left} left和 top \textit{top} top分别增加 11,将 right \textit{right} right和 bottom \textit{bottom} bottom分别减少 1,进入下一层继续遍历,直到遍历完所有元素为止。
【代码】
class Solution:
def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
if not matrix or not matrix[0]:
return list()
rows, columns = len(matrix), len(matrix[0])
order = list()
left, right, top, bottom = 0, columns - 1, 0, rows - 1
while left <= right and top <= bottom:
for column in range(left, right + 1):
order.append(matrix[top][column])
for row in range(top + 1, bottom + 1):
order.append(matrix[row][right])
if left < right and top < bottom:
for column in range(right - 1, left, -1):
order.append(matrix[bottom][column])
for row in range(bottom, top, -1):
order.append(matrix[row][left])
left, right, top, bottom = left + 1, right - 1, top + 1, bottom - 1
return order
剑指 Offer 22. 链表中倒数第k个节点
【思路】
- 设置p、q两个指针,初始值为head;
- 让p先向后移动k步;
- p、q同时向后移动,直到
p==None
。
【代码】
# 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:
p = q = head
if head:
for _ in range(k):
p = p.next
while p:
p = p.next
q = q.next
return q
【★】剑指 Offer 16. 数值的整数次方
【思路】
- 利用快速幂的思想。
- 当 n < 0 n < 0 n<0时:把问题转化至 n ≥ 0 n \geq 0 n≥0的范围内,即执行 x = 1 / x , n = − n x = 1/x,n = - n x=1/x,n=−n;
- 循环计算:当
n
=
0
n = 0
n=0时跳出;
- 当 n & 1 = 1 n \& 1 = 1 n&1=1时:将当前 x x x乘入 r e s res res(即 r e s ∗ = x res *= x res∗=x);
- 执行 x = x 2 x = x^2 x=x2(即 x ∗ = x x *= x x∗=x);
- 执行 n n n右移一位(即 n > > = 1 n >>= 1 n>>=1)。
【代码】
class Solution:
def myPow(self, x: float, n: int) -> float:
if x == 0: return 0
res = 1
if n < 0: x, n = 1 / x, -n
while n:
if n & 1: res *= x
x *= x
n >>= 1
return res
【★】剑指 Offer 17. 打印从1到最大的n位数
【思路】
range()
解决,但不兼容大数。- 解决大数问题:
- 变量类型使用
string
, - 生成数字字符集,通过递归完成 n n n位 0 − 9 0 - 9 0−9的全排列,
- 解决高位的 0 0 0,并且让列表从 1 1 1开始。
- 变量类型使用
【代码】
class Solution:
# 非大数
def printNumbers(self, n: int) -> List[int]:
return list(range(1, 10 ** n))
# 大数
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0': res.append(int(s))
if n - self.start == self.nine: self.start -= 1
return
for i in range(10):
if i == 9: self.nine += 1
num[x] = str(i)
dfs(x + 1)
self.nine -= 1
num, res = ['0'] * n, []
self.nine = 0
self.start = n - 1
dfs(0)
return res
【★】剑指 Offer 19. 正则表达式匹配
【思路】
我们用 f [ i ] [ j ] f[i][j] f[i][j]表示 s s s的前 i i i个字符与 p p p中的前 j j j个字符是否能够匹配。在进行状态转移时,我们考虑 p p p的第 j j j个字符的匹配情况:
- 如果
p
p
p的第
j
j
j个字符是一个小写字母,那么我们必须在
s
s
s中匹配一个相同的小写字母,即
f [ i ] [ j ] = { f [ i − 1 ] [ j − 1 ] , s [ i ] = p [ j ] F a l s e , s [ i ] ≠ p [ j ] f[i][j]= \begin{cases} f[i - 1][j - 1] & , {s[i] = p[j]}\\ False & , {s[i] \neq p[j]} \end{cases} f[i][j]={f[i−1][j−1]False,s[i]=p[j],s[i]=p[j]
也就是说,如果 s s s的第 i i i个字符与 p p p的第 j j j个字符不相同,那么无法进行匹配;否则我们可以匹配两个字符串的最后一个字符,完整的匹配结果取决于两个字符串前面的部分。 - 如果
p
p
p的第
j
j
j个字符是
*
,那么就表示我们可以对 p p p的第 j − 1 j−1 j−1个字符匹配任意自然数次。- 匹配 s s s末尾的一个字符,将该字符扔掉,而该组合还可以继续进行匹配;
- 不匹配字符,将该组合扔掉,不再进行匹配。
f [ i ] [ j ] = { f [ i − 1 ] [ j ] o r f [ i ] [ j − 2 ] , s [ i ] = p [ j − 1 ] f [ i ] [ j − 2 ] , s [ i ] ≠ p [ j − 1 ] f[i][j]= \begin{cases} f[i - 1][j] \space or \space f[i][j - 2] & , {s[i] = p[j - 1]}\\ f[i][j - 2] & , {s[i] \neq p[j - 1]} \end{cases} f[i][j]={f[i−1][j] or f[i][j−2]f[i][j−2],s[i]=p[j−1],s[i]=p[j−1]
- 在任意情况下,只要
p
[
j
]
p[j]
p[j]是
.
,那么 p [ j ] p[j] p[j]一定成功匹配 s s s中的任意一个小写字母。
最终的状态转移方程如下:
f
[
i
]
[
j
]
=
{
i
f
(
p
[
j
]
≠
‘
∗
’
)
=
{
f
[
i
−
1
]
[
j
−
1
]
,
m
a
t
c
h
e
s
(
s
[
i
]
,
p
[
j
]
)
F
a
l
s
e
,
o
t
h
e
r
w
i
s
e
o
t
h
e
r
w
i
s
e
=
{
f
[
i
−
1
]
[
j
]
o
r
f
[
i
]
[
j
−
2
]
,
m
a
t
c
h
e
s
(
s
[
i
]
,
p
[
j
−
1
]
)
f
[
i
]
[
j
−
2
]
,
o
t
h
e
r
w
i
s
e
f[i][j]= \begin{cases} if (p[j] \neq ‘*’) = \begin{cases} f[i - 1][j - 1] & ,{matches(s[i], p[j])}\\ False & ,{otherwise} \end{cases} \\ otherwise = \begin{cases} f[i - 1][j] \space or \space f[i][j - 2] & ,{matches(s[i], p[j - 1])}\\ f[i][j - 2] & ,{otherwise} \end{cases} \end{cases}
f[i][j]=⎩⎪⎪⎪⎪⎨⎪⎪⎪⎪⎧if(p[j]=‘∗’)={f[i−1][j−1]False,matches(s[i],p[j]),otherwiseotherwise={f[i−1][j] or f[i][j−2]f[i][j−2],matches(s[i],p[j−1]),otherwise
其中
matches
(
x
,
y
)
\textit{matches}(x, y)
matches(x,y)判断两个字符是否匹配的辅助函数。只有当
y
y
y是.
或者
x
x
x和
y
y
y本身相同时,这两个字符才会匹配。
【代码】
class Solution:
def isMatch(self, s: str, p: str) -> bool:
m, n = len(s), len(p)
def matches(i: int, j: int) -> bool:
if i == 0:
return False
if p[j - 1] == '.':
return True
return s[i - 1] == p[j - 1]
f = [[False] * (n + 1) for _ in range(m + 1)]
f[0][0] = True
for i in range(m + 1):
for j in range(1, n + 1):
if p[j - 1] == '*':
f[i][j] |= f[i][j - 2]
if matches(i, j - 1):
f[i][j] |= f[i - 1][j]
else:
if matches(i, j):
f[i][j] |= f[i - 1][j - 1]
return f[m][n]
剑指 Offer 24. 反转链表
【思路】
递归 or 迭代。
【代码】
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, x):
# self.val = x
# self.next = None
# 迭代
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head:
return head
pre, p = None, head
while p.next:
nxt = p.next
p.next = pre
pre = p
p = nxt
p.next = pre
return p
# 递归
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
newhead = self.reverseList(head.next)
head.next.next = head
head.next = None
return newhead
剑指 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 not head:
return head
dummy = ListNode(0)
dummy.next = head
p = dummy
while p and p.next:
if p.next.val == val:
p.next = p.next.next
p = p.next
return dummy.next
剑指 Offer 35. 复杂链表的复制
【思路】
穿针引线法。
对
于
链
表
A
→
B
→
C
A
→
B
→
C
,
我
们
可
以
将
其
拆
分
为
A
→
A
′
→
B
→
B
′
→
C
→
C
′
。
对
于
任
意
一
个
原
节
点
S
,
其
拷
贝
节
点
S
′
即
为
其
后
继
节
点
。
对于链表 A \rightarrow B \rightarrow CA→B→C,我们可以将其拆分为 A \rightarrow A' \rightarrow B \rightarrow B' \rightarrow C \rightarrow C'。对于任意一个原节点 S,其拷贝节点 S'即为其后继节点。
对于链表A→B→CA→B→C,我们可以将其拆分为A→A′→B→B′→C→C′。对于任意一个原节点S,其拷贝节点S′即为其后继节点。
然后再进行遍历,将random域
和next域
修正即可。
【代码】
/*
// Definition for a Node.
class Node {
int val;
Node next;
Node random;
public Node(int val) {
this.val = val;
this.next = null;
this.random = null;
}
}
*/
class Solution {
public Node copyRandomList(Node head) {
if (head == null) {
return null;
}
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = new Node(node.val);
nodeNew.next = node.next;
node.next = nodeNew;
}
for (Node node = head; node != null; node = node.next.next) {
Node nodeNew = node.next;
nodeNew.random = (node.random != null) ? node.random.next : null;
}
Node headNew = head.next;
for (Node node = head; node != null; node = node.next) {
Node nodeNew = node.next;
node.next = nodeNew.next;
nodeNew.next = (nodeNew.next != null) ? nodeNew.next.next : null;
}
return headNew;
}
}
剑指 Offer 40. 最小的k个数
【思路】
- 方法一:
维护一个 s i z e = k size = k size=k的大顶堆,插入数据的过程中, s i z e > k size > k size>k就取掉顶部元素(最大元素)。 - 方法二:
利用快排思想,当一次划分partition()
后,基准元素位置在第k个位置,则该位置之前的元素是数组中最小的k个值。
【代码】
// 堆
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
int[] vec = new int[k];
if (k == 0) {
return vec;
}
PriorityQueue<Integer> queue = new PriorityQueue<Integer>(new Comparator<Integer>() {
public int compare(Integer n1, Integer n2) {
return n2 - n1; // 大顶堆 (小顶堆是n1 - n2)
}
});
for (int i = 0; i < k; ++i) {
queue.offer(arr[i]);
}
for (int i = k; i < arr.length; ++i) {
if (queue.peek() > arr[i]) {
queue.poll();
queue.offer(arr[i]);
}
}
for (int i = 0; i < k; ++i) {
vec[i] = queue.poll();
}
return vec;
}
}
// 快排
class Solution {
public int[] getLeastNumbers(int[] arr, int k) {
if (k == 0) {
return new int[]{};
}
return Arrays.copyOfRange(arr, 0, quickSort(arr, 0, arr.length - 1, k - 1) + 1);
}
public int quickSort(int[] nums, int l, int r, int index) {
int q = partition(nums, l, r);
if (q == index) {
return q;
}
return (q > index) ? quickSort(nums, l, q - 1, index) : quickSort(nums, q + 1, r, index);
}
public int partition(int[] nums, int l, int r) {
int x = nums[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (nums[j] < x) {
swap(nums, ++i, j);
}
}
swap(nums, i + 1, r);
return i + 1;
}
public void swap(int[] nums, int i, int j) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}