劍指offer力扣刷題總結

總結使用python3的刷題之路,日積月累,天道酬勤。

1.兩數之和--2020/3/31

暴力方法,即迭代num1,同時每次迭代去尋找滿足num2=target-num1的值。使用pyhton的in操作是一次遍歷搜索。在C中一次循環。暴力方法要注意判斷num2出現次數和避免找到num1自己。進一步提升:迭代num1(除第一個數),但在每次迭代中只對該數前面的數進行搜索(列表切片)。但最有意思的使用哈希求解

def twoSum(nums, target):
    hashmap={}
    for ind,num in enumerate(nums):
        hashmap[num] = ind#獲得哈希表
    for i,num in enumerate(nums):
        j = hashmap.get(target - num)
        if j is not None and i!=j:
            return [i,j]

2.二維數組中的查找--2020/4/2

若使用暴力法遍歷矩陣 matrix ,則時間複雜度爲 O(N*M) 。暴力法未利用矩陣 “從上到下遞增、從左到右遞增” 的特點,顯然不是最優解法。
本題解利用矩陣特點引入標誌數,並通過標誌數性質降低算法時間複雜度

標誌數引入: 此類矩陣中左下角和右上角元素有特殊性,稱爲標誌數。左上角和右下角分別是最大和最小的數,沒有標誌作用。

左下角元素: 爲所在列最大元素,所在行最小元素。
右上角元素: 爲所在行最大元素,所在列最小元素。
算法流程: 根據以上性質,設計算法在每輪對比時消去一行(列)元素,以降低時間複雜度。

從矩陣 matrix 左下角元素(索引設爲 (i, j) )開始遍歷,並與目標值對比:
當 matrix[i][j] > target 時: 行索引向上移動一格(即 i--),即消去矩陣第 i 行元素;
當 matrix[i][j] < target 時: 列索引向右移動一格(即 j++),即消去矩陣第 j 列元素;
當 matrix[i][j] == target 時: 返回true 。
若行索引或列索引越界,則代表矩陣中無目標值,返回 false 。
算法本質: 每輪 i 或 j 移動後,相當於生成了“消去一行(列)的新矩陣”, 索引(i,j) 指向新矩陣的左下角元素(標誌數),因此可重複使用以上性質消去行(列)。

複雜度分析:
時間複雜度 O(M+N) :其中,N 和 M分別爲矩陣行數和列數,此算法最多循環 M+N次。
空間複雜度 O(1) : i, j 指針使用常數大小額外空間。

3. 替換空格

第一種是使用replace函數,return s.replace(' ','%20'),第二種是遍歷整個字符串,字符串是不可變類型,即無法直接修改字符串的某一位字符,可以通過s[i]來提取。所以第一步將其抓換成list,時間複雜度O(n),空間複雜度O(n)

class Solution(object):
    def replaceSpace(self, s):
        s = list(s)
        for i in range(len(s)):
            if s[i] == ' ':s[i] = '%20'
        return ''.join(s)#將空給join in s

4.重建二叉樹,採用遞歸的方法,先在前序中找到根節點,然後再中序中用根節點來區分左孩子和右孩子,依此遞歸。

# 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 len(inorder) == 0:
            return None#由於遞歸中先序要彈出根節點,葉子節點也會被當作根節點判斷所以不會爲空。
        root = TreeNode(preorder[0])
        idx = inorder.index(preorder[0])#找到根節點在中序中的index.說明有index個左孩子
        #由定義,先序中的[1:idx+1]爲左孩子,中序中的[:idx]爲左孩子
        root.left = self.buildTree(preorder[1:idx+1],inorder[:idx])
        root.right = self.buildTree(preorder[idx+1:],inorder[idx+1:])
        return root

5.用兩個棧實現隊列,棧是先進後出,隊列是先進先出。A棧用來接收,B棧用來倒序後彈出最先進的元素。

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:return self.B.pop()
        if not self.A:return -1
        while self.A:
            self.B.append(self.A.pop())
        return self.B.pop()

6.斐波拉且數列,自己使用了一種動態規劃的方式,用a,b記錄,沒有直接按定義迭代return fib(n-1) + fib(n-2)。取模是因爲防止數字太大溢出。

