【數組和字符串】(一) 數組簡介

目錄

一、數組簡介

1.1 尋找數組的中心索引

1.2 搜索插入位置

1.3 合併區間


一、數組簡介

1.1 尋找數組的中心索引

1.1.1 問題描述

1.1.2 求解過程

法一:直接使用 for loop +  sum() + 切片來無腦暴力求解,複雜度至少 O(n^2)。對序列求和需要遍歷範圍內的所有元素,在 for 循環內對切片反覆遍歷求和,進行了太多的重複冗餘計算,以至於效率肯定很低!雖然 Python 內置函數經過了優化,但也不能隨便亂用,這時體現了數據結構與算法-複雜度分析的重要性了。(對具有 k 個元素的 seq 使用 sum() 複雜度爲 O(k))

2020/05/31 - 8.50 % - 相當低效!

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

法二:維護左、右兩個求和列表(雙指針),然後再進行遍歷與對比,減少了大量不必要的求和操作,複雜度 O(n)。

2020/05/31 - 94.08% - 還有優化空間

class Solution:
    def pivotIndex(self, nums: List[int]) -> int:
        if nums == [0]:
            return 0
            
        left_sum = [0]  # 從左到右累計和表
        right_sum = [0]  # 從右到左累計和表
        # 求和
        for i in range(len(nums)-1):                     # 例:nums=[1,7,3,6,5,6]
            left_sum.append(nums[i] + left_sum[i])       # 左:left_sum=[0,1,8,11,17]
            right_sum.append(nums[-i-1] + right_sum[i])  # 右:right_sum=[0,6,11,17,20]
        # 查表比較
        for j in range(len(left_sum)):
            if left_sum[j] == right_sum[-j-1]:
                return j
        return -1

法三:結合 Python 內置函數 sum() 和 enumerate() 加速求和與迭代,以減少 for 循環的使用次數,複雜度 O(n)。

2020/05/31 - 97.71% - 效率最高

class Solution:
    def pivotIndex(self, nums: List[int]) -> int:  
        right_sum = sum(nums)
        left_sum = 0
        
        for i, num in enumerate(nums):
            right_sum -= nums[i]
            if left_sum == right_sum:
                return i
            else:
                left_sum += nums[i]
        return -1

1.2 搜索插入位置

1.2.1 問題描述

1.2.2 求解過程

 法一:順序查找,複雜度 O(n)

2020/05/31 - 72.76% - 還有提升空間!(使用一個變量維護 index 沒有 enumerate() 快!) (其實本法僅次於二分查找)

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        for index, num in enumerate(nums):
            if target <= num:
                return index
        return index+1
# ---------------------------------------------------------------------------
class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        nums.append(target)
        for index, num in enumerate(nums):
            if target <= num:
                return index

 法二:手寫二分查找,複雜度 O(logn)。但寫法不恰當,可能踩雷了!

2020/05/31 - 31.37% - 不恰當的寫法下,還不如順序查找!

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        # 兩端特殊情況處理
        if target <= nums[0]:  # 特殊情況 nums=[1], target=1
            return 0
        if target > nums[-1]:
            return len(nums)
        # 中間正常二分查找
        start = 0
        end = len(nums)-1
        while True:
            current = (start+end) // 2  # 中間當前 index
            
            if nums[current-1] < target <= nums[current]:
                return current
            elif target > nums[current]:
                start = current + 1
            else:
                end = current - 1

 法二:內置二分查找,複雜度 O(logn)。

2020/05/31 - 50.93% - 竟然也沒有快多少??

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:        
        if target > nums[-1]:
            return len(nums)
        from bisect import bisect_left
        return bisect_left(nums, target)

 法三: 參考 - 二分查找,複雜度 O(logn)。

2020/05/31 - 50.93%

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        
        if nums[right] < target:
            return len(nums)
        
        while left <= right :
            mid = (left + right) // 2   # 更慢?
            if nums[mid] > target:
                right = mid - 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                return mid
        return left

 法三: 參考 - 標準二分查找,複雜度 O(logn)。目測是 while True 整除 // 導致速度大幅下降?!!

