LeetCode 面試題 08.03. 魔術索引 | Python

choubou發表於2021-09-09

面試題 08.03. 魔術索引


題目來源:力扣(LeetCode)

題目


魔術索引。 在陣列A[0…n-1]中,有所謂的魔術索引,滿足條件A[i] = i。給定一個有序整數陣列,編寫一種方法找出魔術索引,若有的話,在陣列A中找出一個魔術索引,如果沒有,則返回-1。若有多個魔術索引,返回索引值最小的一個。

示例1:

 輸入:nums = [0, 2, 3, 4, 5]
 輸出:0
 說明: 0下標的元素為0

示例2:

 輸入:nums = [1, 1, 1]
 輸出:1

說明:

  • nums長度在[1, 1000000]之間
  • 此題為原書中的 Follow-up,即陣列中可能包含重複元素的版本

解題思路


思路:剪枝,分治

這裡先提一下,只是看本題的話,可能會有些爭議,後面再解釋。

現在先審題,理清其中的關鍵點:

  • 給定的陣列是有序的(題目中只說有序,並未說是升序還是降序,這裡結合示例,先預設是升序);
  • 題目要求的魔術索引,即是滿足條件 A[i] = i;
  • 陣列中可能存在多個魔術索引,存在時,返回索引值最小的;
  • 不存在魔術索引,則返回 -1。

現在,結合上面羅列的關鍵點,可以發現陣列有序(前面說了先預設升序),即使存在多個魔術索引,返回的也是最小索引的一個。那麼現在能直接想到的就是直接遍歷陣列,當滿足條件 nums[i]==i,直接返回即可,沒有就返回 -1。相關程式碼大致如下:

class Solution:
    def findMagicIndex(self, nums: List[int]) -> int:
        for i in range(len(nums)):
            if nums[i] == i:
                return nums[i]
        return -1

就前面開篇說的爭議,稍微吐槽下,當寫完上面段程式碼時,總覺得事情沒那麼簡單。後面去翻閱了官方題解下的評論,的確有很大的爭議。

這裡大致說下,只看本題的話,最直觀的就是直接遍歷,暴力法解決。可以回顧下題目,題目最後的說明中提及到 【此題為原書中的 Follow-up,即陣列中可能包含重複元素的版本】。

這裡說的是此題是可能存在重複元素的版本,所以筆者也去翻閱了原題。查閱的結果是,原題是有兩部分:

  • 一個是給定陣列升序,元素值各不相同,要求給定的陣列中是否存在魔術索引;
  • 另一個是給定不下降序列,元素值可能相同,求是否存在魔術索引。(也就是本題)

而且題目中 說明:思考是否存在一個複雜度優於 O(n) 的方法。

那麼就現在就查閱的資料結合本題,進行展開說明。

這裡,在原題第一部分的基礎上,重新來看這題。題目中說明,【有可能存在多個魔術索引,元素可能重複】。那我們將情況分開討論:

  • 首先先討論,當只有一個魔術索引時。

我們先假設存在魔術索引 i,那麼也就意味著 [0, i-1] 這個區間中,索引對應的值均小於索引值,而 [i+1, n-1] 這個區間的索引對應的值則均大於索引值。我們舉個例子來說明,例如給定如下陣列:

nums = [-1, 0, 1, 3, 5]

那麼,上面陣列中每個索引對應的元素值與索引的差值如下:

[-1, -1, -1, 0, 1]

可以看到,上面的陣列是具有單調性的,那麼我們只要使用二分查詢,找到 0 所在的位置,返回答案即可。

  • 再看可能存在多個魔術索引的情況,這裡也同樣將可能重複的情況引入進來。

假設存在這樣的一個陣列

nums = [0, 0, 1, 1, 4, 6]

還是按照前面的策略,用元素值減去元素對應的索引,得:

[0, -1, -1, -2, 0, 1]

現在,我們可以看到,結果並不具備單調性,在這裡不能夠直接用二分查詢的方法去解決。在這裡,我們要進行適當的剪枝,具體的做法如下:

  • 同樣還是先取中間元素,如果中間元素剛好是魔術索引,這個時候要注意,因為有可能有多個魔術索引,當前的魔術索引並不是最小的那個。但是我們可以確定的是,中間元素右側的索引值較大,即使存在魔術索引也不是更小的那個,那麼此時可以拋開右側部分,轉而去查詢左側部分是否還有更小的魔術索引。
  • 如果中間元素不是魔術索引,這裡無法確定魔術索引可能存在於哪側。這個時候,兩邊都要進行搜尋。

在這裡,根據上述的做法,我們用遞迴的方法去搜尋。

具體程式碼實現如下(含註釋)。

再囉嗦一句,其實到最後,使用遞迴的方法,也是往左側找答案,反而有可能影響效率。不過,這也是一種方法,當然也可以不追究原題如何,直接一次遍歷查詢答案。

程式碼實現


class Solution:
    def findMagicIndex(self, nums: List[int]) -> int:
        return self.find_magic_index(nums, 0, len(nums) - 1)

    def find_magic_index(self, nums, left, right):
        # 未找到的符合要求的返回 -1
        if left > right:
            return -1

        # 取中間元素
        mid = left + (right - left) // 2

        # 先看是否能找到第一個魔術索引,先往左側找
        left_ans = self.find_magic_index(nums, left, mid-1)

        # 如果左側存在,那麼返回 left_ans
        if left_ans != -1:
            return left_ans
        # 如果不存在,先比較 nums[mid] == mid
        elif nums[mid] == mid:
            return mid
        # 不存在,且 nums[mid] != mid 時,往右側尋找
        return self.find_magic_index(nums, mid + 1, right)

實現結果


圖片描述

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/4548/viewspace-2825977/,如需轉載,請註明出處,否則將追究法律責任。

相關文章