目录
一、数组简介
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/