【Leetcode 二分】 滑动窗口中位数(480)
题目
中位数是有序序列最中间的那个数。如果序列的大小是偶数,则没有最中间的数;此时中位数是最中间的两个数的平均数。
例如:
[2,3,4]
,中位数是 3
[2,3]
,中位数是 (2 + 3) / 2 = 2.5
给出一个数组 nums,有一个大小为 k 的窗口从最左端滑动到最右端。窗口中有 k 个数,每次窗口移动 1 位。你的任务是找出每次窗口移动后得到的新窗口中元素的中位数,并输出由它们组成的数组。
例如:
给出 nums = [1,3,-1,-3,5,3,6,7]
,以及 k = 3
。
窗口位置 中位数
--------------- -----
[1 3 -1] -3 5 3 6 7 1
1 [3 -1 -3] 5 3 6 7 -1
1 3 [-1 -3 5] 3 6 7 -1
1 3 -1 [-3 5 3] 6 7 3
1 3 -1 -3 [5 3 6] 7 5
1 3 -1 -3 5 [3 6 7] 6
因此,返回该滑动窗口的中位数数组 [1,-1,-1,3,5,6]。
提示:
假设k是合法的,即:k 始终小于输入的非空数组的元素个数.
解答
题目和滑动窗口最大值异常相似
1,AC: 爆破(居然没超时),切出每个窗口先排序再找中位数,中间排序K·log(K) ,总时间复杂度: O(N·K·logK), 空间复杂度: O(K)
2,快速选择 tiemout。切出每个窗口,对窗口元素做快速选择找中位数,奇数找一次,偶数找两次。理论上比排序快,快速选择平均时间复杂度O(N),最差O(N^2)。Time: O(N·K), Space: O(K)
3,AC:二分。cur
表示当前窗口,每次向右滑动时加入一个新元素,并删除左边元素,再计算中位数。窗口滑动过程中不必每次都重新排序,维护cur
有序,新元素用bisect二分插入,删除也用二分查找删。
维护cur
有序:二分插入总复杂度O(N)
(其中找位置log(N)
+插入O(N)
),删除总复杂度O(N)
(其中找位置log(N)
+删除O(N)
);加上外层遍历,整体复杂度Time: O(N·K), Space: O(K)
这种方法要先排序第一个窗口,记录已排序的cur
4,窗口求中位数用BFPRT应该也能做出来,BFPRT时间复杂度O(N),不存在最差情况。窗口数为偶数时,应该要两次BFPRT,复杂度O(2N),总复杂度O(N·K),待更ing...
代码如下:
import bisect
class Solution:
# 二分 Time: O(N*K), Space: O(K)
def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
if not nums:
return []
# 初始化win
win = nums[:k]
win.sort()
ans = [self.get_median(win, k)]
length = len(nums)
for i in range(k, length):
del_index = bisect.bisect(win, nums[i-k]) - 1 # 二分找删除元素的位置
win.pop(del_index)
# 插入新的,维护有序
bisect.insort(win, nums[i])
ans.append(self.get_median(win, k))
return ans
def get_median(self, win, k):
return win[k//2] if k%2 != 0 else (win[k//2]+win[k//2-1])/2.0
# # 爆破 Time: O(N*K*logK), Space: O(K)
# def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
# if not nums:
# return []
# length = len(nums)
# ans = []
# for i in range(length-k+1):
# t = nums[i:i+k]
# t.sort()
# ans.append(self.get_median(t, k))
# return ans
# # 快速选择 Time: O(N*K), Space: O(K)
# import random
# def medianSlidingWindow(self, nums: List[int], k: int) -> List[float]:
# if not nums:
# return []
# length = len(nums)
# ans = []
# for i in range(length-k+1):
# t = nums[i:i+k]
# if k%2 != 0:
# ans.append(self.quick_select(t, 0, k-1, k//2))
# else:
# a = self.quick_select(t, 0, k-1, k//2)
# b = self.quick_select(t, 0, k-1, k//2-1)
# ans.append((a+b)/2.0)
# return ans
# def quick_select(self, nums, left, right, k):
# if left >= right:
# return nums[left]
# base = random.randint(left, right)
# base_index = self.partition(nums, left, right, base)
# if base_index == k:
# return nums[k]
# elif base_index > k:
# return self.quick_select(nums, left, base_index -1, k)
# else:
# return self.quick_select(nums, base_index + 1, right, k)
# def partition(self, nums, left, right, base):
# if left >= right:
# return
# temp = nums[base]
# max_index = left
# nums[base], nums[right] = nums[right], nums[base]
# for i in range(left, right):
# if nums[i] < temp:
# nums[i], nums[max_index] = nums[max_index], nums[i]
# max_index += 1
# nums[max_index], nums[right] = nums[right], nums[max_index]
# return max_index
s = Solution()
print(s.medianSlidingWindow([1, 3, -1, -3, 5, 3, 6, 7], 3))