class Solution:
    def fib(self, n: int) -> int:
        self.a,self.b= 0,1
        if n== 0:return self.a
        elif n == 1:return self.b
        else:
            for i in range(2,n+1):
                self.a,self.b = self.b,int((self.b+self.a)%(1e9+7))#py語言的特性省略中間變量
                i += 1
            return self.b

7.青蛙跳臺階問題,這個問題是斐波拉且數列數列的改編。最後一步有兩張選擇,一種跳一步,一種跳兩步,即所有可能爲f(n)=f(n-1)+f(n-2)

8.旋轉數組的最小數字,把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉,例如,數組 [3,4,5,1,2] 爲遞增數組[1,2,3,4,5]的一個旋轉,1爲旋轉點,由於是遞增數組,即爲數組中最小的數字。直接return(min(numbers)),時間複雜O(N),空間O(1)。使用二分法搜索時,時間複雜度O(logn),空間複雜度O(1)

9.矩陣中的路徑查找,使用深度優先搜索DFS,首先遍歷整個矩陣,嘗試將每個點作爲起點開始搜索,對於超越邊界和當前匹配字符不正確的情況,返回False。只有一種情況返回True,對於查找到字符個數等於目標字符長度k == len(word)-1。如果前面的這兩次條件篩選都沒有返回值,說明只是匹配當前值成功,繼續往下遞歸查找,對當前字符的左右上下(具體時只有三種選擇,另外一種爲來時的路'/')進行匹配嘗試,當然在嘗試前要將當前字符進行替換,防止二次進入。時間複雜度 O(3^KMN), 空間複雜度 O(K) 。

class Solution:
    def exist(self, board: List[List[str]], word: str) -> bool:
        def dfs(i,j,k):
            if not (0<=i<len(board)) or not (0<=j<len(board[0])) or board[i][j]!=word[k]:return False
            if k == len(word)-1:return True
            tmp,board[i][j] = 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] = tmp
            return res
        for i in range(len(board)):
            for j in range(len(board[0])):
                if dfs(i,j,0):return True
        return False

10.機器人的運動範圍計算,和前面這道題一樣都是矩陣中的搜索,可以使用DFS和BFS兩種方式,題目只用到矩陣的座標,所以沒有對已訪問進行修改,而是使用一個元組進行記錄。其中在求數位時還可以進行一定的優化。

#BFS,一般使用隊列來存儲同級的元素,方便按序逐個篩選。BFS把每走一步的所有可能計算完,才走下一步。
class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        queue, visited,  = [(0, 0)], set()
        while queue:
            i, j = queue.pop(0)
            if i >= m or j >= n or i%10+i//10+j%10+j//10 >k or (i, j) in visited: continue
            visited.add((i,j))
            queue.append((i + 1, j))
            queue.append((i, j + 1))
        return len(visited)
#DFS,類似按照一條道走到最遠,看看能不能找到。
class Solution:
    def movingCount(self, m: int, n: int, k: int) -> int:
        def dfs(i,j):
            if not 0<=i<m or not 0<=j<n or i%10+i//10+j%10+j//10>k or (i,j) in visited:return 0
            visited.add((i,j))
            return 1+dfs(i+1,j)+dfs(i,j+1)#從(0,0)開始只有兩個方向。

        visited = set()
        n = dfs(0,0)
        return n

11.剪繩子,使用動態規劃進行求解,當然也有方法使用通過一定分析後得到的分治法求解。我們發現任何大於 3 的數都可以拆分爲數字 1,2,3 的和,乘4或5...都沒有拆分合算。且它們對 3 的餘數總是0,1,2,因此我們可以僅用 dp[0],dp[1],dp[2] 表示所有大於 3 的值,這樣空間複雜度可降到 O(1),時間複雜度O(N)

動態規劃與其說是一個算法,不如說是一種方法論。該方法論主要致力於將合適的問題拆分成三個子目標一一擊破:

  1. 建立狀態轉移方程
  2. 利用數組緩存並複用以往結果(重要特徵)
  3. 按順序從小往大算
class Solution:
    def cuttingRope(self, n: int) -> int:
        dp = [0 for _ in range(n+1)]
        dp[2] = 1
        for i in range(3,n+1):
            for j in range(i):#後面可優化range(i)爲range(1,4)
                dp[i] = max(dp[i],max((i-j)*j,dp[i-j]*j))#緩存複用,剪掉j不再剪和繼續剪比較
        return dp[n]
