劍指offer 1-11


AcWing 13. 找出數組中重復的數字

給定一個長度為 n 的整數數組 nums,數組中所有的數字都在 0∼n−1 的范圍內。

數組中某些數字是重復的,但不知道有幾個數字重復了,也不知道每個數字重復了幾次。

請找出數組中任意一個重復的數字。

注意:如果某些數字不在 0∼n−1 的范圍內,或數組中不包含重復數字,則返回 -1;

樣例
給定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。

from collections import defaultdict
class Solution(object):
    def duplicateInArray(self, nums):
        """
        :type nums: List[int]
        :rtype int
        """
        mp = defaultdict(int)
        res = -1
        for it in nums:
            if it >= len(nums) or it <0: #遺漏
                return -1
            if mp[it] != 0:
                res = it
            mp[it] += 1
        return res # 遺漏

(數組遍歷) O(n)
首先遍歷一遍數組,如果存在某個數不在0到n-1的范圍內,則返回-1。

下面的算法的主要思想是把每個數放到對應的位置上,即讓 nums[i] = i。

從前往后遍歷數組中的所有數,假設當前遍歷到的數是 nums[i]=x,那么:

如果x != i && nums[x] == x,則說明 x出現了多次,直接返回 x即可;
如果nums[x] != x,那我們就把 x 交換到正確的位置上,即 swap(nums[x], nums[i]),交換完之后如果nums[i] != i,則重復進行該操作。由於每次交換都會將一個數放在正確的位置上,所以swap操作最多會進行 n 次,不會發生死循環。

循環結束后,如果沒有找到任何重復的數,則返回-1。

時間復雜度分析
每次swap操作都會將一個數放在正確的位置上,最后一次swap會將兩個數同時放到正確位置上,一共只有 n 個數和 n 個位置,所以swap最多會進行 n−1次。所以總時間復雜度是 O(n)。

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        int n = nums.size();
        for (auto x : nums)
            if (x < 0 || x >= n)
                return -1;
        for (int i = 0; i < n; i ++ ) {
            while (nums[nums[i]] != nums[i]) swap(nums[i], nums[nums[i]]);
            if (nums[i] != i) return nums[i];
        }
        return -1;
    }
};

AcWing 14. 不修改數組找出重復的數字

給定一個長度為 n+1 的數組nums,數組中所有的數均在 1∼n 的范圍內,其中 n≥1。

請找出數組中任意一個重復的數,但不能修改輸入的數組。

樣例
給定 nums = [2, 3, 5, 4, 3, 2, 6, 7]。

返回 2 或 3。
思考題:如果只能使用 O(1) 的額外空間,該怎么做呢?
yxc大佬
(分治,抽屜原理) O(nlogn)
這道題目主要應用了抽屜原理和分治的思想。

抽屜原理:n+1 個蘋果放在 n 個抽屜里,那么至少有一個抽屜中會放兩個蘋果。

用在這個題目中就是,一共有 n+1 個數,每個數的取值范圍是1到n,所以至少會有一個數出現兩次。

然后我們采用分治的思想,將每個數的取值的區間[1, n]划分成[1, n/2]和[n/2+1, n]兩個子區間,然后分別統計兩個區間中數的個數。
注意這里的區間是指 數的取值范圍,而不是 數組下標。即這題跟題目給的數的順序無關

划分之后,左右兩個區間里一定至少存在一個區間,區間中數的個數大於區間長度。
這個可以用反證法來說明:如果兩個區間中數的個數都小於等於區間長度,那么整個區間中數的個數就小於等於n,和有n+1個數矛盾。

因此我們可以把問題划歸到左右兩個子區間中的一個,而且由於區間中數的個數大於區間長度,根據抽屜原理,在這個子區間中一定存在某個數出現了兩次。

依次類推,每次我們可以把區間長度縮小一半,直到區間長度為1時,我們就找到了答案。

復雜度分析
時間復雜度:每次會將區間長度縮小一半,一共會縮小 \(O(logn)\)次。每次統計兩個子區間中的數時需要遍歷整個數組,時間復雜度是 O(n)。所以總時間復雜度是 \(O(nlogn)\)
空間復雜度:代碼中沒有用到額外的數組,所以額外的空間復雜度是 O(1)。

二分經典問題

class Solution(object):
    def duplicateInArray(self, nums):
        """
        :type nums: List[int]
        :rtype int
        """
        n = len(nums)
        l = 1
        r = n 
        
        while l < r:
            mid = (l + r) >> 1
            s = 0
            for it in nums:
                if it >= l and it <= mid:
                    s += 1
            # print(mid, s)
            if s > mid - l + 1:
                r = mid
            else:
                l = mid + 1 
        return l

