贪心算法

贪心法:遵循某种规律,不断贪心的选取当前最优策略的算法设计方法。

钞票支付问题

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

如:x=628

最佳支付方法:
3张200块的,1张20块的,1张5块的,3张1块的;
共需要3+1+1+3=8张。
直觉告诉我们:尽可能多的使用面值较大的钞票!

为何这么做一定 是对的?
面额为1元、5元、10元、20元、100元、200元,任意面额是比自己小的面额的
倍数关系。
所以当使用一张较大面额钞票时,若用较小面额钞票替换,一定需要更多的其他面额的钞票!
如果增加面额7元,则贪心不成立.

#include <stdio.h>

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

int main(){
    const int RMB[]={200,100,20,10,5,1};
    const int num=6;
    int x=628;
    int count=0,i,use=0;
    for(i=0;i<6;i++){
        use=x/RMB[i];
        count+=use;
        x=x-RMB[i]*use;
        printf("需面额为%d的%d张,剩余%d元\n",RMB[i],use,x);                 
    }
    printf("共需%d张\n",count);
    getchar();
    
}

在这里插入图片描述

分糖果(455)

LeetCode455题

已知一-些孩子和一-些糖果,每个孩子有需求因子g,每个糖果有大小s,当某个糖果的大小s>=某个孩子的需求因子g时,代表该糖果可以满足该孩子;求使用这些糖果,最多能满足多少孩子? (注意,某个孩子最多只能用1个糖果满足)
例如,需求因子数组g= [5, 10,2, 9, 15,9];糖果大小数组s=[6, 1, 20, 3, 8];最多
可以满足3个孩子。

例如,需求因子数组g=[5, 10,2,9, 15, 9];糖果大小数组s=[6, 1,20,3, 8]。.

为了更明显的判断某个孩子可以被某个糖果满足,对g, s进行排序后观察:
g=[2,5,9,9, 10, 15]; s=[1,3, 6, 8, 20]。
1.当某个孩子可以被多个糖果满足时,是否需要优先用某个糖果满足这个孩子?
2.当某个糖果可以满足多个孩子时,是否需要优先满足某个孩子?

分析

需求因子
数组g=[2,5,9,9, 10, 15];
糖果大小数组s=[1,3, 6,8, 20]。
核心目标:
让更多孩子得到满足,有如下规律:
1.某个糖果如果不能满足某个孩子,则该糖果也一定 不能满足需求因子更大的孩子。
如,
糖果1(s= 1)不能满足孩子1(g=2),则不能满足孩子2、孩子3、…孩子7;
糖果2(s= 3)不能满足孩子2(g= 5),则不能满足孩子3、孩子4、…孩子7;
2.某个孩子可以用更小的糖果满足,则没必要用更大糖果满足,因为可以保笛更大的糖果
满足需求因子更大的孩子。(贪心!)
如,
孩子1(g=2),可以被糖果2(s= 3)满足,则没必要用糖果3、糖果4、糖果5满足;
孩子2(g=5),可以被糖果3(s = 6)满足,则没必要用糖果4、糖果5满足;
3.孩子的需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试,可以得到正
确的结果。

算法思路

1.对需求因子数组g与糖果大小数组s进行从小到大
的排序。
2.按照从小到大的顺序使用各糖果尝试是否可满足某个孩子,每个糖果只尝试1
次;若尝试成功,则换下一个孩子尝试;直到发现没更多的孩子或者没更多的糖果,循环结束。

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        std::sort(g.begin(),g.end());
        std::sort(s.begin(),s.end());
        int child=0,cookie=0;
        while(child<g.size()&&cookie<s.size()){
             if(g[child]<=s[cookie]){
                  child++;
             }
             cookie++;
        }
        return child;
    }
};

摇摆序列(力扣376)

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例 1:

输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
示例 2:

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:

输入: [1,2,3,4,5,6,7,8,9]
输出: 2

思路

使用状态机记录上升,下降或开始状态.

在这里插入图片描述
如果出现联系上升或下降,需要删除一下元素以保持摆动,关键问题在于删除哪些,很明显,如果上升保留最后那个,因为最后的最大更容易与后面元素形成摆动,同理如果下降也保留最后的.

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if(nums.length<2)   return nums.length;
        State state=State.start;
        int len=1;
        for(int i=1;i<nums.length;i++){
            switch(state){
                case start:
                    if(nums[i-1]>nums[i]){
                        len++;
                        state=State.down;
                    } else if(nums[i-1]<nums[i]){
                        len++;
                        state=State.up;
                    }
                    break;
                case up:
                    if(nums[i-1]>nums[i]){
                        len++;
                        state=State.down;
                    }
                    break;
                case down:
                    if(nums[i-1]<nums[i]){
                        len++;
                        state=State.up;
                    }
                    break;
            }
        }

        return len;
    }
}