優化後:拆分爲1,2,3較合算,模3是爲了節省空間。
class Solution:
    def cuttingRope(self, n: int) -> int:
        dp = [0,1,1]#數組存儲,緩存複用,進行優化,與拆分不同,模3是出於節省空間,且比4好。
        for i in range (3,n+1):
            dp[i%3] = max(max(dp[(i-1)%3],i-1),#拆分爲1,2,3,依次嘗試
                          2*max(dp[(i-2)%3],i-2),  
                          3*max(dp[(i-3)%3],i-3))#比較第一次剪3後,繼續剪(拆分)和不剪得長度。
        return dp[n%3]

12.數值得整數次方,用x =x**2進行優化,使得時間複雜度從單純的O(N)到O(log2n)。一般分治奇偶,但使用數位運算可以統一,&1與>>=1...值得注意x*=x與x=x**2的計算結果相同,但python處理方式不同,前者數大溢出結果變爲inf,後者溢出會報錯。

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 #把冪n用二進制表示,&1
            x*=x
            n>>=1
        return res

13.正則表達式匹配,較爲困難,使用遞歸或DP,遞歸容易理解,時間複雜度較高,DP較爲複雜。

class Solution:
    def isMatch(self, s: str, p: str) -> bool:
        #返回not s,若p爲空,s爲空,則匹配返回True,若p爲空,s不爲空,則不匹配。
        if not p :return not s
        #比較當前的第一個字符,p中的第一個字符肯定不能是*,
        first_match = bool(s and p[0] in {s[0],'.'})
        #當*在第二位p[1]時,有兩種可能:*和*前的沒用,如##與a*##,從p[2:]開始比較。
        #有用,此時f_m比爲真,如aaab和a*b,先丟掉一個,比較aab和a*b,遞歸變爲b和a*b,成爲第一種。
        if len(p)>=2 and p[1] =='*':
            return self.isMatch(s,p[2:]) or first_match and self.isMatch(s[1:],p)
        else:
            return first_match and self.isMatch(s[1:],p[1:])#第二位不爲*時,遞歸

14.合併兩個排序的鏈表,對於鏈表的操作:雙指針,創建新鏈表,創建僞節點(僞節點.next = head)等。

class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        tail = head = ListNode(0)#僞節點,head指向結果的頭,tail一步一步指向尾
        while l1 and l2:
            if l1.val>=l2.val:
                tail.next,l2 = l2,l2.next
            else:
                tail.next,l1 = l1,l1.next
            tail=tail.next
        tail.next = l1 if l1 else l2
        return head.next
class Solution:
    def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:
        if not l1:return l2#逃出遞歸
        if not l2:return l1
        if l1.val>=l2.val:
            head = ListNode(l2.val)
            head.next = self.mergeTwoLists(l1,l2.next)#遞歸項
        else:
            head = ListNode(l1.val)
            head.next = self.mergeTwoLists(l1.next,l2)
        return head#遞歸需要找出逃出遞歸的結果,初始值,和遞歸項。時復較大。

15.樹的子結構,採用遞歸+DFS,首先找到A樹中餘B樹根節點相等的點,然後調用方法進行匹配,標誌res只有在找到相同點,調用匹配方法,且DFS後,B樹爲空,才爲真。

class Solution:
    def isSubStructure(self, A: TreeNode, B: TreeNode) -> bool:
        res = False
        if A and B:#A與B都不能爲空,題目要求
            if A.val == B.val:
                res = self.isSubTree(A,B)
            if not res:#沒有找到子結構繼續找
                res = self.isSubStructure(A.left,B)
            if not res:
                res = self.isSubStructure(A.right,B)
        return res
    def isSubTree(self,root_A,root_B):
        if not root_B:return True#B被匹配完,返回真
        if not root_A:return False
        if root_A.val!=root_B.val:return False
        return self.isSubTree(root_A.left,root_B.left) and self.isSubTree(root_A.right,root_B.right)

16.順時針打印矩陣,一般才用簡單模擬規則的方式,這裏充分利用了pyhon中對list的操作。

