劍指offer_Python解題(一)

1、二維數組中的查找

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

1.1 思路

順序排序,查找。典型的二分遞歸思路:
具體思路如下:
0- 先判斷是否在數組的數據分佈範圍內
1- 判斷是在下半行還是中間行的下半段,縮小數組
判斷是在上半行還是中間行的上半段,縮小數組
2- 當僅僅剩餘一個值時,判斷兩者是否相等

1.2 解題

# 二分遞歸
# 例子:
#     arr = [[1, 2, 3, 4], [5, 6, 7, 8]
#         , [9, 10, 11, 12],[15, 16, 17, 18]
#         , [20, 22, 25, 27], [29, 30, 40, 41 ]]
#     n_in = 28
#     find_int(arr, n_in)
import copy
def find_int(arr_in, n_in):
    arr = copy.deepcopy(arr_in)
    m = len(arr) - 1
    n = len(arr[0]) - 1
    # 0. 不在查找的範圍
    if (n_in < arr[0][0]) or (n_in > arr[m][n]):
        return False
    else:
        # 對奇數偶數一同處理
        m_mid = m // 2 + m % 2
        n_mid = n // 2 + n % 2
        # 1. 一般情況 在後面行
        if (n_in > arr[m_mid][n_mid]) and (n_in > arr[m_mid][n]):
            arr = arr[m_mid:]
            print('now array is:', arr)
            return find_int(arr, n_in)
        # 2. 在中間行後半段
        elif (n_in >= arr[m_mid][n_mid]) and (n_in <= arr[m_mid][n]):
            arr = [arr[m_mid][n_mid:]]
            print('now array is:', arr)
            if (len(arr[0]) == 1):
                return arr[0] == n_in
            else:
                return find_int(arr, n_in)
        # 3. 一般情況 在前面行
        elif (n_in < arr[m_mid][n_mid]) and (n_in < arr[m_mid][0]):
            arr = arr[:m_mid]
            print('now array is:', arr)
            return find_int(arr, n_in)
        # 4. 在中間行前半段
        elif (n_in <= arr[m_mid][n_mid]) and (n_in >= arr[m_mid][0]):
            arr = [arr[m_mid][:n_mid]]
            print('now array is:', arr)
            if (len(arr[0]) == 1):
                return arr[0] == n_in
            else:
                return find_int(arr, n_in)

arr = [[1, 2, 3, 4], [5, 6, 7, 8], [9, 10, 11, 12], [15, 16, 17, 18]
        , [20, 22, 25, 27], [29, 30, 40, 41]]
n_in = 26
find_int(arr, n_in)
"""
>>> find_int(arr, n_in)
now array is: [[15, 16, 17, 18], [20, 22, 25, 27],
[29, 30, 40, 41]]
now array is: [[25, 27]]
now array is: [[25]]
False
"""

2、替換空格

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

2.1 思路

替換問題,顯然需要遍歷,當需要遍歷的時候就可以雙指針同時遍歷來加速:
具體思路如下:
0- 雙向同時遍歷,正向正常空格替換 %20,反向空格替換爲 02%
1- 合併前半 和 後半(逆序)字符
2- 考慮字段長度奇偶問題

2.2 解題

# 雙向遍歷
def replaceSpace(s):
    s_l = len(s) - 1
    s_s, s_e = 0, s_l
    s_outs, s_oute = '', ''
    while s_s <= s_e:
        # 考慮字段長度奇偶問題
        if s_s == s_e:
            s_outs = s_outs + '%20' if s[s_s] == ' ' else s_outs + s[s_s]
        else:
            s_outs = s_outs + '%20' if s[s_s] == ' ' else s_outs + s[s_s]
            s_oute = s_oute + '02%' if s[s_e] == ' ' else s_oute + s[s_e]
        s_s += 1
        s_e -= 1

    return s_outs + s_oute[::-1]


s = 'We are happy '
replaceSpace(s)
"""
>>> replaceSpace(s)
'We%20are%20happy%20'
"""

3、從尾到頭打印鏈表

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

3.1 思路

讀取指針值,添加到list, 最後逆方向返回

3.2 解題

def printListFromTailToHead(self, listNode):
    if not listNode:
        return []
    else:
        p = listNode
        stack = []
        while p:
            stack.append(p.val)
            p = p.next
        return stack[::-1]

4、重建二叉樹

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

設L、D、R分別表示遍歷左子樹、訪問根結點和遍歷右子樹, 則對一棵二叉樹的遍歷有三種情況:DLR(稱爲先根次序遍歷),LDR(稱爲中根次序遍歷),LRD (稱爲後根次序遍歷)。

4.1 思路

二叉樹,遞歸思路:
0- 從DRL中找到 節點值
1- 從LDR中找到 節點的位置
2- 然後拆分成左右進行遞歸