enum State{
    up,down,start,
}

移除k个数字(402)

给定一个以字符串表示的非负整数 num,移除这个数中的 k 位数字,使得剩下的数字最小。

注意:

num 的长度小于 10002 且 ≥ k。
num 不会包含任何前导零。
示例 1 :

输入: num = “1432219”, k = 3
输出: “1219”
解释: 移除掉三个数字 4, 3, 和 2 形成一个新的最小的数字 1219。
示例 2 :

输入: num = “10200”, k = 1
输出: “200”
解释: 移掉首位的 1 剩下的数字为 200. 注意输出不能有任何前导零。
示例 3 :

输入: num = “10”, k = 2
输出: “0”
解释: 从原数字移除所有的数字,剩余为空就是0。

分析

在这里插入图片描述

若去掉某- -位数字,为了使得到的新数字最小,需要尽可能让得到的新数字优先
最高位最小,其次次高位最小,再其次第3位最小…
例如:
一个4位数“1***”,一定比任何9***”.“
8***.、…“2****
小!
一个4位数若最高位确定,如“51**>
一定比任何“ 59**”、“58**. … 、“52**小!
一个4位数若最高、次高位确定,如“531*-定比任何“539*”、“538*"、 …
“532*小!

从高位向低位遍历,如果对应的数字大十下一位.数字,则把该位数字去掉,得到的数字最小!
但是如果暴力遍历复杂度较高,可以使用栈.
使用栈存储最终结果或删除工作,从高位向低位遍历num,如果遍历的数字大于栈顶元素,则将该数字push入栈,如果小于栈顶元素则进行pop弹栈,直到栈为空或不能再删除数
字(k==0)或栈顶小于当前元素为止。

还需考虑遍历完后k不为0,如num=12834,k=2,遍历完后num=1234,k=1,此时应从栈顶删除,结果为123;还有一种情况是串中含有0.

class Solution {
public:
    string removeKdigits(string num, int k) {
        std::vector<int> S; //使用vector当作占(因为vector可以遍历)
        std::string result =""; //存储最终结果的字符串
        for (int i = 0; i < num.length(); i++) { //从最高位循环扫描数字num
            int number = num[i] -'0'; //将字符型的num转化为整数使用
            while(S.size() != 0 &&S[S.size()-1] > number&&k>0){
                S.pop_back();
                k--;
            }
            if(number!=0||S.size()!=0) {
                S.push_back(number) ; //将数字number压入栈中
            }
        }
        while(S.size() != 0 && k > 0){ //如果栈不空,且仍然可以删除数字
            S.pop_back();
            k--; //删除数字后更新K
        }
        //将栈中的元素从头遍历,存储至result
        for(int i=0;i<S.size();i++){
            result.append(1, '0' + S[i]);
        }
        if (result =="") {
            //如果result为空, result即为0
            result = "0";
        }
        return result ;

    }
};

跳跃游戏(55)

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。

示例 1:

输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:

输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

用一个值来记录能跳到的最远位置,从第一个开始遍历寻找能跳到的最远位置,途中如果i>reach表示没法达到i,则返回false.
时间复杂度为O(n)

class Solution {
    public boolean canJump(int[] nums) {
        int reach=0;
        for(int i=0;i<nums.length&&i<=reach;i++){
            reach=Math.max(i+nums[i], reach);
        }
        return reach>=nums.length-1;
    }
}

跳跃游戏2(45)

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

分析:
在到达某点前若一直不跳跃,发现从该点不能跳到更远的地方了,在这
之前肯定有次必要的跳跃!
结论:在无法到达更远的地方时,在这之前应该跳到- -个可以到达更远位置的位置!

算法思路:
1.设置Current. max. index为 当前可达到的最远位置;
2.设置pre. max
max
index为在遍历各个位置的过程中,各个位置可达到的最远位置;
3.设置jump_ min为 最少跳跃的次数。
4.利用i遍历nums数组,若i超过current_ max_ index, jump_ min加1,current max index =pre_ max_ max_ index
5.遍历过程中,若nums[i]+i (index[i])更大,则更新pre_ max_ max index = nums[i] + i.

	public int jump(int[] nums) {
		if(nums.length<2)
			return 0;
		int cur_max=nums[0],pre_max=nums[0],jump=1;
		for (int i = 1; i < nums.length; i++) {
			if(i>cur_max) {
				jump++;
				cur_max=pre_max;
			}
			if(pre_max<i+nums[i])
				pre_max=i+nums[i];
		}
		return jump;
    }

