劍指offer【50~59】

題目鏈接:

劍指offer 50-59


目錄:

50. 第一個只出現(xiàn)一次的字符位置
51. 數(shù)組中的逆序?qū)?/a>
52. 兩個鏈表的第一個公共結(jié)點
53. 數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)
54. 二叉查找樹的第 K 個結(jié)點
55.1 二叉樹的深度
55.2 平衡二叉樹
56. 數(shù)組中只出現(xiàn)一次的數(shù)字
57.1 和為 S 的兩個數(shù)字
57.2 和為 S 的連續(xù)正數(shù)序列
58.1 翻轉(zhuǎn)單詞順序列
58.2 左旋轉(zhuǎn)字符串
59. 滑動窗口的最大值


Python 實現(xiàn):

50. 第一個只出現(xiàn)一次的字符位置

使用大小為 256 的數(shù)組記錄每個字符出現(xiàn)的次數(shù)。遍歷兩遍即可。

# -*- coding:utf-8 -*-
class Solution:
    def FirstNotRepeatingChar(self, s):
        # write code here
        dic = [0] * 256
        for i in range(len(s)):
            dic[ord(s[i])] += 1
        for i in range(len(s)):
            if dic[ord(s[i])] == 1:
                return i
        return -1

51. 數(shù)組中的逆序?qū)?/a>
# -*- coding:utf-8 -*-
class Solution:
    def InversePairs(self, data):
        # write code here
        self.ans = 0  # 逆序?qū)?shù)量
        self.mergeSort(data)   # 歸并排序
        return self.ans % 1000000007
    
    def mergeSort(self, nums):
        # 遞歸過程
        if len(nums) <= 1:
            return nums
        mid = len(nums) // 2
        left = self.mergeSort(nums[:mid])
        right = self.mergeSort(nums[mid:])
        return self.merge(left, right)

    # 歸并過程 + 統(tǒng)計逆序?qū)?shù)量
    def merge(self, left, right):
        result = []  # 保存歸并后的結(jié)果
        i = j = 0
        left_len = len(left)
        while i < len(left) and j < len(right):
            if left[i] <= right[j]:
                result.append(left[i])
                i += 1
            else:  # left[i] > right[j]
                self.ans += (left_len - i)  # 核心:說明 nums[i:] 都大于 nums[j]
                result.append(right[j])
                j += 1
        result = result + left[i:] + right[j:] # 剩余的元素直接添加到末尾
        return result

52. 兩個鏈表的第一個公共結(jié)點
  • 設(shè) A 的長度為 a + c,B 的長度為 b + c,其中 c 為尾部公共部分長度,可知 a + c + b = b + c + a。
  • 當訪問鏈表 A 的指針 L1 訪問到鏈表尾部時,令它從鏈表 B 的頭部重新開始訪問鏈表 B;同樣地,當訪問鏈表 B 的指針 L2 訪問到鏈表尾部時,令它從鏈表 A 的頭部重新開始訪問鏈表 A。這樣就能控制 L1 和 L2 能同時訪問到公共結(jié)點。即 L1 總共走了 (a + c) + b,L2 總共走了 (b + c) + a。
  • 這樣,鏈表 A 和鏈表 B 都走了 a + b + c 步,時間復(fù)雜度為 O(n),空間復(fù)雜度為 O(1)。
# -*- 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
        l1, l2 = pHead1, pHead2
        while l1 != l2:
            l1 = l1.next if l1 != None else pHead2
            l2 = l2.next if l2 != None else pHead1
        return l1 # 或者 l2

53. 數(shù)字在排序數(shù)組中出現(xiàn)的次數(shù)

排序數(shù)組,很明顯二分查找,找到第一個 >= k 的元素索引以及第一個 > k 的元素索引,兩者相減即為答案,即 lowerBound - upperBound。時間復(fù)雜度為 O(logn),空間復(fù)雜度為 O(1)。

