每日算法总结——堆扩展:数据流的中位数、贪心算法题目:金条切割问题、利润最大化问题

一、数据流的中位数

  • LeetCode原题:295. 数据流的中位数 - 力扣(LeetCode)

  • 难度:Hard

  • 解题思路:

    1. 准备两个堆,一个大根堆B,一个小根堆S

    2. 添加数据:

      1. 第一个数据一定放入大根堆

      2. 之后每个数据在放入前,都要和大根堆的最大值比较

      3. 如果该数据小于最大值,则将其放入大根堆

        • 如果该数据大于最大值,则将其放入小根堆
      4. 平衡两个堆:如果两个堆大小差距超过1就采取平衡操作,把大堆堆顶值放入小堆中。

    3. 获取中位数:

      1. 哪个堆的size大就返回哪个堆的堆顶。
      2. 相同时,两个堆顶值取平均。
class MedianFinder {
    public PriorityQueue<Integer> maxQueue;
    public PriorityQueue<Integer> minQueue;

    public class maxComparator implements Comparator<Integer> {
        @Override
        public int compare(Integer a, Integer b) {
            return b - a;
        }
    }
    public MedianFinder() {
        maxQueue = new PriorityQueue<>(new maxComparator());
        minQueue = new PriorityQueue<>();
    }
    
    public void addNum(int num) {
        if (maxQueue.isEmpty()) {
            maxQueue.add(num);
        } else {
            if (num < maxQueue.peek()) {
                maxQueue.add(num);
            } else {
                minQueue.add(num);
            }
            if (maxQueue.size() - minQueue.size() > 1) {
                minQueue.add(maxQueue.poll());
            } else if (minQueue.size() - maxQueue.size() > 1) {
                maxQueue.add(minQueue.poll());
            }
        }
    }
    
    public double findMedian() {
        if (maxQueue.size() > minQueue.size()) {
            return (double) maxQueue.peek();
        } else if (maxQueue.size() < minQueue.size()) {
            return (double) minQueue.peek();
        } else {
            int a = maxQueue.peek();
            int b = minQueue.peek();
            return (a + b) / 2.0;
        }
    }
}

二、金条切割问题

  • 题目描述

    • 一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。

    • 一群人想整分整块金条,怎么分最省铜板?
      例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。金条要分成10、20、30三个部分。如果先把长度为60的金条分成10和50,花费60;再把长度为50的金条分成20和30,花费50;一共花费110铜板。
      但是如果先把长度为60的金条分成30和30,花费60;再把长度为30金条分成10和20,花费30;一共花费90铜板
      输入一个数组,返回分割的最小代价。

  • 解题思路:对于该题,显然每次切割时的花费只和待切割金条的长度相同,与怎么切无关,所以我们需要保证下一次切割时的花费最小,也就是下一次要切割金条的长度最短。

    显然这就是哈夫曼编码问题,从下到上依次构造哈夫曼树,长的金条先分割,短的金条后分割。

  • 为什么不能按照正向切割的思想,即优先切割出长的金条?

    • 存在反例:如{1,2,2,1,1},按照正向切割,应花费7+5+3+2=17,而按照哈夫曼树应花费2+3+4+7=16
public class LessMoneySplitGold {
    /**
     * 逆向还原的思想
     */
    public static int lessMoney1(int[] arr) {
        PriorityQueue<Integer> pQ = new PriorityQueue<>();
        for (int j : arr) {
            pQ.add(j);
        }
        int sum = 0;
        int cur = 0;
        while (pQ.size() > 1) {
            cur = pQ.poll() + pQ.poll();
            sum += cur;
            pQ.add(cur);
        }
        return sum;
    }
}

三、利润最大化问题

  • 输入:
    正数数组costs
    正数数组profits
    正数k
    正数m
  • 含义:
    costs[i]表示i号项目的花费
    profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润)
    k表示你只能串行的最多做k个项目
    m表示你初始的资金
  • 说明:
    你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。
  • 输出:
    你最后获得的最大钱数。

解题思路:将所有的项目花费放入小根堆,从小根堆中选出项目花费小于当前资金m的项目,并放入大根堆中,选出利润最大的项目,当前资金m加上该利润,然后重复上述操作。

public class IPO {
    /**
     * 最小花费比较器
     */
    public static class MinCostComparator implements Comparator<Pair<Integer, Integer>> {
        @Override
        public int compare(Pair<Integer, Integer> a, Pair<Integer, Integer> b) {
            return a.getKey() - a.getValue();
        }
    }

    /**
     * 最大利润比较器
     */
    public static class MaxProfitComparator implements Comparator<Pair<Integer, Integer>> {
        @Override
        public int compare(Pair<Integer, Integer> a, Pair<Integer, Integer> b) {
            return b.getValue() - a.getValue();
        }
    }

    /**
     * 求取最大化资金
     * @param k 最大可参与的项目数
     * @param w 初始资金
     * @param profits 各个项目的利润
     * @param capital 参与各个项目所需要的资金
     * @return 做完k个项目后最大化的资金
     */
    public static int findMaximizedCapital(int k, int w, int[] profits, int[] capital) {
        PriorityQueue<Pair<Integer, Integer>> minCostQueue = new PriorityQueue<>(new MinCostComparator());
        PriorityQueue<Pair<Integer, Integer>> maxProfitQueue = new PriorityQueue<>(new MaxProfitComparator());
        // 所有项目扔到锁池中,花费组织的最小堆
        for (int i = 0; i < profits.length; i++) {
            // Pair<花费,利润>
            minCostQueue.add(new Pair<>(capital[i], profits[i]));
        }
        // 进行k轮
        for (int i = 0; i < k; i++) {
            // 能力所及的项目全部解锁
            while (!minCostQueue.isEmpty() && minCostQueue.peek().getKey() < w) {
                maxProfitQueue.add(minCostQueue.poll());
            }
            if (maxProfitQueue.isEmpty()) {
                return w;
            }
            w += maxProfitQueue.poll().getValue();
        }
        return w;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值