参考大神https://blog.csdn.net/zjulyx1993/article/details/108327108
1.剑指 Offer 03. 数组中重复的数字(数组)
1 """ 找出数组中重复的数字。 2 3 在一个长度为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字。 4 2 <= n <= 100000 5 6 示例 1: 7 8 输入: 9 [2, 3, 1, 0, 2, 5, 3] 10 输出:2 或 3 11 12 """ 13 14 #集合法,依次将列表中的数放入集合,若已在集合中重复,则输出,时间复杂度O(n),空间复杂度O(n) 15 def findRepeatNumber1(nums): 16 d = set() 17 for i in nums: 18 if i not in d: 19 d.add(i) 20 else: 21 return i 22 23 #注意数字范围在0~n-1之间, 这说明每个数都可以放到等同于其自身值的下标中,时间复杂度O(n),空间复杂度降到O(1) 24 def findRepeatNumber2(nums): 25 for i in range(len(nums)): 26 27 # for循环中每遍历一次位置i,会一直交换至nums[i]==i 28 while i != nums[i]: 29 # 当前下标i和值nums[i]不相等, 进入/继续内层循环,如果相等则说明此位置符合要求 30 j = nums[i] 31 if nums[i] == nums[j]: 32 # 前提条件: i!=j, 所以nums[i]和nums[j]是数组的两个数字, 它们值相等, 即为重复数字 33 return nums[i] 34 # 交换两个值, 使得nums[j] == j, 这样可以继续循环判断i和新的nums[i] 35 nums[i], nums[j] = nums[j], nums[i] 36 37 return -1 #如果给定的数组没有重复值的话,返回-1
2.剑指 Offer 04. 二维数组中的查找(数组)
1 """ 在一个 n * m 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。 2 3 示例: 4 5 现有矩阵 matrix 如下: 6 [ 7 [1, 4, 7, 11, 15], 8 [2, 5, 8, 12, 19], 9 [3, 6, 9, 16, 22], 10 [10, 13, 14, 17, 24], 11 [18, 21, 23, 26, 30] 12 ] 13 给定 target = 5,返回 true。 给定 target = 20,返回 false。 14 15 限制: 16 0 <= n <= 1000 17 0 <= m <= 1000 18 """ 19 #初始化坐标为右上角, 然后判断当前点与 target 的关系, 大于的话列号减 1, 小于的话行号+1, 直到找到等于 target 的点, 或者超出矩阵范围 20 #时间复杂度O(R+C),空间复杂度O(1) 21 def findNumberIn2DArray(matrix, target): 22 if not matrix: 23 return False 24 row, col = len(matrix), len(matrix[0]) 25 r, c = 0, col-1 26 while r<row and c>-1: 27 if target < matrix[r][c]: 28 c -= 1 29 elif target > matrix[r][c]: 30 r += 1 31 else: 32 return True 33 return False
3.剑指 Offer 05. 替换空格(字符串)
1 """ 请实现一个函数,把字符串 s 中的每个空格替换成"%20"。 2 3 示例 1: 4 5 输入:s = "We are happy." 6 输出:"We%20are%20happy." 7 8 限制: 0 <= s 的长度 <= 10000 9 """ 10 11 def replaceSpace(s): 12 # return s.replace(' ', '%20') 13 return '%20'.join(s.split())
4.剑指 Offer 06. 从尾到头打印链表(链表)
1 """ 输入一个链表的头节点,从尾到头反过来返回每个节点的值(用数组返回)。 2 3 示例 1: 4 5 输入:head = [1,3,2] 6 输出:[2,3,1] 7 8 限制:0 <= 链表长度 <= 10000 9 class Solution: 10 def reversePrint(self, head: ListNode) -> List[int]: 11 """ 12 13 # Definition for singly-linked list. 14 class ListNode: 15 def __init__(self, x): 16 self.val = x 17 self.next = None 18 19 #先正向存储到数组,再翻转返回 20 def reversePrint1(head: ListNode): 21 lst = [] 22 while head: 23 lst.append(head.val) 24 head = head.next 25 return lst[::-1] 26 27 #使用递归 28 def reversePrint2(head: ListNode): 29 res = [] 30 31 def add(x: ListNode): 32 if not x: #递归出口 33 return 34 add(x.next) 35 res.append(x.val) 36 37 add(head) 38 return res 39 40 #不能翻转,不能使用递归,只能用栈迭代 41 def reversePrint3(head: ListNode): 42 stack = [] 43 while head: 44 stack.append(head.val) #正向压入栈中 45 head = head.next 46 47 res = [] 48 while stack: 49 res.append(stack.pop().val) #再一个个弹出 50 return res
5.剑指 Offer 07. 重建二叉树(树)
1 """ 输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 2 3 例如,给出 4 前序遍历 preorder = [3,9,20,15,7] 5 中序遍历 inorder = [9,3,15,20,7] 6 返回如下的二叉树: 7 8 3 9 / 10 9 20 11 / 12 15 7 13 14 class Solution: 15 def buildTree(self, preorder: List[int], inorder: List[int]) -> TreeNode: 16 """ 17 18 # Definition for a binary tree node. 19 class TreeNode: 20 def __init__(self, x): 21 self.val = x 22 self.left = None 23 self.right = None 24 25 26 #递归法 27 def buildTree1(preorder, inorder): 28 if not preorder: 29 return None 30 31 valueToInorderIndex = {} #使用一个value=>inorder index的字典加速运算, 为了使得查找中序下标的操作变为O(1), 不需要扫描整个中序数组 32 n = len(preorder) 33 34 for i in range(n): 35 valueToInorderIndex[inorder[i]] = i 36 37 def build(pb, pe, ib, ie): #(pb, pe)是当前前序遍历的起点和终点 (ib, ie)是当前中序遍历的起点和终点 38 if pb > pe: 39 return None #递归出口 40 41 root = TreeNode(preorder[pb]) #前序遍历的当前第一个元素即为当前的根节点 42 if pb == pe: 43 return root 44 45 im = valueToInorderIndex[root.val] #根节点对应的中序遍历的下标,以此作为分界点 46 pm = pb + im - ib #根节点对应的前序遍历的下标(对应部分的前序和中序长度应该相等, 即im-ib=pm-pb,由此得出) 47 48 #前序[pb ]pm[ pe] 49 #中序[ib ]im[ ie] 50 root.left = build(pb + 1, pm, ib, im-1) #左子树部分,前序(pb+1, pm),中序(ib, im-1) 51 root.right = build(pm + 1, pe, im + 1, ie) #右子树部分,前序(pm+1, pe),后序(im+1, ie) 52 53 return root 54 return build(0, n-1, 0, n-1) 55 56 #迭代法 57 def buildTree2(preorder, inorder): 58 if not preorder: 59 return None 60 61 root = TreeNode(preorder[0]) 62 stack = [root] 63 64 ii = 0 #ii表示当前的中序下标 65 66 for pi in range(1, len(preorder)): 67 prenode = stack[-1] #记录上一个栈顶节点,一定不为空 68 curnode = TreeNode(preorder[pi]) #当前节点curnode位于上一节点的左子树或者右子树 69 70 if prenode.val != inorder[ii]: #上一个节点不是当前中序节点, 意味着现在还没到上一个节点的右边部分, 所以当前节点位于左子树, 71 prenode.left = curnode 72 else: #上一节点就是当前中序节点,意味着当前节点在右子树上 73 while stack and ii < len(inorder) and stack[-1].val == inorder[ii]: 74 prenode = stack.pop() #找最上层一个(也即倒置前序序列最后一个)与当前中序节点相同的节点 75 ii += 1 76 prenode.right = curnode #那个节点的右儿子就是当前节点 77 78 stack.append(curnode) #将当前节点加入stack中, 作为下次循环的上一个节点 79 80 return root
6.剑指 Offer 09. 用两个栈实现队列(栈/队列)
1 """ 用两个栈实现一个队列。队列的声明如下,请实现它的两个函数 appendTail 和 deleteHead ,分别完成在队列尾部插入整数和在队列头部删除整数的功能。(若队列中没有元素,deleteHead 操作返回 -1 2 3 示例 1: 4 输入: 5 ["CQueue","appendTail","deleteHead","deleteHead"] 6 [[],[3],[],[]] 7 输出:[null,null,3,-1] 8 9 示例 2: 10 输入: 11 ["CQueue","deleteHead","appendTail","appendTail","deleteHead","deleteHead"] 12 [[],[],[5],[2],[],[]] 13 输出:[null,-1,null,null,5,2] 14 15 提示:1 <= values <= 10000,最多会对 appendTail、deleteHead 进行 10000 次调用 16 """ 17 # 两个队列stack1和stack2,队列尾部插入数据,即数据压入stack1,队列头部删除整数,判断三种情况 18 # 1.第二个栈不为空,则从第二个栈中弹出数据 2.第一个栈不为空,则从第一个栈中把元素依次弹出并压入第二个栈中 3.第一个栈为空,即删除失败 19 class CQueue: 20 def __init__(self): 21 self.stack1 = [] 22 self.stack2 = [] 23 24 def appendTail(self, value: int): #在队列尾部插入整数 25 self.stack1.append(value) 26 27 def deleteHead(self): #在队列头部删除整数 28 if self.stack2: #对应情况1 29 return self.stack2.pop() 30 if not self.stack1: #对应情况3 31 return -1 32 33 while self.stack1: #对应情况2 34 x = self.stack1.pop() 35 self.stack2.append(x) 36 return self.stack2.pop() 37 38 39 # Your CQueue object will be instantiated and called as such: 40 # obj = CQueue() 41 # obj.appendTail(value) 42 # param_2 = obj.deleteHead()
7.剑指 Offer 10- I. 斐波那契数列(动态规划)
1 """ 写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项(即 F(N))。斐波那契数列的定义如下: 2 F(0) = 0, F(1) = 1 3 F(N) = F(N - 1) + F(N - 2), 其中 N > 1. 4 斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。 5 6 答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1。 7 """ 8 9 def fib(n): 10 if n < 2: 11 return n 12 13 x, y, z = 0, 1, 1 14 while n-2: 15 x, y = y, z 16 z = x + y 17 n = n - 1 18 return z % 1000000007 19 20 #用数组更清晰 21 def fib2(n): 22 if n < 2: 23 return n 24 25 dp=[0]*(n+1) 26 dp[1]=1 27 for k in range(2, n+1): 28 dp[k] = dp[k-1] + dp[k-2] 29 return dp[-1]%1000000007
8.剑指 Offer 10- II. 青蛙跳台阶问题(动态规划)
本质和上一题一样
9.剑指 Offer 11. 旋转数组的最小数字(二分查找)
1 """ 把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。 2 输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。 3 例如,数组 [3,4,5,1,2] 为 [1,2,3,4,5] 的一个旋转,该数组的最小值为1。 4 5 输入:[3,4,5,1,2] 6 输出:1 7 8 输入:[2,2,2,0,1] 9 输出:0 10 """ 11 #题目的意思是数组有两段递增序列,找分段点,如果只有一段递增序列,那就是开头 12 #二分查找 13 def minArray(numbers): 14 n = len(numbers) 15 i, j = 0, n-1 16 while i < j: 17 mid = (i + j) // 2 18 if numbers[mid] < numbers[j]: #如果中间值小于末尾, 那么一定说明该数字之后(后半段)有序. 19 j = mid 20 elif numbers[mid] > numbers[j]: #如果中间值大于末尾, 那么毫无疑问后半段无序. 21 i = mid + 1 22 else: #如果中间值等于末尾, 那就不好判断是前半段无序还是后半段无序,退化为逐个遍历 23 j -= 1 24 return numbers[i]
类似的还有面试题
面试题 10.03. 搜索旋转数组
1 """ 搜索旋转数组。给定一个排序后的数组,包含n个整数,但这个数组已被旋转过很多次了,次数不详。 2 请编写代码找出数组中的某个元素,假设数组元素原先是按升序排列的。若有多个相同元素,返回索引值最小的一个 3 4 输入: arr = [15, 16, 19, 20, 25, 1, 3, 4, 5, 7, 10, 14], target = 5 5 输出: 8(元素5在该数组中的索引) 6 """ 7 8 #理解一下:旋转一次和旋转多次没有任何区别, 最终还是只有一个旋转点, 以及不多于 2 个的有序区间 9 def search(arr, target): 10 left, right = 0, len(arr) - 1 11 while left <= right: 12 13 mid = (left + right) // 2 14 if arr[left] == target: 15 return left 16 elif arr[mid] == target: #mid和right值等于target时,未必是索引值最小的,还要继续遍历 17 right = mid 18 elif arr[right] == target: 19 left = mid + 1 20 elif arr[mid] < arr[right]: #后半段有序 21 if arr[mid] < target < arr[right]: #target在后半段里 22 left = mid + 1 23 else: #target在前半段里 24 right = mid - 1 25 elif arr[mid] > arr[right]: #前半段有序 26 if arr[left] < target < arr[mid]: #target在前半段里 27 right = mid - 1 28 else: #target在后半段里 29 left = mid + 1 30 else: #前后段谁有序不清楚,逐一遍历 31 right -= 1 32 return -1
10.剑指 Offer 12. 矩阵中的路径(回溯法)
1 """ 给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 2 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。 3 4 输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED" 5 输出:true 6 7 输入:board = [["a","b"],["c","d"]], word = "abcd" 8 输出:false 9 10 """ 11 12 #直接修改board记录每个点是否被访问过 13 def exist(board, word): #题干强调了同一个单元格内的字母不允许被重复使用 14 15 def dfs(i, j, k): 16 if not 0<= i < len(board) or not 0<= j < len(board[0]) or board[i][j] != word[k]: 17 return False 18 if k == len(word) - 1: 19 return True 20 board[i][j] = ' ' 21 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) 22 board[i][j] = word[i][j] 23 return res 24 25 for i in range(len(board)): 26 for j in range(len(board[0])): 27 if dfs(i, j, 0): 28 return True 29 return False 30 31 #使用集合visited(C++中使用矩阵)记录每个点是否被访问过 32 def exist2(board, word): 33 34 def dfs(i, j, k, visited): 35 if not 0<= i < len(board) or not 0<= j < len(board[0]) or board[i][j] != word[k] or (i,j) in visited: 36 return False 37 if k == len(word) - 1: 38 return True 39 visited.add((i,j)) 40 res = dfs(i-1, j, k+1, visited) or dfs(i+1, j, k+1, visited) or dfs(i, j-1, k+1, visited) or dfs(i, j+1, k+1, visited) 41 visited.remove((i, j)) 42 return res 43 44 for i in range(len(board)): 45 for j in range(len(board[0])): 46 visited = set() 47 if dfs(i, j, 0, visited): 48 return True 49 return False