# -*- coding:utf-8 -*-
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        if not data:
            return 0
        lo, hi = 0, len(data) - 1
        while lo < hi:
            mid = lo + (hi - lo) // 2
            if data[mid] >= k:
                hi = mid
            elif data[mid] < k:
                lo = mid + 1
        ind1 = lo if data[lo] >= k else len(data)  # >=k 的第一個元素
        lo, hi = 0, len(data) - 1
        while lo < hi:
            mid = lo + (hi - lo) // 2
            if data[mid] > k:
                hi = mid
            elif data[mid] <= k:
                lo = mid + 1
        ind2 = lo if data[lo] > k else len(data)  # # >k 的第一個元素
        return ind2 - ind1  # 相減即為答案

更簡潔的,可以使用 Python 的 bisect 模塊中的函數(shù)實現(xiàn)??梢詤⒖疾┛停?a href="http://m.itdecent.cn/p/741c3d29a1ff" target="_blank">二分查找及其變形與Python的bisect模塊的關(guān)系。

# -*- coding:utf-8 -*-
import bisect
class Solution:
    def GetNumberOfK(self, data, k):
        # write code here
        return bisect.bisect_right(data, k) - bisect.bisect_left(data, k)

54. 二叉查找樹的第 K 個結(jié)點

中序遍歷,找到第 k 個數(shù)即可。

#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
class Solution:
    # 返回對應(yīng)節(jié)點TreeNode
    def __init__(self):
        self.cnt = 0
        
    def KthNode(self, pRoot, k):
        # write code here
        if not pRoot or self.cnt >= k:  # self.cnt >= k 剪枝
            return None
        left = self.KthNode(pRoot.left, k)
        if left:
            return left
        self.cnt += 1
        if self.cnt == k:
            return pRoot
        right = self.KthNode(pRoot.right, k)
        if right:
            return right
        return None

55.1 二叉樹的深度

遞歸左右子樹找最大高度。注意:根的高度為 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 not pRoot:
            return 0
        return max(self.TreeDepth(pRoot.left), self.TreeDepth(pRoot.right)) + 1
55.2 平衡二叉樹

平衡二叉樹(AVL)的左右子樹的高度差不超過 1,因此引入求高度的函數(shù)。并且,判斷該樹的每個結(jié)點的左右子樹是否也滿足 AVL 的定義。

# -*- 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 not pRoot:
            return True
        if abs(self.TreeDepth(pRoot.left) - self.TreeDepth(pRoot.right)) > 1:  # 不滿足 AVL 的定義
            return False
        return self.IsBalanced_Solution(pRoot.left) and self.IsBalanced_Solution(pRoot.right)
        
    def TreeDepth(self, pRoot):
        if not pRoot:
            return 0
        return max(self.TreeDepth(pRoot.left), self.TreeDepth(pRoot.right)) + 1

改進:自頂向下在調(diào)用求樹的高度的函數(shù)時,有很多重復(fù)的操作。因此,可以使用自底向上的方法,一邊計算樹的高度,一邊判斷是否是 AVL 樹。

# -*- 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
        self.isBalance = True
        self.TreeDepth(pRoot)  # 在求樹的高度過程中判斷 AVL
        return self.isBalance
        
    def TreeDepth(self, pRoot):
        if not pRoot:
            return 0
        left = self.TreeDepth(pRoot.left)
        right = self.TreeDepth(pRoot.right)
        if abs(left - right) > 1:   # 不滿足 AVL 定義,修改 self.isBalance 的值
            self.isBalance = False
        return max(left, right) + 1

56. 數(shù)組中只出現(xiàn)一次的數(shù)字

