[2021校招必看之Java版《剑指offer》-63] 数据流中的中位数

文章目录

  • 1、题目描述
  • 2、解题思路
    • 2.1 暴力法
    • 2.2 堆方法
  • 3、解题代码
    • 3.1 暴力法
    • 3.2 堆方法
  • 4、解题心得

1、题目描述

  【JZ63】如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
  知识点:排序,堆
  难度:☆☆

2、解题思路

2.1 暴力法

  本题的关键点不在于取中位数,而是排序,使用暴力方法无非是在存的时候排序或者在取的时候排序。因为输入和输出都是取一个数,其实在输入时候排序或输出时候排序都差不多。
  假设我需要在插入的时候完成排序。
  插入的步骤:
    1、判断序列是否为空或者比最后一个还大,是则直接插入,否则下一步;
    2、不满足1,则必然存在一个位置可以插入;
    3、假设待插入数字num大于等于第i-1个位置元素,小于等于第i个位置元素,则位置i就是数字num的可插入位置;
    4、第i个元素开始全部往后挪一步,num插入到第i个位置。
  取值的步骤:
    1、若序列元素数量为偶数,则取中间两个数字的平均值;
    2、若序列元素数量为基数,则直接取中间的数字。

2.2 堆方法

  题目知识点还提示使用堆,基本思路是使用一个大顶堆和小顶堆来保存序列的值,把序列分成:[0 ... median - 1][median + 1 ... arr.sise() - 1]两部分,前半部分用大顶堆,后半部分用小顶堆。
  于是,当我要取出中位数时,先判断两个堆元素数目是否相同,若不同,则取出元素多的那个的顶元素,若相同,则取出两个堆顶元素求平均。
  其实关键还是排序,分别利用两个优先队列即可解决。

3、解题代码

3.1 暴力法

package pers.klb.jzoffer.simple;

import java.util.ArrayList;
import java.util.List;

/**
 * @program: JzOffer2021
 * @description: 数据流中的中位数(暴力法)
 * @author: Meumax
 * @create: 2020-06-19 09:08
 **/
public class Median {
     

    public List<Integer> list = new ArrayList();

    /**
     * 插入数据流
     *
     * @param num
     */
    public void Insert(int num) {
     
        // 若是第一个数字或者要插入的数字比最后一个都大,则直接添加
        if (list.size() == 0 || list.get(list.size() - 1) <= num) {
     
            list.add(num);
        } else {
     
            if (num <= list.get(0)) {
        // 比第一个还小
                list.add(0);    // 长度再拓展一位,存入随便一个数字
                // 从倒数第二个元素开始
                for (int i = list.size() - 2; i >= 0; i--) {
     
                    // 当前位置元素赋值给下一个位置
                    list.set(i + 1, list.get(i));
                }
                list.set(0, num);
            } else {
      // 比第一个大且比最后一个小
                // 从第1个到倒数第1个中间找出待插入的位置 i (索引从0开始)
                for (int i = 1; i < list.size(); i++) {
     
                    if (list.get(i-1) <= num && num <= list.get(i)) {
        // 找到合适的位置 i
                        // 长度再拓展一位,存入随便一个数字
                        list.add(0);
                        // 把原来从i开始的元素整体往后挪一位,腾出位置i给新元素
                        for (int j = list.size() - 2; j >= i; j--) {
     
                            // 现在list新添加了一个位置,最后一个有效数字的位置是在倒数第二位
                            // 从倒数第二位开始,到第i位结束,后一个元素值等于前一个元素值
                            list.set(j + 1, list.get(j));
                        }

                        // 往位置i存入新元素
                        list.set(i, num);
                        break;
                    }
                }
            }
        }
    }

    /**
     * 获取当前读取数据的中位数
     *
     * @return
     */
    public Double GetMedian() {
     
        if (list.size() == 0) {
     
            return null;
        } else {
     
            // 偶数个元素
            // 0 1 2 3 4 5 6
            // size = 7
            if (list.size() % 2 == 0) {
     
                Double num1 = list.get(list.size() / 2 - 1).doubleValue();
                Double num2 = list.get(list.size() / 2).doubleValue();
                return (num1 + num2) / 2;
            } else {
      // 奇数个元素
                return list.get((list.size() - 1) / 2).doubleValue();
            }
        }
    }
}

3.2 堆方法

package pers.klb.jzoffer.simple;

import java.util.*;

/**
 * @program: JzOffer2021
 * @description: 数据流中的中位数(堆方法)
 * @author: Meumax
 * @create: 2020-06-19 09:08
 **/
public class Median {
     

    // 大顶堆(降序,前半段)
    private Queue<Integer> maxHeap = new PriorityQueue(new Comparator<Integer>() {
     
        public int compare(Integer o1, Integer o2) {
     
            return o2 - o1;
        }
    });

    // 小顶堆(升序,后半段)
    private Queue<Integer> minHeap = new PriorityQueue(new Comparator<Integer>() {
     
        @Override
        public int compare(Integer o1, Integer o2) {
     
            return o1 - o2;
        }
    });

    public void Insert(int num) {
     
        // 大顶堆插入数据,并自动排序
        maxHeap.add(num);

        // 平衡两个堆的元素数量
        if (((maxHeap.size() - minHeap.size()) > 1)) {
     
            minHeap.add(maxHeap.poll());
        }
    }

    public Double GetMedian() {
     

        int size = maxHeap.size() + minHeap.size();
        if (size % 2 == 0) {
         // 偶数
            Double num1 = maxHeap.peek().doubleValue();
            Double num2 = minHeap.peek().doubleValue();
            return (num1 + num2) / 2;
        } else {
       // 奇数
            if (size == 1) {
     
                return maxHeap.peek().doubleValue();
            } else {
     
                if (maxHeap.peek() > minHeap.peek()) {
     
                    minHeap.add(maxHeap.poll());
                    return minHeap.peek().doubleValue();
                } else {
     
                    return maxHeap.peek().doubleValue();
                }
            }
        }
    }

    public void show() {
     
        Object[] max = maxHeap.toArray();
        Object[] min = minHeap.toArray();
        System.out.println(Arrays.toString(max) + Arrays.toString(min));
    }
}

4、解题心得

  其实本题关键点在于排序,而且没有不能调API的有要求,像堆方法就是利用了优先队列自动排序的特性,整体难度较低,不知道什么给标上了2颗星。如果禁止调API,2颗星还是可以的。

你可能感兴趣的:(剑指offer(Java语言),数据结构,java,算法,剑指offer,堆)