射击气球(452)

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以y坐标并不重要,因此只要知道开始和结束的x坐标就足够了。开始坐标总是小于结束坐标。平面内最多存在104个气球。

一支弓箭可以沿着x轴从不同点完全垂直地射出。在坐标x处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。

Example:

输入:
[[10,16], [2,8], [1,6], [7,12]]

输出:
2

解释:
对于该样例,我们可以在x = 6(射爆[2,8],[1,6]两个气球)和 x = 11(射爆另外两个气球)。

分析:
1.对于某个气球,至少需要使用1只亏箭将它击穿。
2.在这只气球将其击穿的同时, 1
尽可能击穿其他更多的气球! (贪心!)

思路:
1.对各个气球进行排序,按照气球的左端点从小到大排序。
2.遍历气球数组,同时维护- -个射击区间,在满足可以将当前气球射穿的
情况下,尽可能击穿更多的气球,每击穿- -个新的气球,更新- -次射
击区间(保证射击区间可以将新气球也击穿)。
3.如果新的气球没办法被击穿了,则需要增加-名弓箭手,即维护- -个新的射击区间(将该气球击穿),随后继续遍历气球数组。

在这里插入图片描述
在这里插入图片描述

	public int findMinArrowShots(int[][] points) {
		if (points.length == 0) return 0;

	    // sort by x_end
	    Arrays.sort(points, new Comparator<int[]>() {
	      @Override
	      public int compare(int[] o1, int[] o2) {
	        return o1[1] - o2[1];
	      }
	    });

	    int arrows = 1;
	    int xStart, xEnd, firstEnd = points[0][1];
	    for (int[] p : points) {
	      xStart = p[0];
	      xEnd = p[1];
	      if (firstEnd < xStart) {
	        arrows++;
	        firstEnd = xEnd;
	      }
	    }

	    return arrows;
    }

最优加油方法

已知一条公路上,有一个起点与-个终点,这之间有n个加油站;已知从这n个加
油站到终点的距离d与各个加油站可以加油的量l,起点位置至终点的距离L与起
始时刻油箱中汽油量P;假设使用1个单位的汽油即走1个单位的距离,油箱没有
上限,最少加几次油,可以从起点开至终点?(如果无法到达终点,返回-1)

思考:
汽车经过n个加油站,对于这n个加油站,应该在哪个加油站停1、来加油,
最终既能到达终点,又使得加油次数最少?
若按顺序遍历加油站,则面临:
如果在某个加油站停下来加油,可能是没必要的,有可能不进行这次加
油也能到达终点;
如果在某个加油站不停下来加油,可能由于汽油不够而无法到达终点
或者后面要更多次的加油才能到达终点。

何时加油最合适?
油用光的时候加油最合适!
在哪个加油站加油最合适? .
在油量最多的加油站加油最合适!

思路:
1.设置一个最大堆,用来存储经过的加油站的汽油量。
2.按照从起点至终点的方向,遍历各个加油站之间的距离。
3.每次需要走两个加油站之间的距离d,如果发现汽油不够走距离d时,从最大堆中取出- -个油量添加,直到可以足够走距离d。
4.如果把最大堆的汽油都添加仍然不够行进距离d,则无法达到终点。
5.当前油量P减少d。
6.将当前加油站油量添加至最大堆。

在这里插入图片描述

#include <vector>
#include <algorithm>
#include <queue>

bool cmp(const std::pair<int, int> &a,const std::pair<int ,int> &b) {
	return a.first > b.first;
}
int get_minimum_stop(int L,int p, //L为起点到终点的距离, P为起点初始的汽油量
std::vector<std::pair<int, int> > &stop) {
// pair<加油站至终点的距离,加油站汽油量
	std::priority queue<int> Q; //存储油量的最大堆
	int result = 0; //记录加过几次油的变量
	stop.push_back(std::make_ pair(00));
	//将重点作为-一个停靠点,添加至stop数组
	std::sort(stop.begin(), stop.end(), cmp); //以停靠点至终点距离从大到小进行排序
	for (int i = 0; i < stop.size(); i++){ //遍历各个停靠点
		int dis = L - stop[i] . first;
	//当前要走的距离即为当前距终点距离
		while(!Q.empty() &&P < dis ) {
			P += Q.top();
			Q.pop() ;
			result++;
		}

		if (Q.empty() &&P < dis){
			return -1;
		}
		P= P-dis;
		L = stop[i].first;
		Q.push(stop[i].second); //更新L为当前停靠点至终点距离
	}
	return result;
}

