贪心算法小结

1.最佳支付方法

有1元、5元、10元、20元、100元、200元的钞票无穷多张。现使用这些钞票支付X元,最少需要多少张。

例如,X=628,最佳支付方法:3张200的,1张20的,3张1块的,共需要3+1+1+3=8张。

这里给出的钱金额:1,5,10,20,100是成倍数关系的,所以当使用一张较大面额的钞票时,若用较小面额钞票替换,一定需要更多的其他面额的钞票,所以尽可能使用面额较大的钞票,所以可以用贪心算法

代码实现:

public class TanXin {
	public static void main(String[] args)
	{
		int[] a = {200,100,20,10,5,1};
		int X = 628;
		int count = 0;    
		for(int i=0;i<a.length;i++){
			if(a[i]<=X){
				count+=X/a[i];
				X = X%a[i];
			}
		}
		System.out.println("count: "+count);
	}
}

说明:这里能使用贪心的条件是,给出的面额成倍数,如果给出的面额不是成倍数,就不一定能使用贪心算法,举例如果给出钱金额:1,10,求组合达到金额为14的最少组合方法,这时候使用贪心算法就无能为力了,因为14=10+1+1+1+1,e而显然最优解是14=7+7。

2.分糖果问题

已知一些小孩和一些糖果,每个孩子有需求因子g,每个糖果有大小s,当某个糖果的大小s>=某个孩子的需求因子g时,代表该糖果可以满足该孩子,求使用这些糖果,最多能满足多少孩子?(注意某个孩子只能用一个糖果满足)

例如,需求因子数组g=[5,10,2,9,15,9];糖果大小数组s=[6,1,20,3,8],最多可以满足3个孩子。

思路:典型的贪心思想,最大的糖果满足需求因子最大的学生,次大的糖果满足次大需求的学生。先将需求因子数组和糖果大小数组都排序,得到s=[1,3,6,8,20],需求因子g=[2,5,9,910,15],如果最小的糖果能满足最小的需求因子,就将最小的糖果给最小的需求因子,例中的1无法满足需求因子2,所以试着将次小的糖果给最小的需求因子,仍然无法满足,然后将第三小的糖果给最小的需求因子,此时可以满足,然后将第四小的糖果给第二小的需求因子,依此下去。

代码实现:

class TanXin
{
	public static void main(String[] args)
	{
		int[] g = {5,10,2,9,15,9};
		int[] s = {6,1,20,3,8};
		Arrays.sort(g);
		Arrays.sort(s);
		int i = 0;
		int j = 0;
		int count = 0;
		while(i<g.length&&j<s.length)
		{
			if(s[j]>=g[i])
			{
				j++;
				i++;
				count++;
			}
			else
			{
				j++;
			}
		}
		System.out.println("满足的小朋友个数: "+count);
	}
}

3.摇摆序列

一个整数序列,如果两个相邻元素的查恰好正负(负正)交替出现,则该序列被称为摇摆序列。

例如:序列[1,7,4,9,2,5],相邻元素的差(6,-3,5,-7,3),该序列为摇摆序列。

理解思路,写并不好写,这里使用状态机做,但是基本思想是简单的,如果一组数字是递增的,那么就选取这组数字中最大的一个,如果一组数字是单调递减的,那么就选取这组数字中最小的那个,这里体现了贪心的思想

4.移除k个数字

已知一个字符串表示的非负整数num,num中的k个数字移除,求移除k个数字后,可以获得的最小的可能的新数字。(num不会以0开头,num长度小于10002)。

举例:输入num="1432219",k=3,去掉3个数字后,得到的最小的数字是1219。

思考:让得到的数字最小,需要尽可能让得到的新数字优先最高位最小,其次次高位最小,再一次高位最小。例如输入num是1432219,此时可以使用栈存储最终的结果或删除工作,从高位向低位遍历num,如果遍历的数字大于栈顶元素,则将该数字push入栈,如果小于栈顶元素则进行pop弹栈,直到栈为空,或者不能再删除数字(k==0)或栈顶小于当前元素为止

第一种特殊情况:一种特殊情况是在栈为空的时候,进去的为0,例如数字是num=100200,此时先取出1,1进入栈,但是第二个0的时候,此时1出栈,但0不能进栈,因为此时栈内为空。

第二种特殊情况:数字是单调递增的时候,例如12345678,此时在入栈完成后,检查栈的大小,如果栈的size()大于原先的字符串中的数字的长度减去2,此时要做处理,直接删除最后的两个数字。

代码实现:

class TanXin
{
	public static void main(String[] args)
	{
		//测试用例
		//String num = "1432219";
		//String num = "12345678";		
		//String num = "1002345";
		//String num = "100200";
		String num = "123411";
		int k = 2;
		Stack<Integer> al = new Stack<>();
		for(int i=0;i<num.length();i++)
		{
			int number = num.charAt(i) - '0';
			while(al.size()!=0&&al.peek()>number&&k>0)	//如果进入一个小于栈顶数的数字
			{
				al.pop();
				k--;
			}
			if(number!=0||al.size()!=0)	//栈为空的时候不能进去值为0
			{
				al.push(number);
			}
		}
		//如果是顺序的那么把最后的k位数字删除
		while(al.size()>0&&k>0)
		{
			al.pop();
			k--;
		}
		//打印最终结果,因为栈实现了Iterator接口,所以可以使用for each语句
		System.out.println(al.toString());
		for(int c:al)
		{
			{
				System.out.print(c+" ");

			}
		}
	}

说明:最终的栈的输出要从栈底输出,因为栈实现了Iterator接口,所以这里用for each语句最为方便,否则从栈顶出栈后,还要颠倒一次顺序。

代码实现:

第二种特殊情况:进入一个单调递增的数,此时

5.跳跃游戏

一个数组存储了非负整型数据,数组中第i个元素nums[i],代表了可以从数组第i个位置最多向前跳跃nums[i]步,已知数组各元素的情况下,求是否可以从数组第0个位置跳跃到数组的最后一个元素的位置。

跳跃游戏有两种问题:一种问能不能从第0个位置跳跃到数组的最后一个元素的位置,另一个中是从第0个位置跳跃到数组最后一个位置,最少需要跳跃多少步。

思路:例如:nums = [2,3,1,1,4],从第0个位置(2)可以跳至第一个位置(3)和第二个位置(1),从第一个位置(3)又可以跳至第二(1)、第三(1)、第四个位置(4),从第二个位置(1)又可以跳至第三个位置,所以从第0个位置是跳到第一个位置还是跳到第二个位置呢?此时选择跳到该位置后能够继续跳到的更远的位置。例如跳到第一个位置后最远能跳到第4个位置,而跳到第二个位置后最远能跳到第三个位置,所以我们就选择从第0个位置,跳到第1个位置,这也是典型的一种贪心思想。

如果不能到达的情况,例如nums=[3,2,1,0,4],此时不能达到,那么这该怎么判断呢?可以继续使用上面的办法,这时会出现在第3个位置(值为0)的地方停止,此时发现走一步后在原地,且该位置不是最终位置,说明停滞在该位置,说明无法到达最后位置了,代码描述:nums[i]+i==i&&i!=nums.length-1。

class TanXin
{
	public static void main(String[] args)
	{
		//int[] nums = {2,3,1,1,4};
		int[] nums = {3,2,1,0,4};
		//int[] nums = {3,2,1,0};
		int count = 0;
		int i = 0;
		boolean  flag = true;
		while(count<nums.length&&(i+nums[i]<=nums.length-1)){	//防止到达不到停死在那,和已经走过两种情况
			if(nums[i]+i==i&&i!=nums.length-1){			//如果停滞在那边,就说明无法到达
				flag = false;
				break;
			}
			if(nums[i]+i==nums.length-1){		         //如果下一步能到达,那么加一后退出
				count++;
				break;
			}
			int k = i + nums[i];
			int maxReach=0;
			int j;
			int save = 0;					 //这一个保存变量很重要,记录上次到达的位置
			for(j=i+1;j<=k;j++){
				if(maxReach<=(nums[j]+j))                //这边的等于号必须加,{3,2,1,0,4}
				{
					System.out.println("maxReach: "+maxReach+"nums[j]+j: "+(nums[j]+j));
					save = j;
					maxReach = nums[j]+j;
				}
			}
			i = save;
			System.out.println("i: "+i);
			count++;
		}
		
		System.out.println("count: "+count);
		if(flag==false){
			System.out.println("无法到达");
		}
		else
		System.out.println("经过"+count+"步后到达");
	}
}

6.射气球问题

已知在一个平面上有一定数量的气球,平面可以看作是一个坐标系,在平面的x轴的不同位置安排弓箭手向y轴方向射箭,弓箭可以向y轴走无穷远,给定气球的宽度xstart<=x<=xend,问至少需要多少弓箭手,将全部气球 打爆?

例如:四个气球:[[10,16],[2,8],[1,6],[7,12]],至少需要2个弓箭手。

分析:对于某个气球,至少需要使用1只弓箭将它击穿,在这只气球将其击穿的同时,尽可能击穿其他更多的气球。(这里体现了贪心的思想)。做法:使用一个类Node代表一个气球,类中的first代表的气球的左坐标,类中的second代表气球的右坐标,对各个气球(即Node类)进行排序,按照气球的左端点从小到大排序(即按照Node类的first属性排序),遍历气球数组,同时维护一个射击区间(通过两个变量[shoot_begin,shoot_end]),在满足就将当前气球射穿的情况下,尽可能击穿更多的气球,每击穿一个新的气球,更新一次射击区间。具体的更新方法:如果下一个first属性大于上一个的first属性,那么shoot_beign更新,如果下一个second属性下雨上一个的second属性,那么更新。

代码实现:

class Node implements Comparable<Node>
{
	int first;
	int second;
	public Node(int first,int second)
	{
		this.first = first;
		this.second = second;
	}

	@Override
	public int compareTo(Node o) {
		// TODO Auto-generated method stub
		return first-o.first;
	}
}
class TanXin
{
	public static void main(String[] args)
	{
		Node n1 = new Node(10,16);
		Node n2 = new Node(2,8);
		Node n3 = new Node(1,6);
		Node n4 = new Node(7,12);
		ArrayList<Node> al = new ArrayList<>();
		al.add(n1);
		al.add(n2);
		al.add(n3);
		al.add(n4);
		Collections.sort(al);		//排序,等会总结一下排序
//		for(Node n:al)
//		{
//			System.out.println(n.first+" "+n.second);
//		}
		int shoot_num = 1;
		int shoot_begin = n1.first;
		int shoot_end = n1.second;
		for(int i=1;i<al.size();i++)
		{
			if(al.get(i).first<=shoot_end)
			{
				shoot_begin = al.get(i).first;        //因为已经排序过了,所以直接赋值
				if(shoot_end>al.get(i).second)        //跟新设计区间的右坐标
				{
					shoot_end = al.get(i).second;
				}
			}
			else
			{
				shoot_num++;                    
				shoot_begin = al.get(i).first;    //更新射击区间
				shoot_end = al.get(i).second;
			}
		}
		System.out.println("shootNum: "+shoot_num);
	}
}

猜你喜欢

转载自blog.csdn.net/chenkaibsw/article/details/80214911