贪心算法——常见试题集 (精华)

定义

  • 贪心算法或贪心思想采用贪心的策略,保证每次操作都是局部最优的,从而使最后得到的结果是全局最优的。(但是不一定是正确解,但是会无限逼近)

集合覆盖问题

  • 假设存在如下表的需要付费的广播台,以及广播台信号可以覆盖的地区。 如何选择最少 的广播台,让所有的地区都可以接收到信号

在这里插入图片描述

核心思路

  • 遍历所有的广播电台, 找到一个覆盖了最多未覆盖的地区的电台(此电台可能包含一些已覆盖的地区,但没有关系)
  • 将这个电台加入到一个集合中(比如ArrayList), 想办法把该电台覆盖的地区在下次比较时去掉
  • 重复第1步直到覆盖了全部的地区

注意事项

  • 贪心算法所得到的结果不一定是最优的结果(有时候会是最优解),但是都是相对近似(接近)最优解的结果

Java 代码

package Algorithm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;

public class GreedyAlgorithm {

	public static void main(String[] args) {

     	// 创建存储 广播台 	与 覆盖地区 Map
 	    HashMap<String, HashSet<String>> broadcasts = new HashMap<String,HashSet<String>>();
	  
 	    HashSet<String> hashSet1 = new HashSet<String>();
		hashSet1.add("北京");
		hashSet1.add("上海");
		hashSet1.add("天津");
		
		HashSet<String> hashSet2 = new HashSet<String>();
		hashSet2.add("广州");
		hashSet2.add("北京");
		hashSet2.add("深圳");
		
		HashSet<String> hashSet3 = new HashSet<String>();
		hashSet3.add("成都");
		hashSet3.add("上海");
		hashSet3.add("杭州");
		
		
		HashSet<String> hashSet4 = new HashSet<String>();
		hashSet4.add("上海");
		hashSet4.add("天津");
		
		HashSet<String> hashSet5 = new HashSet<String>();
		hashSet5.add("杭州");
		hashSet5.add("大连");
	
		//加入到map
		broadcasts.put("K1", hashSet1);
		broadcasts.put("K2", hashSet2);
		broadcasts.put("K3", hashSet3);
		broadcasts.put("K4", hashSet4);
		broadcasts.put("K5", hashSet5);
		
		
		HashSet<String> allAreas = new HashSet<String>();
		allAreas.addAll(hashSet1) ;   // 此方法自动去重
		allAreas.addAll(hashSet2) ;  
		allAreas.addAll(hashSet3) ;
		allAreas.addAll(hashSet4) ;
		allAreas.addAll(hashSet5) ;
		
		
		    //存放选择的电台集合
		   ArrayList<String> selects = new ArrayList<String>();
				
				//定义一个临时的集合, 在遍历的过程中,存放遍历过程中的电台覆盖的地区和当前还没有覆盖的地区的交集
		        // 比如 K1 电台所包含的地区  与 allAreas 的交集
		   
				HashSet<String> tempSet = new HashSet<String>();
				
				//定义给maxKey , 保存在一次遍历过程中,能够覆盖最大未覆盖的地区对应的电台的key
				//如果maxKey 不为null , 则会加入到 selects
				String maxKey = null;
				while(allAreas.size() != 0) { // 如果allAreas 不为0, 则表示还没有覆盖到所有的地区
					//每进行一次while,需要
					maxKey = null;
					
					//遍历 broadcasts, 取出对应key
					for(String key : broadcasts.keySet()) {
						//每进行一次for
						tempSet.clear();
						//当前这个key能够覆盖的地区
						HashSet<String> areas = broadcasts.get(key);
						tempSet.addAll(areas);
						//求出tempSet 和   allAreas 集合的交集, 交集会赋给 tempSet
						tempSet.retainAll(allAreas);
						//如果当前这个集合包含的未覆盖地区的数量,比maxKey指向的集合地区还多
						//就需要重置maxKey
						// tempSet.size() >broadcasts.get(maxKey).size()) 体现出贪心算法的特点,每次都选择最优的
						if(tempSet.size() > 0 && 
								(maxKey == null || tempSet.size() >broadcasts.get(maxKey).size())){
							maxKey = key;
						}
					}
					//maxKey != null, 就应该将maxKey 加入selects
					if(maxKey != null) {
						selects.add(maxKey);
						//将maxKey指向的广播电台覆盖的地区,从 allAreas 去掉
						allAreas.removeAll(broadcasts.get(maxKey));
					}
					
				}
				
				System.out.println("得到的选择结果是" + selects);//[K1,K2,K3,K5]
	}

}