4.2 解題

# 0 由於python 中無樹結構,需要自己構建
class Binary_Tree():
    def __init__(self, value=None, left=None, right=None):
        self.value = value
        self.left = left  
        self.right = right

# 1 按照思路重建二叉樹
def reConstrauctBtree(dlr, ldr):
    if dlr == []:
        return None
    ldr_dc = dict(zip(ldr, range(len(ldr))))
    n = ldr_dc[dlr[0]]
    root = Binary_Tree(dlr[0])
    print('node:', dlr[0], 'left :', dlr[1:n+1], 'right :', dlr[n+1:])
    root.left = reConstrauctBtree(dlr[1:n+1], ldr[:n])
    root.right = reConstrauctBtree(dlr[n+1:], ldr[n+1:])
    return root
    
dlr = [1, 2, 4, 7, 3, 5, 6, 8]
ldr = [4, 7, 2, 1, 5, 3, 8, 6]
r = reConstrauctBtree(dlr, ldr)

# 2 前項遍歷查看
def pretree(root):
    print(root.value)
    if root.left:
        pretree(root.left)
    if root.right:
        pretree(root.right)

pretree(r)

>>> r = reConstrauctBtree(dlr, ldr)
node: 1 left : [2, 4, 7] right : [3, 5, 6, 8]
node: 2 left : [4, 7] right : []
node: 4 left : [] right : [7]
node: 7 left : [] right : []
node: 3 left : [5] right : [6, 8]
node: 5 left : [] right : []
node: 6 left : [8] right : []
node: 8 left : [] right : []

>>> pretree(r)
1
2
4
7
3
5
6
8

5、用兩個棧實現一個隊列

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

跳過

6、旋轉數組中的最小數字

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

本意爲:一個遞增數組,將大的數幫到前面,求改數組的最小值

6.1 思路

本質是順序數組,首先考慮二分遞歸:
0- 求數組的中位數,左中位數,右中位數
1- 分4種情況比較三個中位數
2- 取小段再遞歸
3- 直到左中位數等於右中位數

6.2 解題



import copy
def minNumberInRotateArray(rotateArray: list) -> int:
    """
    二分遞歸
    """
    r_ = copy.deepcopy(rotateArray)
    if r_ == []:
        return 0
        
    n = len(r_) - 1
    mid = n // 2 + n % 2
    left_m = mid // 2 + mid % 2
    right_m = (n - mid) // 2 + (n - mid) % 2 + mid
    print('left mid:', r_[left_m], 'mid:', r_[mid], 'right mid:'
    , r_[right_m]) #, 'r_', r_)
    if (left_m == right_m):
        return r_[left_m]
    # 0- l < m < r
    elif (r_[mid] <= r_[right_m]) and (r_[mid] >= r_[left_m]):
        return minNumberInRotateArray(r_[:mid+1])
    # 1- l > m > r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] <= r_[left_m]):
        return minNumberInRotateArray(r_[mid:])
    # 2- l > m < r
    elif (r_[mid] <= r_[right_m]) and (r_[mid] <= r_[left_m]):
        return minNumberInRotateArray(r_[left_m:right_m+1])
    # 3- l < m > r , l > r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] >= r_[left_m]) and (r_[left_m] >= r_[right_m]):
        return minNumberInRotateArray(r_[mid:])
    # 4- l < m > r , l < r
    elif (r_[mid] >= r_[right_m]) and (r_[mid] >= r_[left_m]) and (r_[left_m] <= r_[right_m]):
        return minNumberInRotateArray(r_[:mid+1])


minNumberInRotateArray([6, 7, 8, 9, 1, 2, 3, 4, 5])

簡單二分

def smpl_minNumberInRotateArray(rotateArray: list) -> int:
    """
    簡單二分
    """
    if rotateArray == []:
        return 0
    l = 0
    r = len(rotateArray) - 1
    while l < r:
        mid = (l + r)//2
        print(l, r)
        if rotateArray[mid] > rotateArray[r]:
            l = mid + 1
        else:
            r = mid
    return rotateArray[l]

7、斐波那契數列

要求輸入一個整數n,請你輸出斐波那契數列的第n項(從0開始,第0項爲0),n<=39

7.1 思路

遞歸:
Fib(n) = Fib(n-1) + Fib(n-2)]

7.2 解題

from datetime import datetime 

def Fib(n):
    if n <= 1:
        return n
    return Fib(n-1) + Fib(n-2)

a = datetime.now()
Fib(35)
b = datetime.now() - a
print('Fib(35) cost:',b.total_seconds())

