LeetCode215题 数组中的第K个最大元素
堆排序
把待排序数组视为数组表示的完全二叉树,然后自底向上不断调整,使其成为一个大顶堆(升序排序)/小顶堆(降序排序),之后可以通过不断弹出堆顶元素进行排序
建堆过程时间复杂度为O(n),堆排序过程时间复杂度为O(nlogn)
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
n = len(nums)
# 从第一个父节点调整堆,进而构建大顶堆
for i in range((n - 1) // 2, -1, -1):
self.adjustHeap(nums, i, n)
# 堆排序
for i in range(1, k + 1):
nums[0], nums[-i] = nums[-i], nums[0]
self.adjustHeap(nums, 0, n - i)
return nums[-k]
def adjustHeap(self, nums, idx, n):
left = 2 * idx + 1
right = 2 * idx + 2
max_idx = idx
if left < n and nums[max_idx] < nums[left]:
max_idx = left
if right < n and nums[max_idx] < nums[right]:
max_idx = right
if max_idx != idx:
nums[idx], nums[max_idx] = nums[max_idx], nums[idx]
self.adjustHeap(nums, max_idx, n)
归并排序
归并排序是在递归的后序位置进行自底向上的排序,会有logn次的“并”,每次“并”的时间复杂度是O(n),所以整体时间复杂度为O(nlogn)
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return self.mergeSort(nums)[-k]
def mergeSort(self, nums):
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = self.mergeSort(nums[:mid])
right = self.mergeSort(nums[mid:])
cur_left = 0
cur_right = 0
r = []
while cur_left < len(left) and cur_right < len(right):
if left[cur_left] <= right[cur_right]:
r.append(left[cur_left])
cur_left += 1
else:
r.append(right[cur_right])
cur_right += 1
if cur_left < len(left):
r = r + left[cur_left:]
if cur_right < len(right):
r = r + right[cur_right:]
return r
快速排序
快速排序是在递归的前序位置进行自顶向下的排序,平均时间复杂度为O(nlogn),但由于快速排序在极端情况下复杂度为O(n^2),本题中会超时
class Solution:
def findKthLargest(self, nums: List[int], k: int) -> int:
return self.quickSortK(nums, 0, len(nums) - 1, len(nums) - k)
def quickSortK(self, nums, left, right, k):
pivot = nums[right]
pivot_index = left # 代表比pivot小的最后一个元素索引 +1
for i in range(left, right):
if pivot > nums[i]:
nums[pivot_index], nums[i] = nums[i], nums[pivot_index]
pivot_index += 1
nums[pivot_index], nums[right] = nums[right], nums[pivot_index]
if k == pivot_index:
return nums[k]
elif k < pivot_index:
return self.quickSortK(nums, left, pivot_index - 1, k)
else:
return self.quickSortK(nums, pivot_index + 1, right, k)
LeetCode315题 计算右侧小于当前元素的个数
归并排序的应用:归并排序过程中的左右区间附带着元素位置信息,可以在“并”的过程中累加左区间元素的结果
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
self.res = [0] * len(nums)
self.mergeSortProcess([(i, j) for i, j in enumerate(nums)])
return self.res
def mergeSortProcess(self, nums):
if len(nums) <= 1:
return nums
mid = len(nums) // 2
left = self.mergeSortProcess(nums[:mid])
right = self.mergeSortProcess(nums[mid:])
cur_left = 0
cur_right = 0
r = []
while cur_left < len(left) and cur_right < len(right):
if left[cur_left][1] <= right[cur_right][1]:
self.res[left[cur_left][0]] += cur_right
r.append(left[cur_left])
cur_left += 1
else:
r.append(right[cur_right])
cur_right += 1
if cur_left < len(left):
for i in left[cur_left:]:
self.res[i[0]] += len(right)
r = r + left[cur_left:]
if cur_right < len(right):
r = r + right[cur_right:]
return r
LeetCode327题 区间和的个数
前缀和数组 + 归并排序 + 双指针的应用:
- 构建前缀和数组后,任意的区间和都可以表示为归并排序过程中的right[j] - left[i] / right[j];
- 且对于有序的left、right,可以用双指针技巧计算right[j] - left[i] / right[j]是否在区间[lower,upper]中,时间复杂度为O(n),这样整体的复杂度就是O(nlogn),与归并排序相同
class Solution:
def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
# 构建前缀和数组
for i in range(1, len(nums)):
nums[i] += nums[i - 1]
self.res = 0
self.mergeSort(nums, lower, upper)
return self.res
def mergeSort(self, nums, lower, upper):
if len(nums) <= 1:
if len(nums) == 1 and lower <= nums[0] <= upper:
self.res += 1
return nums
mid = len(nums) // 2
left = self.mergeSort(nums[:mid], lower, upper)
right = self.mergeSort(nums[mid:], lower, upper)
cur_l = cur_r = 0
cur_left = cur_right = 0
r = []
while cur_left < len(left) and cur_right < len(right):
if left[cur_left] <= right[cur_right]:
r.append(left[cur_left])
cur_left += 1
while cur_l < len(right) and right[cur_l] - r[-1] < lower:
cur_l += 1
while cur_r < len(right) and right[cur_r] - r[-1] <= upper:
cur_r += 1
self.res += cur_r - cur_l
else:
r.append(right[cur_right])
cur_right += 1
if cur_left < len(left):
for v in left[cur_left:]:
r.append(v)
while cur_l < len(right) and right[cur_l] - r[-1] < lower:
cur_l += 1
while cur_r < len(right) and right[cur_r] - r[-1] <= upper:
cur_r += 1
self.res += cur_r - cur_l
if cur_right < len(right):
r = r + right[cur_right:]
return r