如果只出現(xiàn)一次的數(shù)字只有一個,很好做,就是全部異或即可。但是,只出現(xiàn)一次的數(shù)字有兩個怎么做呢?

  • 假設(shè)只出現(xiàn)一次的數(shù)字為 x 和 y,首先,還是先全部異或得到一個結(jié)果 xor,則 x ^ y = xor(相同的數(shù)字異或后抵消為 0)
  • 因為 x 和 y 肯定不同,那么它們的二進制表示中肯定有一位一個是 0, 一個是 1。比如 x = 6 (110),y = 4 (100),xor = 2 (10),則我們對 xor 從后往前找到倒數(shù)第一個 1 的位置 bits(倒數(shù)第 2 位),則以這個 1 為界限,x 和 y 的倒數(shù)第 2 位肯定是不同。
  • 因此,對原來的數(shù)組重新異或,將 bits 位置為 1 的全部異或,bits 為 0 的全部異或,就是最終的兩個返回的結(jié)果。
# -*- coding:utf-8 -*-
class Solution:
    # 返回[a,b] 其中ab是出現(xiàn)一次的兩個數(shù)字
    def FindNumsAppearOnce(self, array):
        # write code here
        xor = 0
        ans = [0, 0]
        for num in array:
            xor ^= num
        bits = 0  # 找到倒數(shù)第 i 位為 1
        while xor & 1 << bits == 0:
            bits += 1
        for num in array:
            if num >> bits & 1 == 1:  # num 的倒數(shù)第 i 位為 1
                ans[0] ^= num
            else:  # num 的倒數(shù)第 i 位為 0
                ans[1] ^= num
        return ans

57.1 和為 S 的兩個數(shù)字

雙指針指向首尾,往中間走,碰到第一對和為 S 的就是答案。

# -*- coding:utf-8 -*-
class Solution:
    def FindNumbersWithSum(self, array, tsum):
        # write code here
        lo, hi = 0, len(array) - 1
        while lo < hi:   # 不能指向相同的數(shù)字
            if array[lo] + array[hi] == tsum:
                return [array[lo], array[hi]]
            elif array[lo] + array[hi] < tsum:
                lo += 1
            else:
                hi -= 1
        return []
57.2 和為 S 的連續(xù)正數(shù)序列

經(jīng)典的滑動窗口例題,只不過該題的窗口大小不確定。用一個遍歷 left 記錄窗口左側(cè)的值,window_sum 將窗口中數(shù)進行累加。當發(fā)現(xiàn) window_sum >= S 時,相等時輸出結(jié)果,更新 left 和 window_sum。