"""
>>> a = datetime.now()
>>> Fib(35)
9227465
>>> b = datetime.now() - a
>>> print('Fib(35) cost:',b.total_seconds())
Fib(35) cost: 4.406252
"""

7.3 優化思路

遞歸會存在重複計算的情況:
Fib(n) = Fib(n-1) + Fib(n-2)]
將fib的計算結果全部記錄到memo字典中,減少不必要的計算

  • 快速fib,減少重複計算
def Fib_q(n, memo):
    if n not in memo:  # 減少了重疊部分的計算
        memo[n] = Fib_q(n-1, memo) + Fib_q(n-2, memo)
    return memo[n]

a = datetime.now()
Fib_q(35, {0:0, 1:1})
b = datetime.now() - a
print('Fib_q(35) cost:', b.total_seconds())

"""
>>> a = datetime.now()
>>> Fib_q(35, {0:0, 1:1})
9227465
>>> b = datetime.now() - a
>>> print('Fib_q(35) cost:', b.total_seconds())
Fib_q(35) cost: 0.119007
"""

Fib_q增加內存佔有,降低了時間複雜度。

7.4 優化思路2

參考: https://zhuanlan.zhihu.com/p/75864673

只需定義兩個整型變量,b表示後面的一個數字,a表示前面的數字即可。每次進行的變換是: a,b = b,a+b

def Fibonacci(n):
    if n <= 0:
        return 0
    a = b = 1
    for i in range(2,n):
        a,b = b,a+b
    return b

a = datetime.now()
Fibonacci(35)
b = datetime.now() - a
print('Fib_q(35) cost:', b.total_seconds())
""" 測試後該方法 運行較快 也較穩定(<0.3s)
>>> Fibonacci(35)
9227465
>>> b = datetime.now() - a
>>> print('Fibonacci(35) cost:', b.total_seconds())
Fibonacci(35) cost: 0.225013
"""

優化1,雖然也減少了循環次數,但是Fib_q(n-1, memo) + Fib_q(n-2, memo),仍然是對其的兩次調用。而優化2,無論怎了都是O(n)的時間複雜度

8、跳臺階

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

8.1 思路

枚舉+排列組合:
1- 以2爲步數,遍歷 1步和兩步的組合
2- 對每個組合進行排列組合

m=xtimesm = x_{times}

n=xtimes+ytimesn = x_{times} + y_{times}

tp=n!m!(mn)!=(ytimes+1,n)!xtimes!tp=\frac{n!}{m!(m-n)!}=\frac{(y_{times}+1,n)!}{x_{times}!}

3- 輸出最後所有排列組合的情況總和

8.2 解題

from toolz import reduce

def jumpFloor(n):
    x_start = 2 - n % 2
    way_ls = []
    for x in range(x_start, n+1, 2):
        x_times = x
        y_times = (n - x) // 2
        all_tp = reduce(lambda x, y: x * y, range(y_times + 1, x_times + y_times + 1)) / \
            reduce(lambda x, y: x * y, range(1, x_times + 1))
        way_ls.append(all_tp)
        print(f'x_times:{x_times}, y_times:{y_times}, ways:{all_tp}')
    return  sum(way_ls)

jumpFloor(23)
"""
>>> jumpFloor(23)
x_times:1, y_times:11, ways:12.0
x_times:3, y_times:10, ways:286.0
x_times:5, y_times:9, ways:2002.0
x_times:7, y_times:8, ways:6435.0
x_times:9, y_times:7, ways:11440.0
x_times:11, y_times:6, ways:12376.0
x_times:13, y_times:5, ways:8568.0
x_times:15, y_times:4, ways:3876.0
x_times:17, y_times:3, ways:1140.0
x_times:19, y_times:2, ways:210.0
x_times:21, y_times:1, ways:22.0
x_times:23, y_times:0, ways:1.0
46368.0
"""

8.3 優化解題

  • 思路

典型的動態規劃問題
對於到達第n階臺階來說,有兩種辦法
一種是從第n-1階,爬一個臺階
一種是從第n-2階,爬兩個臺階。
也就是說,到第n臺階是, 到達n-1臺階的方法加上到達n-2臺階的方法總和

F(n)=F(n1)+F(n2)F(n) = F(n-1) + F(n-2)

def jumpFloor(number):
        if number <= 2:
            return number if number >= 0 else 0
        result = [1, 2]  # 1 的時候result[0], 2 的時候result[1]
        for i in range(2, number):
            result.append(result[i-1] + result[i-2])
        return result[-1]

jumpFloor(23)

9、變態跳臺階

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

9.1 思路

  • 思路

對於到達第n階臺階來說,有n種辦法
一種是從第n-1階,上來
一種是從第n-2階,上來
一種是從第n-3階,上來

一種是從第1階,上來

