參考大神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