跟着左神学算法——数据结构与算法学习日记(八)

161 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情

贪心算法

在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫做贪心算法。 也就是说,不从整体最优上加以考虑,所作出的是在某种意义上的局部最优解。 局部最优 ——> 整体最优

贪心算法在笔试时的解题套路:

  1. 实现一个不依靠贪心策略的解法X,可以使用最暴力的尝试
  2. 脑补除贪心策略A、贪心策略B、贪心策略C...
  3. 用解法X和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确
  4. 不要去纠结贪心策略的证明

使用对数器的场景

  1. 有一个你想要测的方法a
  2. 实现一个绝对正确,但是复杂度不好的方法b
  3. 实现一个随机样本产生器
  4. 实现对比算法a和算法b的方法
  5. 比较方法a和方法b来验证方法a是否正确
  6. 如果有一个样本使得比对出错,分析是哪个方法出错
  7. 当样本数量很多时,如果比对测试仍然正确,可以确定方法a正确

贪心算法经典题目

项目收益问题

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

思路:利用小根堆(根据花费)存储这些项目,视为未解锁状态(不能做),根据资金m来将能够进行的项目依次从小根堆中弹出进入到大根堆(根据利润),然后从大根堆中拿出利润最高的项目进行执行。再继续根据资金m来解锁小根堆中的项目进入到大根堆,重复以上步骤,直到资金不足以做大根堆中的项目或者已经做了k个项目为止

image.png

代码实现:

//项目定义
public static class Node {
	public int p;//利润
	public int c;//花费

	public Node(int p, int c){
		this.p = p;
		this.c = c;
	}
}
//用于比较花费的比较器
public static class MinCostComparator implements Comparator<Node>{

	@Override
	public int compare(Node o1,Node o2){
		return o1.c - o2.c;
	}

}

//用于比较利润的比较器
public static class MaxProfitComparator implements Comparator<Node>{

	@Override
	public int compare(Node o1,Node o2){
		return o2.p - o1.p;
	}
	
}

//主方法
public static int findMaximizedCapital(int k,int M,int[] Profits,int[] Capital){
	PriorityQueue<Node> minCostQ = new PriorityQueue(new MinCostComparator());//小根堆
	PriorityQueue<Node> maxProfitQ = new PriorityQueue(new MaxCostComparator());//大根堆
	//把所有项目放入小根堆中 
	for(int i = 0;i<Profits.length;i++){
		minCostQ.add(new Node(Profits[i],Capital[i]));
	}
	//将能够进行的项目放入大根堆中
	for(int i = 0li<k;i++){
		while(!minCostQ.isEmpty() && minCost.peek().c <= M){
			maxProfitQ.add(minCostQ.poll());
		}
		if(maxProfitQ.isEmpty()){
			return M;
		}
		M += maxProfitQ.poll().p;
	}
	return M;
}

数据流中获取中位数

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

思路:使用两个堆,一个大根堆,一个小根堆,将小的一半放在大根堆里,将大的一半放在小根堆里,则可以依据大根堆和小根堆的堆顶元素判断出中位数。

思路图解: image.png

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;  
	   }  
	}