讲解DeBug链接

分配问题

  • 有一群孩子和一堆饼干,每个孩子有一个饥饿度,每个饼干都有一个大小。每个孩子只能吃最多一个饼干,且只有饼干的大小大于孩子的饥饿度时,这个孩子才能吃饱。求解最多有多少孩子可以吃饱。🐤🐤

输入: 1 2
1 2 3
输出 : 2

输入输出说明

  • 第一行输入为孩子的饥饿度。第二行输入为饼干的分量

核心思路

  • 因为饥饿度最小的孩子最容易吃饱,所以我们先考虑这个孩子。为了尽量使得剩下的饼干可以满足饥饿度更大的孩子,所以我们应该把大于等于这个孩子饥饿度的、且大小最小的饼干给这个孩子。满足了这个孩子之后,我们采取同样的策略,考虑剩下孩子里饥饿度最小的孩子,直到没有满足条件的饼干存在。
  • 简而言之,这里的贪心策略是,给剩余孩子里最小饥饿度的孩子分配最小的能饱腹的饼干。
  • 至于具体实现,因为我们需要获得大小关系,一个便捷的方法就是把孩子和饼干分别排序。
  • 这样我们就可以从饥饿度最小的孩子和大小最小的饼干出发,计算有多少个对子可以满足条件。

Java 代码

package Algorithm;

import java.util.Arrays;
import java.util.Scanner;

/**
 * @author LZH.create
 *    贪心算法之分配饼干问题
  */
public class FindContentChildren {

	public static void main(String[] args) {
       	int [] children = {1,2} ;  //  分别表示孩子的饥饿度
       	int [] cookies = {1,2,3} ;
       	
		System.out.println(fun(children,cookies));
	}
	
	
	public static int fun(int [] children ,int [] cookies) {
		Arrays.sort(children);
		Arrays.sort(cookies);
		
		// 定义两个指针
		int child =  0 ;
		int cookie = 0 ;
		
		while(child < children.length && cookie < cookies.length) {
			if(children[child] < cookies[cookie++]) child++ ;
		}
		
		return child ;
	}
	
	

}

区间问题

  • 给定多个区间,计算让这些区间互不重叠所需要移除区间的最少个数。起止相连不算重叠。

  • 输入输出样例
    输入是一个数组,数组由多个长度固定为2的数组组成,表示区间的开始和结尾。输出一个整数,表示需要移除的区间数量。

Input:[[1,2],[2,4],[1,3]]
Output: 1

在这个样例中,我们可以移除区间[1,3] 便得猁示的区间 [ [1,2] [2,4] ]互不重叠。

LeetCode 435
跳转链接

核心思路

  • 在选择要保留区间时,区间的结尾十分重要:选择的区间结尾越小,余留给其它区间的空间就越大,就越能保留更多的区间。(这里体现贪心策略)
  • 因此,我们采取的贪心策略为,优先保留结尾小且不相交的区间。具体实现方法为先把区间按照结尾的大小进行增序排序,每次选择结尾最小且和前一个选择的区间不重叠的区间。
  • 在样例中,排序后的数组为[1,2],[1,3],[2,4].按照我们的贪心策略,首先初始化为区间[1,2] ,由于[1,3][1,2]相交,我们跳过该区间;由于[2,4][1,2]不相交,我们将其保留。因此最终保留的区间为[1,2][2,4]

Java 代码

package Algorithm;

import java.util.Arrays;
import java.util.Comparator;

