一、数据流的中位数
-
LeetCode原题:295. 数据流的中位数 - 力扣(LeetCode)
-
难度:Hard
-
解题思路:
-
准备两个堆,一个大根堆B,一个小根堆S
-
添加数据:
-
第一个数据一定放入大根堆
-
之后每个数据在放入前,都要和大根堆的最大值比较
-
如果该数据小于最大值,则将其放入大根堆
- 如果该数据大于最大值,则将其放入小根堆
-
平衡两个堆:如果两个堆大小差距超过1就采取平衡操作,把大堆堆顶值放入小堆中。
-
-
获取中位数:
- 哪个堆的size大就返回哪个堆的堆顶。
- 相同时,两个堆顶值取平均。
-
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;
}
}