2020/05/31 - 96.65% - 基本是最快的了

class Solution:
    def searchInsert(self, nums: List[int], target: int) -> int:
        left = 0
        right = len(nums) - 1
        
        if nums[right] < target:
            return len(nums)
        
        while left <= right :
            mid = int((left + right) / 2)  # 修正!?!  ## 爲什麼這個比 // 更快呢?
            if nums[mid] > target:
                right = mid - 1
            elif nums[mid] < target:
                left = mid + 1
            else:
                return mid
        return left

 小結:在最快的方法中,沒有使用 while True 的!!但不少還是使用了 // 的。然而,網上並未找到有關精確除 / 和 整除(地板除) // 之間的效率差異問題,也沒有找到 Python3 中 while True 效率低的原因。

1.3 合併區間

1.3.1 問題描述

1.3.2 求解過程

法一:先用 if 語句排除特殊的少元素情況,複雜度 O(1)。然後,由於初始列表元素可能是亂序的,難以用固定規則把控,所以根據左元素從小到大排序,複雜度至少 O(nlogn)。然後用 for 訓練遍歷列表元素並比較,複雜度 O(n)。最後收尾 append(),複雜度 O(1)。綜上,複雜度取決於排序過程,爲 O(nlogn)。

2020/05/31 - 82.70% - 還有優化空間!

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        if len(intervals) <= 1:  # 特殊情況 [], [[1,2]]
            return intervals
        # 左元素從小到大排序
        intervals.sort(key=lambda x: x[0])  # 特殊情況-亂序 [[2,3],[4,5],[6,7],[8,9],[1,10]]
        result = []
        temp = intervals[0]  # 首個 list 元素
        for index in range(1, len(intervals)):  
            if temp[1] < intervals[index][0]:  # 特殊情況-無交集 [[1,4],[5,6]]
                result.append(temp)  # 保存
                temp = intervals[index]  # 更新
            else:  # 正常情況-有交集 [[0,4],[1,4]]
                temp[1] = max(temp[1], intervals[index][1])
        result.append(temp)  # 收尾
        return result

小貼士Timsort 是一種混合、穩定高效的排序算法,源自合併排序和插入排序,旨在很好地處理多種真實數據。它由 Tim Peters 於2002年用在 Python 中。該算法查找已排序的數據的子序列,並使用該知識更有效地對其餘部分進行排序。這是通過將已識別的子序列 (稱爲運行) 與現有運行合併直到滿足某些條件來完成的。從 2.3 版本開始,Timsort 一直是 Python 的標準排序算法。如今,Timsort 已是 Python、Java、Android 平臺和 GNU Octave 的默認排序算法。

本質上,Timsort 是一個經過大量優化的歸併排序,而歸併排序已經到達了最壞情況下,比較排序算法時間複雜度的下界。故最壞情況下,Timsort 時間複雜度爲 O(nlogn)。最佳情況下,即輸入已排好序,它則以線性時間運行 O(n)。可見 Timsort 是目前最好的排序方式。

參考鏈接:https://blog.csdn.net/sinat_35678407/article/details/82974174

法二:使用 enumerate() 來優化 for-range 循環,並簡化表示

2020/05/31 - 92.64% - 效率最高

class Solution:
    def merge(self, intervals: List[List[int]]) -> List[List[int]]:
        if len(intervals) <= 1:  # 特殊情況 [], [[1,2]]
            return intervals
        # 左元素從小到大排序, 複雜度 O(nlogn)
        intervals.sort(key=lambda x: x[0])  # 特殊情況-亂序 [[2,3],[4,5],[6,7],[8,9],[1,10]]
        result = []
        temp = intervals[0]  # 首個 list 元素
        for index, elem in enumerate(intervals):  # 使用 enumerate() 以避免 range()
            if temp[1] < elem[0]:
                result.append(temp)
                temp = elem
            else:
                temp[1] = max(temp[1], elem[1])
        result.append(temp)  # 收尾
        return result

網址:https://leetcode-cn.com/explore/learn/card/array-and-string/198/introduction-to-array/1427/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章