活动安排问题

设有n个活动的集合E={1,2.,…n} ,其中,每个活动都要求使用同一资源,如演讲会场等,而在同一时间内只有一个活动能使用这一资源。
每个活动都有一个要求使用该资源的起始时间s和一个结束时间f, 且s<f。
如果选择了活动i ,则它在半开区间[S, f)内占用资源。
若区间[s, f)与区间[s, f)不相交,则称活动i与活动j是相容的。
活动安排问题:在所给的活动集合中选出最大的相容活动子集。

设计思路

该问题要求高效地安排一系列争用某一 公共资源的活动
如果一个活动能尽早结束那就能留更多的时间给别的活动,所以尽可能地选择结束早的.
那我们就可以先对活动结束时间升序排序,再检测是否相容.

步骤:

按结束时间对活动进行升序排列
选择一个结束时间最 早的活动
依次检查后续活动是否与当前已选择的所有活动相容,若相容则将该活
动加入已选择活动集合中,再继续检查下一活动, 否则直接检查下一活

直到所有活动全部检查完毕

例如:
i为活动,s[i]为开始时间,f[i]为结束时间.
在这里插入图片描述
若被检查的活动的开始时间s;小于最近选择的活动j的结束时间f,则不选择
活动i,否则选择活动i加入已选活动集合中

首先选择最早结束的活动1,s[2]<f[1],所以不选2,s[3]<f[1],也不选,s[4]>=f[1],所以选4,以此类推得到最大相容活动子集{1,4,8,11}.

public class Greedy_Activaty {
	
	public static void main(String[] args) {
		Scanner sc=new Scanner(System.in);
		int n=sc.nextInt();
		Activaty[] activaties=new Activaty[n];
		for (int i = 0; i < n; i++) {
			activaties[i]=new Activaty(sc.nextInt(), sc.nextInt(), sc.nextInt());
		}
		Deque<Activaty> reult=getResult(activaties);
		for(Activaty t:reult) {
			System.out.println(t.id);
		}
	}
	
	public static Deque<Activaty> getResult(Activaty[] activaties) {
		Arrays.sort(activaties);
		Deque<Activaty> result=new LinkedList<Activaty>();
		result.add(activaties[0]);
		for(int i=1;i<activaties.length;i++) {
			Activaty t=activaties[i],pre=result.getLast();
			if (t.start>=pre.end) {
				result.add(t);
			}
		}
		return result;
	}
}

class Activaty implements Comparable<Activaty>{
	int id;//活动编号
	int start;
	int end;//开始和结束时间
	@Override
	public int compareTo(Activaty o) {//对结束时间升序排序
		return this.end-o.end;
	}
	
	public Activaty(int id,int start,int end) {
		this.id=id;
		this.start=start;
		this.end=end;
	}
}

在这里插入图片描述

部分背包问题

有n个物品,他们的重量分别为w[],价值为v[],现有容量为c的背包,这些物品允许部分装入,求背包能装入的最大价值.

部分背包问题是背包问题中最简单的了,直接使用贪心策略,尽量选择性价比最大的物品装入背包.对于性价比大的如果能整个装入那就整个装入,不能就装入一部分.

public class SomePackage {

	public static float some_package(int[] w,int[] v,int n,int c){
		Thing[] things=new Thing[n];
		float result=0;
		for (int i = 0; i < n; i++) {
			things[i]=new Thing(w[i], v[i]);
		}
		Arrays.sort(things);
		for(Thing t:things) {
			if(t.w<=c) {
				result+=t.v;
				c-=t.w;
			}else {
				result+=(float)c/t.w*t.v;
				c=0;
				break;
			}
				
		}
		return result;
	}
}

class Thing implements Comparable<Thing>{
	int w;
	int v;
	float v_w;
	public Thing(int w,int v){
		this.w=w;
		this.v=v;
		v_w=(float)v/w;
	}
	@Override
	public int compareTo(Thing o) {//从大到小排序的比较
		// TODO Auto-generated method stub
		if (o.v_w-this.v_w>0) {
			return 1;
		}else if (o.v_w-this.v_w<0) {
			return -1;
		}else{
			return 0;
		}
	}
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值