「LeetCode算法精講」數組中的最短無序連續子數組(Python)

2021-01-11 數據藝術家

題目內容

給定一個整數數組,你需要尋找一個連續的子數組,如果對這個子數組進行升序排序,那麼整個數組都會變為升序排序。

你找到的子數組應是最短的,請輸出它的長度。

示例 1:

輸入: [2, 6, 4, 8, 10, 9, 15] 輸出: 5 解釋: 你只需要對 [6, 4, 8, 10, 9] 進行升序排序,那麼整個表都會變為升序排序。說明:

輸入的數組長度範圍在 [1, 10,000]。輸入的數組可能包含重複元素 ,所以升序的意思是<=。來源:力扣(LeetCode)連結:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray

解法效率

LeetCode的Python執行用時隨緣,只要時間複雜度沒有明顯差異,執行用時一般都在同一個量級,僅作參考意義。

解法一(暴力的棧實現):

【思路】使用棧存儲當前遍歷的數組,如果發現新的數(n)比棧頂元素小,記錄棧頂的坐標作為排序數組的結束坐標(reverse_max_idx);從棧中不斷取出元素,直到取出的元素比該數(n)大或達到棧底,此時的坐標即為排序數組的起始坐標(reverse_min_idx);再將取出的元素升序放回棧中(可用另一個棧實現)。排序數組的起始坐標一旦被確定,就不再更改;而排序數組的結束坐標,則不斷更新。最終的結果即為排序樹組的結束坐標減去起始坐標。這種方法一直在維護一個升序的棧,但如果整個數組是一個倒序的數組,則會有巨大的時間複雜度。

def findUnsortedSubarray(self, nums: List[int]) -> int: stack = [] reverse_min_idx = None reverse_max_idx = None for i in range(len(nums)): n = nums[i] if len(stack) == 0 or n >= stack[-1]: stack.append(n) else: reverse_end_idx = i reverse_start_idx = i # 使用臨時棧到倒序部分升序 temp_stack = [] while len(stack) > 0 and n < stack[-1]: reverse_start_idx -= 1 temp_stack.append(stack.pop(-1)) temp_stack.append(n) while temp_stack: stack.append(temp_stack.pop(-1)) # 更新升序數組起始坐標和升序數組結尾坐標 if reverse_min_idx is None or reverse_start_idx < reverse_min_idx: reverse_min_idx = reverse_start_idx if reverse_max_idx is None or reverse_end_idx > reverse_max_idx: reverse_max_idx = reverse_end_idx if reverse_min_idx is not None and reverse_max_idx is not None: return reverse_max_idx - reverse_min_idx + 1 else: return 0解法二(暴力的維護最大值數組):

【思路】解法一中,我們發現實際上我們需要維護完整的升序數組,只需要維護最大值數組即可。但是,這種方法和解法一擁有相似的時間複雜度,這種方法仍然無法解決倒序數組時間複雜度過大的問題。

def findUnsortedSubarray(self, nums: List[int]) -> int: max_list = [-1] * len(nums) reverse_min_idx = None reverse_max_idx = None for i in range(len(nums)): if (i > 0 and nums[i] >= max_list[i - 1]) or i == 0: max_list[i] = nums[i] else: # 維護最大值數組 max_list[i] = max_list[i - 1] # 計算起始坐標 idx = i - 1 while idx >= 0 and max_list[idx] > nums[i]: idx -= 1 idx += 1 # 更新升序數組起始坐標和升序數組結尾坐標 if reverse_min_idx is None or idx < reverse_min_idx: reverse_min_idx = idx if reverse_max_idx is None or i > reverse_max_idx: reverse_max_idx = i if reverse_min_idx is not None and reverse_max_idx is not None: return reverse_max_idx - reverse_min_idx + 1 else: return 0解法三(暴力的兩層循環):

【思路】通過解法一和解法二我們發現,與其使用O(N^2)的時間複雜度來維護一個升序棧或一個最大值數組,還不如直接用O(N^2)的時間複雜度來比較數組中所有數之間的大小,僅維護需升序數組的起始坐標(reverse_min_idx)和結束坐標(reverse_max_idx)。我們比較數組中每兩個數組的大小,如果發現左邊的比右邊的大,那麼就判斷是否需要更新起始坐標(reverse_min_idx)和結束坐標(reverse_max_idx)。但是這種方法的時間複雜度仍然是O(N^2)。

def findUnsortedSubarray(self, nums: List[int]) -> int: reverse_min_idx = len(nums) - 1 reverse_max_idx = 0 for i in range(len(nums)): for j in range(i + 1, len(nums)): if nums[i] > nums[j]: reverse_min_idx = min(reverse_min_idx, i) reverse_max_idx = max(reverse_max_idx, j) if reverse_min_idx > reverse_max_idx: return 0 else: return reverse_max_idx - reverse_min_idx + 1解法四(排序):

