目錄
一、數組簡介
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/