AcWing 15. 二維數組中的查找

在一個二維數組中,每一行都按照從左到右遞增的順序排序,每一列都按照從上到下遞增的順序排序。

請完成一個函數,輸入這樣的一個二維數組和一個整數,判斷數組中是否含有該整數。

樣例

輸入數組:

[ [1,2,8,9], [2,4,9,12], [4,7,10,13], [6,8,11,15] ]

如果輸入查找數值為7,則返回true,

如果輸入查找數值為5,則返回false。

yxc 大佬做法
算法
(單調性掃描) \(O(n+m)\)
核心在於發現每個子矩陣右上角的數的性質:

如下圖所示,x左邊的數都小於等於x,x下邊的數都大於等於x。
在這里插入圖片描述

因此我們可以從整個矩陣的右上角開始枚舉,假設當前枚舉的數是 xx:

  • 如果 x等於target,則說明我們找到了目標值,返回true;
  • 如果 x 小於target,則 x左邊的數一定都小於target,我們可以直接排除當前一整行的數;
  • 如果 x 大於target,則 x下邊的數一定都大於target,我們可以直接排序當前一整列的數;

排除一整行就是讓枚舉的點的橫坐標加一,排除一整列就是讓縱坐標減一。
當我們排除完整個矩陣后仍沒有找到目標值時,就說明目標值不存在,返回false。

時間復雜度分析
每一步會排除一行或者一列,矩陣一共有 n 行,mm 列,所以最多會進行 n+m 步。所以時間復雜度是 O(n+m)。

class Solution(object):
    def searchArray(self, array, target):
        """
        :type array: List[List[int]]
        :type target: int
        :rtype: bool
        """
        if len(array) == 0 or len(array[0]) == 0:
            return False
            
        i = 0
        j = len(array[0]) - 1
        
        while i < len(array) and j >=0:
            if target == array[i][j]:
                return True
            elif target > array[i][j]:
                i += 1
            elif target < array[i][j]:
                j -= 1
        
        return False

AcWing 16. 替換空格

請實現一個函數,把字符串中的每個空格替換成"%20"。

你可以假定輸入字符串的長度最大是1000。
注意輸出字符串的長度可能大於1000。

樣例
輸入:"We are happy."

輸出:"We%20are%20happy."

class Solution(object):
    def replaceSpaces(self, s):
        """
        :type s: str
        :rtype: str
        """
        return s.replace(" ","%20")
        

AcWing 17. 從尾到頭打印鏈表

輸入一個鏈表的頭結點,按照 從尾到頭 的順序返回節點的值。

返回的結果用數組存儲。

樣例
輸入:[2, 3, 5]
返回:[5, 3, 2]

# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None
class Solution(object):
    def printListReversingly(self, head):
        """
        :type head: ListNode
        :rtype: List[int]
        """
        res = []
        while head != None:
            res.append(head.val)
            head = head.next
        res.reverse()
        return res

AcWing 18. 重建二叉樹

輸入一棵二叉樹前序遍歷和中序遍歷的結果,請重建該二叉樹。

注意:

二叉樹中每個節點的值都互不相同;
輸入的前序遍歷和中序遍歷一定合法;
樣例

給定: 前序遍歷是:[3, 9, 20, 15, 7] 中序遍歷是:[9, 3, 15, 20, 7]

返回:[3, 9, 20, null, null, 15, 7, null, null, null, null]

返回的二叉樹如下所示:
3
/ \
9 20
/ \
15 7

算法
(遞歸) O(n)
遞歸建立整棵二叉樹:先遞歸創建左右子樹,然后創建根節點,並讓指針指向兩棵子樹。

具體步驟如下:

  1. 先利用前序遍歷找根節點:前序遍歷的第一個數,就是根節點的值;
  2. 在中序遍歷中找到根節點的位置 k,則 k左邊是左子樹的中序遍歷,右邊是右子樹的中序遍歷;
  3. 假設左子樹的中序遍歷的長度是 l,則在前序遍歷中,根節點后面的 l個數,是左子樹的前序遍歷,剩下的數是右子樹的前序遍歷;
  4. 有了左右子樹的前序遍歷和中序遍歷,我們可以先遞歸創建出左右子樹,然后再創建根節點;

時間復雜度分析
我們在初始化時,用哈希(unordered_map<int,int>)記錄每個值在中序遍歷中的位置,這樣我們在遞歸到每個節點時,在中序遍歷中查找根節點位置的操作,只需要 O(1)的時間。此時,創建每個節點需要的時間是 O(1),所以總時間復雜度是 O(n)。