# -*- coding:utf-8 -*-
class Solution:
    def FindContinuousSequence(self, tsum):
        # write code here
        if tsum <= 1:  # 特殊情況
            return []
        ans = []
        left, window_sum = 1, 0  # left 記錄窗口左界
        for i in range(1, (tsum + 1) // 2 + 1):  # 窗口中至少兩個數(shù)
            window_sum += i  # 窗口中的累加和
            while window_sum >= tsum:
                if window_sum == tsum:  # 輸出一組結(jié)果
                    ans.append([j for j in range(left, i + 1)])
                window_sum -= left  # 修改窗口中的累加和
                left += 1  # 修改窗口的左界
        return ans

58.1 翻轉(zhuǎn)單詞順序列

如果要求空間復(fù)雜度為 O(1),即只能使用字符串本身,該怎么操作呢?

  • 如 s = "I am a student.",先將各個單詞翻轉(zhuǎn),得 s = ".tneduts a ma I";
  • 再將整個字符串翻轉(zhuǎn),得 s = "student. a am I"

這樣就可以在字符串本身上做修改,使得空間復(fù)雜度為 O(1) 了。

注意:但是使用 Python 實現(xiàn)得話,由于不能修改字符串本身,所以還是先要將字符串轉(zhuǎn)化為列表。但是如果使用 C++ 的字符數(shù)組,就不用開辟空間了。

# -*- coding:utf-8 -*-
class Solution:
    def ReverseSentence(self, s):
        # write code here
        def reverseWord(s, i, j):
            while i < j:
                s[i], s[j] = s[j], s[i]
                i += 1
                j -= 1
        
        s = list(s)  # python 中 s 不能修改,先轉(zhuǎn)化為 list 
        start = 0  # start 指向當前單詞的起始位置
        lens = len(s)
        for i in range(lens + 1):
            if i == lens or s[i] == ' ':
                reverseWord(s, start, i - 1) # 先翻轉(zhuǎn)每個單詞
                start = i + 1  # 下一個單詞的起始位置
        reverseWord(s, 0, lens - 1)  # 再將整個句子翻轉(zhuǎn)
        return "".join(s)
58.2 左旋轉(zhuǎn)字符串

如果也不能使用空間,怎么做呢?參考上面的 58.1,如 s = "abcXYZdef",n = 3,先將 "abc" 和 "XYZdef" 分別翻轉(zhuǎn),得到 "cbafedZYX",然后再把整個字符串翻轉(zhuǎn)得到 "XYZdefabc"。這樣就可以空間復(fù)雜度為 O(1) 了。

# -*- coding:utf-8 -*-
class Solution:
    def LeftRotateString(self, s, n):
        # write code here
        def reverseWord(s, i, j):
            while i < j:
                s[i], s[j] = s[j], s[i]
                i += 1
                j -= 1
        
        if not s:
            return ""
        s = list(s)
        lens = len(s)
        n %= lens  # 循環(huán)左移
        reverseWord(s, 0, n - 1)  # 先將前 n 個字符翻轉(zhuǎn)
        reverseWord(s, n, lens - 1)  # 再將后面的字符翻轉(zhuǎn)
        reverseWord(s, 0, lens - 1)  # 最后將整個字符串翻轉(zhuǎn)
        return "".join(s)  # 再轉(zhuǎn)化回字符串

59. 滑動窗口的最大值

使用雙向遞減隊列,隊列中始終維護的是窗口中的遞減值。

  • 如果隊列的大小達到了 size,則應(yīng)該把隊列最前面的數(shù)字刪除掉。
  • 如果從最右邊加入了一個較大的數(shù)字,需要從右開始退隊列(while 循環(huán)),使得隊列是單調(diào)遞減的。
  • 由于雙向隊列中,從左邊出隊列和從右邊出隊列的操作時間復(fù)雜度均為 O(1),因此該算法的時間復(fù)雜度為 O(n),空間復(fù)雜度為 O(n)。

例如,num = [2,3,4,2,6,2,5,1],size = 3,雙向隊列的變化情況為 dq: [2] -> [3] -> [4] -> [4,2] -> [6] (6 比 2 和 4 都大) -> [6,2] -> [6,5] (5 比 2 大) -> [5,1] (6 超出窗口,從隊列首部出隊)。

# -*- coding:utf-8 -*-
import collections
class Solution:
    def maxInWindows(self, num, size):
        # write code here
        if size > len(num) or size < 1:
            return []
        dq = collections.deque()  # [num[i], i]
        ans = []
        for i in range(len(num)):
            if dq and i - dq[0][1] >= size:
                dq.popleft()  # O(1)
            while dq and num[i] > dq[-1][0]: # 從后往前刪除比 num[i] 大的數(shù)
                dq.pop()  # O(1)
            dq.append([num[i], i])
            if i >= size - 1:
                ans.append(dq[0][0])  # 隊列首部始終最大
        return ans

劍指 offer 終于過了一遍,大多數(shù)題目還是很簡單的,但是題目具有代表性,涉及鏈表、數(shù)組、深搜回溯、字符串、數(shù)組、數(shù)學(xué)、位運算、動態(tài)規(guī)劃等。這里做一個總結(jié):

劍指offer【03~09】
劍指offer【10~19】
劍指offer【20~29】
劍指offer【30~39】
劍指offer【40~49】
劍指offer【50~59】
劍指offer【60~68】

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

友情鏈接更多精彩內(nèi)容