class Solution:
    def spiralOrder(self, matrix: List[List[int]]) -> List[int]:
        res = []
        while matrix:#注意
            # 從左到右
            res.extend(matrix.pop(0)) #pop(index)彈出當前的首行。彈出後matrix中沒有此行
            # 從上到下
            for i in range(len(matrix)-1):#注意此處計算當前的matrix的行數
                if matrix[i]:res.append(matrix[i].pop())#彈出每行的最後一個。
            # 從右到左
            if matrix:res.extend(matrix.pop()[::-1])#彈出最後一行,倒序
            # 從下到上
            for i in range(len(matrix)-1,-1,-1):#減到-1跳出
                if matrix[i]:res.append(matrix[i].pop(0))#彈出每行的第一個。
        return res

17.包含min函數的棧,題目中要求包括min()在內的幾個函數的時間複雜度爲O(1),普通的需要O(N)才能解決min(),這裏使用輔助棧。

class MinStack:
    def __init__(self):
       
        self.A,self.B = [],[]#A爲主棧,B爲輔助棧,B棧接收比B棧棧頂還小的數,即當前最小數。
    def push(self, x: int) -> None:
        self.A.append(x)
        if not self.B or self.B[-1]>=x:self.B.append(x) #B爲空或者B中棧頂還小
    def pop(self) -> None:
        if self.A.pop() == self.B[-1]:self.B.pop()#同步B棧
    def top(self) -> int:return self.A[-1]
    def min(self) -> int:return self.B[-1]

18.棧的壓入,彈出序列,題目是判斷一個序列是否可能爲壓入序列的彈出序列。採用模擬規則的方式,由於pushed已經完成,所以必須申請一個新的棧來模仿正在進行操作的pushed。在判定時and具有先後順序,遇到假就返回,全爲真則是真。先判斷正在鑑定的poped和tem都沒有空,然後兩個存在值相同。模擬後,新棧沒有元素則爲真。

class Solution:
    def validateStackSequences(self, pushed: List[int], popped: List[int]) -> bool:
        tem = []
        while pushed:#把pushed中的元素按照一樣的存儲順序給tem
            tem.append(pushed.pop(0))
            while popped and tem and tem[-1] == popped[0]:#注意順序,不然出現超出index錯誤
                popped.pop(0)
                tem.pop()
        return False if tem else True

19.從上到下打印二叉樹,從左至右打印子節點。對於子節點需要儲存,可以使用List棧,collections.deque(),deque雙端隊列,collections是python內建的一個集合模塊,裏面封裝了許多集合類,其中隊列相關的集合只有一個:deque,雙端隊列兩邊都可以增刪元素,作用上相當於兩個list棧。按照題目應該使用BFS,需要儲存同寬度的數據這也是BFS的特點。

class Solution:
    def levelOrder(self, root: TreeNode) -> List[int]:
        if not root:return []
        res,queue = [],[root]#res存儲結果,queue緩存節點。
        while queue:#沒有子節點跳出
            out = []#先加左節點,後加右節點,但要先彈出左節點,因此使用out緩存queue當前點的子節點
            for i in queue:
                res.append(i.val)
                if i.left:out.append(i.left)
                if i.right:out.append(i.right)
            queue = out#out,queue可以使用collection中的deque代替。
        return res

20.二叉搜索樹的後序遍歷序列,判斷序列是否爲二叉搜索樹的後續遍歷(左右根,根節點在最後一位)。二叉搜索樹:左子樹中所有節點的值 < 根節點的值;右子樹中所有節點的值 >>根節點的值;其左、右子樹也分別爲二叉搜索樹。這裏使用遞歸解答。時間複雜度O(N*2),空間複雜度O(N)

class Solution:
    def verifyPostorder(self, postorder: List[int]) -> bool:
        def recur(i,j):
            if i>=j:return True#說明已經遞歸到底,此子樹節點數量≤1 ,無需判別正確性,因此直接返回 
            #上面是終止遞歸的條件,下面是通項操作
            p = i
            while postorder[p]<postorder[j]:p+=1
            m = p#找到第一個大於根節點的元素
            while postorder[p]>postorder[j]:p+=1
            return p == j and recur(i,m-1) and recur(m,j-1)
        return recur(0,len(postorder)-1)

