剑指offer刷题
剑指 Offer 03. 数组中重复的数字(简单)
返回数组中重复出现的任意数字。
对于 Python 可以使用 dict 或 set 进行解体, 因为 Python 的 dict
和 set
都是使用 hash 表实现的所以查找操作都是 O(1)的时间复杂度。
但是, 如果想使用 list
进行解题, 则不应该使用 in 操作符, 因为 Python list 使用顺序表实现, 所以其 in 的时间复杂度为 O(n).
class Solution(object):
def findRepeatNumber(self, nums):
table = set()
for i in xrange(len(nums)):
if nums[i] in table: # set可用in O(1)
return nums[i]
else:
table.add(nums[i])
def findRepeatNumber(self, nums):
array = [0] * len(nums)
for n in nums:
if array[n] > 0: # list只能用赋值并判断大小,不能 in
return n
else:
array[n] += 1
剑指 Offer 04. 二维数组中的查找(简单)
在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
标志数引入:
此类矩阵中左下角和右上角元素有特殊性,称为标志数。
左下角元素: 为所在列最大元素,所在行最小元素。
右上角元素: 为所在行最大元素,所在列最小元素。
将 matrix 中的左下角元素(标志数)记作 flag ,则有:
- 若 flag > target ,则 target 一定在 flag 所在行的上方,即 flag 所在行可被消去。
- 若 flag < target ,则 target 一定在 flag 所在列的右方,即 flag 所在列可被消去。
def findNumberIn2DArray(self, matrix: List[List[int]], target: int) -> bool:
i, j = len(matrix) - 1, 0 # 从矩阵左下角元素开始遍历
while i >= 0 and j < len(matrix[0]):
if matrix[i][j] > target: # target一定在上方
i -= 1
elif matrix[i][j] < target: # target一定在右边
j += 1
else: # matrix[i][j] == target
return True
return False
剑指 Offer 05. 替换空格(简单)
请实现一个函数,把字符串 s 中的每个空格替换成"%20"。
# 44 ms, 13.6 MB
def replaceSpace(self, s: str) -> str:
sList = s.split(" ")
print(sList)
s_new = sList[0]
for i in range(1, len(sList)):
s_new += ("%20")
s_new += (sList[i])
return s_new
# 36 ms, 13.4 MB
def replaceSpace(self, s: str) -> str:
res = []
for c in s:
if c == ' ':
res.append("%20")
else:
res.append(c)
return "".join(res)
剑指 Offer 06. 从尾到头打印链表(简单)
输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。
- 反转
def reversePrint(self, head: ListNode) -> List[int]:
res = []
while head:
res.append(head.val)
head = head.next
return res[::-1]
[::-1]
用来反转list。
- 递归
def reversePrint(self, head: ListNode) -> List[int]:
if not head:
return []
return self.reversePrint(head.next) + [head.val]
- 辅助栈
- 入栈: 遍历链表,将各节点值 push 入栈。(Python 使用 append() 方法,Java借助 LinkedList 的addLast()方法)。
- 出栈: 将各节点值 pop 出栈,存储于数组并返回。
def reversePrint(self, head: ListNode) -> List[int]:
stack = []
while head:
stack.append(head.val)
head = head.next
res = []
while stack:
res.append(stack.pop())
return res
剑指 Offer 07. 重建二叉树(中等)
输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
什么序遍历说针对根节点的:
- 前序遍历顺序:根节点–左子树–右子树
- 中序遍历顺序:左子树–根节点–右子树
- 后序遍历顺序:左子树–右子树–根节点
def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode:
self.dic, self.po = {}, preorder
for i in range(len(inorder)): # 用dic储存中序遍历的值和索引
self.dic[inorder[i]] = i
# dic:{9: 0, 3: 1, 15: 2, 20: 3, 7: 4}
return self.recur(0, 0, len(inorder) - 1)
# pre_root:前序遍历根节点index;in_left:中序遍历左边界index;in_right:中序遍历右边界index
def recur(self, pre_root, in_left, in_right): # 终止条件:中序遍历为空
if in_left > in_right:
return
root = TreeNode(self.po[pre_root]) # 前序遍历第一个node是根节点
i = self.dic[self.po[pre_root]] # 获取根节点在中序遍历中的index,根节点在inorder中划分出左右节点
root.left = self.recur(pre_root + 1, in_left, i - 1)
root.right = self.recur(i - in_left + pre_root + 1, i + 1, in_right)
return root
剑指 Offer 09. 用两个栈实现队列(简单)
用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 )
栈无法实现队列删除栈底元素。
双栈进行列表倒序,然后删除队首元素:设有含三个元素的栈 A = [1,2,3]A=[1,2,3] 和空栈 B = []B=[]。若循环执行 AA 元素出栈并添加入栈 BB ,直到栈 AA 为空,则 A = []A=[] , B = [3,2,1]B=[3,2,1] ,即 栈 B 元素实现栈 AA 元素倒序 。倒序后,BB 执行出栈则相当于删除了 AA 的栈底元素,即对应队首元素。
class CQueue:
def __init__(self):
self.A, self.B = [], []
def appendTail(self, value: int) -> None:
self.A.append(value)
def deleteHead(self) -> int:
if self.B: # 当栈 B 不为空:B中仍有已完成倒序的元素,因此直接返回 B 的栈顶元素。
return self.B.pop()
if not self.A: # 当 A 为空: 即两个栈都为空,无元素,因此返回 -1。
return -1
while self.A: # 将栈 A元素全部转移至栈 B 中,实现元素倒序,并返回栈 B 的栈顶元素。
self.B.append(self.A.pop())
return self.B.pop()
剑指 Offer 62. 圆圈中最后剩下的数字(简单)
0,1,n-1这n个数字排成一个圆圈,从数字0开始,每次从这个圆圈里删除第m个数字。求出这个圆圈里剩下的最后一个数字。
l = list(range(10))
print(l)
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
def lastRemaining(self, n: int, m: int) -> int:
i, a = 0, list(range(n))
while len(a) > 1:
i = (i+m-1) % len(a)
a.pop(i)
return a[0]
剑指 Offer 24. 反转链表(简单)
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
- 双指针:prev用作新生成链表的当前节点,curr用于链表的遍历。
def reverseList(self, head: ListNode) -> ListNode:
prev, curr = None, head
while curr:
curr.next, prev, curr = prev, curr, curr.next
return prev
deque
2. deque储存链表,再一次pop元素,重新构建反向链表。
from collections import deque
def reverseList(self, head: ListNode) -> ListNode:
if not head:
return None
# 储存链表
deq = deque()
while head.next:
deq.append(head)
head = head.next
print(head)
print(".....")
temp = head # 尾节点
# 反向链表
while deq:
node = deq.pop()
node.next = temp.next # 新链表的头节点是旧链表的尾节点
temp.next = node # 向前推进
temp = node
return head
剑指 Offer 38. 字符串的排列(中等)
输入一个字符串,打印出该字符串中字符的所有排列。你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
l = list(“abcde”)
print(l)
[‘a’, ‘b’, ‘c’, ‘d’, ‘e’]
深度优先搜索所有排列方案。即通过字符交换,先固定第 11 位字符( nn 种情况)、再固定第 22 位字符( n-1n−1 种情况)、… 、最后固定第 nn 位字符( 11 种情况)。
def permutation(self, s: str) -> List[str]:
c, res = list(s), []
def dfs(x): # 当前固定位:x
if x == len(c) - 1: # 固定到最后一位,完成,直接append
print(f"res{c}")
res.append(''.join(c))
return
dic = set() # set排除重复结果
for i in range(x, len(c)): #
# 防止重复
if c[i] in dic:
continue
dic.add(c[i]) # 检测重复
# 将第 x 位字符与 i∈[x,len(c)] 字符分别交换
c[i], c[x] = c[x], c[i]
dfs(x + 1)
c[i], c[x] = c[x], c[i]
dfs(0)
return res
剑指 Offer 48. 最长不含重复字符的子字符串(中等) DP
class Solution:
def lengthOfLongestSubstring(self, s: str) -> int:
dic = {} # 记录各字符最后一次出现的索引位置
res = tmp = 0 # tmp = dp[j] 代表以字符 s[j] 为结尾的 “最长不重复子字符串” 的长度。
for j in range(len(s)): # j是子字符串的右边界
i = dic.get(s[j], -1) # 这个字符上一次出现的索引,之前没出现过 -> 返回-1
dic[s[j]] = j # 更新表,记录该字符出现的索引,成为最近一次出现的索引
if tmp < j - i: # dp[j−1]<j−i ,说明字符 s[i] 在子字符串 dp[j-1] 区间之外
tmp = tmp + 1 # dp[j] = dp[j-1] + 1
else: # dp[j−1]≥j−i ,说明字符 s[i] 在子字符串 dp[j-1] 区间之中
tmp = j - i
res = max(res, tmp)
return res