《劍指offer》 刷題記錄(Python)

本博客同時發佈於個人主頁:www.doctorsrn.cn

《劍指offer》 刷題記錄

最近使用Python把《劍指offer》刷了一遍,自己能第一時間有想法的題目就直接寫,沒有思路的題目就看懂書上的思路和參考其他開源的實現後再自己寫一遍。主要以牛客網《劍指offer》作爲在線評測網站,有些題目牛客網沒有的再找其他網站進行在線評測,主要使用的其他網站有:

刷題過程主要參考的開源實現有:

本博客對應的代碼倉庫在此。

今年企業縮招,找工作的行情不容樂觀,秋招路漫漫啊。

3.數組中重複數字

在一個長度爲n的數組裏的所有數字都在0到n-1的範圍內。 數組中某些數字是重複的,但不知道有幾個數字是重複的。也不知道每個數字重複幾次。請找出數組中任意一個重複的數字。 例如,如果輸入長度爲7的數組{2,3,1,0,2,5,3},那麼對應的輸出是第一個重複的數字2。

## 方法一:排序,然後查找
## 時間複雜度:O(nlog n)  空間複雜度:*
# -*- coding:utf-8 -*-
class Solution:
    # 這裏要特別注意~找到任意重複的一個值並賦值到duplication[0]
    # 函數返回True/False
    def duplicate(self, numbers, duplication):
        # write code here
        if not numbers or len(numbers) <= 1:
            return False
        for i in numbers:
            if i<0 or i > len(numbers)-1:
                return False
        numbers_sorted = self.quickSort(numbers)
        for i,j in zip(numbers_sorted[:-1],numbers_sorted[1:]):
            if i == j:
                duplication[0] = i
                return True
         
        return False
 
         
    def quickSort(self, arr):
        if len(arr) <= 1:
            return arr
         
        length = len(arr)
        pivot = arr[length // 2]
         
        left = [x for x in arr if x > pivot]
        right = [x for x in arr if x < pivot]
        mid = [x for x in arr if x == pivot]
        
        # 升序排列可以通過,降序排列無法通過全部測試樣例
        return self.quickSort(right) + mid + self.quickSort(left)
        #return self.quickSort(left) + mid + self.quickSort(right)
## 方法二:引入hash表(字典)
## 時間複雜度:O(n)  空間複雜度:O(n)
# -*- coding:utf-8 -*-
class Solution:
    # 這裏要特別注意~找到任意重複的一個值並賦值到duplication[0]
    # 函數返回True/False
    def duplicate(self, numbers, duplication):
        # write code here
        if not numbers or len(numbers) <= 1:
            return False
        for i in numbers:
            if i < 0 or i > len(numbers) - 1:
                return False
             
        dic = {}
        for i in numbers:
            if dic.has_key(i):
                duplication[0] = i
                return True
            else:
                dic[i]=1
        return False

## 方法三:利用數字自身下標和值的key-value特性
## 時間複雜度:O(n)  空間複雜度:O(1)
# -*- coding:utf-8 -*-
class Solution:
    # 這裏要特別注意~找到任意重複的一個值並賦值到duplication[0]
    # 函數返回True/False
    def duplicate(self, numbers, duplication):
        # write code here
        if not numbers or len(numbers) <= 1:
            return False
        for i in numbers:
            if i < 0 or i > len(numbers) - 1:
                return False
        i = 0
        while i < len(numbers):
            if i == numbers[i]:
                i += 1
            elif numbers[i] == numbers[numbers[i]]:
                duplication[0] = numbers[i]
                return True
            else:
                tmp = numbers[i]
                numbers[i] = numbers[tmp]
                numbers[tmp] = tmp
        return False

4.二維數組的查找

在一個二維數組中(每個一維數組的長度相同),每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數

# 方法一:暴力遍歷法
# 時間複雜度:O(n^2)
# -*- coding:utf-8 -*-
class Solution:
    # array 二維列表
    def Find(self, target, array):
        # write code here
        if len(array) < 1:
            return False
        
        for i in array:
            for j in i:
                if target == j:
                    return True
        
        return False

# 方法二:反向剔除不滿足元素法:從右上角進行查找
# -*- coding:utf-8 -*-
class Solution:
    # array 二維列表
    def Find(self, target, array):
        # write code here
        if len(array) < 1 or len(array[0]) < 1:
            return False
        
        r_l = len(array)
        c_l = len(array[0])
        
        # 從右上角開始搜索
        i, j = 0, c_l-1
        while target != array[i][j]:
            if target > array[i][j]:
                if i < r_l - 1:
                    i += 1
                else:
                    return False
            else:
                if j > 0:
                    j -= 1
                else:
                    return False
        return True

5.替換空格

請實現一個函數,將一個字符串中的每個空格替換成“%20”。例如,當字符串爲We Are Happy.則經過替換之後的字符串爲We%20Are%20Happy。

# 方法一:遍歷判斷然後替換法,時間複雜度O(n)
# -*- coding:utf-8 -*-
class Solution:
    # s 源字符串
    def replaceSpace(self, s):
        # write code here
        if not isinstance(s, str) or len(s) < 1 or s == None:
            return s
        tmp = []
        for i in s:
            if i != ' ':
                tmp.append(i)
            else:
                tmp.append("%20")
        
        res = ''.join(tmp)
        return res
# 方法二:從後向前遍歷替換法,時間複雜度O(n)
# -*- coding:utf-8 -*-
class Solution:
    # s 源字符串
    def replaceSpace(self, s):
        # write code here
        if not isinstance(s, str) or len(s) < 1 or s == None:
            return ''
        
        spaceN = 0 
        for i in s:
            if i == ' ':
                spaceN += 1
        total = len(s) + 2 * spaceN
        newStr = [None] * total
        
        indexO, indexN = len(s)-1, total-1
        
        while indexO >= 0:
            if s[indexO] == ' ':
                newStr[indexN]='0'
                newStr[indexN-1]='2'
                newStr[indexN-2]='%'
                indexN -= 3
                indexO -= 1
            else:
                newStr[indexN] = s[indexO]
                indexN -= 1
                indexO -= 1
        return ''.join(newStr)

6.從尾到頭打印鏈表

輸入一個鏈表,按鏈表值從尾到頭的順序返回一個ArrayList。

# 方法一: 使用棧
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # 返回從尾部到頭部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        if listNode == None:
            return []
        if listNode.next == None:
            return listNode.val
        
        stack = []
        nextNode = listNode
        while nextNode.next != None:
            stack.append(nextNode.val)
            nextNode = nextNode.next
        
        stack.append(nextNode.val)
        
        return stack[::-1]
# 方法二:遞歸的方法

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    # 返回從尾部到頭部的列表值序列,例如[1,2,3]
    def printListFromTailToHead(self, listNode):
        # write code here
        if not listNode:
            return []
        if listNode.next == None:
            res = []
            res.append(listNode.val)
            return res
        re = self.printListFromTailToHead(listNode.next)
        re.append(listNode.val)
        return re

L=[]
l=ListNode(1)
t = l
for i in range(4):
    t.next = ListNode(i)
    t = t.next

# while l.next != None:
#     print(l.val)
#     l = l.next
# print(l.val)
s = Solution()
s.printListFromTailToHead(l)

7.重建二叉樹

輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建出該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重複的數字。例如輸入前序遍歷序列{1,2,4,7,3,5,6,8}和中序遍歷序列{4,7,2,1,5,3,8,6},則重建二叉樹並返回。

# 遞歸法
# 按照前序遍歷和中序遍歷的特點進行遞歸重建
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回構造的TreeNode根節點
    def reConstructBinaryTree(self, pre, tin):
        # write code here
        # 有效性判斷
        if set(pre) != set(tin):
            return None
        
        # 遞歸終止條件
        if len(pre) < 1  or len(tin) < 1:
            return None
        if len(pre) == 1 or len(tin) == 1:
            return TreeNode(pre[0])
        
        root = pre[0]
        root_index = tin.index(root)
        L_tin, R_tin = tin[:root_index], tin[root_index+1:]
        L_pre, R_pre = pre[1:1+len(L_tin)], pre[1+len(L_tin):]
        
        # 構建左樹
        L_child = self.reConstructBinaryTree(L_pre, L_tin)
        
        # 構建右樹
        R_child = self.reConstructBinaryTree(R_pre, R_tin)
        
        node = TreeNode(root)
        node.left, node.right = L_child, R_child
        
        return node

8.二叉樹的下一個節點

給定一個二叉樹和其中的一個結點,請找出中序遍歷順序的下一個結點並且返回。注意,樹中的結點不僅包含左右子結點,同時包含指向父結點的指針。

# -*- coding:utf-8 -*-
class TreeLinkNode:
    def __init__(self, x):
        self.val = x
        self.left = None
        self.right = None
        self.next = None
# -*- coding:utf-8 -*-
# class TreeLinkNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#         self.next = None
class Solution:
    def GetNext(self, pNode):
        # write code here
        if not pNode:
            return pNode
        
        # 如果存在右子節點,則下一個節點是右子節點中的最左節點
        if pNode.right:
            pNode = pNode.right
            while pNode.left:
                pNode = pNode.left
            return pNode
        # 如果不存在右子節點,則反向根據父節點和子節點的關係確定
        else:
            pC = pNode  # 記錄當前節點
            # 當父節點不爲根節點時,如果當前節點是父左子節點,返回當前節點
            while pNode.next: 
                pN = pNode.next
                if pN.left and pN.left == pNode:
                    return pN
                else:
                    pC = pNode
                    pNode = pN
            
            # 當父節點是根節點時,如果當前節點是根節點的左子節點,則返回根節點
            if pNode.left and pNode.left == pC:
                return pNode
            else:
                return None

a = TreeLinkNode('a')
b = TreeLinkNode('b')
c = TreeLinkNode('c')
d = TreeLinkNode('d')
e = TreeLinkNode('e')
f = TreeLinkNode('f')
g = TreeLinkNode('g')
h = TreeLinkNode('h')
i = TreeLinkNode('i')
a.left, a.right = b, c
b.left, b.right, b.next = d, e, a
c.left, c.right, c.next = f, g, a
d.next = b
e.left, e.right, e.next = h, i, b
h.next = e
i.next = e
f.next, g.next = c, c

s = Solution()
print(s.GetNext(i).val)

9.用兩個棧實現隊列

用兩個棧來實現一個隊列,完成隊列的Push和Pop操作。 隊列中的元素爲int類型。

# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack1 = [] # 用於push
        self.stack2 = [] # 用於pop
    def push(self, node):
        # write code here
        self.stack1.append(node)
    def pop(self):
        if len(self.stack1) == 0 and len(self.stack2) == 0:
            return None
        # stack2不爲空時,彈出棧頂元素即爲隊列頭部元素
        elif len(self.stack2) > 0:  
            res = self.stack2.pop()
            return res
        else:
            # stack2爲空時,將stack1元素彈出後壓入stack2,則stack2棧頂元素即爲隊列頭部元素
            while len(self.stack1) > 0: 
                i = self.stack1.pop()
                self.stack2.append(i)
            res = self.stack2.pop()
            return res

10. 斐波那契數列

大家都知道斐波那契數列,現在要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0)。
n<=39