21.二叉樹中和爲某一值的路徑,二叉樹方案搜索問題,使用DFS解決,其包含 先序遍歷 + 路徑記錄 兩部分。由於需要記錄路徑所以申請path[]。回溯是DFS的特點,一般判斷子結構通過返回真假進行回溯,搜索路徑問題通過彈出該點返回上一個狀態進行回溯目的就是爲了保證現有狀態的正確,再去搜索每種可能。時間和空間複雜度都爲O(N)

class Solution:
    def pathSum(self, root: TreeNode, sum: int) -> List[List[int]]:
        res,path = [],[]
        def dfs(root,tar):
            if not root:return None#遍歷每個節點前,爲空,則返回空。
            path.append(root.val)#加入當前遍歷的節點
            tar -= root.val
            if tar == 0 and not root.left and not root.right:
                res.append(list(path))
            dfs(root.left,tar)
            dfs(root.right,tar)
            path.pop()#回溯,保證path中都是正確的路徑。無論已經加入res,嘗試其他,還是返回了空。
        dfs(root,sum)
        return res

22.複雜鏈表的複製,複製有深淺拷貝之分,可使用copy模塊中的copy()和deepcopy()方法,它們都是深拷貝,可以直接retrun copy.deepcopy(head)。複製鏈表可以使用DFS方法。

class Solution:
    def copyRandomList(self, head: 'Node') -> 'Node':
        def dfs(head):
            if not head: return None
            if head in visited:return visited[head]#
            copy = Node(head.val, None, None)# 創建新結點
            visited[head] = copy# 賦值是簡單的引用,複製內存地址,其中一個改變另外一個也會改變。
            copy.next = dfs(head.next)
            copy.random = dfs(head.random)
            return copy#DFS必定會向上一級遞歸返回,回溯
        visited = {}#添加是否被訪問過的標識字典。原鏈表的節點head爲索引,相應新節點爲值
        return dfs(head)

23.二叉搜索樹與雙向鏈表,將二叉搜索樹變爲排序後的循環雙向鏈表。可以知道,二叉搜索樹的中序遍歷是升序的遍歷。同時將樹節點的指向左兒子的指針變爲指向前驅,指向右兒子的指針指向後繼。時間空間:O(N)

class Solution:#方法嵌套要比分開寫要好理解一點兒。父方法主流程,子方法遍歷
    def treeToDoublyList(self, root: 'Node') -> 'Node':
        def dfs(cur):#中序遍歷
            if not cur: return
            dfs(cur.left) # 遞歸左子樹
            if self.pre: # 修改節點引用
                self.pre.right, cur.left = cur, self.pre # 右孩子指針指向後繼
            else: # 爲空,則記錄頭節點
                self.head = cur
            self.pre = cur # 保存 cur
            dfs(cur.right) # 遞歸右子樹
        
        if not root: return
        self.pre = None # 先申請一個全局變量pre,指向當前節點的前驅
        dfs(root)
        self.head.left, self.pre.right = self.pre, self.head # 添加首尾循環
        return self.head

24.字符串的排列,不重複地返回該字符串的全排列。首先,可以考慮DFS的方法,逐一嘗試,同時記得要回溯。將該字符串分爲兩個部分,一個是當前位,當前位後面的每一位依次和當前爲互換,知道當前位爲最後一位記錄結果,然後回溯。注意,要在例如“abb”中剪枝掉重複的。即在當前位爲a時,後面的第一個b可以和a互換,但第二個b不能與a互換。此處可以設置一個元組,使得當前位置上,每個字符只能出現一次。第一個b和a交換後,首位出現過b了,將b加入元組,則後面的b不能在首位。時間O(N!),空間O(N*2)。

class Solution:
    def permutation(self, s: str) -> List[str]:
        c,res = list(s),[] # 相對dfs爲全局變量。
        def dfs(x): # x 爲當前位,當前位後面的位依次和當前位交換,當前位前的固定
            if x == len(c)-1: # 完成當前方案
                res.append(''.join(c)) # ''.join(c)將list的c轉化爲一個字符串
                return None
            dic=set() # 注意dic元組爲局部變量
            for i in range(x,len(c)): 
                if c[i] in dic:continue # i的範圍爲[當前位,最後位]且全排列
                dic.add(c[i])
                c[x],c[i] = c[i],c[x] # 進行交換
                dfs(x+1) # 交換後DFS下一位
                c[x],c[i] = c[i],c[x] # 回溯
        dfs(0)
        print(res)
        return res