【思路】我們想到與其分別比較所有數組,還不如直接將數組排序;比較原數組和排序後數組不同之處的最小坐標和最大坐標,這些坐標也就是需要升序數組的起始坐標和結束坐標。這種方法的時間複雜度為排序的O(NlogN)和排序後遍歷的O(N),時間複雜度大幅度下降。

def findUnsortedSubarray(self, nums: List[int]) -> int: minimum = len(nums) - 1 maximum = 0 sorts = sorted(nums) for i in range(len(nums)): if sorts[i] != nums[i]: minimum = min(minimum, i) maximum = max(maximum, i) return maximum - minimum + 1 if minimum < maximum else 0解法五(解法四的優化):

【思路】我們再在解法四的基礎上做一個小小的優化,在遍歷時不再完整遍歷,而是從前往後、從後往前分別找到第一個存在差異的點即可。

def findUnsortedSubarray(self, nums: List[int]) -> int: sorts = sorted(nums) for i in range(len(nums)): if sorts[i] != nums[i]: minimum = i break else: return 0 for i in range(len(nums) - 1, -1, -1): if sorts[i] != nums[i]: maximum = i break else: return 0 return maximum - minimum + 1

相關焦點

  • LeetCode 例題精講 | 16 最大子數組和:子數組類問題的動態規劃技巧
    接下來的文章中,我會講解動態規劃問題中針對不同類型問題的小技巧。今天要講的是關於「子數組」類題目的常見技巧。在講具體問題之前,我們要明確一下「子數組」和「子序列」的概念。子序列 (subsequence) 可以是不連續的。
  • LeetCode數組類知識點&題型總結
    順序存儲就是把數據存儲在一塊連續的空間內。數組(array)就是典型的順序存儲,而鍊表就是典型的非順序存儲。數組通常用於存儲一系列相同類型的數據。當我們在創建數組時,會在內存中劃分出一塊連續的內存用於存儲數據,插入數據時,會將數據按順序存儲在這塊連續的內存中,讀取時通過訪問數組的索引迅速取出。數組名就是一個指針,指向這段內存的起始地址。
  • LeetCode刷題第三周【數組(簡單)】
    數組是目前Leetcode上題量最多的一個模塊了。刷題前我們來了解一下什麼是數組:數組是在程序設計中,為了處理方便, 把具有相同類型的若干元素按有序的形式組織起來的一種形式。首先,數組會利用 索引 來記錄每個元素在數組中的位置,且在大多數程式語言中,索引是從 0 算起的。我們可以根據數組中的索引,快速訪問數組中的元素。事實上,這裡的索引其實就是內存地址。
  • LeetCode:連續子數組的最大和
    輸入一個整型數組,數組中的一個或連續多個整數組成一個子數組。求所有子數組的和的最大值。
  • 絕對差不超過限制的最長連續子數組
    題目URL:https://leetcode-cn.com/problems/longest-continuous-subarray-with-absolute-diff-less-than-or-equal-to-limit
  • [LeetCode] 912. Sort an Array 數組排序
    題目給定了每個數字的範圍是 [-50000, 50000],並不是特別大,這裡可以使用記數排序 Count Sort,在 LeetCode 中也有直接利用這個解法的題Sort Colors,建立一個大小為 100001 的數組 count,然後統計 nums 中每個數字出現的個數,然後再從0遍歷到 100000,對於每個遍歷到的數字i,若個數不為0,則加入 count 數組中對應個數的 i-50000
  • 筆試 | 字節跳動 最優連續子序列
    ,使得該段連續子序列的交替和最大。給定一個長度為n的整數序列,找出其中一段連續子序列,使得該段連續子序列的和最大。這是一道經典的動態規劃-「最大子段和問題」。所以問題可以轉化為求兩個數組的最大子段和問題。
  • [LeetCode] 918. Maximum Sum Circular Subarray 環形子數組的最大和
    既然是子數組,則意味著必須是相連的數字,而由於環形數組的存在,說明可以首尾相連,這樣的話,最長子數組的範圍可以有兩種情況,一種是正常的,數組中的某一段子數組,另一種是分為兩段的,即首尾相連的,可以參見 大神 lee215 的帖子 中的示意圖。
  • LeetCode145|數組中數字出現的次數II
    一,數組中數字出現的次數II1,問題描述在一個數組 nums 中除一個數字只出現一次之外,其他數字都出現了三次
  • 如何使用Numpy數組?
    【連續「Python利用Numpy數組進行數據處理(一)」】2.【聚合函數】數學和統計方法[軸和0]可以通過數組上的一組數學函數對整個數組或某個軸向的數組進行統計計算。>說明sum對數組中全部或某軸向的元素求和。
  • 旋轉數組的最小數字
    原題把一個數組最開始的若干個元素搬到數組的末尾,我們稱之為數組的旋轉。輸入一個遞增排序的數組的一個旋轉,輸出旋轉數組的最小元素。例如,數組 [3,4,5,1,2] 為 [1,2,3,4,5] 的一個旋轉,該數組的最小值為 1。
  • 詳解連續子數組的最大累乘之動態規劃解法
    這個題目,當然可以用窮舉所有子數組的方法,找出最大值,時間複雜度妥妥地為O(n^2),這顯然不是我們想要的。如何用DP降低到O(n)才是我們的目標,這才是算法的魅力所在,接下來,總結DP求解的思維過程。
  • 數據結構與算法:05 Leetcode同步練習(一)
    題目01:兩數之和https://leetcode-cn.com/problems/two-sum/給定一個整數數組 nums 和一個目標值 target,請你在該數組中找出和為目標值的那 兩個整數,並返回他們的數組下標。
  • [LeetCode] 996. Number of Squareful Arrays 平方數組的個數
    的全排列數組是個平方數組。其實這道題有兩個難點,一個是如何求全排列,另一個是如何在生成全排列的過程中驗證平方數組。LeetCode 有好幾道關於全排列的題,比較基本的就是這兩道 Permutations 和 Permutations II,很明顯這道題中的數字是有可能重複的,所以跟後者更接近。其實這道題的解法就是基於 Permutations II 來做的,只不過在過程中加入了判斷平方數組的步驟。
  • Python入門教程(二):Numpy數組基礎
    Python中的數組操作幾乎等同於Numpy數組操作,今天我們會展示用Numpy數組操作獲取數據或者子數組,對數組進行分裂,變形和連接的例子。首先,我們先介紹幾類基本的數組操作:數組的屬性確定數組的大小,形狀,儲存大小,數據類型數組的索引:獲取和設置各個元素的值數組的切分:在大的數組中獲取或設置更小的子數組
  • Go 刷 leetcode|一道簡單難度卻讓我陷入沉思的題目
    題目描述 給定一個整數數組 nums ,找到一個具有最大和的連續子數組(子數組最少包含一個元素),返回其最大和。示例:輸入: [-2,1,-3,4,-1,2,1,-5,4],輸出: 6解釋: 連續子數組 [4,-1,2,1] 的和最大,為 6。進階:如果你已經實現複雜度為 O(n) 的解法,嘗試使用更為精妙的分治法求解。
  • Python使用ctypes模塊調用DLL函數之C語言數組與numpy數組傳遞
    前面兩篇已經講了傳遞數值/指針/字符串參數、傳遞結構體參數的例子,大家可以回看一下,這樣可以更好的理解本次要講的內容。詳細細節請參考:python使用ctypes模塊調用DLL函數之傳遞數值、指針與字符串參數、Python使用ctypes模塊調用DLL函數之傳遞結構體參數這次講一下在Python中使用ctypes模塊調用DLL中的庫函數傳遞數組參數的情況。
  • Python使用ctypes模塊調用DLL函數之複數數組的參數傳遞
    引言前段時間在作信號分析處理方面的項目時,需要將時域數據通過快速傅立葉變換(FFT)轉換到頻域以便作進一步的後續處理,由於涉及到實時運算速度方面的要求,需要考慮程序算法的運算性能問題,因此,信號處理算法是在C語言裡面完成的,然後將算法的實現函數封裝到動態連結庫(DLL)文件中,最後在
  • 圖解NumPy,這是理解數組最形象的一份教程了
    import numpy as np創建數組我們可以通過傳遞一個 python 列表並使用 np.array()來創建 NumPy 數組(極大可能是多維數組)。在本例中,python 創建的數組如下圖右所示:通常我們希望 NumPy 能初始化數組的值,為此 NumPy 提供了 ones()、zeros() 和 random.random() 等方法。我們只需傳遞希望 NumPy 生成的元素數量即可:一旦創建了數組,我們就可以盡情對它們進行操作。
  • 圖解:「歸併排序」
    ,這一步屬於「分」的過程。,這一同樣是「分」的過程,但同時也應該注意到,這一步和第一步的操作過程是一致的,也就說第一步和第二步是同一個功能,所以最終你會在代碼中看到遞歸實現,後面會專門講歸併排序所蘊含的遞歸思想。