# 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 buildTree(self, preorder, inorder):
        """
        :type preorder: List[int]
        :type inorder: List[int]
        :rtype: TreeNode
        """
        if len(preorder) == 0:
            return None
            
        pos = [None for i in range(max(inorder) + 1)]
        for i in range(len(inorder)):
            pos[inorder[i]] = i

        def build(il, ir, pl, pr):
            if pl > pr:
                return None
            root = TreeNode(preorder[pl])
            k = pos[root.val]
            
            if il < k: root.left = build(il, k - 1, pl + 1, pl + (k - 1 - il) + 1)
            if ir > k: root.right = build(k+1 , ir, pl + k - il + 1, pr)
            
            return root
        return build(0, len(preorder) - 1, 0, len(preorder) - 1)
                    

AcWing 19. 二叉樹的下一個節點

給定一棵二叉樹的其中一個節點,請找出中序遍歷序列的下一個節點。

注意:

如果給定的節點是中序遍歷序列的最后一個,則返回空節點;
二叉樹一定不為空,且給定的節點一定不是空節點;
樣例

假定二叉樹是:[2, 1, 3, null, null, null, null], 給出的是值等於2的節點。

則應返回值等於3的節點。

解釋:該二叉樹的結構如下,2的后繼節點是3。
2
/ \
1 3

算法
(模擬) O(h)
這道題目就是讓我們求二叉樹中給定節點的后繼。

分情況討論即可,如下圖所示:

  1. 如果當前節點有右兒子,則右子樹中最左側的節點就是當前節點的后繼。比如F的后繼是H;
  2. 如果當前節點沒有右兒子,則需要沿着father域一直向上找,找到第一個是其father左兒子的節點,該節點的father就是當前節點的后繼。比如當前節點是D,則第一個滿足是其father左兒子的節點是F,則C的father就是D的后繼,即F是D的后繼。
    在這里插入圖片描述

時間復雜度分析
不論往上找還是往下找,總共遍歷的節點數都不大於樹的高度。所以時間復雜度是 O(h),其中 h 是樹的高度。

# Definition for a binary tree node.
# class TreeNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.left = None
#         self.right = None
#         self.father = None
class Solution(object):
    def inorderSuccessor(self, q):
        """
        :type q: TreeNode
        :rtype: TreeNode
        """
        
        if q.right != None:
            
            p = q.right
            while p.left != None:
                p = p.left
            return p
        else:
            p = q
            while p.father != None and p.father.right == p:
                p = p.father
            return p.father

AcWing 20. 用兩個棧實現隊列

請用棧實現一個隊列,支持如下四種操作:

push(x) – 將元素x插到隊尾;
pop() – 將隊首的元素彈出,並返回該元素;
peek() – 返回隊首元素;
empty() – 返回隊列是否為空;
注意:

你只能使用棧的標准操作:push to top,peek/pop from top, size 和 is empty;
如果你選擇的編程語言沒有棧的標准庫,你可以使用list或者deque等模擬棧的操作;
輸入數據保證合法,例如,在隊列為空時,不會進行pop或者peek等操作;
樣例
MyQueue queue = new MyQueue();

queue.push(1);
queue.push(2);
queue.peek(); // returns 1
queue.pop(); // returns 1
queue.empty(); // returns false

class MyQueue(object):

    def __init__(self):
        """
        Initialize your data structure here.
        """
        self.sta = []

    def push(self, x):
        """
        Push element x to the back of queue.
        :type x: int
        :rtype: void
        """
        self.sta.append(x)

    def pop(self):
        """
        Removes the element from in front of queue and returns that element.
        :rtype: int
        """
        
        return self.sta.pop(0)

    def peek(self):
        """
        Get the front element.
        :rtype: int
        """
        return self.sta[0]

    def empty(self):
        """
        Returns whether the queue is empty.
        :rtype: bool
        """
        if len(self.sta) == 0:
            return True
        return False
        


# Your MyQueue object will be instantiated and called as such:
# obj = MyQueue()
# obj.push(x)
# param_2 = obj.pop()
# param_3 = obj.peek()
# param_4 = obj.empty()

AcWing 21. 斐波那契數列

輸入一個整數 n ,求斐波那契數列的第 n 項。

假定從0開始,第0項為0。(n<=39)

樣例 輸入整數 n=5

返回 5