25.數組中出現次數超過一半的數字,找出一個數組中出現次數超過長度一般的數,容易想到的方法有:

哈希表統計法: 遍歷數組 nums ,用 HashMap 統計各數字的數量,最終超過數組長度一半的數字則爲衆數。此方法時間和空間複雜度均爲 O(N)。
數組排序法: 將數組 nums 排序,由於衆數的數量超過數組長度一半,因此 數組中點的元素 一定爲衆數。此方法時間複雜度 O(N log2 N)
摩爾投票法: 核心理念爲 “正負抵消” ;時間和空間複雜度分別爲 O(N) 和 O(1);是本題的最佳解法。注:核心就是減少數組的長度,最後減少到只剩衆數(定義衆數個數超過一半)。設真正的衆數爲x,當備選衆數不爲x,則減少了數組長度,減少長度中不超過一半得衆數,則後面衆數仍然超過一半。當備選爲x,後面的數則不是x,抵消一半衆數。

class Solution:
    def majorityElement(self, nums: List[int]) -> int:
        votes, count = 0, 0
        for num in nums:
            if votes == 0: x = num # 正負抵消後,設置當前首字符爲備選衆數
            votes += 1 if num == x else -1 # 是備選衆數的爲正,不是爲負。
        # 驗證數組中是否有衆數,若確定有,後面可以省略
        for num in nums:
            if num == x: count += 1
        return x if count > len(nums) // 2 else 0

26.數據流中的中位數,方法一:我們將添加的數保存在數組中,返回中位數時,只需將數組排序,返回中間位置數即可,時間O(nlogn),空間O(n)。

方法二:方法一的缺點在於對數組進行了排序操作,導致時間複雜度較高,假如每次插入一個值前數組已經排好序了呢?這樣我們只需考慮每次將值插在合適的位置即可,所以使用二分查找來找到這個合適的位置,會將時間複雜度降低到O(n)(查找: O(logn),插入: O(n),將其他位往後移動),使用 bisect.insort_left(self.store, num) 插入每個數,bisect是python模塊

方法三:通過大小頂堆,Python 中 heapq 模塊是小頂堆。實現 大頂堆 方法: 小頂堆的插入和彈出操作均將元素 取反 即可。取中位數只需要排序數組的中間兩位,大小頂堆就是確保中間兩位的正確,不管其它位,即保存較大部分的小頂堆的最小位和保存較小部分的大頂堆的最大位。每次加進去一個數,重點是對兩個頂堆都要進行調整。單一從一個堆開始插入,這樣無法利用起來兩個堆,因此交叉對兩個進行操作,先對B插入,彈出B的堆頂到A,下一次插入反過來。

from heapq import *
class MedianFinder:
    def __init__(self):
        self.A = [] # 小頂堆,保存較大的一半
        self.B = [] # 大頂堆,保存較小的一半
    def addNum(self, num: int) -> None:#
        if len(self.A) != len(self.B):
            heappush(self.A, num)
            heappush(self.B, -heappop(self.A))
        else:
            heappush(self.B, -num)
            heappush(self.A, -heappop(self.B))
    def findMedian(self) -> float:
        return self.A[0] if len(self.A) != len(self.B) else (self.A[0] - self.B[0]) / 2.0

27.1~n整數中1出現的次數,遞歸,f(n))函數的意思是1~n這n個整數的十進制表示中1出現的次數,將n拆分爲兩部分,最高一位的數字high和其他位的數字last,分別判斷情況後將結果相加。high爲1時,假如n爲1234,遞歸尋找[1-999]中的+確定最高位1後[1001-1234](==last即[001-234])+在[1001-1234]確定最高位1的次數+1000(100,10,1)中的1。high爲其他,類似,分別遞歸,最後+1000-1999(100-199,10-19)最高位的1的次數。

class Solution:
    def countDigitOne(self, n: int) -> int:
        if n <= 0:return 0
        nums = str(n)
        high = int(nums[0])
        pows = 10**(len(nums)-1)
        last = n - pows*high
        if high == 1:
            return self.countDigitOne(pows-1)+self.countDigitOne(last)+last+1
        else:
            return self.countDigitOne(pows-1)*high+self.countDigitOne(last)+pows

