「左程云-算法与数据结构笔记」| P10 暴力递归(下)

231 阅读6分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 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. 假设有数组: image.png
  2. 那么首先把数据放进小根堆中,由于我们使用小根堆知识想要一个排序,也就是每次要拿到最小的数据,与它的树形结构无关,此处就省略了树形结构的画法 image.png
  3. 拿出最小的两个数1、2结合为 3 放进小根堆 image.png
  4. 拿出最小的两个数3、3结合为 6 放进小根堆 image.png
  5. 后面依次类推 image.png
  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次

图解举例

  • 假设有以下七个项目: image.png
  • 现在启动资金为:1,能串行做的项目个数为:7
  • 首先初始化能够进行的项目的堆,以项目的成本作为排序的依据:

image.png

  • 现在的启动资金是1,拿出能做的项目放进大根堆,并以利润作为标准排序:

image.png

  • 然后从中选出最大利润的:1,做这个项目,此时的资金就是2,大根堆为空,小根堆为: image.png
  • 同样这一轮只能做成本为2的项目,然后资金变为5,此处就不做演示,现在资金为5,小根堆和大根堆分别为:

image.png

image.png

  • 此时就要选择利益更高,在大根堆排在更前的项目:成本为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、数据流中的中位数

题目:一个数据流中,随时可以取得中位数


实现思路

  1. 维护两个堆:一个大根堆、一个小根堆
  2. 然后插入数据的时候:第一个插入大根堆,然后后续的数据做判断:
    1. 如果小于等于大根堆的堆顶,就插入大根堆
    2. 如果大于大根堆的堆顶,就插入小根堆
    3. 而且如果两个堆的大小相差2,就需要平衡。
    4. 例如:大根堆的大小为4,小根堆的大小为2,那么就要弹出大根堆的堆顶结点,插入到小根堆中,保证两个堆的大小差值小于2
  3. 然后拿中位数直接弹出大小更大的那个堆的堆顶就行

图解举例

  1. 首先维护两个堆: image.png
  2. 现在有数据流:5、3、7、4
  3. 首先数据 5,放在大根堆的堆顶 image.png
  4. 然后插入数据 3,3<5,放在大根堆 image.png
  5. 但是发现大根堆的大小为2,小根堆的大小为0,此时就要把大根堆的堆顶元素放到小根堆中 image.png
  6. 然后插入数据 7,7>3,放在小根堆:
  7. 然后插入数据 4,4>3,放在小根堆中,但插入后二者的大小又相差2,就要把4弹到大根堆中,就有了:

image.png

实现代码

	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.


实现思路

  1. 比如有四皇后问题,在第一行插入如下图所示的一个皇后

image.png

  1. 那么就可以理解为这一点对于下一行的限制为:

image.png 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;  
	}