class Solution(object):
    def Fibonacci(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0: return 0
        a = 0
        b = 1
        for i in range(2, n+1):
            a, b = b, a + b
        return b

生成器
生成器講解

class Solution(object):
    def Fibonacci(self, n):
        """
        :type n: int
        :rtype: int
        """
        if n == 0: return 0
        def f(n):
            a = 0
            b = 1
            for i in range(n):
                yield b # 生成器
                a, b = b, a + b
        res = 0
        for it in f(n):
            res = it
        return res

AcWing 22. 旋轉數組的最小數字

把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。

輸入一個升序的數組的一個旋轉,輸出旋轉數組的最小元素。

例如數組{3,4,5,1,2}為{1,2,3,4,5}的一個旋轉,該數組的最小值為1。

數組可能包含重復項。

注意:數組內所含元素非負,若數組大小為0,請返回-1。

樣例 輸入:nums=[2,2,2,0,1]

輸出:0

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 0:
            return -1
        else:
            return min(nums)

yxc 大佬
算法
(二分) O(n)
為了便於分析,我們先將數組中的數畫在二維坐標系中,橫坐標表示數組下標,縱坐標表示數值,如下所示:
在這里插入圖片描述

圖中水平的實線段表示相同元素。

我們發現除了最后水平的一段(黑色水平那段)之外,其余部分滿足二分性質:豎直虛線左邊的數滿足 nums[i]≥nums[0];而豎直虛線右邊的數不滿足這個條件。
分界點就是整個數組的最小值。

所以我們先將最后水平的一段刪除即可。

另外,不要忘記處理數組完全單調的特殊情況:

當我們刪除最后水平的一段之后,如果剩下的最后一個數大於等於第一個數,則說明數組完全單調。

時間復雜度分析
二分的時間復雜度是 O(logn),刪除最后水平一段的時間復雜度最壞是 O(n),所以總時間復雜度是 O(n)。

class Solution:
    def findMin(self, nums):
        """
        :type nums: List[int]
        :rtype: int
        """
        if len(nums) == 0:
            return -1
        n = len(nums) - 1
        
        while nums[n] == nums[0]:
            n -= 1
            
        l = 0
        r = n
        
        while l < r:
            mid = (l + r ) >> 1
            if nums[mid] >= nums[0]:
                l = mid + 1
            else :
                r = mid
        return min(nums[0], nums[l])
            

AcWing 23. 矩陣中的路徑

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串所有字符的路徑。

路徑可以從矩陣中的任意一個格子開始,每一步可以在矩陣中向左,向右,向上,向下移動一個格子。

如果一條路徑經過了矩陣中的某一個格子,則之后不能再次進入這個格子。

注意:

輸入的路徑不為空;
所有出現的字符均為大寫英文字母;

樣例 matrix= [
["A","B","C","E"],
["S","F","C","S"],
["A","D","E","E"]
]

str="BCCE" , return "true"

str="ASAE" , return "false"

算法
DFS
\(O(n^{2} 3^k)\)
在深度優先搜索中,最重要的就是考慮好搜索順序。

我們先枚舉單詞的起點,然后依次枚舉單詞的每個字母。
過程中需要將已經使用過的字母改成一個特殊字母,以避免重復使用字符。

時間復雜度分析:單詞起點一共有 \(n^2\) 個,單詞的每個字母一共有上下左右四個方向可以選擇,但由於不能走回頭路,所以除了單詞首字母外,僅有三種選擇。所以總時間復雜度是 \(O(n^{2} 3^k)\)

class Solution(object):
    def hasPath(self, matrix, string):
        """
        :type matrix: List[List[str]]
        :type string: str
        :rtype: bool
        """
  
        
        for i in range(len(matrix)):
            for j in range(len(matrix[i])):
                st = [[0 for j in range(len(matrix[i]))] for i in range(len(matrix))]
                if self.dfs(i, j, 0, st, matrix, string) == True:
                    return True
        return False

     
    def dfs(self, i, j, u, st, matrix, string):
        dx = [0, 0, 1, -1]
        dy = [1, -1, 0, 0]
        
        if matrix[i][j] != string[u] : return False
        st[i][j] = 1
        #print(i, j, matrix[i][j], u)
        #print(st)
        if u == len(string) - 1: return True
        
            
        for t in range(4):
            x = i + dx[t]
            y = j + dy[t]
            if x < len(matrix) and x >= 0 and  y < len(matrix[x]) and y >= 0 and st[x][y] == 0 :
                st[x][y] = 1
                if self.dfs(x, y, u + 1, st, matrix, string) == True:
                    return True
                st[x][y] = 0
        return False
         
    
                    
                


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM