【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))
posted @ 2019-12-20 17:36  961897  阅读(330)  评论(0编辑  收藏  举报