剑指Offer 41 42

181 阅读2分钟

这是我参与8月更文挑战的第20天,活动详情查看:8月更文挑战

剑指 Offer 41. 数据流中的中位数

题目

如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。

例如,

[2,3,4] 的中位数是 3

[2,3] 的中位数是 (2 + 3) / 2 = 2.5

设计一个支持以下两种操作的数据结构:

  • void addNum(int num) - 从数据流中添加一个整数到数据结构中。
  • double findMedian() - 返回目前所有元素的中位数。

示例 1:

输入:
["MedianFinder","addNum","addNum","findMedian","addNum","findMedian"]
[[],[1],[2],[],[3],[]]
输出:[null,null,null,1.50000,null,2.00000]

示例 2:

输入:
["MedianFinder","addNum","findMedian","addNum","findMedian"]
[[],[2],[],[3],[]]
输出:[null,null,2.00000,null,2.50000]

限制:

  • 最多会对 addNum、findMedian 进行 50000 次调用。

方法一

暴力做法:维护一个集合,添加操作时向集合中添加;求中位数时,先排序,再按题意返回中位数;

class MedianFinder {
​
    List<Integer> note;
​
    /** initialize your data structure here. */
    public MedianFinder() {
        note = new ArrayList<>();
    }
    
    public void addNum(int num) {
        note.add(num);
    }
    
    public double findMedian() {
        int n = note.size();
        Collections.sort(note);
        if (n % 2 == 0) {
            double x = note.get(n / 2), y = note.get(n / 2 - 1);
            return (x + y) / 2;
        }else {
            return (double)note.get(n / 2);
        }
    }
}
​
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

方法二

大根堆+小根堆:

  • 大根堆中存小于中位数的数,小根堆中存大于中位数的数

  • 添加元素时,

    • 先往大根堆中添加,若发现大根堆的堆顶元素大于小根堆的堆顶元素时,交换两个堆顶元素;
    • 若大根堆的数量比小根堆的数量大2,说明不平衡,大根堆分一个给小根堆维持平衡;
  • 返回中位数时,

    • 若总数为奇数,返回大根堆堆顶元素
    • 否则,返回两个堆堆顶元素的平均值
class MedianFinder {
​
    PriorityQueue<Integer> maxHeap, minHeap;
​
    /** initialize your data structure here. */
    public MedianFinder() {
        minHeap = new PriorityQueue<>();
        maxHeap = new PriorityQueue(new Comparator<Integer>() {
            public int compare(Integer a, Integer b) {
                return b - a;
            }
        });
    }
    
    public void addNum(int num) {
        maxHeap.offer(num);
        if (minHeap.size() > 0 && maxHeap.peek() > minHeap.peek()) {
            int t = minHeap.poll(), u = maxHeap.poll();
            minHeap.offer(u); maxHeap.offer(t);
        }
        if (maxHeap.size() > minHeap.size() + 1) {
            int t = maxHeap.poll();
            minHeap.offer(t);
        }
    }
    
    public double findMedian() {
        if (((minHeap.size() + maxHeap.size()) & 1) == 1) return maxHeap.peek();
        else return (minHeap.peek() + maxHeap.peek()) / 2.0;
    }
}
​
/**
 * Your MedianFinder object will be instantiated and called as such:
 * MedianFinder obj = new MedianFinder();
 * obj.addNum(num);
 * double param_2 = obj.findMedian();
 */

注意: 大根堆元素个数始终和小根堆元素个数相等或者大1

剑指 Offer 42. 连续子数组的最大和

题目

输入一个整型数组,数组中的一个或连续多个整数组成一个子数组。求所有子数组的和的最大值。

要求时间复杂度为O(n)。

示例1:

输入: nums = [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。

提示:

  • 1 <= arr.length <= 10^5
  • -100 <= arr[i] <= 100

方法一

朴素做法:利用前缀和,首先固定子数组的右端点,然后从起点开始枚举,一直到右端点,在枚举的过程中更新答案,如此一遍操作时间复杂度为O(n),有n个点,总共时间复杂度为O(n*n),不符合题意

进阶做法:在枚举右端点的同时,记录下右端点左边前缀和的最小值,每次只要用右端点减去该最小值即可,省去了从起点再重新遍历的过程,时间复杂度为O(n)

class Solution {
    public int maxSubArray(int[] nums) {
​
        int n = nums.length;
        int[] sum = new int[n + 1];
​
        for (int i = 1; i <= n; i ++ ) sum[i] = sum[i - 1] + nums[i - 1];
​
        int minv = 0;
        int res = -0x3f3f3f3f;
        for (int i = 1; i <= n; i ++ ) {
            res = Math.max(res, sum[i] - sum[minv]);
            if (sum[minv] > sum[i]) minv = i;
        }
        return res;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)