f(n)={0n=01n=1f(n1)+f(n2)n&gt;1 f(n) = \begin{cases} 0 &amp; n = 0 \\ 1 &amp; n = 1 \\ f(n-1)+f(n-2) &amp; n &gt; 1 \end{cases}

# 方法一:循環法實現
# 時間複雜度爲:O(n)
# 自下而上的求解
# -*- coding:utf-8 -*-
class Solution:
    def Fibonacci(self, n):
        # write code here
        if not isinstance(n,int) or n < 0 or n > 39:
            return None
        if n == 0:
            return 0
        if n == 1:
            return 1
        
        fn1, fn2 = 1, 0
        for i in range(2,n+1):
            fn = fn1 + fn2
            fn2 = fn1
            fn1 = fn
        
        return fn
# 方法二:遞歸法實現----運行超時
# 時間複雜度爲n的指數級
# 自上而下的求解
# -*- coding:utf-8 -*-
class Solution:
    def Fibonacci(self, n):
        # write code here
        if not isinstance(n, int) or n < 0 or n > 39:
            return 0
        
        if n == 0:
            return 0
        if n == 1:
            return 1
        
        return self.Fibonacci(n-1) + self.Fibonacci(n-2)

方法三:時間複雜度爲O(log&ThinSpace;n)O(log \, n)的不實用解法

可證明存在如下公式:
[f(n)f(n1)f(n1)f(n2)]=[1110]n1\begin{bmatrix} f(n)&amp; f(n-1)\\ f(n-1)&amp; f(n-2) \end{bmatrix} = \begin{bmatrix} 1 &amp; 1 \\ 1 &amp; 0 \end{bmatrix}^{n-1}
利用上述公式可以根據[1110]n1\begin{bmatrix} 1 &amp; 1 \\ 1 &amp; 0 \end{bmatrix}^{n-1}求得f(n)f(n)

# 方法一:更簡潔的循環實現

# -*- coding:utf-8 -*-
class Solution:
    def Fibonacci(self, n):
        # write code here
        tempArray = [0,1]
        if n >= 2:
            for i in range(2, n+1):
                tempArray[i%2] = tempArray[0] + tempArray[1]
        return tempArray[n%2]

10.2 跳臺階問題

一隻青蛙一次可以跳上1級臺階,也可以跳上2級。求該青蛙跳上一個n級的臺階總共有多少種跳法(先後次序不同算不同的結果)。

**分析:**設跳法是臺階數n的函數f(n), n>2時第一次跳有兩種跳法,跳一個臺階則共f(n-1)種,跳兩個臺階則共f(n-2)種,所以總共f(n)=f(n-1)+f(n-2)

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloor(self, number):
        # write code here
        if number < 1:
            return 0
        if number == 1:
            return 1
        if number == 2:
            return 2
        
        fn1 = 2
        fn2 = 1
        for i in range(3, number+1):
            fn = fn1 + fn2
            
            fn2 = fn1
            fn1 = fn
        return fn

10.3 變態跳臺階問題

一隻青蛙一次可以跳上1級臺階,也可以跳上2級……它也可以跳上n級。求該青蛙跳上一個n級的臺階總共有多少種跳法。

**解析:**類比普通跳臺階問題,設跳法是臺階數n的函數f(n), 則有:
f(n)=f(n1)+f(n2)+...+f(1)+1f(n)=f(n-1)+f(n-2)+...+f(1)+1
遞推可得:f(n1)=f(n2)+f(n3)+...+f(1)+1f(n-1)=f(n-2)+f(n-3)+...+f(1)+1
兩式相減可得:f(n)f(n1)=f(n1)f(n)-f(n-1)=f(n-1)
f(n)=2f(n1)f(n)=2f(n-1),即f(n)f(n)是公比爲2的等比級數:f(n)=2n1f(n)=2^{n-1}

# -*- coding:utf-8 -*-
class Solution:
    def jumpFloorII(self, number):
        # write code here
        if number < 1:
            return 0
        fn = 1
        if number == 1:
            return fn
        for _ in range(2,number+1):
            fn *= 2
        
        return fn

10.4 矩形覆蓋問題

我們可以用21的小矩形橫着或者豎着去覆蓋更大的矩形。請問用n個21的小矩形無重疊地覆蓋一個2*n的大矩形,總共有多少種方法?

# -*- coding:utf-8 -*-
class Solution:
    def rectCover(self, number):
        # write code here
        if number < 1 or not isinstance(number, int):
            return 0
        
        if number == 1:
            return 1
        if number == 2:
            return 2
        
        fn1 = 2
        fn2 = 1
        for i in range(3, number+1):
            fn = fn1 + fn2
            
            fn2 = fn1
            fn1 = fn
        
        return fn

11.旋轉數組中的最小數字

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之爲數組的旋轉。 輸入一個非減排序的數組的一個旋轉,輸出旋轉數組的最小元素。 例如數組{3,4,5,1,2}爲{1,2,3,4,5}的一個旋轉,該數組的最小值爲1。 NOTE:給出的所有元素都大於0,若數組大小爲0,請返回0。

# 方法一:暴力遍歷法--不推薦
# 時間複雜度爲:O(n)
# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if len(rotateArray) < 1:
            return 0
        small = rotateArray[0]
        for i in rotateArray:
            if small > i:
                small = i
                return small
        return small
# 方法二:二分法
# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if len(rotateArray) < 1:
            return 0
        if len(rotateArray) == 1:
            return rotateArray[0]
        
        index = len(rotateArray) // 2  # index >= 1
        base = len(rotateArray) // 2
        while 0<= base < len(rotateArray):
            if rotateArray[base-1] > rotateArray[base]:  # 如果前一個大於當前數,則當前數爲最小
                return rotateArray[base]
            elif base + 1 < len(rotateArray) and rotateArray[base] > rotateArray[base+1]: # 如果後一個數存在且小於當前數,則後一個數爲最小
                return rotateArray[base+1]
            else:
                # 否則重新搜索,以數組第一個數爲基準移動索引
                if rotateArray[base] > rotateArray[0]:
                    base += index // 2
                else:
                    base -= index // 2
                index //= 2
        
        return rotateArray[base]

## 這個方法存在問題,當針對[1,1,1,1,1,0,1]時無法正常判斷,所以應該修改爲當base與左右值相等時採用順序查找的方法搜索最小值
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if len(rotateArray) < 1:
            return 0
        if len(rotateArray) == 1:
            return rotateArray[0]
        
        index = len(rotateArray) // 2  # index >= 1
        base = len(rotateArray) // 2
        while 0<= base < len(rotateArray):
            if rotateArray[base-1] > rotateArray[base]:  # 如果前一個大於當前數,則當前數爲最小
                return rotateArray[base]
            elif base + 1 < len(rotateArray) and rotateArray[base] > rotateArray[base+1]: # 如果後一個數存在且小於當前數,則後一個數爲最小
                return rotateArray[base+1]
            else:
                # 否則重新搜索,以數組第一個數爲基準移動索引
                if rotateArray[base] > rotateArray[0]:
                    base += index // 2
                elif rotateArray[base] < rotateArray[0]:
                    base -= index // 2
                else:  # 順序查找
                    minNum = self.minSearch(rotateArray)
                    return minNum
                index //= 2
        
        return rotateArray[base]
    def minSearch(self, rotateArray):
        small = rotateArray[0]
        for i in rotateArray:
            if small > i:
                small = i
                return small
        return small
# 方法三:二分法示例代碼
# -*- coding:utf-8 -*-
class Solution:
    def minNumberInRotateArray(self, rotateArray):
        # write code here
        if len(rotateArray) < 1:
            return 0
        if len(rotateArray) == 1:
            return rotateArray[0]
        # 雙指針
        first = 0
        end = len(rotateArray)-1
        while end - first != 1:
            mid = (first + end) // 2
            if rotateArray[first] == rotateArray[mid] == rotateArray[end]: # 特殊情況,使用順序查找
                minN = rotateArray[0]
                for i in rotateArray[1:]:
                    if i < minN:
                        minN = i
                return minN
            elif rotateArray[first] < rotateArray[end]:  # 特殊情況,整個數組是升序,沒有進行旋轉
                return rotateArray[first]
            elif rotateArray[mid] >= rotateArray[first]:
                first = mid
            else:
                end = mid
        return rotateArray[end]

12.矩陣中的路徑

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。如果一條路徑經過了矩陣中的某一個格子,則之後不能再次進入這個格子。 例如 [abcesfcsadee] \begin{bmatrix} a &amp; b &amp; c &amp; e \\ s &amp; f &amp; c &amp; s \\ a &amp; d &amp; e &amp; e \end{bmatrix} 這樣的3 X 4 矩陣中包含一條字符串"bcced"的路徑,但是矩陣中不包含"abcb"路徑,因爲字符串的第一個字符b佔據了矩陣中的第一行第二個格子之後,路徑不能再次進入該格子。

# 回溯法--使用遞歸實現
# -*- coding:utf-8 -*-
class Solution:
    def hasPath(self, matrix, rows, cols, path):
        # write code here
        if len(matrix) < 1 or rows < 1 or cols < 1 or len(path) < 1:
            return False
        
        visited = [0] * (rows * cols)
        pathL = 0
        for row in range(rows):
            for col in range(cols):
                if self.if_has_path(matrix, rows, cols, path, row, col, visited, pathL):
                    return True
        return False
    
    def if_has_path(self, matrix, rows, cols, path, row, col, visited, pathL):
        # 遞歸終止條件
        if pathL == len(path):
            return True
        hasPath = False
        if 0 <= row < rows and 0 <= col < cols and \
            matrix[row * cols + col] == path[pathL] and \
            not visited[row * cols + col]:
                pathL += 1
                visited[row * cols + col] = 1
                
                hasPath = self.if_has_path(matrix, rows, cols, path, row-1, col, visited, pathL) or \
                          self.if_has_path(matrix, rows, cols, path, row+1, col, visited, pathL) or \
                          self.if_has_path(matrix, rows, cols, path, row, col-1, visited, pathL) or \
                          self.if_has_path(matrix, rows, cols, path, row, col+1, visited, pathL)
                if not hasPath:
                    pathL -= 1
                    visited[row * cols + col] = 0
                
        return hasPath

13.機器人的運動範圍

地上有一個m行和n列的方格。一個機器人從座標0,0的格子開始移動,每一次只能向左,右,上,下四個方向移動一格,但是不能進入行座標和列座標的數位之和大於k的格子。 例如,當k爲18時,機器人能夠進入方格(35,37),因爲3+5+3+7 = 18。但是,它不能進入方格(35,38),因爲3+5+3+8 = 19。請問該機器人能夠達到多少個格子?

# 回溯法
# -*- coding:utf-8 -*-
class Solution:
    def movingCount(self, threshold, rows, cols):
        # write code here
        if threshold < 1 or rows < 1 or cols < 1:
            return 0
        visited = [0] * (rows * cols)    # 記錄當前位置是否訪問過
        available = [0] * (rows * cols)  # 記錄當前位置是否可達

        self.ifAvailable(threshold, rows, cols, 0, 0, visited, available)
        
        return sum(available)
            
    def ifTrue(self, row, col, threshold):
        res = 0
        while row != 0:
            res += row % 10
            row //= 10
        while col != 0:
            res += col % 10
            col //= 10
        
        if res <= threshold:
            return True
        else:
            return False
        
    def ifAvailable(self, threshold, rows, cols, row, col, visited, available):
        if 0 <= row < rows and 0 <= col < cols and self.ifTrue(row, col, threshold) and \
            not visited[row * cols + col]:
            available[row * cols + col] = 1
            visited[row * cols + col] = 1
            self.ifAvailable(threshold, rows, cols, row-1, col, visited, available)
            self.ifAvailable(threshold, rows, cols, row+1, col, visited, available)
            self.ifAvailable(threshold, rows, cols, row, col-1, visited, available)
            self.ifAvailable(threshold, rows, cols, row, col+1, visited, available)
        else:
            # visited[row * cols + col] = 0
            return
# 回溯法--示例代碼
# -*- coding:utf-8 -*-
class Solution:
    def movingCount(self, threshold, rows, cols):
        visited = [False] * (rows * cols)
        count = self.movingCountCore(threshold, rows, cols, 0, 0, visited)
        return count

    def movingCountCore(self, threshold, rows, cols, row, col, visited):
        count = 0
        if self.check(threshold, rows, cols, row, col, visited):
            visited[row * cols + col] = True
            count = 1 + self.movingCountCore(threshold, rows, cols, row-1, col, visited) + \
                        self.movingCountCore(threshold, rows, cols, row+1, col, visited) + \
                        self.movingCountCore(threshold, rows, cols, row, col-1, visited) + \
                        self.movingCountCore(threshold, rows, cols, row, col+1, visited)
        return count

    def check(self, threshold, rows, cols, row, col, visited):
        if row >= 0 and row < rows and col >= 0 and col < cols and self.getDigitSum(row) + self.getDigitSum(col) <= threshold and not visited[row * cols + col]:
            return True
        return False

    def getDigitSum(self, number):
        sum = 0
        while number > 0:
            sum += (number % 10)
            number = number // 10
        return sum

14.剪繩子

leetcode 343題一樣,中文版
給定一個正整數 n,將其拆分爲至少兩個正整數的和,並使這些整數的乘積最大化。 返回你可以獲得的最大乘積。

注意:存儲最優解的數組的前三個值是剪開後或者不剪情況下繩子的最大值,而不僅僅是剪開後乘積的最大值
參考題解:https://blog.csdn.net/weixin_41796401/article/details/84899457

# 方法一:動態規劃法
# 時間複雜度爲O(n^2),空間複雜度爲O(n)
class Solution(object):
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 2:
            return 0
        if n == 2:
            return 1
        if n == 3:
            return 2
        # mem[0] not used
        # !!! mem中存儲的是剪開後或者不剪情況下繩子的最大值,而不僅僅是剪開後乘積的最大值
        mem = [0] * (n+1)  
        mem[1], mem[2], mem[3] = 1, 2, 3  
        for i in range(4, n+1):
            max_product = 0
            for j in range(1, i//2+1):
                max_product = max(max_product, mem[j] * mem[i-j])
            mem[i] = max_product
        
        return mem[n]
            
# 方法二:貪婪法
# 證明:n > 4時,3(n-3) >= 2(n-2) > n
# 儘可能多的將數分解成3,當餘下是4時,分解成2*2
class Solution(object):
    def integerBreak(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n < 2:
            return 0
        if n == 2:
            return 1
        if n == 3:
            return 2
        
        p = (n // 3)
        r = (n % 3)
        if r == 0:
            return pow(3, p)
        elif r == 1:
            return pow(3, p-1)*4
        elif r == 2:
            return pow(3, p) *2

15.二進制中1的個數

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。

解析:

  • 使用位運算的效率高於除法
  • 負數的右移運算會在高位進行補1,如果沒考慮這一點,可能導致程序陷入死循環
  • 可以使用與運算實現1的個數統計
# 方法一:常規不會陷入死循環的方法
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        # write code here
        if n == 0:
            return 0
        # 設置循環終止條件
        MAX_INT = (1 << (32-1)) 
        flag = 1
        count = 0
        while n and flag <= MAX_INT :
            if n & flag:
                count += 1
                n -= flag
            flag = flag << 1
            
        return count
# 方法二:技巧性方法:正數減去1,再和原來的數做與運算,會把正數最右邊的1變成0
# 參考:https://www.cnblogs.com/klchang/p/8017627.html
# 或者根據負數補碼的特點,使用模將負數轉化:n = n & 0xffffffff,然後再進行減1做與運算
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1(self, n):
        # write code here
        if n == 0:
            return 0
        count = 0
        # 假設系統的整數是四字節,由於python自身整數是8字節,所以手動設置整數的界限
        # 例如-1在8字節情況下的補碼是:1111.......1111(64個),在4字節下1111.......1111(32個)
        MAX_INT = 1 << (32-1)
        while n != 0:
            if -MAX_INT > n or n > MAX_INT:  # 主要是針對負數,當n爲負數時,隨着n = n & (n-1),n將越界
                break                        # 如果不添加該終止循環的條件,程序將陷入死循環
            count += 1
            n = n & (n-1)
        return count

16.數值的整數次方

給定一個double類型的浮點數base和int類型的整數exponent。求base的exponent次方。

# 方法一:普通方法
# 注意邊界條件的控制、功能的測試、
# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        if base == 0:
            return 0
        res = 1
        if exponent == 0:
            return res
        elif exponent > 0:
            for _ in range(exponent):
                res *= base
            return res
        else:
            for _ in range(abs(exponent)):
                res *= base
            if res != 0:
                return 1/res
            else:
                return 

方法二:高效法

利用如下的遞推公式:
an={an/2an/2na(n1)/2a(n1)/2an a^n = \begin{cases} a^{n/2}\cdot a^{n/2} &amp; n 爲偶數 \\ a^{(n-1)/2}\cdot a^{(n-1)/2}\cdot a &amp; n 爲奇數 \end{cases}

# -*- coding:utf-8 -*-
class Solution:
    def Power(self, base, exponent):
        # write code here
        # 要考慮base=0而exponent爲負數這種異常情況的處理
        if base == 0 and exponent < 0:
            raise Exception('Error: base is zero ')
        if base == 0:
            return 0
        if exponent == 0:
            return 1
        if exponent > 0:
            return self.power_re(base, exponent)
        else:
            return 1/self.power_re(base, abs(exponent))
    # 遞歸求解
    def power_re(self, base, exponent):
        if exponent == 1:
            return base
        if (exponent & 0x1) == 0:
            return self.power_re(base, (exponent >> 1))*self.Power(base, (exponent >> 1))
        else:
            return self.power_re(base, ((exponent-1) >> 1)) * self.Power(base, ((exponent-1) >> 1))*base

17. 打印1到最大的n位數

解析:主要需要解決大數問題

# 方法一:使用字符串數組解決大數問題,實現進位功能
class Solution:
    def Print1ToMaxofNDigits(self, n):
        if n < 1:
            raise Exception('invalid input n')
        
        str_num = ['0'] * n
        
        while True:
            # 確定進位的位置
            i = len(str_num) - 1
            while str_num[i] == '9' and i > 0:
                i -= 1
            
            # 打印結束終止條件
            if str_num[0] == '9' and i == 0:
                break
            
            # 不需要進位
            if i == len(str_num)-1:
                str_num[i] = str(int(str_num[i])+1)
                self.Print(str_num)
            # 需要進位
            else:
                str_num[i] = str(int(str_num[i])+1)
                for j in range(i+1,len(str_num)):
                    str_num[j] = '0'
                self.Print(str_num)
                    
    def Print(self, str_num):
        index = 0
        for i in range(len(str_num)):
            if str_num[i] != '0': 
                index = i
                break
        print(''.join(str_num[index:]))
                  
s = Solution()
s.Print1ToMaxofNDigits(3)   
# 方法二:使用數字排列的方法實現
# 使用遞歸實現數字排列:從上到下分析,從下到上實現。。
# 從上到下分析:從最高位開始排列,高位排列完後排列低一位,直到排列完最低位
class Solution:
    def Print1ToMaxofNDigits(self, n):
        if n < 1:
            raise Exception('invalid input n')
        
        str_num = ['0'] * n
        
        for i in range(10):
            str_num[0] = str(i)
            self.recursivePrint1ToMaxofNDigits(str_num, len(str_num), index=0)
    
    def recursivePrint1ToMaxofNDigits(self, str_num, length, index):
        # 遞歸終止條件
        if index == length - 1:
            self.Print(str_num)
            return
        
        for i in range(10):
            str_num[index + 1] = str(i)
            self.recursivePrint1ToMaxofNDigits(str_num, length, index + 1)
            
    def Print(self, str_num):
        index = -1
        for i in range(len(str_num)):
            if str_num[i] != '0': 
                index = i
                break
        if index != -1:
            print(''.join(str_num[index:]))        
        
s = Solution()
s.Print1ToMaxofNDigits(2) 

18.刪除鏈表節點

題目一:在O(1)O(1)時間內刪除鏈表節點

給定單向鏈表的頭指針和一個節點指針,定義一個函數在O(1)O(1)時間內刪除該節點。

lintcode: https://www.lintcode.com/problem/delete-node-in-a-linked-list/description

# 解題思路:將要刪除節點的下一個節點複製到當前節點即可
"""
Definition of ListNode
class ListNode(object):

    def __init__(self, val, next=None):
        self.val = val
        self.next = next
"""


class Solution:
    """
    @param: node: the node in the list should be deleted
    @return: nothing
    """
    def deleteNode(self, node):
        # write your code here
        if not node or not node.next:
            node = None
            return

        node.val = node.next.val
        node.next = node.next.next

題目二:刪除鏈表中的重複節點

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5 處理後爲 1->2->5

# 標準解答:https://github.com/apachecn/awesome-algorithm/blob/master/docs/%E5%89%91%E6%8C%87offer/Python/56-%E5%88%A0%E9%99%A4%E9%93%BE%E8%A1%A8%E4%B8%AD%E9%87%8D%E5%A4%8D%E7%9A%84%E7%BB%93%E7%82%B9.py
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def deleteDuplication(self, pHead):
        # write code here
        if pHead is None or pHead.next is None:
            return pHead
        
        first = ListNode(-1)
        first.next = pHead
        last = first  # 記錄上一個有效節點
        # 遍歷不重複節點,更新last自身和last指向的節點
        while pHead and pHead.next:
            if pHead.val == pHead.next.val:
                # 遍歷得到與當前值不相等的節點,並將last指向該節點
                val = pHead.val
                while pHead and val == pHead.val:
                    pHead = pHead.next
                last.next = pHead
            else:
                # 如果當前節點有效,則更新last節點
                last = pHead
                pHead = pHead.next
        
        return first.next

19. 正則表達式的匹配

請實現一個函數用來匹配包括’.‘和’*‘的正則表達式。模式中的字符’.‘表示任意一個字符,而’*'表示它前面的字符可以出現任意次(包含0次)。 在本題中,匹配是指字符串的所有字符匹配整個模式。例如,字符串"aaa"與模式"a.a"和"ab*ac*a"匹配,但是與"aa.a"和"ab*a"均不匹配

題解參考:遞歸求解,根據s和pattern長度不同進行分情況討論:

  • 遞歸終止條件的確定:s和pattern長度均爲0,或s不爲0而pattern爲0
  • s長度爲0,pattern長度不爲0
  • s和pattern長度都不爲0,此時根據pattern第二位是否爲"*"進行分情況討論
    • pattern第二位不爲"*",s和pattern比較後各後移一位
    • pattern第二位爲"*",根據首位是否相等討論:
      • 首位不相等,則s不變,pattern向後移動2位,相當於pattern前兩位當成空
      • 首位相等,則有三種情況:
        1. pattern後移2位,s不變;相當於把pattern前兩位當成空,匹配後面的
        2. pattern後移2位,s後移1位;相當於pattern前兩位與s[0]匹配
        3. pattern不變,s後移1位;相當於pattern前兩位與s中的多位進行匹配

另外可以使用動態規劃來優化遞歸,參考題解

# -*- coding:utf-8 -*-
class Solution:
    # s, pattern都是字符串
    def match(self, s, pattern):
        # write code here
        #if not s and not pattern:
        #    return False
        return self.match_recursion(s, pattern)
    
    def match_recursion(self, s, pattern):
        # 遞歸終止條件
        if len(s) == 0 and len(pattern) == 0:
            return True
        elif len(s) != 0 and len(pattern) == 0:
            return False
        elif len(s) == 0 and len(pattern) != 0:
            if len(pattern) > 1 and pattern[1] is "*":
                return self.match_recursion(s, pattern[2:])
            else:
                return False
        else:
            # s和pattern都不爲空
            if len(pattern) > 1 and pattern[1] is "*":
                # s和pattern第一個元素不相同,則s不變,pattern向後移動2位,相當於pattern前兩位當成空
                if s[0] != pattern[0] and pattern[0]  != ".":
                    return self.match_recursion(s, pattern[2:])
                else:
                    # 如果s[0]和pattern[0]相同,此時有三種情況
                    # 1.pattern後移2位,s不變;相當於把pattern前兩位當成空,匹配後面的
                    # 2.pattern後移2位,s後移1位;相當於pattern前兩位與s[0]匹配
                    # 3.pattern不變,s後移1位;相當於pattern前兩位與s中的多位進行匹配
                    return self.match_recursion(s,pattern[2:]) or self.match_recursion(s[1:], pattern[2:]) or self.match_recursion(s[1:], pattern)
            
            else:
                if s[0] == pattern[0] or pattern[0] is ".":
                    return self.match_recursion(s[1:], pattern[1:])
                else:
                    return False
            

20.表示數值的字符串

請實現一個函數用來判斷字符串是否表示數值(包括整數和小數)。例如,字符串"+100",“5e2”,"-123",“3.1416"和”-1E-16"都表示數值。 但是"12e",“1a3.14”,“1.2.3”,"±5"和"12e+4.3"都不是。

# -*- coding:utf-8 -*-
class Solution:
    # s字符串
    def isNumeric(self, s):
        # write code here
        if len(s) < 1:
            return False
        dot_index = -1
        e_index = -1
        for i in range(len(s)):
            if s[i] is '.':
                dot_index = i
            if s[i] is 'e' or s[i] is 'E':
                e_index = i
                break
        res = True
        if dot_index > 0:
            # 小數點不在首位
            res = res and self.isInt(s[:dot_index], 1)
            if e_index > 0:
                # 存在e或者E
                res = res and self.scanInt(s[dot_index+1:e_index])
                res = res and self.isInt(s[e_index+1:], 0)
            else:
                res = res and self.scanInt(s[dot_index+1:])
        elif dot_index == 0:
            # 小數點在首位
            if e_index > 0:
                # 存在e或者E
                res = res and self.scanInt(s[dot_index+1:e_index])
                res = res and self.isInt(s[e_index+1:], 0)
            else:
                res = res and self.scanInt(s[dot_index+1:])
        else:
            # 不存在小數點
            if e_index > 0:
                # 存在e或者E  -+E
                if s[dot_index+1] == '-' or s[dot_index+1] == '+':
                    res = res and self.scanInt(s[dot_index+2:e_index])
                else:
                    res = res and self.scanInt(s[dot_index+1:e_index])
                res = res and self.isInt(s[e_index+1:], 0)
            else:
                # 不存在小數點和E
                res = res and self.isInt(s[dot_index+1:], 0)
        
        return res
            
            
    def isInt(self, s, case):
        # 是否是整形數字,即“-+”在首位的0--9數字
        # case = 1:表示要判斷的部分在數字的開頭,此時可以僅有“-+”,而沒有數字
        if len(s) < 1:
            return False
        if s[0] == '+' or s[0] == '-':
            if len(s) == 1 and case:
                return True
            else:
                return self.scanInt(s[1:])
        else:
            return self.scanInt(s[0:])
    
    def scanInt(self, s):
        # 必須在0--9之間
        if len(s) < 1:
            return False
        a = [str(x) for x in range(10)] 
        for i in s:
            if not i in a:
                return False
        return True

21.調整數組順序使奇數位於偶數前面

輸入一個整數數組,實現一個函數來調整該數組中數字的順序,使得所有的奇數位於數組的前半部分,所有的偶數位於數組的後半部分,並保證奇數和奇數,偶數和偶數之間的相對位置不變。

# -*- coding:utf-8 -*-
class Solution:
    def reOrderArray(self, array):
        if len(array) < 1:
            return array
        # write code here
        odd_number = []
        even_number = []
        for i in array:
            if i%2 == 1:
                odd_number.append(i)
            else:
                even_number.append(i)
        return odd_number+even_number

22.鏈表中倒數第k個節點

輸入一個鏈表,輸出該鏈表中倒數第k個結點

# 方法1:先計數,在輸出
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        if head is None or k == 0:
            return None
        
        count = 1
        temp = head
        while temp.next != None:
            count += 1
            temp = temp.next
        
        if count < k:
            return None
        else:
            k = count - k
            count = 0
            while count != k:
                count += 1
                head = head.next
            return head
# 方法二:雙指針法,使用兩個間隔爲k-1的指針
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def FindKthToTail(self, head, k):
        # write code here
        if head is None or k == 0:
            return None
        
        fP = head
        sP = head
        count = 0
        while count < k:
            # 注意對fP是否爲空的判斷
            if fP is None:
                return None
            fP = fP.next
            count += 1
            
        while fP != None:
            fP = fP.next
            sP = sP.next
        
        return sP

23.鏈表中環的入口節點

給一個鏈表,若其中包含環,請找出該鏈表的環的入口結點,否則,輸出null。

# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def EntryNodeOfLoop(self, pHead):
        # write code here
        if pHead is None:
            return None
        
        node_num = self.hasCircle(pHead)
        if node_num == 0:
            return None
        else:
            # 存在環,環中節點數爲node_num,查找環的入口節點
            fP = pHead # first point
            sP = pHead # second point
            count = 0
            while count < node_num:
                fP = fP.next
                count += 1
            while fP != sP:
                fP = fP.next
                sP = sP.next
            return sP
        
        
    def hasCircle(self, pHead):
        # 判斷是否存在circle,返回0表示不存在,返回正整數表示環中節點個數
        fast = pHead
        slow = pHead
        
        while fast != None:
            # fast 移動兩步,slow移動一步
            fast = fast.next
            if fast == None:
                break
            fast = fast.next
            slow = slow.next
            
            if fast == slow:
                # 存在環,統計環中節點的個數
                num = 1
                fast = fast.next
                while fast != slow:
                    num += 1
                    fast = fast.next
                return num
        return 0

24.反轉鏈表

輸入一個鏈表,反轉鏈表後,輸出新鏈表的表頭。

# 藉助三個指針,一邊遍歷鏈表一邊修改當前節點的指向,直到遍歷完整個鏈表
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if pHead is None:
            return None
        
        previous_p = pHead
        current_p = pHead.next
        if current_p is None:
            # 只有一個節點
            return pHead
        next_p = current_p.next
        if next_p is None:
            # 只有兩個節點
            previous_p.next = None
            current_p.next = previous_p
            return current_p
        
        #節點數大於等於3,藉助三個指針一邊遍歷一邊修改指向
        previous_p.next = None
        while next_p != None:
            current_p.next = previous_p
            # 更新各個指針指向。向前移動
            previous_p = current_p
            current_p = next_p
            next_p = next_p.next
        
        current_p.next = previous_p
        
        return current_p
            
            
        
        
        
# 方法二:遞歸法實現,從尾部開始修改指向
# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    # 返回ListNode
    def ReverseList(self, pHead):
        # write code here
        if pHead is None:
            return None
        
        previous_p = pHead
        current_p = pHead.next
        if current_p is None:
            # 只有一個節點
            return pHead
        
        #節點數大於等於2,遞歸實現反轉
        previous_p.next = None
        return self.Rev_recursion(previous_p, current_p)
    
    def Rev_recursion(self, pre, cur):
        # 遞歸終止條件:遍歷至尾節點
        if cur.next is None:
            cur.next = pre
            return cur
        
        next_node = cur.next  # 保存當前節點下一節點的指向
        cur.next = pre  # 修改當前節點指向上一節點
        
        return self.Rev_recursion(cur, next_node)

    def PrintList(self, pHead):
        while pHead.next != None:
            print(pHead.val)
            pHead =pHead.next

        print(pHead.val)

# test
a = ListNode(1)
b = ListNode(2)
c = ListNode(3)
d = ListNode(4)
a.next = b
b.next = c
c.next = d

s=Solution()
h = s.ReverseList(a)
s.PrintList(h)
4
3
2
1

25.合併兩個排序鏈表

輸入兩個單調遞增的鏈表,輸出兩個鏈表合成後的鏈表,當然我們需要合成後的鏈表滿足單調不減規則。

Tips:python對象如果是結構體或類,則把對象的引用值修改,那麼原始對象的值也會被改變。

# 方法1:確定頭節點後,將另一個鏈表插入頭節點所在的鏈表
# -*- coding:utf-8 -*-
class ListNode:
    def __init__(self, x):
        self.val = x
        self.next = None
class Solution:
    # 返回合併後列表
    def Merge(self, pHead1, pHead2):
        # write code here
        if pHead1 is None:
            return pHead2
        elif pHead2 is None:
            return pHead1
        
        # 確定頭結點
        if pHead1.val > pHead2.val:
            head = self.insertMerge(pHead2, pHead1)
        else:
            head = self.insertMerge(pHead1, pHead2)
        return head
    def insertMerge(self, head, insertL):
        # head指頭節點所在的鏈表,insertL鏈表中的元素將被插入head指向的鏈表
        pHead = head
        insert_h = insertL
        while pHead.next != None:
            pre1 = pHead
            cur1 = pHead.next
            insert_p = insert_h
            if insert_p.val <= cur1.val:
                # 介於兩個節點之間,滿足插入條件,插入後移動head所在鏈表和insertL鏈表
                # !!!!必須先更新,在重新連接節點,否則變量的值已經被改變
                # 如果按照註釋中的順序執行,則變量pHead和insert_h的值就會收到前面賦值的影響
                # 此處insert_h值的更新必須要在insert_p.next的賦值之前,因爲結構體內部值的改變會間接改變原始變量的值
#                 pre1.next = insert_p
#                 insert_p.next = cur1
#                 pHead = pHead.next
#                 insert_h = insert_h.next
                
                pHead = pHead.next
                insert_h = insert_h.next
                
                pre1.next = insert_p
                insert_p.next = cur1
                
            else:
                # 不滿足插入條件,移動head所在鏈表,insertL鏈表不動
                pHead = pHead.next
        
        if insert_h != None:
            pHead.next = insert_h
            
        return head
    
    def PrintList(self, pHead):
        while pHead.next != None:
            print(pHead.val)
            pHead =pHead.next

        print(pHead.val)

a = ListNode(1)
b = ListNode(3)
c = ListNode(5)
d = ListNode(7)
a.next = b
b.next = c
c.next = d

e = ListNode(2)
f = ListNode(4)
g = ListNode(6)
h = ListNode(8) 
e.next = f
f.next = g
g.next = h

s = Solution()
h = s.Merge(a, e)
# s.PrintList(h)

# 方法二:遞歸法
# 只對兩個鏈表的頭節點進行重排,直到其中一個鏈表排至尾部時終止
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    # 返回合併後列表
    def Merge(self, pHead1, pHead2):
        # write code here
        # 遞歸終止條件
        if pHead1 is None:
            return pHead2
        elif pHead2 is None:
            return pHead1
        
        if pHead1.val < pHead2.val:
            pHead1.next = self.Merge(pHead1.next, pHead2)
            return pHead1
        else:
            pHead2.next = self.Merge(pHead1, pHead2.next)
            return pHead2

26.樹的子結構

輸入兩棵二叉樹A,B,判斷B是不是A的子結構。(ps:我們約定空樹不是任意一個樹的子結構)

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def HasSubtree(self, pRoot1, pRoot2):
        # write code here
        # 特殊邊界情況判斷
        if pRoot1 is None or pRoot2 is None:
            return False
        
        res = False
        if pRoot1.val == pRoot2.val:
            res = self.isEqual(pRoot1, pRoot2)
        if not res:
            # 當前節點不相等則繼續遞歸遍歷樹
            if pRoot1.left != None:
                res = self.HasSubtree(pRoot1.left, pRoot2)
            if not res and pRoot1.right != None:
                res = self.HasSubtree(pRoot1.right, pRoot2)
        
        return res
    
    def isEqual(self, pRoot1, pRoot2):
        # 遞歸終止條件
        if pRoot2 is None :
            return True
        if pRoot1 is None:
            return False
        
        if pRoot2.val == pRoot1.val:
            res = self.isEqual(pRoot1.left, pRoot2.left)
        else:
            return False
        # 遞歸
        res = res and self.isEqual(pRoot1.left, pRoot2.left) and self.isEqual(pRoot1.right, pRoot2.right)
        
        return res

27.二叉樹的鏡像

操作給定的二叉樹,將其變換爲源二叉樹的鏡像。
輸入描述:
二叉樹的鏡像定義:
源二叉樹

    	    8
    	   /  \
    	  6   10
    	 / \  / \
    	5  7 9 11

鏡像二叉樹

    	    8
    	   /  \
    	  10   6
    	 / \  / \
    	11 9 7  5

非遞歸實現:

  • 藉助於棧,先交換兩棵子樹,再求完一棵子樹的鏡像後在求還有一棵子樹的鏡像(縱向,深度優先)
  • 藉助於隊列以用廣度優先的順序遍歷一遍實現逐層鏡像(橫向,廣度優先)
# 遞歸法
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回鏡像樹的根節點
    def Mirror(self, root):
        # write code here
        # 異常情況判斷
        if root is None:
            return
        # 遞歸終止條件:遍歷至非葉子節點
        if root.left is None and root.right is None:
            return
        
        
        if root.left != None and root.right != None:
            root.left, root.right = root.right, root.left
            self.Mirror(root.left)
            self.Mirror(root.right)
        elif root.left != None and root.right is None:
            root.left, root.right = None, root.left
            self.Mirror(root.right)
        elif root.left is None and root.right != None:
            root.left, root.right = root.right, None
            self.Mirror(root.left)
        
        return 
# 更簡潔的遞歸實現
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回鏡像樹的根節點
    def Mirror(self, root):
        # write code here
        # 異常情況判斷
        if root is None:
            return
        # 遞歸終止條件:遍歷至非葉子節點
        if root.left is None and root.right is None:
            return
        root.left, root.right = root.right, root.left
        self.Mirror(root.left)
        self.Mirror(root.right)

        return 

28.對稱的二叉樹

請實現一個函數,用來判斷一顆二叉樹是不是對稱的。注意,如果一個二叉樹同此二叉樹的鏡像是同樣的,定義其爲對稱的。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        # 空樹對稱
        if pRoot is None:
            return True
        
        return self.issymm_recursion(pRoot.left, pRoot.right)

    def issymm_recursion(self, left, right):
        # 如果左右節點均爲None,則遍歷結束,且滿足對稱
        if left is None and right is None:
            return True
        # 反之僅有一個節點爲空則不滿足對稱
        if left is None or right is None:
            return False
        
        # 當兩個節點均不爲空時,對其值進行判斷
        res = False
        if left.val == right.val: # 兩個節點的值相等時,對其子節點進行遞歸判斷
            # 對稱前序遍歷遞歸實現
            res = self.issymm_recursion(left.left, right.right)
            res = res and self.issymm_recursion(left.right, right.left)

        return res
        
# 更簡潔的參考實現:
# https://github.com/apachecn/awesome-algorithm/blob/master/docs/%E5%89%91%E6%8C%87offer/Python/58-%E5%AF%B9%E7%A7%B0%E7%9A%84%E4%BA%8C%E5%8F%89%E6%A0%91.py
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def isSymmetrical(self, pRoot):
        # write code here
        return self.selfIsSym(pRoot,pRoot)
    
    def selfIsSym(self,root1,root2):
        if root1 == root2 and root2 == None:
            return True
        if root1 == None or root2 == None:
            return False
        if root1.val != root2.val:
            return False
        return self.selfIsSym(root1.left, root2.right) and self.selfIsSym(root1.right,root2.left)

29.順時針打印矩陣

輸入一個矩陣,按照從外向裏以順時針的順序依次打印出每一個數字,例如,如果輸入如下4 X 4矩陣: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 則依次打印出數字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

# 方法一:藉助四個數指定打印範圍
# -*- coding:utf-8 -*-
class Solution:
    # matrix類型爲二維列表,需要返回列表
    def printMatrix(self, matrix):
        # write code here
        if len(matrix) < 1:
            return None
        if len(matrix[0]) <= 1:
            m = [x[0] for x in matrix]
            return m
        row_len = len(matrix) # 行
        col_len = len(matrix[0]) # 列
        
        # 四個數確定打印的邊界
        a, b, c, d = 0, row_len-1, 0, col_len-1
        
        m = []
        while a <= b and c <= d:
            # 在當前邊界條件下打印一次
            m = m + self.Print(matrix, a, b, c, d)

            # 更新邊界
            a += 1
            b -= 1
            c += 1
            d -= 1
        return m
    
    def Print(self, matrix, a, b, c, d):
        m = []
        # 打印過程爲:先橫向右移,再縱向下移,再橫向左移,最後縱向上移
        m = matrix[a][c:d+1] # 橫向右移打印
        
        # 如果a=b,則只需要橫向打印一次即完成打印
        if a == b:
            return m
        m = m + [x[d] for x in matrix[a+1:b+1]] # 縱向下移打印

        if c == 0: #橫向左移打印
            m = m + (matrix[b][d-1::-1])
        else:
            m = m + (matrix[b][d-1:c-1:-1])
        
        m = m + ([x[c] for x in matrix[b-1:a:-1]]) # 縱向上移打印

        return m

s = Solution()
a = [[1,2],[3,4]]
s.printMatrix(a)
[1, 2, 4, 3]
# 方法二:根據規律左上角的座標中行標和列標總是相等的
# 參考代碼
# https://github.com/apachecn/awesome-algorithm/blob/master/docs/%E5%89%91%E6%8C%87offer/Python/19-%E9%A1%BA%E6%97%B6%E9%92%88%E6%89%93%E5%8D%B0%E7%9F%A9%E9%98%B5.py

class Solution:
    # matrix類型爲二維列表,需要返回列表
    def printMatrix(self, matrix):
        if not matrix:
            return []
        
        rows = len(matrix)
        columns = len(matrix[0])
        start = 0
        result = []
        while rows > start * 2 and columns > start * 2:
            self.PrintMatrixInCircle(matrix, columns, rows, start,result)
            start += 1
        return result    
            
    def PrintMatrixInCircle(self, matrix, columns, rows,start,result):
        endX = columns - 1 - start
        endY = rows - 1 - start

        # 從左到右打印一行
        for i in range(start, endX+1):
            #number = matrix[start][i]
            result.append(matrix[start][i])

        # 從上到下打印一行
        if start < endY:
            for i in range(start+1, endY+1):
                #number = matrix[i][endX]
                result.append(matrix[i][endX])

        # 從右到左打印一行
        if start < endX and start < endY:
            for i in range(endX-1, start-1, -1):
                #number = matrix[endY][i]
                result.append(matrix[endY][i])

        # 從下到上打印一行
        if start < endX and start < endY-1:
            for i in range(endY-1, start, -1):
                #number = matrix[i][start]
                result.append(matrix[i][start])

30.包含min函數的棧

定義棧的數據結構,請在該類型中實現一個能夠得到棧中所含最小元素的min函數(時間複雜度應爲O(1))。

# 藉助輔助棧實現
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.stack = []
        self.assist_stack = []
        
    def push(self, node):
        # write code here
        self.stack.append(node)
        if len(self.assist_stack) < 1:
            self.assist_stack.append(node)
        elif node < self.assist_stack[-1]:
            self.assist_stack.append(node)
        else:
            self.assist_stack.append(self.assist_stack[-1])
                
    def pop(self):
        # write code here
        if len(self.stack) < 1:
            return None
        else:
            self.assist_stack.pop()
            return self.stack.pop()
    def top(self):
        # write code here
        return self.stack[-1]
    def min(self):
        # write code here
        return self.assist_stack[-1]

31.棧的壓入、彈出序列

輸入兩個整數序列,第一個序列表示棧的壓入順序,請判斷第二個序列是否可能爲該棧的彈出順序。假設壓入棧的所有數字均不相等。例如序列1,2,3,4,5是某棧的壓入順序,序列4,5,3,2,1是該壓棧序列對應的一個彈出序列,但4,3,5,1,2就不可能是該壓棧序列的彈出序列。(注意:這兩個序列的長度是相等的)

# 方法:藉助輔助棧實現
# -*- coding:utf-8 -*-
class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        if len(pushV) != len(popV):
            return False
        elif len(pushV) < 1:
            return True
        
        stack = []
        
        for i in popV:
            # 輔助棧爲空時,根據情況向輔助棧壓入一個元素
            if len(stack) < 1:
                if len(pushV) < 1:
                    return False
                else:
                    stack.append(pushV.pop(0))
            while i != stack[-1]:
                # 判斷輔助棧棧頂元素是否和當前彈出元素相等,不相等則將壓入序列中元素不斷壓入輔助棧,直到發現相等元素
                if len(pushV) < 1:
                    # 如果壓入序列爲空,則沒有元素可以匹配,所以不滿足彈出順序
                    return False
                else:
                    stack.append(pushV.pop(0))
            # 如果彈出序列和輔助棧棧頂元素相等則,將輔助棧棧頂元素彈出
            stack.pop()
        return True
# 參考實現

# -*- coding:utf-8 -*-
class Solution:
    def IsPopOrder(self, pushV, popV):
        # write code here
        if pushV == [] or popV == []:
            return False
        
        stack = []
        for i in pushV:
            stack.append(i)
            while len(stack) and stack[-1] == popV[0]:
                stack.pop()
                popV.pop(0)
            
        if len(stack): 
            return False
        else:
            return True

32.從上到下打印二叉樹

從上往下打印出二叉樹的每個節點,同層節點從左至右打印。

# 方法:藉助輔助隊列實現
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回從上到下每個節點值列表,例:[1,2,3]
    def PrintFromTopToBottom(self, root):
        # write code here
        if root is None:
            return []
        
        que = []
        res = []
        que.append(root)
        while len(que) >= 1:
            node = que.pop(0)
            res.append(node.val)
            
            if node.left != None:
                que.append(node.left)
            if node.right != None:
                que.append(node.right)
        return res

        

32.2 把二叉樹打印成多行

從上到下按層打印二叉樹,同一層結點從左至右輸出。每一層輸出一行。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回二維列表[[1,2],[4,5]]
    def Print(self, pRoot):
        # write code here
        if pRoot is None:
            return []
        
        que1 = []  # 存儲每行元素構成的隊列,que1是二維數組
        que2 = []  # 將一行元素作爲隊列存儲
        
        que2.append(pRoot)
        que1.append(que2)
        
        res = [] # 存儲最終結果,是二維數組
        res1 = [] # 存儲每一行打印的結果
        while len(que1) >= 1:
            # 打印所有行
            nodes = que1.pop(0)
            que2 = []
            for node in nodes:
                # 打印一行
                res1.append(node.val)
                
                if node.left != None:
                    que2.append(node.left)
                if node.right != None:
                    que2.append(node.right)
            if len(que2) >= 1:
                que1.append(que2)
            # 存儲當前行打印的結果
            res.append(res1)
            res1 = []
        return res

32.3 之字形打印二叉樹

請實現一個函數按照之字形打印二叉樹,即第一行按照從左到右的順序打印,第二層按照從右至左的順序打印,第三行按照從左到右的順序打印,其他行以此類推。

# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Print(self, pRoot):
        # write code here
        if pRoot is None:
            return []
        
        count = 1  # 打印層數記錄
        res, res1 = [], []
        nodes = [pRoot]
        while nodes:
            que = []
            if count % 2 == 1:
                for node in nodes:
                    res1.append(node.val)
                    # 節點存儲順序由左至右,然後翻轉
                    if node.left != None:
                        que.append(node.left)
                    if node.right != None:
                        que.append(node.right)
            else:
                for node in nodes:
                    res1.append(node.val)
                    # 節點存儲順序由右至左,然後翻轉
                    if node.right != None:
                        que.append(node.right)
                    if node.left != None:
                        que.append(node.left)

            nodes = que[::-1]
            res.append(res1)
            res1 = []
            count += 1
        
        return res

33.二叉搜索樹的後續遍歷序列

輸入一個整數數組,判斷該數組是不是某二叉搜索樹的後序遍歷的結果。如果是則輸出Yes,否則輸出No。假設輸入的數組的任意兩個數字都互不相同。

# -*- coding:utf-8 -*-
class Solution:
    def VerifySquenceOfBST(self, sequence):
        # write code here
        if len(sequence) == 0:
            return False
        
        return self.recursion_verify(sequence) 
                
    def recursion_verify(self, sequence):
        if len(sequence) <= 1:
            return True
        
        root = sequence[-1]
        
        for i in range(len(sequence)-1):
            if sequence[i] > root:
                break
        # 如果只有左子樹,僅對左子樹遞歸判斷
        if i == len(sequence)-2:
            return self.recursion_verify(sequence[0:i])
        
        for j in sequence[i:-1]:
            if j < root:
                return False
        
        # 左右子樹都有節點,則對左右子樹遞歸判斷
        return self.recursion_verify(sequence[0:i]) and self.recursion_verify(sequence[i:-1])
    
s=Solution()
# a = [4,8,6,12,16,14,10]
a = [5,4,3,2,1]
print(s.VerifySquenceOfBST(a))
True

34.二叉樹中和爲某一值的函數

輸入一顆二叉樹的跟節點和一個整數,打印出二叉樹中結點值的和爲輸入整數的所有路徑。路徑定義爲從樹的根結點開始往下一直到葉結點所經過的結點形成一條路徑。(注意: 在返回值的list中,數組長度大的數組靠前)

# 方法:遞歸法
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回二維列表,內部每個列表表示找到的路徑
    def FindPath(self, root, expectNumber):
        # write code here
        if root is None:
            return []
        stack = []  # 輔助棧,存儲路徑節點
        path = []  # 存儲路徑
        sums = root.val # 和值
        stack.append(root)
        
        res = self.recursion_find(root, expectNumber, stack, sums, path)
        return res
    
    def recursion_find(self, root, expectNumber, stack, sums, path):
        # 遞歸終止條件:遞歸至葉節點
        # 或只有根節點的情況
        if root.left is None and root.right is None:
            if sums == expectNumber:
                res = [x.val for x in stack]  # 將路徑節點轉換爲值列表
                path.append(res)
                return path
            else:
                return path
        else:  #VLR前序遞歸
            if root.left != None:  # 遞歸左節點
                stack.append(root.left)
                sums += root.left.val 
                self.recursion_find(root.left, expectNumber, stack, sums, path)
                stack.pop() # 一次遞歸結束後彈出當前節點,並恢復原來的和值
                sums -= root.left.val

            if root.right != None:  # 遞歸右節點
                stack.append(root.right)
                sums += root.right.val
                self.recursion_find(root.right, expectNumber, stack, sums, path)
                stack.pop() # 一次遞歸結束後彈出當前節點,並恢復原來的和值
                sums -= root.right.val
        
        return sorted(path, key=lambda x: -len(x))  # 降序排列
        
        
# 參考解法
# https://github.com/apachecn/awesome-algorithm/blob/master/docs/%E5%89%91%E6%8C%87offer/Python/24-%E4%BA%8C%E5%8F%89%E6%A0%91%E4%B8%AD%E5%92%8C%E4%B8%BA%E6%9F%90%E4%B8%80%E5%80%BC%E7%9A%84%E8%B7%AF%E5%BE%84.py
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回二維列表,內部每個列表表示找到的路徑
    def FindPath(self, root, expectNumber):
        # write code here
        if not root or root.val > expectNumber:  # 沒有找到路徑,遞歸終止
            return []
        
        if not root.left and not root.right and root.val == expectNumber:  # 找到路徑
            return [[root.val]]
        else:
            expectNumber -= root.val
            left = self.FindPath(root.left,expectNumber)
            right = self.FindPath(root.right,expectNumber)
            
            result = [[root.val]+i for i in left]
            for i in right:
                result.append([root.val]+i)
            
        return sorted(result, key=lambda x:-len(x))

35.複雜鏈表的複製

輸入一個複雜鏈表(每個節點中有節點值,以及兩個指針,一個指向下一個節點,另一個特殊指針指向任意一個節點),返回結果爲複製後複雜鏈表的head。(注意,輸出結果中請不要返回參數中的節點引用,否則判題程序會直接返回空)

# 解法一: 分兩步:第一步:順序複製鏈表,第二步:複製特殊指針,每次從原始鏈表頭部開始遍歷查找特殊指針的位置
# 時間複雜度:O(n^2)
# -*- coding:utf-8 -*-
# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if pHead is None:
            return None
        
        pH_new_c = RandomListNode(pHead.label) # 複製鏈表的頭結點
        pH_new = pH_new_c # 定義臨時變量,用於遍歷
        next_node = pHead.next # 定義臨時變量,用於遍歷
        
        # 先順序複製
        while next_node != None:
            copy_next_node = RandomListNode(next_node.label)
            pH_new.next = copy_next_node
            
            next_node = next_node.next
            pH_new = copy_next_node
        
        # 複製特殊指針
        pH_new = pH_new_c # 臨時變量,用於遍歷
        next_node = pHead
        rand_node = pHead.random
        while next_node != None:
            rand_node = next_node.random
            if rand_node == None:
                pH_new.random = None
            else:
                temp = pHead  # 原始頭結點
                copy_random = pH_new_c # 複製鏈表的頭結點
                # 同時遍歷原始鏈表和複製鏈表,尋找複製鏈表中特殊指針指向的節點
                while temp != None:
                    if temp.label == rand_node.label:  # 找到特殊指針指向的節點
                        break
                    else:
                        temp = temp.next
                        copy_random = copy_random.next
                pH_new.random = copy_random # 給複製鏈表當前節點的特殊指針賦值
            # 進行下個節點特殊指針的賦值
            next_node = next_node.next
            pH_new = pH_new.next
            
        return pH_new_c
# 解法二:時間換空間法,藉助哈希表存儲當前節點和特殊指針之間的對應關係
# 時間和空間複雜度均爲:O(n)
# -*- coding:utf-8 -*-
# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if pHead is None:
            return None
        
        pH_new_c = RandomListNode(pHead.label) # 複製鏈表的頭結點
        pH_new = pH_new_c # 定義臨時變量,用於遍歷
        next_node = pHead.next # 定義臨時變量,用於遍歷
        
        N_Nc = {} # 定義字典,存儲賦值前--賦值後節點對
        N_Nc[pHead.label] = pH_new
        
        # 先順序複製
        while next_node != None:
            copy_next_node = RandomListNode(next_node.label)
            pH_new.next = copy_next_node
            N_Nc[next_node.label] = copy_next_node # 存儲賦值前--賦值後節點對
            
            next_node = next_node.next
            pH_new = copy_next_node
        
        # 複製特殊指針
        pH_new = pH_new_c # 臨時變量,用於遍歷
        next_node = pHead
        rand_node = pHead.random
        while next_node != None:
            rand_node = next_node.random
            if rand_node == None:
                pH_new.random = None
            else:
                # 藉助哈希表存儲的賦值前--賦值後節點對,給複製鏈表當前節點的特殊指針賦值
                pH_new.random = N_Nc[rand_node.label] 
            # 進行下個節點特殊指針的賦值
            next_node = next_node.next
            pH_new = pH_new.next
            
        return pH_new_c
# 解法三:不使用輔助空間,在原始鏈表上添加複製鏈表成爲一個長鏈表,然後拆分出複製鏈表
# -*- coding:utf-8 -*-
# class RandomListNode:
#     def __init__(self, x):
#         self.label = x
#         self.next = None
#         self.random = None
class Solution:
    # 返回 RandomListNode
    def Clone(self, pHead):
        # write code here
        if pHead is None:
            return None

        node = pHead # 定義臨時變量,用於遍歷
        # 先順序複製,將複製的節點接在原始節點後面,得到一個長鏈表
        while node != None:
            next_node = node.next
            copy_next_node = RandomListNode(node.label)
            copy_next_node.next = next_node
            
            node.next = copy_next_node
            node = next_node
        
        node = pHead
        while node != None:
            next_node = node.next
            if node.random != None:
                next_node.random = node.random.next

            node = next_node.next
        
        node = pHead
        pH_new = pH_new_c = node.next  # 複製鏈表的頭節點
        node.next = pH_new.next # 需要藉助node.next進行節點的賦值
        node = node.next
        while node != None:
            pH_new_c.next = node.next # 複製節點
            pH_new_c = pH_new_c.next # 複製節點
            node.next = pH_new_c.next # 原始節點,需要藉助node.next進行節點的賦值
            node = node.next #原始節點
            
        return pH_new

36.二叉搜索樹與雙向鏈表

輸入一棵二叉搜索樹,將該二叉搜索樹轉換成一個排序的雙向鏈表。要求不能創建任何新的結點,只能調整樹中結點指針的指向。

# 方法:將二叉搜索樹的左右節點分別重置爲雙向鏈表的向前、向後指針
# 使用LVR遞歸遍歷實現
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Convert(self, pRootOfTree):
        # write code here
        if pRootOfTree is None:
            return None

        return self.recursion_modify(pRootOfTree)
        
    def recursion_modify(self, pRoot):
        if pRoot is None:
            return None
        if pRoot.left is None and pRoot.right is None:
            return pRoot

        self.recursion_modify(pRoot.left)
        left = pRoot.left
        if left != None:
            while left.right != None:
                left = left.right
            pRoot.left = left
            left.right = pRoot
        
        self.recursion_modify(pRoot.right)
        right = pRoot.right
        if right != None:
            while right.left != None:
                right = right.left
            pRoot.right = right
            right.left = pRoot
            
        while pRoot.left != None:
            pRoot = pRoot.left
        
        return pRoot

37.序列化二叉樹

請實現兩個函數,分別用來序列化和反序列化二叉樹

# 使用前序遍歷實現序列化和反序列化
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def Serialize(self, root):
        # write code here
        if root is None:
            return '$,'
        
        return str(root.val) + ',' + self.Serialize(root.left) + self.Serialize(root.right)
        
    def Deserialize(self, s):
        # write code here
        # 特殊情況處理
        if len(s) < 1:
            return None
        # 原始二叉樹爲空時,序列化得到的是"$,",此時直接返回None
        elif len(s) == 2 and s[0] == '$':
            return None
        
        s_list = s.split(',')
        root = TreeNode(int(s_list[0])) # 設置頭節點
        s_list.pop(0) # 將用過的節點彈出
        # 遞歸重構二叉樹
        root.left = self.recursion_deserialize(s_list)
        root.right = self.recursion_deserialize(s_list)
        
        return root
    def recursion_deserialize(self, s):
        if len(s) < 1:
            return None
        if s[0] == '$':
            s.pop(0)
            return None
        # 遞歸重構二叉樹
        val = s.pop(0)
        node = TreeNode(int(val))
        node.left = self.recursion_deserialize(s)
        node.right = self.recursion_deserialize(s)
        
        return node

38.字符串的排列

輸入一個字符串,按字典序打印出該字符串中字符的所有排列。例如輸入字符串abc,則打印出由字符a,b,c所能排列出來的所有字符串abc,acb,bac,bca,cab和cba。
輸入描述:
輸入一個字符串,長度不超過9(可能有字符重複),字符只包括大小寫字母。

# 遞歸法:將字符串分爲頭部+頭部之外兩部分
# -*- coding:utf-8 -*-
class Solution:
    def Permutation(self, ss):
        # write code here
        if ss is None:
            return []
        if len(ss) == 1:
            return list(ss)

        s = list(ss)
        s.sort()  # 得到升序排列的字符數組
        res = []
        
        for i in range(len(s)):
            if i > 0 and s[i] == s[i-1]: # 重複字符排列過就不用再重排
                continue
            temp = self.Permutation(''.join(s[:i])+''.join(s[i+1:]))
            for j in temp:
                res.append(s[i] + j)
            
        return res

39.數組中出現次數超過一半的數字

數組中有一個數字出現的次數超過數組長度的一半,請找出這個數字。例如輸入一個長度爲9的數組{1,2,3,2,2,2,5,4,2}。由於數字2在數組中出現了5次,超過數組長度的一半,因此輸出2。如果不存在則輸出0。

# 方法一:藉助字典實現,統計每個數字出現的頻次
# 時間和空間複雜度均爲:O(n)
# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        if numbers is None:
            return 0
        count = {}
        length = len(numbers) // 2
        for i in numbers:
            if i in count:
                count[i] += 1
            else:
                count[i] = 1
        
        for key in count:
            if count[key] > length:
                return key
        
        return 0
# 方法二:根據數組特點尋找,次數超過一半
# 使用兩個變量記錄當前數字和數字出現的次數,出現與當前數字相同的數時次數加1,否則減1,若減爲0,則將之前的數改爲當前的數字
# 最後存儲的數字在進行是否頻次大於長度一半的驗證即可
# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        if numbers is None:
            return 0
        count = 1
        num = numbers[0]
        for i in numbers[1:]:
            if i == num:
                count += 1
            else:
                if count > 0:
                    count -= 1
                else:
                    count += 1
                    num = i
        # 驗證num是否次數大於長度一半
        count = 0
        for i in numbers:
            if i == num:
                count += 1
        if count > len(numbers) // 2:
            return num
        else:
            return 0
# 方法三:利用快速排序得到排序後的數組,判斷排序後的數組中位數是否出現頻次大於數組長度一半
# 時間複雜度:O(nlogn)
# -*- coding:utf-8 -*-
class Solution:
    def MoreThanHalfNum_Solution(self, numbers):
        # write code here
        if numbers is None:
            return 0

        nums = self.quickSort(numbers) # 排序
        num = nums[len(nums)//2] # 取中位數
        
        # 驗證num是否次數大於長度一半
        count = 0
        for i in numbers:
            if i == num:
                count += 1
        if count > len(numbers) // 2:
            return num
        else:
            return 0
    
    # 快排
    def quickSort(self, numbers):
        if len(numbers) <= 1:
            return numbers
        pivot = numbers[0]
        left = [x for x in numbers if x < pivot]
        mid = [x for x in numbers if x == pivot]
        right = [x for x in numbers if x > pivot]
        
        return self.quickSort(left) + mid + self.quickSort(right)

40.最小的K個數

輸入n個整數,找出其中最小的K個數。例如輸入4,5,1,6,2,7,3,8這8個數字,則最小的4個數字是1,2,3,4,。

# 方法一:排序後取前K個
# 時間複雜度由排序算法決定:快排則爲O(nlogn)
# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if tinput is None or len(tinput) < k:
            return []
        tinput = self.quickSort(tinput)
        return tinput[0:k]
    
    def quickSort(self, arr):
        if len(arr) <= 1:
            return arr
        
        pivot = arr[0]
        
        left = [x for x in arr if x < pivot]
        mid = [x for x in arr if x == pivot]
        right = [x for x in arr if x > pivot]
        return self.quickSort(left) + mid + self.quickSort(right)
# 方法二:類似計數排序法,計數後取出前K個
# 時間複雜度O(n+k)
# -*- coding:utf-8 -*-
class Solution:
    def GetLeastNumbers_Solution(self, tinput, k):
        # write code here
        if tinput is None or len(tinput) < k or k == 0:
            return []
        tinput = self.countingSort(tinput, k)
        return tinput

    def countingSort(self, arr, k):
        min_num = min(arr)
        max_num = max(arr)
        
        counts = [0] * (max_num-min_num+1)
        
        for i in arr:
            counts[i-min_num] += 1
        res = []
        count = 0
        for i in range(len(counts)):
            for j in range(counts[i]):
                res.append(i+min_num)
                if len(res) == k:
                    return res

41.數據流中的中位數

如何得到一個數據流中的中位數?如果從數據流中讀出奇數個數值,那麼中位數就是所有數值排序之後位於中間的數值。如果從數據流中讀出偶數個數值,那麼中位數就是所有數值排序之後中間兩個數的平均值。我們使用Insert()方法讀取數據流,使用GetMedian()方法獲取當前讀取數據的中位數。

# 方法一:使用有序數組
# 插入數據和查找中位數的時間複雜度分別爲:O(n),O(1)
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.num = []
    
    def Insert(self, num):
        # write code here
        if num != None:
            length = len(self.num)
            for i in range(length):
                if num <= self.num[i]:
                    self.num.insert(i, num)
                    break
            # 如果沒有插入,則說明該數最大,所以放置於數組末尾
            if length == len(self.num):
                self.num.append(num)
                        
        
    def GetMedian(self, x):
        # write code here
        if len(self.num) < 1:
            return None

        length = len(self.num)
        if length % 2 == 1:
            return self.num[length//2]
        else:
            return (self.num[length//2]+self.num[length//2-1])/2.0
# 方法二:使用大頂堆和小頂堆存儲數據,實現中位數的查找
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.min_heap = [] # 小頂堆
        self.max_heap = [] # 大頂堆
    def Insert(self, num):
        # write code here
        # 根據數據總個數插入,當數據總數爲奇數時插入大頂堆,否則插入小頂堆
        length = len(self.min_heap) + len(self.max_heap)
        if length % 2 == 1: # 總數爲奇數,將元素插入大頂堆
            self.min_heap.append(num)
            min_num = self.adjustMin()
            self.min_heap.pop(0)  # 將小頂堆最小元素彈出後放入大頂堆
            self.adjustMin() # 彈出後再次調整堆結構
            self.max_heap.append(min_num)
            self.adjustMax()
        else: # 總數爲偶數,將元素插入小頂堆
            self.max_heap.append(num)
            max_num = self.adjustMax()
            self.max_heap.pop(0)  # 將小頂堆最小元素彈出後放入大頂堆
            self.adjustMax()  # 彈出後再次調整堆結構
            self.min_heap.append(max_num)
            self.adjustMin()
        
    def GetMedian(self, x):
        # x 爲無效參數
        # write code here
        length = len(self.min_heap) + len(self.max_heap)
        if length == 0:
            return []
        if length % 2 == 1:
            return self.min_heap[0]
        else:
            return (self.max_heap[0]+self.min_heap[0])/2.0
    
    def adjustMax(self):
        # 大頂堆調整,返回堆頂元素
        length = len(self.max_heap)
        if length < 1:
            return
        if length == 1:
            return self.max_heap[0]
        i = length // 2 - 1

        while i >= 0:
            left = 2*i + 1
            right = 2*i + 2
            if right < length and self.max_heap[left] < self.max_heap[right]:
                left = right
            if self.max_heap[left] > self.max_heap[i]:
                self.max_heap[i], self.max_heap[left] = self.max_heap[left], self.max_heap[i]
                if left <= length // 2 - 1:
                    i = left
                    continue
            i -= 1
            
        return self.max_heap[0]
        
    def adjustMin(self):
        # 小頂堆調整,返回堆頂元素
        length = len(self.min_heap)
        if length < 1:
            return
        if length == 1:
            return self.min_heap[0]
        i = length // 2 - 1

        while i >= 0:
            left = 2*i + 1
            right = 2*i + 2
            if right < length and self.min_heap[left] > self.min_heap[right]:
                left = right
            if self.min_heap[left] < self.min_heap[i]:
                self.min_heap[i], self.min_heap[left] = self.min_heap[left], self.min_heap[i]
                if left <= length // 2 - 1:
                    i = left
                    continue
            i -= 1
                
        return self.min_heap[0]

42.連續子數組的最大和

HZ偶爾會拿些專業問題來忽悠那些非計算機專業的同學。今天測試組開完會後,他又發話了:在古老的一維模式識別中,常常需要計算連續子向量的最大和,當向量全爲正數的時候,問題很好解決。但是,如果向量中包含負數,是否應該包含某個負數,並期望旁邊的正數會彌補它呢?例如:{6,-3,-2,7,-15,1,2,2},連續子向量的最大和爲8(從第0個開始,到第3個爲止)。給一個數組,返回它的最大連續子序列的和,你會不會被他忽悠住?(子向量的長度至少是1)要求時間複雜度爲O(n)O(n)

# 方法一:根據數組和的規律進行查找
# -*- coding:utf-8 -*-
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        if array is None or len(array) == 0:
            return
        if len(array) == 1:
            return array[0]
        
        max_sum = 0
        final_max = array[0]  # 存儲最大和

        for i in range(len(array)):
            max_sum += array[i]
            
            print(max_sum)
            print(array[i])
            if max_sum < array[i]:
                max_sum = array[i]
                
            if final_max < max_sum:
                final_max = max_sum

        return final_max
# 方法二:動態規劃法
# 設f(i)表示第i個數結尾的的子數組的最大和,則要求的最大和爲max(f(i))
# -*- coding:utf-8 -*-
class Solution:
    def FindGreatestSumOfSubArray(self, array):
        # write code here
        if array is None or len(array) == 0:
            return
        if len(array) == 1:
            return array[0]
        
        max_sum = 0  # f(i)
        final_max = array[0]  # 存儲最大和 max(f(i))
        
        for i in array:
            # 循環實現動態規劃
            if max_sum <= 0:
                max_sum = i
            else:
                max_sum += i
            
            if final_max < max_sum:
                final_max = max_sum
            
        return final_max

43. 1~n整數中1出現的次數

求出113的整數中1出現的次數,並算出1001300的整數中1出現的次數?爲此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。

# 方法一:暴力遍歷法,時間複雜度爲O(nlogn)
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        if n is None:
            return
        count = 0
        for i in range(1,n+1):
            count += self.countNum(i)
        
        return count
    
    def countNum(self, n):
        count = 0
        while n > 0:
            if n % 10 == 1:
                count += 1
            n = n // 10
        
        return count

s = Solution()
s.NumberOf1Between1AndN_Solution(10000)
4001
# 方法二:遞歸法,根據數字的規律進行遞歸求解
# 時間複雜度:O(logn)
# -*- coding:utf-8 -*-
class Solution:
    def NumberOf1Between1AndN_Solution(self, n):
        # write code here
        if n is None or n < 1:
            return 0
        
        return self.recursion_count(n)
        
    def recursion_count(self, n):
        if n == 0:
            return 0
        if n < 10:
            return 1
        
        count = 0
        high_num, time = self.getHigh(n)
        # 1在最高位時
        if high_num > 1:
            count = pow(10, time-1)
        else:
            count = n - pow(10, time-1) + 1
        
        # 1在除最高位之外的位置時
        count += high_num * (time - 1) * pow(10, time - 2) # 排列組合
        # 遞歸
        count += self.recursion_count(n - high_num*pow(10, time-1))
        
        return count
        
    def getHigh(self, n):
        # 獲取高位數字和總的位數
        if n == 0:
            return 0
        time = 0
        while n > 0:
            time += 1
            if n < 10:
                return n, time
            
            n = n // 10

44.數字序列中每一位的數字

數字以01234567891011121314…的格式排列。在這個序列中,第5位(從0開始計)是5,第13位是1,第19位是4。求任意第n爲對應的數字。

**PS:**此題牛客網沒有對應的在線評測,可以在acwing第57題進行在線評測。

# 方法:根據數字不同位數所佔個數的規律進行求解
class Solution(object):
    def digitAtIndex(self, n):
        """
        :type n: int
        :rtype: int
        """
        i = 1 # 數字位數
        while n - self.count_of_integers(i) >= 0:
            n = n - self.count_of_integers(i)
            i += 1
        rest = n // i # 相對當前位數數字所處的位置
        inte = n % i # 相對當前數字的位置

        return self.get_number(i, rest, inte)
        
        
    def count_of_integers(self, n):
        # 計算不同位數數字的個數
        if n == 1:
            return 10
        else:
            return (pow(10, n) - pow(10, n-1)) * n
    
    def get_number(self, i, rest, inte):
        # 根據數字位數i,相對i位數起始第rest個數字,得到第rest個數字的第inte位的數字
        if i == 1:
            num = 0 + rest
        else:
            num = pow(10, i-1) + rest
        
        inte = i - inte - 1
        while inte != 0:
            num = num // 10
            inte -= 1
            
        return num % 10

45.把數組排成最小的數

輸入一個正整數數組,把數組裏所有數字拼接起來排成一個數,打印能拼接出的所有數字中最小的一個。例如輸入數組{3,32,321},則打印出這三個數字能排成的最小數字爲321323。

# 方法:重新定義比較大小函數,根據字符串m+n與n+m的比較結果進行排序
# python3中利用functools模塊,實現自定義排序
# -*- coding:utf-8 -*-
import functools
class Solution:
    def PrintMinNumber(self, numbers):
        # write code here
        if numbers is None or len(numbers) == 0:
            return ''
        
        numbers = [str(x) for x in numbers]
        numbers.sort(key=functools.cmp_to_key(self.comp))
        
        return "".join(numbers)
    
    def comp(self, x, y):
        if (str(x)+str(y)) < (str(y)+str(x)):
            return -1
        else:
            return 1
s = Solution()
num = [3,5,1,4,2]
#num = [3, 32, 321]
s.PrintMinNumber(num)
'12345'

46.把數字翻譯成字符串

給定一個數字,我們按照如下規則把它翻譯爲字符串:

0翻譯成”a”,1翻譯成”b”,……,11翻譯成”l”,……,25翻譯成”z”。

一個數字可能有多個翻譯。例如12258有5種不同的翻譯,它們分別是”bccfi”、”bwfi”、”bczi”、”mcfi”和”mzi”。

請編程實現一個函數用來計算一個數字有多少種不同的翻譯方法。

PS: 此題牛客網沒有對應的在線評測,可以在acwing第55題進行評測。

方法一:遞歸法,自上而下的實現

自上而下的實現(即從字符串左至右遞歸),存在很多重複計算的問題。
遞歸公式:f(i)=f(i+1)+g(i,i+1)f(i+2)f(i)=f(i+1)+g(i,i+1)*f(i+2)

其中f(i)f(i)表示從第i位數字開始的翻譯數目,g(i,i+1)g(i,i+1)表示第i位和第i+1爲組合的數字如果在10–25範圍則爲1,否則爲0

class Solution:
    def getTranslationCount(self, s):
        """
        :type s: str
        :rtype: int
        """
        if int(s) < 0 or s is None:
            return 0
        
        # 將數字拆分
        s = [x for x in s]

        return self.recursion_count(s)
        
    def recursion_count(self, s):
        # 遞歸終止條件

        if len(s) < 2:
            return 1
        
        count = 0
        # 將s拆分爲1個元素和其餘元素
        count += self.recursion_count(s[1:])
        # 將s拆分爲前兩個元素和其餘元素
        if 10 <= int(s[0]+s[1]) <= 25:
            if len(s) >= 3:
                count += self.recursion_count(s[2:])
            else:
                count += 1
        return count

方法二:遞歸法,自下而上使用循環實現

自下而上實現(即從字符串右至左循環)效率更高。遞歸公式相同:f(i)=f(i+1)+g(i,i+1)f(i+2)f(i)=f(i+1)+g(i,i+1)*f(i+2)

從i=len(s)開始循環計算。

class Solution:
    def getTranslationCount(self, s):
        """
        :type s: str
        :rtype: int
        """
        if int(s) < 0 or s is None:
            return 0
        
        # 將數字拆分
        s = [x for x in s]

        return self.curlce_count(s)
        
    def curlce_count(self, s):
        # 遞歸終止條件

        if len(s) < 2:
            return 1
        
        count = 0
        length = len(s)
        # 遞推公式fi = fi_1 + g(i,i+1)*fi_2
        # 初始賦值,i從len(s)-1開始,此時賦值如下
        fi_2,fi_1,fi = 0, 0, 1
        
        for i in range(length-2, -1, -1):
            if i+2 > length:
                fi_2 = 0
            # 更新
            fi_2, fi_1 = fi_1, fi

            if 10 <= int(s[i]+s[i+1]) <= 25:
                fi = fi_1 + fi_2
            else:
                fi + fi_1
            
        return fi

47.禮物的最大價值

在一個m×n的棋盤的每一格都放有一個禮物,每個禮物都有一定的價值(價值大於0)。
你可以從棋盤的左上角開始拿格子裏的禮物,並每次向右或者向下移動一格直到到達棋盤的右下角。
給定一個棋盤及其上面的禮物,請計算你最多能拿到多少價值的禮物?

**PS:**此題牛客網沒有對應的在線評測,可以在acwing第60題進行評測。

# 方法一:動態規劃
# 遞推公式:f(i,j) = max(f(i, j-1), f(i-1, j)) + gift[i, j]
class Solution(object):
    def getMaxValue(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        if grid is None or len(grid) < 1 or len(grid[0]) < 1:
            return 0
        m, n = len(grid), len(grid[0])
        
        assist_list = [[0 for _ in range(n)] for _ in range(m)] # 輔助二維數組
        
        # 遞推公式爲:f(i,j) = max(f(i, j-1), f(i-1, j)) + gift[i, j]
        
        
        for i in range(m):
            for j in range(n):
                if i == 0 and j == 0:
                    assist_list[0][0] = grid[0][0]
                elif i == 0:
                    assist_list[0][j] = assist_list[0][j-1] + grid[0][j]
                elif j == 0:
                    assist_list[i][0] = assist_list[i-1][0] + grid[i][0]
                else:
                    assist_list[i][j] = max(assist_list[i-1][j], assist_list[i][j-1]) + grid[i][j]
                    
                
        return assist_list[-1][-1]

# 方法二: 在動態規劃的基礎上,可以進一步優化空間使用,只使用一維數組進行輔助

48.最長不含重複字符的子字符串

請從字符串中找出一個最長的不包含重複字符的子字符串,計算該最長子字符串的長度。
假設字符串中只包含從’a’到’z’的字符。

這個題沒有包含在牛客網《劍指offer》題庫裏,可以在以下網址進行在線評測:

長度爲n的字符串,其子字符串的個數爲n(n+1)/2+1n*(n+1)/2+1,所以從長度爲n的字符串中生成子串的時間複雜度爲O(n2)O(n^2).

則暴力法的時間複雜度爲:O(n3)O(n^3)

# 方法一:雙指針法
class Solution:
    def longestSubstringWithoutDuplication(self, s):
        """
        :type s: str
        :rtype: int
        """
        if s is None:
            return 0
        
        s = [x for x in s]
        length = len(s)
        assist_list = [-1]*26  # 輔助數組,記錄字符出現的位置
        
        left, right = 0, 0 # 左右雙指針掃描
        max_l, count = 0, 0
        
        for left in range(length):
            right = left
            while right < length and assist_list[ord(s[right])-ord('a')] == -1: # 當右指針未到末尾且當前字符未出現過時繼續掃描
                count += 1
                assist_list[ord(s[right])-ord('a')] = right
                right += 1
            
            if max_l < count:
                max_l = count
            # 從left下一位置重新掃描計數 
            count = 0
            assist_list = [-1]*26
            
        return max_l 

方法二:動態規劃法

遞推公式:f(i)=f(i1)+1f(i) = f(i-1) + 1, f(i)表示以第i個字符結尾的不重複子串的最長長度

分情況討論:

  • 當前字符之前未出現過:f(i)=f(i1)+1f(i) = f(i-1) + 1
  • 當前字符之前出現過,設重複字符之間相距d:
    • d&lt;=f(i)d &lt;= f(i): f(i)=df(i) = d
    • d&gt;f(i)d &gt; f(i) : f(i)=f(i1)+1f(i) = f(i-1) + 1
class Solution:
    def longestSubstringWithoutDuplication(self, s):
        """
        :type s: str
        :rtype: int
        """
        if s is None:
            return 0
        
        s = [x for x in s]
        length = len(s)
        assist_list = [-1]*26
        
        fi_1, fi, d = 0, 0, 0
        max_l = 0
        
        for i in range(length):
            if assist_list[ord(s[i])-ord('a')] == -1:
                fi = fi + 1
            else:
                d = i - assist_list[ord(s[i])-ord('a')]
                if d <= fi:
                    fi = d
                else:
                    fi = fi + 1
            # 更新字符位置   
            assist_list[ord(s[i])-ord('a')] = i
            # 記錄最大值
            if max_l < fi:
                max_l = fi
        return max_l 

49.醜數

把只包含質因子2、3和5的數稱作醜數(Ugly Number)。例如6、8都是醜數,但14不是,因爲它包含質因子7。 習慣上我們把1當做是第一個醜數。求按從小到大的順序的第N個醜數。

# 方法一:暴力遍歷法,時間複雜度過大,不可取
# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index < 1:
            return 0
        i, n = 1, 1
        while i < index:
            n += 1
            if self.ifUgly(n):
                i += 1
        return n
        
    def ifUgly(self, number):
        while number % 2 == 0:
            number = number // 2
        while number % 3 == 0:
            number = number // 3
        while number % 5 == 0:
            number = number // 5
        
        return number == 1
# 方法二:根據醜數生成的規律,使用輔助數組進行醜數生成和查找
# -*- coding:utf-8 -*-
class Solution:
    def GetUglyNumber_Solution(self, index):
        # write code here
        if index < 1:
            return 0
        i, n = 1, 1
        ugly_number_list = [1] # 輔助數組,存儲所有的醜數
        Max = ugly_number_list[0] # 當前最大的醜數
        T2, T3, T5 = 0, 0, 0  # 記錄上一次大於最大丑數的位置
        M2, M3, M5 = 0, 0, 0  # 記錄當前大於最大丑數的第一個數
        while i < index:
            for j in range(T2, len(ugly_number_list)):
                if ugly_number_list[j] * 2 > Max:
                    M2  = ugly_number_list[j] * 2
                    T2 = j
                    break
            for j in range(T3, len(ugly_number_list)):
                if ugly_number_list[j] * 3 > Max:
                    M3  = ugly_number_list[j] * 3
                    T3 = j
                    break
            for j in range(T5, len(ugly_number_list)):
                if ugly_number_list[j] * 5 > Max:
                    M5  = ugly_number_list[j] * 5
                    T5 = j
                    break
            ugly_number_list.append(min(M2,M3,M5)) # 按順序添加醜數
            Max = ugly_number_list[-1] # 更新當前的最大丑數
            i += 1
        return ugly_number_list[-1]
        

50.第一個只出現一次的字符

在一個字符串(0<=字符串長度<=10000,全部由字母組成)中找到第一個只出現一次的字符,並返回它的位置, 如果沒有則返回 -1(需要區分大小寫)

# 方法:藉助字典統計字符出現次數,確定第一個出現一次的字符,時間複雜度O(n)
# -*- coding:utf-8 -*-
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        if s is None or len(s) < 1:
            return -1
        
        count_dict = {}
        # 使用字典統計字符出現次數
        for i in s:
            if count_dict.has_key(i):
                count_dict[i] += 1
            else:
                count_dict[i] = 1
        # 再次遍歷,確定第一個只出現一次的字符
        for p, i in enumerate(s):
            if count_dict[i] == 1:
                return p
        return -1

50.1 字符流中第一個只出現一次的字符

請實現一個函數用來找出字符流中第一個只出現一次的字符。例如,當從字符流中只讀出前兩個字符"go"時,第一個只出現一次的字符是"g"。當從該字符流中讀出前六個字符“google"時,第一個只出現一次的字符是"l"。

# 方法:藉助字典統計字符出現的位置,當之前出現過時,將位置置爲特殊標記,比如-1
# 插入和查找的時間複雜度分別爲O(1)、O(n)
# -*- coding:utf-8 -*-
class Solution:
    # 返回對應char
    def __init__(self):
        self.count_dict = {} # 輔助數組
        self.counter = 0 # 記錄字符個數,即字符出現位置
    def FirstAppearingOnce(self):
        # write code here
        firstChar, count = None, 1000000 # count表示第一個出現一次字符的位置,初始時賦一個大值
        for char, count_ in self.count_dict.items():
            if count_ != -1 and count_ < count:
                firstChar = char
                count = count_
        if firstChar is not None:
            return firstChar
        else:
            return '#'
        
    def Insert(self, char):
        # write code here
        if char is None:
            return
        self.counter += 1 
        if self.count_dict.has_key(char):
            self.count_dict[char] = -1
        else:
            self.count_dict[char] = self.counter

51.數組中的逆序對

在數組中的兩個數字,如果前面一個數字大於後面的數字,則這兩個數字組成一個逆序對。輸入一個數組,求出這個數組中的逆序對的總數P。並將P對1000000007取模的結果輸出。 即輸出P%1000000007

**PS:**此題牛客網Python實現都超時無法通過,在AcWing65題評測使用Python可以通過。

# 方法一:暴力法,時間複雜度O(n^2),時間複雜度過大,不可取
class Solution:
    def InversePairs(self, data):
        # write code here
        if data is None or len(data) < 2:
            return 0
        
        P = 0
        for i in range(len(data)-1):
            for j in range(i+1, len(data)):
                if data[i] > data[j]:
                    P += 1
        return P%1000000007
s = Solution()
data = [1,2,3,4,5,6,7,0]
s.InversePairs(data)

7
# 方法二:基於分治法--歸併排序思路進行統計
# 時間複雜度:O(nlogn),但是在牛客網評測只通過50%,然後超時。在AcWing網站評測可以通過
# 牛客網使用Python 無法AC此題
# -*- coding:utf-8 -*-
class Solution:
    def __init__(self):
        self.count = 0
    def InversePairs(self, data):
        # write code here
        if data is None or len(data) < 2:
            return 0
        self.mergeSort(data)
        
        return self.count%1000000007
    def mergeSort(self, data):
        if len(data) <= 1:
            return data
        
        mid = len(data) // 2
        # 分
        left = self.mergeSort(data[:mid])
        right = self.mergeSort(data[mid:])
        
        # 治
        return self.merge(left, right)
        
    def merge(self, left, right):
        i, j = 0, 0
        res = []
        
        while i < len(left) and j < len(right):
            if left[i] > right[j]:
                res.append(left[i])
                i += 1
                self.count += len(right[j:])
            else:
                res.append(right[j])
                j += 1
        if i < len(left):
            res += left[i:]
        if j < len(right):
            res += right[j:]
            
        return res

52.兩個鏈表的第一個公共節點

輸入兩個鏈表,找出它們的第一個公共結點。

方法:
存在公共節點的鏈表呈Y字形分佈,倒敘遍歷將分叉。

  • 1.暴力遍歷法,時間複雜度O(n2)O(n^2)
  • 2.藉助兩個輔助棧,倒敘遍歷至分叉前的節點即爲第一個公共節點,時間複雜度O(n+m)O(n+m),空間複雜度O(n+m)O(n+m)
  • 3.統計鏈表節點個數,讓長鏈表先遍歷若干步,然後同時開始遍歷,第一個相等節點即爲第一個公共節點,時間複雜度O(n+m)O(n+m)
# 方法三:統計鏈表節點個數,讓長鏈表先遍歷若干步,然後同時開始遍歷,第一個相等節點即爲第一個公共節點
# -*- coding:utf-8 -*-
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution:
    def FindFirstCommonNode(self, pHead1, pHead2):
        # write code here
        if pHead1 is None or pHead2 is None:
            return None
        count1, count2 = 1, 1
        
        # 統計鏈表節點個數
        p = pHead1
        while p.next != None:
            count1 += 1
            p = p.next
        p = pHead2
        while p.next != None:
            count2 += 1
            p = p.next
        # 讓較長的鏈表先遍歷若干步
        dis = count1 - count2
        p1, p2 = pHead1, pHead2
        if dis > 0:
            rest_length = count2
            for _ in range(dis):
                p1 = p1.next
        else: 
            rest_length = count1
            dis = abs(dis)
            for _ in range(dis):
                p2 = p2.next
        # 尋找第一個公共節點
        for _ in range(rest_length):
            if p1.val == p2.val:
                return p1
            else:
                p1, p2 = p1.next, p2.next

        return None

53.在排序數組中查找數字

53.1 數字在排序數組中出現的次數

統計一個數字在排序數組中出現的次數。

# 方法一:遍歷統計:O(n)
# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if data is None:
            return 0
        count = 0
        for i in data:
            if i == k:
                count += 1
                
        return count
# 方法二:二分查找思路,O(logn)
# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if data is None or len(data) < 1:
            return 0
        # 判斷排序數組是升序還是降序
        up_flag = False
        if data[0] < data[-1]:
            up_flag = True # 升序
            
        # 二分法查找與K相等的值的index 
        left, right = 0, len(data)
        pivot = (left + right) // 2
        previous_pivot = pivot
        while data[pivot] != k:
            if data[pivot] > k:
                if up_flag:
                    right = pivot
                    pivot = (left + right) // 2
                else:
                    left = pivot
                    pivot = (left + right) // 2
            else:
                if up_flag:
                    left = pivot
                    pivot = (left + right) // 2
                else:
                    right = pivot
                    pivot = (left + right) // 2
            if previous_pivot == pivot:
                # 如果兩次的pivot相等,則說明數組裏不存在k值,返回0
                return 0
            previous_pivot = pivot
            
        # 找到k所在位置後,在k左右進行統計
        count = 1
        index = pivot - 1
        while index > -1 and data[index] == k:
            count += 1
            index -= 1
        index = pivot + 1
        while index < len(data) and data[index] == k:
            count += 1
            index += 1
        return count

53.2 0~n-1缺失的數字

一個長度爲n-1的遞增排序數組中的所有數字都是唯一的,並且每個數字都在範圍0到n-1之內。
在範圍0到n-1的n個數字中有且只有一個數字不在該數組中,請找出這個數字。

PS: 牛客網沒有此題在線評測,可在AcWing第68題進行評測。

# 方法:二分法查找與下標不相等的第一個數
class Solution(object):
    def getMissingNumber(self, data):
        """
        :type nums: List[int]
        :rtype: int
        """
        if data is None or len(data) < 1:
            return 0
        # 特例情況
        if len(data) >1 and data[0] != 0:
            return 0
            
        # 二分法查找與K相等的值的index 
        left, right = 0, len(data)
        pivot = (left + right) // 2
        previous_pivot = pivot
        while True:
            if data[pivot] > pivot:
                right = pivot
                pivot = (left + right) // 2
            else:
                left = pivot
                pivot = (left + right) // 2
            if previous_pivot == pivot:
                # 如果兩次的pivot相等,則下一個數即爲不存在的數
                return pivot+1
            previous_pivot = pivot

53.3 數組中數值和下標相等的元素

假設一個單調遞增的數組裏的每個元素都是整數並且是唯一的。
請編程實現一個函數找出數組中任意一個數值等於其下標的元素。
例如,在數組[-3, -1, 1, 3, 5]中,數字3和它的下標相等。

PS: 牛客網沒有此題在線評測,可在AcWing第69題進行評測。

class Solution(object):
    def getNumberSameAsIndex(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        
        if nums is None or len(nums) < 1:
            return -1
        
        left, right = 0, len(nums)
        mid = (left + right) // 2
        previous_mid = mid
        
        while nums[mid] != mid:
            if nums[mid] < mid:
                left = mid 
            else:
                right = mid
            mid = (left + right) // 2
            
            #print(mid)
            if previous_mid == mid:
                return -1
            
            previous_mid = mid
        
        return mid 

54. 二叉搜索樹的第K大節點

給定一棵二叉搜索樹,請找出其中的第k小的結點。例如, (5,3,7,2,4,6,8) 中,按結點數值大小順序第三小結點的值爲4。

# 方法:中序遍歷得到節點數組,然後取節點數組第k個元素
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回對應節點TreeNode
    def KthNode(self, pRoot, k):
        # write code here
        if pRoot is None or k < 1:
            return None

        node_list = self.recursion_LVR(pRoot)
        if len(node_list) < k:
            return None
        else:
            return node_list[k-1]

    def recursion_LVR(self, pRoot):
        # 中序遍歷,返回節點數組
        if pRoot is None:
            return []
        
        return self.recursion_LVR(pRoot.left) + [pRoot] + self.recursion_LVR(pRoot.right)

55.1 二叉樹的深度

輸入一棵二叉樹,求該樹的深度。從根結點到葉結點依次經過的結點(含根、葉結點)形成樹的一條路徑,最長路徑的長度爲樹的深度。

# 遞歸實現計算,選擇較長的子節點所在路徑長度作爲深度
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def TreeDepth(self, pRoot):
        # write code here
        if pRoot is None:
            return 0
        
        left = self.TreeDepth(pRoot.left)
        right = self.TreeDepth(pRoot.right)
        
        return (left+1) if (left > right) else (right+1)

55.2 平衡二叉樹

輸入一棵二叉樹,判斷該二叉樹是否是平衡二叉樹。

# 方法一:遞歸得到每個子節點的深度,判斷是否滿足平衡二叉樹條件
# 該方法存在重複遍歷過程,效率較低,不可取
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def IsBalanced_Solution(self, pRoot):
        # write code here
        if pRoot is None:
            return True
        left = self.tree_depth(pRoot.left)
        right = self.tree_depth(pRoot.right)
        if abs(left-right) > 1:
            return False
        return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)
        
    def tree_depth(self, pRoot):
        # 遞歸遍歷求子節點的深度
        if pRoot is None:
            return 0
        left = self.tree_depth(pRoot.left)
        right = self.tree_depth(pRoot.right)
        
        return (left+1) if (left > right) else (right + 1)
# 方法二:遞歸遍歷子節點的過程,判斷當前子樹是否滿足平衡條件,不滿足則將標誌位置爲False
# 與方法一相比,少了重複遞歸的過程
# -*- coding:utf-8 -*-
# class TreeNode:
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    def __init__(self):
        self.flag = True
    def IsBalanced_Solution(self, pRoot):
        # write code here
        if pRoot is None:
            self.depth = 0
            return True

        self.tree_depth(pRoot)
        return self.flag
        
    def tree_depth(self, pRoot):
        if pRoot is None:
            return 0
        left = self.tree_depth(pRoot.left)
        right = self.tree_depth(pRoot.right)
        
        if abs(left - right) > 1:
            self.flag = False
        
        return (left+1) if (left > right) else (right + 1)

56.數組中數字出現的次數

56.1 數組中只出現一次的兩個數字

一個整型數組裏除了兩個數字之外,其他的數字都出現了兩次。請寫程序找出這兩個只出現一次的數字。

**方法:**根據異或的性質來解題。當一個數組只存在一個出現一次的數,將數組所有數進行異或,則結果爲這個只出現一次的數;當數組存在兩個只出現一次的數,將數組所有數進行異或,然後按照異或結果將數組可分爲兩部分(分類依據是:找出異或結果中第一個非零位,根據該位是否爲零可以將數組分爲兩部分),將這兩部分分別進行異或即可得到兩個各出現一次的數字。

# -*- coding:utf-8 -*-
class Solution:
    # 返回[a,b] 其中ab是出現一次的兩個數字
    def FindNumsAppearOnce(self, array):
        # write code here
        if array is None or len(array) < 2:
            return []
        bin_xor = array[0]
        for i in array[1:]:
            bin_xor = bin_xor ^ i
        if bin_xor != 0:
            first1 = self.getFirst1(bin_xor)
        else:
            return []
        a, b = 0, 0
        for i in array:
            if self.getFirst1(i) == first1:
                a ^= i
            else:
                b ^= i
        return [a, b]
    
    def getFirst1(self, num):
        # 自右向左獲取第一個非零位置
        n = 0
        while num & (1 << n) == 0:
            n += 1
        return n

56.2 數組中唯一出現一次的數字

在一個數組中除了一個數字只出現一次之外,其他數字都出現了三次。
請找出那個只出現一次的數字。
你可以假設滿足條件的數字一定存在。
思考題:
如果要求只使用 O(n) 的時間和額外 O(1) 的空間,該怎麼做呢?

此題牛客網沒有在線評測,可以在AcWing第74題進行評測。

方法:

  • 方法一:基於數字二進制數的特點,統計數組所有數對應二進制數每一位1出線的次數總和,將次數分別對3取餘,取餘不爲零的位則爲1,就可得到只出現一次的數的二進制數,時間複雜度O(n)O(n)
  • 數組排序後得到出現一次的數,時間複雜度O(nlogn)O(nlogn)
  • 藉助哈希表統計數字出現次數,,空間複雜度O(n)O(n)
# 基於方法一
class Solution(object):
    def findNumberAppearingOnce(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if nums is None or len(nums) < 1:
            raise('Invalid input')
        
        digits_count = [0] * 16 # 定義數組,存儲數字二進數數每一位1的總個數
        # 累加數組中數字二進數每一位1的總個數
        for i in nums:
            n = 0
            while i>>n != 0:
                digits_count[n] += (i>>n) & 1

                n += 1 
                
        # 根據每一位1的個數是否能被3整除得到只出現一次的數字對應的二進制數
        flag_index = 0
        for i in range(len(digits_count)):
            if digits_count[i] == 0:
                flag_index = i
                break
            else:
                digits_count[i] %= 3
        # 將只出現一次的數字從二進制數轉化爲整數
        res = 0
        for i, n in enumerate(digits_count[:flag_index]):
            res += pow(2, i) * n
        
        return res

s = Solution()
nums = [1,1,1,2,2,2,3,4,4,4]
s.findNumberAppearingOnce(nums)
3

57.和爲s的數字

57.1 和爲s的兩個數字

輸入一個遞增排序的數組和一個數字S,在數組中查找兩個數,使得他們的和正好是S,如果有多對數字的和等於S,輸出兩個數的乘積最小的。

方法

  • 暴力遍歷法,時間複雜度O(n2)O(n^2)
  • 雙指針向中間遍歷法,時間複雜度O(n)O(n)
# 雙指針法
# -*- coding:utf-8 -*-
class Solution:
    def FindNumbersWithSum(self, array, tsum):
        # write code here
        if array is None or len(array) < 2:
            return []
        
        head, end = 0, len(array) - 1
        
        while head < end:
            s = array[head] + array[end]
            if s == tsum:
                return [array[head], array[end]]
            elif s > tsum:
                end -= 1
            else:
                head += 1
        return []

57.2 和爲s的連續正數數列

小明很喜歡數學,有一天他在做數學作業時,要求計算出9~16的和,他馬上就寫出了正確答案是100。但是他並不滿足於此,他在想究竟有多少種連續的正數序列的和爲100(至少包括兩個數)。沒多久,他就得到另一組連續正數和爲100的序列:18,19,20,21,22。現在把問題交給你,你能不能也很快的找出所有和爲S的連續正數序列? Good Luck!

# 雙指針法,同時從頭開始向後遞增,循環條件爲head < (tsum+1) // 2
# -*- coding:utf-8 -*-
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        if tsum < 1:
            return []
        res = []
        head, end = 1, 2
        
        while head < (tsum+1) // 2:
            s = sum(range(head,end))
            if  s == tsum:
                res.append(range(head, end))
                end += 1
            elif s < tsum:
                end += 1
            else:
                head += 1
        return res

58.翻轉字符串

58.1 翻轉單詞順序

輸入一個英文句子,翻轉句子中單詞的順序,但單詞內字符的順序不變。
爲簡單起見,標點符號和普通字母一樣處理。
例如輸入字符串"I am a student.",則輸出"student. a am I"。

# 方法一:藉助額外的空間記錄句子中的單詞,然後對單詞進行翻轉,空間複雜度O(n)
# -*- coding:utf-8 -*-
class Solution:
    def ReverseSentence(self, s):
        # write code here
        if s is None or len(s) < 1:
            return ""
        
        start, end = 0, 0
        assist_list = []
        for i,c in enumerate(s):
            if c is " ":
                assist_list.append(s[start:i])
                start = i+1
            if i == len(s)-1:
                assist_list.append(s[start:])
        assist_list.reverse()
        return " ".join(assist_list)
# 方法二:兩次翻轉法,不需要額外的空間消耗
# -*- coding:utf-8 -*-
class Solution:
    def ReverseSentence(self, s):
        # write code here
        if s is None or len(s) < 1:
            return ""
        s = list(s)
        # 翻轉句子
        s = self.reverse(s)
        res = []
        # 翻轉單詞
        start, end = 0, 0
        for i, c in enumerate(s):
            if c == " ":
                temp = self.reverse(s[start:i])
                res.append(''.join(temp))
                start = i + 1
            if i == len(s) - 1:
                temp = self.reverse(s[start:])
                res.append(''.join(temp))
                
        return " ".join(res)
                
    def reverse(self, s):
        # 對整個句子進行翻轉
        head, end = 0, len(s)-1
        while head < end:
            s[head], s[end] = s[end], s[head]
            head += 1
            end -= 1
        
        return s

58.2 左旋轉字符串

彙編語言中有一種移位指令叫做循環左移(ROL),現在有個簡單的任務,就是用字符串模擬這個指令的運算結果。對於一個給定的字符序列S,請你把其循環左移K位後的序列輸出。例如,字符序列S=”abcXYZdef”,要求輸出循環左移3位後的結果,即“XYZdefabc”。是不是很簡單?OK,搞定它!

# 方法一:利用python字符串特性直接操作
# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        if s is None or len(s) < 1 or n < 0:
            return ""
        
        length = len(s)
        n = n % length
        
        return s[n:] + s[:n]
# 方法二:從移位點將字符串分割爲左右兩部分,分別進行翻轉,總共進行三次翻轉實現左旋
# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        if s is None or len(s) < 1 or n < 0:
            return ""
        s = list(s)
        length = len(s)
        n = n % length
        
        s = self.reverse(s)
        left = self.reverse(s[:length-n])
        right = self.reverse(s[length-n:])
        
        return "".join(left+right)
    
    def reverse(self, s):
        # 對整個句子進行翻轉
        head, end = 0, len(s)-1
        while head < end:
            s[head], s[end] = s[end], s[head]
            head += 1
            end -= 1
        
        return s

59.隊列的最大值

59.1 滑動窗口的最大值

給定一個數組和滑動窗口的大小,找出所有滑動窗口裏數值的最大值。例如,如果輸入數組{2,3,4,2,6,2,5,1}及滑動窗口的大小3,那麼一共存在6個滑動窗口,他們的最大值分別爲{4,4,6,6,6,5}; 針對數組{2,3,4,2,6,2,5,1}的滑動窗口有以下6個: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。

# 方法:利用雙端隊列存儲窗口的最大值在數組中的位置(下標)
# -*- coding:utf-8 -*-
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        if num is None or len(num) < 1 or size == 0:
            return []
        
        max_deque = []
        max_val = []
        
        for i, n in enumerate(num):
            # 彈出超出窗口的隊首
            if len(max_deque) > 0 and i >= (max_deque[0]+size):
                max_deque.pop(0)
            
            if len(max_deque) > 0 and i < (max_deque[0]+size) and n >= num[max_deque[0]]:
                # 更新隊首爲最大值
                max_deque = []
                max_deque.append(i)

            elif len(max_deque) > 0 and num[max_deque[-1]] <= n:
                # 更新隊尾爲較大值
                while len(max_deque) > 0 and num[max_deque[-1]] <= n:
                    max_deque.pop(-1)
                max_deque.append(i)
            else:
                max_deque.append(i)
                
            # 取出各個窗口的最大值
            if i - size >= -1:
                max_val.append(num[max_deque[0]])
        return max_val
                

59.2 隊列的最大值

定義一個隊列並實現函數 max 得到隊列裏的最大值,要求函數 max、push_back 和 pop_front 的時間複雜度都是O(1)。

**PS:**此題沒有在網上找到對應的在線評測平臺。

方法: 利用雙端隊列存儲隊列最大值,實現O(1)O(1)獲取隊列最大值

class QueueWithMax:
    def __init__(self):
        self.max_q = []
        self.queue_with_max = []
    def push_back(self, n):
        self.queue_with_max.append(n)
        index = len(self.queue_with_max) - 1 # 當前插入值對應的index
        if len(self.max_q) > 0 and n >= self.queue_with_max[self.max_q[0]]:
            self.max_q = []
            self.max_q.append(index)
        elif len(self.max_q) > 0 and self.queue_with_max[self.max_q[-1]] <= n:
            while len(self.max_q) > 0 and self.queue_with_max[self.max_q[-1]] <= n:
                self.max_q.pop(-1)
            self.max_q.append(index)
        else:
            self.max_q.append(index)
            
    def pop_front(self):
        if len(self.queue_with_max) > 0:
            res = self.queue_with_max.pop(0)
            # 彈出最大值隊列隊首元素
            if self.max_q[0]-1 < 0:
                self.max_q.pop(0)
                
            # 將最大值隊列所存儲的索引前移一位
            for i in range(len(self.max_q)):
                self.max_q[i] -= 1 
                
            return res
        else:
            return None
        
    def max(self):
        if len(self.max_q) < 1:
            return None
        else:
            return self.queue_with_max[self.max_q[0]]
        
# Test
s = QueueWithMax()
s.push_back(1)
print(s.max())
s.push_back(5)
print(s.max())
s.push_back(3)
print(s.max())
s.push_back(7)
print(s.max())
s.push_back(2)
print(s.max())
print(s.pop_front())
print(s.max())
print(s.pop_front())
print(s.max())
print(s.pop_front())
print(s.max())
print(s.pop_front())
print(s.max())
s.push_back(5)
print(s.max())
1
5
5
7
7
1
7
5
7
3
7
7
2
5

60.n個骰子的點數

將一個骰子投擲n次,獲得的總點數爲s,s的可能範圍爲n~6n。
擲出某一點數,可能有多種擲法,例如投擲2次,擲出3點,共有[1,2],[2,1]兩種擲法。
請求出投擲n次,擲出n~6n點分別有多少種擲法。

PS: 此題牛客網沒有對應的在線評測,可以在AcWing第80題進行評測。

# 方法一:遞歸法,遞歸公式f(n) += f(n-1),使用輔助數組存儲和值出現的次數,數組的索引即爲和值
#  這種方法時間效率較低,由於遞歸存在很多重複計算
class Solution(object):
    def numberOfDice(self, n):
        """
        :type n: int
        :rtype: List[int]
        """
        if n is None or n < 1:
            return []
        
        return self.recursion_count(n)
        
    def recursion_count(self, n):
        # 遞歸計算不同和值出線的次數
        if n == 1:
            res = [0] * (6*n - n + 1)
            for i in range(1,7): res[i-1] = 1
            return res
        
        res = [0] * (6*n - n + 1) # 統計n時,和值出現的次數
        #print(6*n - n + 1)
        res_pre = self.recursion_count(n-1)
        #print(res_pre)
        for i in range(1,7):
            for j in range(n-1, 6*(n-1)+1):  # n-1時和值的範圍爲[n-1, 6*(n-1)]
                #print(i+j - n)
                res[i+j - n] += res_pre[j-(n-1)] # n-1時和值出現的次數爲res_pre[j-(n-1)]
        
        return res
        # 如果求每個和值的概率,則返回如下
        # sum_count = sum(res)
        # return [x/sum_count for x in res]
# 方法二:將方法一中的遞歸使用循環實現,減少重複計算,時間效率O(n^2)
class Solution(object):
    def numberOfDice(self, n):
        """
        :type n: int
        :rtype: List[int]
        """
        if n is None or n < 1:
            return []
        
        if n == 1:
            res = [0] * (6*n - n + 1)
            for i in range(1,7): res[i-1] = 1
            return res
        
        res_pre = [1] * (6*1 - 1 + 1) # 只有一個骰子的情況記錄
        
        for num in range(2, n+1):
            res = [0] * (6*num - num + 1)
            for i in range(1,7):
                for j in range(num-1, 6*(num-1)+1):  # num-1時和值的範圍爲[num-1, 6*(num-1)]
                    res[i+j - num] += res_pre[j-(num-1)] # num-1時和值出現的次數爲res_pre[j-(num-1)]
            res_pre = res
            
        
        return res
        # 如果求每個和值的概率,則返回如下
        # sum_count = sum(res)
        # return [x/sum_count for x in res]
# 方法三:動態規劃法
# 狀態轉移公式,f(n,k) = f(n-1, k-1) + f(n-1, k-2) + ... + f(n-1, k-6),  n<= k <= 6n
# 初始狀態: f(1, 1)= f(1, 2) = ... = f(1, 5) = f(1, 6) = 1
# 其中f(n , k)表示n個骰子點數和爲k時出現的次數
# 使用1維數組存儲有效的和值次數,比二維數組空間使用率稍高
# 時間複雜度: O(n^2)
class Solution(object):
    def numberOfDice(self, n):
        """
        :type n: int
        :rtype: List[int]
        """
        if n is None or n < 1:
            return []
        
        f1 = [1] * 6
        if n == 1:
            return f1
            
        # 動態規劃
        fn_1 = f1 # n-1時有效和值的次數統計
        for i in range(2, n+1):
            fn = [0] * (6*i - i + 1) # 和值有效範圍[n, 6*n]-->線性映射至(長度爲(6*n - n + 1)的一維數組
            for k in range(len(fn)):
                #fn[k] = 
                k_sum = k + i # 從數組索引反映射回對應的有效和值
                # 此處求f(n,k), f(n,k) = f(n-1, k-1) + f(n-1, k-2) + ... + f(n-1, k-6),
                for j in range(1, 7):
                    if 6*(i-1) >= k_sum-j >= i-1:
                        fn[k] += fn_1[k_sum- j - (i-1)]

            fn_1 = fn  
        return fn

61.撲克牌中的順子

從撲克牌中隨機抽5張牌,判斷是不是一個順子,即這5張牌是不是連續的。
2~10爲數字本身,A爲1,J爲11,Q爲12,K爲13,大小王可以看做任意數字。
爲了方便,大小王均以0來表示,並且假設這副牌中大小王均有兩張。

# 根據數字間的差值和王的數量關係判斷是否成爲順子,同時如果存在對子則無法成爲順子
# -*- coding:utf-8 -*-
class Solution:
    def IsContinuous(self, numbers):
        # write code here
        if numbers is None or len(numbers) != 5:
            return False
        
        numbers.sort()
        zero_num = 0 # 記錄零的個數
        dif = 0 # 統計數字差值
        for i,j in zip(numbers[:-1], numbers[1:]):
            if i == 0:
                zero_num += 1
            elif i == j:  # 如果存在對子,則無法成爲順子
                return False
            else:
                dif += j - i - 1 # 統計數字之間比1大的差值
        
        if dif <= zero_num: # 王的數量是否大於等於數字之間的差值
            return True
        else:
            return False

62.圓圈中最後剩下的數字

0, 1, …, n-1這n個數字(n>0)排成一個圓圈,從數字0開始每次從這個圓圈裏刪除第m個數字。
求出這個圓圈裏剩下的最後一個數字。

約瑟夫環問題。

方法一:使用額外空間存儲數字,模擬刪除過程

時間複雜度O(nm)O(nm),空間複雜度O(n),這種方法在牛客網超時無法通過,在Acwing第82題可以通過。

# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n is None or n <= 1 or m < 1:
            return 0
        
        num = [x for x in range(n)]
        index = 0
        for _ in range(n-1):
            count = 0
            while count < m:
                if num[index] != -1:
                    count += 1
                    index = (index + 1) % n
                else:
                    index = (index + 1) % n
            num[index-1] = -1
        for i in num:
            if i != -1:
                return i
s = Solution()
s.LastRemaining_Solution(5, 3)
3

方法二:分析問題,建立遞歸公式

定義關於n,mn,m的函數f(n,m)f(n,m),表示每次在n個數字0,1,2,&ThinSpace;,n10,1,2,\cdots, n-1中刪除第m個數字後剩下的數字。第一個被刪除的是k=(m1)k = (m-1)%n,剩餘數字爲0,1,&ThinSpace;,k1,k+1,&ThinSpace;,n10,1,\cdots, k-1, k+1,\cdots, n-1,則新的序列爲$k+1,\cdots, n-1, 0,1,\cdots, k-1 ,,可記爲f(n-1, m)$,將新的序列可映射至0 n20~n-2範圍,映射函數爲p(x)=(xk1)%np(x)=(x-k-1)\%n,逆映射爲p1(x)=(x+k+1)%np^{-1}(x)=(x+k+1)\%n。最終可得遞推關係:
f(n,m)={0n=1[f(n1,m)+m]%nn&gt;1f(n,m) = \left\{ \begin{matrix} 0 &amp; n=1 \\ [f(n-1,m)+m]\%n &amp; n &gt; 1 \end{matrix}\right.

# 根據方法二的遞推公式使用循環法從1開始遞推
# -*- coding:utf-8 -*-
class Solution:
    def LastRemaining_Solution(self, n, m):
        # write code here
        if n is None or n < 1 or m < 1:
            return -1

        last = 0
        for i in range(2, n+1):
            last = (last + m)%i
        
        return last

63. 股票的最大利潤

假設把某股票的價格按照時間先後順序存儲在數組中,請問買賣 一次 該股票可能獲得的利潤是多少?
例如一隻股票在某些時間節點的價格爲[9, 11, 8, 5, 7, 12, 16, 14]。
如果我們能在價格爲5的時候買入並在價格爲16時賣出,則能收穫最大的利潤11。

**PS:**此題牛客網沒有在線評測,可在AcWing第83題進行評測。

class Solution(object):
    def maxDiff(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if nums is None or len(nums) <= 1:
            return 0
        
        min_in = nums[0]
        profit = 0
        for i in nums[1:]:
            if i < min_in:
                min_in = i
            elif (i - min_in) > profit:
                profit = i - min_in
        return profit

64. 求1+2+3+…+n

求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等關鍵字及條件判斷語句(A?B:C)。

# 使用遞歸實現累加
# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here
        res = n
        flag = (n > 0) and self.Sum_Solution(n - 1)
        res += flag
        return res
# 另一種遞歸
# -*- coding:utf-8 -*-
class Solution:
    def Sum_Solution(self, n):
        # write code here
        return n and self.Sum_Solution(n - 1) + n

65. 不用加減乘除做加法

寫一個函數,求兩個整數之和,要求在函數體內不得使用+、-、*、/四則運算符號。

# 方法:使用位運算實現
# -*- coding:utf-8 -*-
class Solution:
    def Add(self, num1, num2):
        # write code here
        if num1 is None or num2 is None:
            return -1
        
        while num2 != 0:
            sums = num1 ^ num2
            num2 = (num1&num2)<<1 # 進位
            num1 = sums & 0xFFFFFFFF # 考慮負數
            
        
        return num1 if num1 >> 31 == 0 else num1 - 4294967296

66. 構建乘積數組

給定一個數組A[0,1,...,n1]A[0,1,...,n-1],請構建一個數組B[0,1,...,n1]B[0,1,...,n-1],其中B中的元素B[i]=A[0]A[1]...A[i1]A[i+1]...A[n1]B[i]=A[0]*A[1]*...*A[i-1]*A[i+1]*...*A[n-1]。不能使用除法。

# 方法一暴力法:O(n^2)

# 方法二使用A構造矩陣來構造B
# -*- coding:utf-8 -*-
class Solution:
    def multiply(self, A):
        # write code here
        if A is None or len(A) <= 1:
            return []
        length = len(A)
        BA = [[1 if i == j else A[j] for j in range(length)] for i in range(length)] # 構建矩陣,對角線元素爲1
        
        B = [1] * length
        for i, li in enumerate(BA):
            mul = 1
            for j in li:
                mul *= j 
            B[i] = mul
        return B

67. 把字符串轉整數

將一個字符串轉換成一個整數(實現Integer.valueOf(string)的功能,但是string不符合數字要求時返回0),要求不能使用字符串轉換整數的庫函數。 數值爲0或者字符串不是一個合法的數值則返回0。

# -*- coding:utf-8 -*-
class Solution:
    def StrToInt(self, s):
        # write code here
        if s is None or len(s) < 1:
            return 0
        if len(s) == 1 and (s[0] is "+" or s[0] is "-"):
            return 0
        
        flag = 1
        head = 0
        valid_list = "1234567890"
        if s[0] is "-":
            flag = -1
        elif s[0] != "-" and s[0] != "+":
            if s[0] in valid_list:
                head = int(s[0])
            else:
                return 0
        length,count = len(s), len(s)
        res = 0
        while count > 1:
            if s[count-1] in valid_list:
                res += int(s[count-1])*pow(10, length-count)
                count -= 1
            else:
                return 0
        
        if head == 0:
            return flag*res
        else:
            return head*pow(10, length-1)+res

67.& 更復雜的字符串轉正數

AcWing第87題對字符串轉整數提出更高的要求。

你的函數應滿足下列條件:

  • 忽略所有行首空格,找到第一個非空格字符,可以是 ‘+/−’ 表示是正數或者負數,緊隨其後找到最長的一串連續數字,將其解析成一個整數;
  • 整數後可能有任意非數字字符,請將其忽略;
  • 如果整數長度爲0,則返回0;
  • 如果整數大於INT_MAX(2^31 − 1),請返回INT_MAX;如果整數小於INT_MIN(−2^31) ,請返回INT_MIN;
class Solution(object):
    def strToInt(self, str):
        """
        :type str: str
        :rtype: int
        """
        s = str
        if s is None or len(s) < 1:
            return 0
        if len(s) == 1 and (s[0] is "+" or s[0] is "-"):
            return 0
        
        dig_flag = 1
        head = 0
        valid_list = "1234567890"
        
        # 剔除字符串首的空格,尾的不合法元素
        count = 0
        while  s[0] == " ": #!= "+" and s[0] != "-" and s[0] not in valid_list and :
            count += 1
            s = s[1:]
            if len(s) < 1:
                return 0
        count = len(s)
        while  s[-1] not in valid_list:
            count -= 1
            s = s[:-1]
            if len(s) < 1:
                return 0
        
        # 處理字符串第一個元素
        if s[0] is "-":
            dig_flag = -1
        elif s[0] != "-" and s[0] != "+":
            if s[0] in valid_list:
                head = int(s[0])
            else:
                return 0
        
        # 處理其餘元素
        length,count = len(s), len(s)
        res = 0
        while count > 1:
            if s[count-1] in valid_list:
                res += int(s[count-1])*pow(10, length-count)
                count -= 1
            else:
                return 0

        INT_MAX = pow(2,31)-1
        INT_MIN = -pow(2,31)
        # 合併得到最終值
        if head == 0:
            res = dig_flag*res
        else:
            res = head*pow(10, length-1)+res
        
        # 判斷值的範圍
        if INT_MIN <= res <= INT_MAX:
            return res
        elif res > INT_MAX:
            return INT_MAX
        else:
            return INT_MIN

68.樹中兩個節點的最低公共祖先

給出一個二叉樹,輸入兩個樹節點,求它們的最低公共祖先。
一個樹節點的祖先節點包括它本身。

注意:

  • 輸入的二叉樹不爲空;
  • 輸入的兩個節點一定不爲空,且是二叉樹中的節點;

**PS:**此題牛客網沒有在線評測,可在AcWing第88題進行評測。

方法:

  • 使用鏈表保存從樹根節點到目標節點的路徑,然後問題轉化爲求兩個鏈表的第一個公共節點
  • 使用遞歸進行遍歷,終止條件爲遍歷至樹的末頁節點或者遍歷至目標節點,返回目標節點,當返回的左右子節點均不爲空則說明其根節點爲最低公共祖先,當左右子節點只有其一爲空,則另一個節點必爲最低公共祖先
# 遞歸遍歷
# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None

class Solution(object):
    def lowestCommonAncestor(self, root, p, q):
        """
        :type root: TreeNode
        :type p: TreeNode
        :type q: TreeNode
        :rtype: TreeNode
        """
        if root is None:
            return None
        if root == p or root == q:
            return root
            
        left = self.lowestCommonAncestor(root.left, p, q)
        right = self.lowestCommonAncestor(root.right, p, q)
        if left != None and right != None:
            return root
        
        if left is None:
            return right
        elif right is None:
            return left

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