28.把數組排成最小數,將一個數組中的所有數來組成一個最小的數。本質上是一種排序,與普通的數值大小排序不同,本題需要的是一種'x'+'y'<'y'+'x',則'x'應該排在'y'前。可以使用改編快排或標準排序。修改的標準排序使用了functools.cmp_to_keyfunc ),將舊式的comparison函數轉換位key函數,即轉換爲sort()能接受的key函數。還有回溯的方法

#快排,O(nlogn),每次只確定一個數的位置,下面只修改了while中的比較,每次strs[l]爲標準
class Solution:
    def minNumber(self, nums: List[int]) -> str:
        def fast_sort(l,r):
            if l>=r:return None
            i,j=l,r 
            while i<j:
                while strs[j]+strs[l]>=strs[l]+strs[j] and i<j:j-=1 #strs[j]+strs[l]放在後面
                while strs[i]+strs[l]<=strs[l]+strs[i] and i<j:i+=1
                strs[i],strs[j] = strs[j],strs[i] #找到錯位的i,j,互換
            strs[l],strs[i] = strs[i],strs[l] #當i==j時,該位置即爲標準的位置。
            fast_sort(l,i-1)
            fast_sort(i+1,r)
        strs = [str(num) for num in nums ]
        fast_sort(0,len(strs)-1)
        return ''.join(strs)

29.把數字翻譯成字符串,dp,時間O(n),空間採用取餘也省去dp數組,O(1)。由於本題動態具有對稱,取餘可以看作從右到左統計。當前狀態依賴前面的兩個狀態,可以看出採用dp

class Solution:
    def translateNum(self, num: int) -> int:
        a = b = 1 # 倒數一位和倒數兩位的狀態都爲1,即倒數一位和倒數兩位組成的都只有一種翻譯
        y = num % 10 # y初始化爲個位
        while num != 0:
            num //= 10
            x = num % 10 # 在y前面一位
            tmp = 10 * x + y
            c = a + b if 10 <= tmp <= 25 else a # dp狀態轉移方程
            a, b = c, a # a在前,b在後
            y = x
        return a

30.禮物的最大值,動態規劃,從左邊最上角道右邊最下角所取得禮物最大值,只能向下或者向右。

class Solution:
    def maxValue(self, grid: List[List[int]]) -> int:
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if i == 0 and j == 0: continue
                if i == 0: grid[i][j] += grid[i][j - 1] # 首行的只可能來自旁邊的
                elif j == 0: grid[i][j] += grid[i - 1][j] # 首列的只可能來自上面的
                else: grid[i][j] += max(grid[i][j - 1], grid[i - 1][j]) # 一般的狀態轉移
        return grid[-1][-1]

31.最長不含重複字符的子字符串,哈希+雙指針,也可以使用哈希+dp,

class Solution:
    def lengthOfLongestSubstring(self, s: str) -> int:
        dic, res, i = {}, 0, -1
        for j in range(len(s)):
            if s[j] in dic: # 當出現重複,更新左指針 i
                i = max(dic[s[j]], i) #dic[s[j]],即s[j]上一次出現可能在i-j內,也可能之外
            dic[s[j]] = j # 哈希表記錄
            res = max(res, j - i) # 更新結果,找到最長
        return res

32.醜數,dp ,只包含因子 2、3 和 5 的數稱作醜數。因此,醜數 = 某較小丑數× 某因子(2,3,5),則醜數序列的首個元素開始 *2 or*3 or*5 來計算, 醜數序列也是不會產生漏解的。

合併 3 個有序序列, 最簡單的方法就是每一個序列都各自維護一個指針, 然後比較指針指向的元素的值, 將最小的放入最終的合併數組中, 並將相應指針向後移動一個元素。

class Solution:
    def nthUglyNumber(self, n: int) -> int:
        dp,a,b,c = [1]*n,0,0,0
        for i in range(1,n):
            n2,n3,n5 = dp[a]*2,dp[b]*3,dp[c]*5 # 三個指針,即三個數組
            dp[i] = min(n2,n3,n5)
            if dp[i] == n2: a+=1 # 採用的if...if...if...可以在合併時跳過重複解
            if dp[i] == n3: b+=1
            if dp[i] == n5: c+=1
        return dp[-1]