F(n)=F(n1)+F(n2)+F(n3)+...+F(1)+1F(n) = F(n-1) + F(n-2) + F(n-3) + ... + F(1) + 1

F(1)=1F(1) = 1
F(2)=2F(2) = 2
F(3)=F(2)+f(1)+1=4F(3) = F(2) + f(1) + 1 = 4

F(4)=F(3)+F(2)+f(1)+1=2F(3)=8F(4) = F(3) + F(2) + f(1) + 1 = 2 * F(3) = 8
......
F(n)=2F(n1)F(n) = 2 * F(n-1)

9.2 解題

def jumpFloorII(n):
    if n <= 2:
        return n 
    res = [1, 2]
    for i in range(2, n):
        res.append(res[-1] * 2)
    return res[-1]
    
"""
>>> jumpFloorII(4)
8
"""

9.3 優化解題

F(n) = 2 * F(n-1)化簡 :
F(n)=2(n1)F(n) = 2^{(n-1)}

def jumpFloorII(n):
    return 2 ** (n-1)

10、矩陣覆蓋

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

10.1 思路

  • 思路

動態規劃:
到了n,那麼上一步就有兩種情況
1-橫着, F(n-1)的時候加上一塊
2-豎着, F(n-2)的時候加上兩塊
還有一種特殊情況矩陣的形狀爲 1*2n , 不論n是多少,僅有一種方法

  • 一般情況:

F(1)=1F(1) = 1

F(2)=2F(2) = 2

F(3)=2+1=F(2)+F(1)=3F(3) = 2 + 1 = F(2) + F(1) = 3

  • 特殊情況:
    F(1)=F(2)=F(3)=...=F(n)=1F(1) = F(2) = F(3) = ... = F(n) = 1

10.2 解題

def rectCover(n):
    if n <= 0:
        return 0 
    if n == 1:
        return 1
    if n == 2:
        return 2 + 1
    res = [1, 2]
    for i in range(2, n):
        res.append(res[i-1] + res[i-2])
    # 加上特殊情況
    return res[-1] + 1

"""
>>> rectCover(3)
4
"""

11、二進制中1的個數

輸入一個整數,輸出該數二進制表示中1的個數。其中負數用補碼錶示。例如,9表示1001,因此輸入9,輸出2。

11.1 思路

  • 思路

0- 如果整數不等於0,那麼該整數的二進制表示中至少有1位是1
1- 情況1:(xxxxx1) 如果最後位是1,那個減去1就可計數一次
再判斷減去1之後的數有多少個1,即做一次位運算 x & (x - 1) 的數

2- 情況2:(xxx100)最後位是0,而第m位爲1,該數減去1,結果是(xxx011)
那麼也可計數一次,再判斷之後的數有多少個1,即做一次位運算 x & (x - 1) 的數

3- 結合兩種情況,可以和自己做幾次 x & (x - 1) 即有多少個1

11.2 解題

def getnumb1(n):
    cnt = 0
    while n:
        n = n & (n - 1)
        cnt += 1
    return cnt
"""
>>> getnumb1(10000)
5
>>> len(bin(10000).split('1')) - 1
5
"""

12、數值的整數次方

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

12.1 思路

  • 思路

0- 循環乘

12.2 解題

def Power(x: float, n: int) -> float:
    if x == 0:
        return 0
    if n == 0:
        return 1
    out = 1
    while n:
        out = out * x if n > 0 else out / x
        n = n - 1 if n > 0 else n + 1
    return out

"""
>>> Power(2.1, 8)
378.22859361000013
>>> 2.1 ** 8
378.22859361000013
"""

13、調整數組順序使奇數位於偶數前面

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

13.1 思路

  • 思路

0- 全部遍歷

13.2 解題

def reOrderArray(lst):
    odd_lst = []
    even_lst = []
    for i in lst:
        if i % 2 == 1:
            odd_lst.append(i)
        else:
            even_lst.append(i)
    return odd_lst + even_lst
  • 用lambda
def reOrderArray(lst):
    return sorted(lst, key = lambda x: x%2, reverse=True)

14、鏈表中的倒數第K個節點

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

14.1 思路

  • 思路

0- 由於不知道鏈表的總長度,所以需要一把K長的尺子做衡量
1- 當尺子後端到達鏈表末尾,那邊尺子的前端就是倒數K節點

14.2 解題

def findKtoTail(l, k):
    # 如果鏈表爲空
    if not l or k <= 0:
        return None
    st, ed = l, l
    lon_t = 0
    while (lon_t < k) and (ed):
        ed = ed.next
        lon_t += 1
    # 尺子不夠長時
    if lon_t < k:
        return None
    # 尺子準備就緒, 同步後移
    while ed:
        ed = ed.next
        st = st.next
    return st
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章