持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第十天,点击查看活动详情。
最近在看左神的数据结构与算法,考虑到视频讲的内容和给出的资料的PDF有些出入,不方便去复习,打算出一个左神的数据结构与算法的笔记系列供大家复习,同时也可以加深自己对于这些知识的掌握,该系列以视频的集数为分隔,今天是第八篇:P10|暴力递归
4、金条切割
题目:一块金条切成两半,是需要花费和长度数值一样的铜板的。
比如长度为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
结合为3
放进小根堆 - 拿出最小的两个数
3、3
结合为6
放进小根堆 - 后面依次类推
- 即切割顺序就是:110 -> 45 + 65 -> 25 + 40 + 45 ->…………
实现代码
public static int lessMoney(int[] arr) {
PriorityQueue<Integer> pQ = new PriorityQueue<>();
for (int i = 0; i < arr.length; i++) {
pQ.add(arr[i]);
}
int sum = 0;
int cur = 0;
while (pQ.size() > 1) {
cur = pQ.poll() + pQ.poll();
sum += cur;
pQ.add(cur);
}
return sum;
}
5、最大项目利益
题目: 输入: 正数数组costs、正数数组profits、正数k、正数m 含义: 1️⃣ costs[i]表示i号项目的花费 2️⃣ profits[i]表示i号项目在扣除花费之后还能挣到的钱(利润) 3️⃣ k表示你只能串行的最多做k个项目 4️⃣ m表示你初始的资金 说明: 你每做完一个项目,马上获得的收益,可以支持你去做下一个项目。 输出: 你最后获得的最大钱数。
实现思路
- 首先初始化:把所有项目的花费放进小根堆中,然后初始化一个大根堆用于后续存储利润
- 然后根据目前的资金,解锁出能够进行的项目,以利润为比较器放进大根堆中
- 在k次循环中:每次都是先去解锁,然后放进大根堆,然后去做利润最大的项目
- 如果期间大根堆空了,也就是没有能做的项目了,就返回最大钱数,否则一直循环k次
图解举例
- 假设有以下七个项目:
- 现在启动资金为:1,能串行做的项目个数为:7
- 首先初始化能够进行的项目的堆,以项目的成本作为排序的依据:
- 现在的启动资金是1,拿出能做的项目放进大根堆,并以利润作为标准排序:
- 然后从中选出最大利润的:1,做这个项目,此时的资金就是2,大根堆为空,小根堆为:
- 同样这一轮只能做成本为2的项目,然后资金变为5,此处就不做演示,现在资金为5,小根堆和大根堆分别为:
- 此时就要选择利益更高,在大根堆排在更前的项目:成本为4,利润为6的项目
- 后面就以此类推了
实现代码
public static int findMaximizedCapital(int k, int W, int[] Profits, int[] Capital) {
Node[] nodes = new Node[Profits.length];
for (int i = 0; i < Profits.length; i++) {
nodes[i] = new Node(Profits[i], Capital[i]);
}
PriorityQueue<Node> minCostQ = new PriorityQueue<>(new MinCostComparator());
PriorityQueue<Node> maxProfitQ = new PriorityQueue<>(new MaxProfitComparator());
for (int i = 0; i < nodes.length; i++) {
minCostQ.add(nodes[i]);
}
for (int i = 0; i < k; i++) {
while (!minCostQ.isEmpty() && minCostQ.peek().c <= W) {
maxProfitQ.add(minCostQ.poll());
}
if (maxProfitQ.isEmpty()) {
return W;
}
W += maxProfitQ.poll().p;
}
return W;
}
6、数据流中的中位数
题目:一个数据流中,随时可以取得中位数
实现思路
- 维护两个堆:一个大根堆、一个小根堆
- 然后插入数据的时候:第一个插入大根堆,然后后续的数据做判断:
- 如果小于等于大根堆的堆顶,就插入大根堆
- 如果大于大根堆的堆顶,就插入小根堆
- 而且如果两个堆的大小相差2,就需要平衡。
- 例如:大根堆的大小为4,小根堆的大小为2,那么就要弹出大根堆的堆顶结点,插入到小根堆中,保证两个堆的大小差值小于2
- 然后拿中位数直接弹出大小更大的那个堆的堆顶就行
图解举例
- 首先维护两个堆:
- 现在有数据流:5、3、7、4
- 首先数据 5,放在大根堆的堆顶
- 然后插入数据 3,3<5,放在大根堆
- 但是发现大根堆的大小为2,小根堆的大小为0,此时就要把大根堆的堆顶元素放到小根堆中
- 然后插入数据 7,7>3,放在小根堆:
- 然后插入数据 4,4>3,放在小根堆中,但插入后二者的大小又相差2,就要把4弹到大根堆中,就有了:
实现代码
public static class MedianHolder {
private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());
private void modifyTwoHeapsSize() {
if (this.maxHeap.size() == this.minHeap.size() + 2) {
this.minHeap.add(this.maxHeap.poll());
}
if (this.minHeap.size() == this.maxHeap.size() + 2) {
this.maxHeap.add(this.minHeap.poll());
}
}
public void addNumber(int num) {
if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
maxHeap.add(num);
} else {
minHeap.add(num);
}
modifyTwoHeapsSize();
}
public Integer getMedian() {
int maxHeapSize = this.maxHeap.size();
int minHeapSize = this.minHeap.size();
if (maxHeapSize + minHeapSize == 0) {
return null;
}
Integer maxHeapHead = this.maxHeap.peek();
Integer minHeapHead = this.minHeap.peek();
if (((maxHeapSize + minHeapSize) & 1) == 0) {
return (maxHeapHead + minHeapHead) / 2;
}
return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
}
}
三、N皇后问题
N皇后问题是指在N * N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列, 也不在同一条斜线上。 给定一个整数n,返回n皇后的摆法有多少种。 n=1,返回1。 n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。 n=8,返回92.
实现思路
- 比如有四皇后问题,在第一行插入如下图所示的一个皇后
- 那么就可以理解为这一点对于下一行的限制为:
7. 这里就可以使用二进制来表示
1110
表示该行的限制
实现代码
public static int num(int n) {
if (n < 1 || n > 32) {
return 0;
}
int upperLim = n == 32 ? -1 : (1 << n) - 1;
return process2(upperLim, 0, 0, 0);
}
public static int process(int upperLim, int colLim, int leftDiaLim,
int rightDiaLim) {
if (colLim == upperLim) {
return 1;
}
int pos = 0;
int mostRightOne = 0;
pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));
int res = 0;
while (pos != 0) {
mostRightOne = pos & (~pos + 1);
pos = pos - mostRightOne;
res += process2(upperLim, colLim | mostRightOne,
(leftDiaLim | mostRightOne) << 1,
(rightDiaLim | mostRightOne) >>> 1);
}
return res;
}