33.數組中的逆序對,可以使用暴力法,也就是 O(N^2) 將所有可能枚舉出來,如果滿足逆,則 cnt+1,最後返回 cnt 即可。也可以使用歸併排序對其進行優化。因爲逆序對是前面的數大於後面的數就可以作爲逆序對,因此,歸併排序中歸分完後,進行合併時,判斷前後指針指向數的大小,如果前指針指向的數比後指針指向的數大,則前指針後面的數都比後指針指向的數大,後指針在後移前將該數加入temp中。後指針指向的數對於逆序對的貢獻則爲mid-i+1。時間O(nlogn),空間O(n)。

class Solution:
    def reversePairs(self, nums: List[int]) -> int:
        self.cnt = 0
        def merge(nums, start, mid, end, temp):
            i, j = start, mid + 1
            while i <= mid and j <= end:
                if nums[i] <= nums[j]:
                    temp.append(nums[i]) # 將小的加入temp
                    i += 1
                else:
                    self.cnt += mid - i + 1 # nums[j]對逆序對數的貢獻爲 mid-i+1
                    temp.append(nums[j])
                    j += 1
            while i <= mid:
                temp.append(nums[i])
                i += 1
            while j <= end:
                temp.append(nums[j])
                j += 1
            
            for i in range(len(temp)): # 修改nums中的排序,是的下次遞歸兩部分是有序的
                nums[start + i] = temp[i]
            temp.clear()
                    
        def mergeSort(nums, start, end, temp): # 歸併排序中的“歸”,劃分到最細粒度
            if start >= end: return
            mid = (start + end) >> 1
            mergeSort(nums, start, mid, temp) #避免每次都開闢一個新的temp,因此也當作參數傳入
            mergeSort(nums, mid + 1, end, temp)
            merge(nums, start, mid,  end, temp) # 歸併排序中的“並”
        mergeSort(nums, 0, len(nums) - 1, [])
        return self.cnt

34.數組中數字出現的次數,在數組中有兩個數只出現了一次,其餘的數出現了兩次,找出一次的數,要求在時間O(n),空間O(1)下完成。若沒有要求空間必須在O(1),可以採用一下方法:

#空間爲O(n),第一種採用hash表字典,刪掉次數爲2的。
class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        cnt_dict = {}
        for num in nums:
            if cnt_dict.get(num):# num=1則執行刪除,即已經加入字典的再次遇到就刪除。
                del cnt_dict[num]
            else:cnt_dict[num] = 1
        return list(cnt_dict.keys())# 返回字典鍵值,轉化爲list。
# 第二種直接使用庫函數,返回一個統計次數的字典。
class Solution(object):
    def singleNumber(self, nums):    
        count = collections.Counter(nums) # count爲返回的字典。
        res = []
        for num, c in count.items():
            if c == 1:res.append(num)
        return res

若按照要求空間O(1),則需要使用位運算異或,將 a 和 b 的二進制每一位進行運算,得出的數字。 運算的邏輯是如果同一位的數字相同則爲 0,不同則爲 1。由於a^a=0,a^0=a,a^b=b^a。則可以知道一個數組中假設只有一個數只出現一次,其他都是兩次,如:[a,a,b,b,c,d,d],則數組元素異或結果爲只出現一次的c。題目中說數組中有兩個出現一次的數,則考慮分批異或,但需要找到把數組分批的標準。

class Solution:
    def singleNumbers(self, nums: List[int]) -> List[int]:
        ret,a,b = 0,0,0  # ret爲所有數字異或的結果,a,b爲分批異或結果。
        for n in nums:
            ret ^= n # 一樣的數字抵消,出現一次的兩個數字異或後必定不爲0,爲1則這兩個數該位不同。
        # 找到ret中爲1的最低位,該位作爲分批標準
        h = 1 # h = 0000 0001 使用mask,一位一位的找。
        while(ret & h == 0): 
            h <<= 1 # 左移一位
        # 也可以使用 h = ret & (-ret),假如ret = 01100,則取反加1得-ret = 10100
        for n in nums:
            # 根據該位的不同,將整個數組分爲兩組
            if (h & n == 0): # 和mask進行與計算
                a ^= n
            else:
                b ^= n
        return [a, b]

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章