public class eraseOverlapIntervals {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
        
		
		//  测试数据
		int [][] arr1 = {{1,2},{2,4},{1,3}} ;
		System.out.println(fun(arr1));
		
		int [][] arr2 = {{1,2},{1,2},{1,2}} ;
		System.out.println(fun(arr2));
		
		int [][] arr3 = {{1,2},{2,3}} ;
		System.out.println(fun(arr3));
	}
	
	
	public static int fun(int [][] arr) {
		
		
		if(arr.length == 0) {
			return 0 ;
		}
		
		Arrays.sort(arr ,new Comparator<int []>(){
			@Override 
			public int compare(int [] o1,int [] o2) {
				return o1[1]-o2[1] ;
			}
		});
		
		
		int count = 1 ;
		int pre = arr[0][1] ;
		
		for(int i =1 ; i < arr.length ;i++) {
			if(arr[i][0] >= pre) {
				count ++ ;
				pre = arr[i][1] ;
			}
		}
     return arr.length - count ;	
}
	
}

买股票的最佳时机

在这里插入图片描述

本题跳转链接

核心思路

  • 买股票有许多场景

  • 关于买股票的类似解法推荐下面大佬的总结 💡

参考链接

  • 首先暴力枚举思路 (时间复杂度肯定高)
动态规划思路

关于动态规划问题大家可以看看本蒟蒻之前写的介绍博文哦 📣

跳转链接
状态定义

dp[i][j]:下标为 i 这一天结束的时候,手上持股状态为 j 时,我们持有的现金数。

  • j = 0,表示当前不持股;
  • j = 1,表示当前持股。
  • 注意:这个状态具有前缀性质,下标为 i 的这一天的计算结果包含了区间 [0, i] 所有的信息,因此最后输出 dp[len - 1][0]

状态转移方程 (推导)

Case 1dp[i][0]:规定了今天不持股,有以下两种情况:

  • 昨天不持股,今天什么都不做;
  • 昨天持股,今天卖出股票(现金数增加),

Case 2dp[i][1]:规定了今天持股,有以下两种情况:

  • 昨天持股,今天什么都不做(现金数与昨天一样);
  • 昨天不持股,今天买入股票(注意:只允许交易一次,因此手上的现金数就是当天的股价的相反数)。

状态方程含义

  • dp[i][0] 下标为 i 这天结束的时候,不持股,手上拥有的现金数
  • dp[i][1] 下标为 i 这天结束的时候,持股,手上拥有的现金数
Java 代码
package Algorithm;

/**
 * @author LZH.create
 *  Date: 21.4.13
 */
public class BuyStocksProblem_2 {

	public static void main(String[] args) {

		int [] arr = {7,1,5,3,6,4} ;
		System.out.println(fun(arr));
	}
	
	public static int fun(int [] prices) {
		int len = prices.length ;
		if(len < 2) {
			return 0 ;
		}
		
		int res = 0 ;
		for(int i = 1 ; i < len ;i++) {
			int sub = prices[i] - prices[i-1] ;
			if(sub >0) {
				res += sub ;
			}
		}
		
		return res ;
	}

}


贪心算法思路

  • 贴个可以使用贪心算法解决的题目
  • (因为这里的买股票策略与上面不同可以多次交易)
  • 体现贪心的地方 : 由于不限制交易次数,只要今天股价比昨天高,就交易。

跳转链接

Java 代码
package Algorithm;

/**
 * @author LZH.create
 *  Date: 21.4.13
 */
public class BuyStocksProblem_2 {

	public static void main(String[] args) {

		int [] arr = {7,1,5,3,6,4} ;
		System.out.println(fun(arr));
	}
	
	public static int fun(int [] prices) {
		int len = prices.length ;
		if(len < 2) {
			return 0 ;
		}
		
		int res = 0 ;
		for(int i = 1 ; i < len ;i++) {
			int sub = prices[i] - prices[i-1] ;
			if(sub >0) {
				res += sub ;
			}
		}
		
		return res ;
	}

}

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值