3.算法之贪心算法

举个例子:有1元、5元、20元、100元、200元的钞票无穷多张。现使用这些钞票支付X元,最少需要多少张?
直觉告诉我们:尽可能的使用面值较大的钞票!
当前最优解即为全局最优解时,贪心成立!!

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

455. 分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值 gi ,这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸 sj 。如果 sj >= gi ,我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
注意:
你可以假设胃口值为正。
一个小朋友最多只能拥有一块饼干。
示例 1:
输入: [1,2,3], [1,1]
输出: 1
解释:
你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。
虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。
所以你应该输出1。
示例 2:
输入: [1,2], [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

思考:
1.对需求因子和糖果大小排序后观察,当某个孩子可以被多个糖果满足时,是否需要优先用某个糖果满足这个孩子?
2.当某个糖果可以满足多个孩子时,是否优先满足某个孩子?

**核心目标:**让更多孩子得到满足!

贪心规律:
1.某个糖果不能满足某个孩子,那么该糖果一定不能满足需求因子更大的孩子。
2.某个孩子可以用更小的糖果满足,没必要用更大的糖果满足,因为可以保留更大的糖果满足需求因子更大的孩子。(贪心!)
3.孩子的需需求因子更小则其更容易被满足,故优先从需求因子小的孩子尝试,可以得到正确的结果。

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());//胃口
        sort(s.begin(),s.end());//尺寸
        int child=0;
        int cookie=0;
        while(cookie<s.size()&&child<g.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
进阶:
你能否用 O(n) 时间复杂度完成此题?

解贪心算法的一个好方法是举例子。
在这里插入图片描述在这里插入图片描述自动机转换:
在这里插入图片描述

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
       if(nums.size()<2)//序列个数小于2直接为摇摆序列
           return nums.size();
        //状态机定义
        static const int BEGIN=0;
        static const int UP=1;
        static const int DOWN=2;
        int STATE=BEGIN;
        int max_len=1;//摇摆序列最大长度至少为1
        int nums_sz=nums.size();
        for(int i=1;i<nums_sz;i++)
        {
            switch(STATE)
            {
                case BEGIN:
                    if(nums[i]>nums[i-1]){
                        STATE=UP;
                        max_len++;
                    }
                    else if(nums[i]<nums[i-1]){
                        STATE=DOWN;
                        max_len++;
                    }
                    break;
                case UP:
                    if(nums[i]<nums[i-1]){
                        STATE=DOWN;
                        max_len++;
                    }
                    break;
                case DOWN:
                     if(nums[i]>nums[i-1]){
                        STATE=UP;
                        max_len++;
                    }
                    break;     
            }
        }
        return max_len;
    }
};

402. 移掉K位数字

给定一个以字符串表示的非负整数 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。
 1.当所有数字都扫描完成后,k仍然>0,应该做怎样的处理?

2.当数字中有0出现时,应该有怎样的特殊处理?
例如num=100200,k=1时。
3.如何将最后结果存储为字符串并且返回?

class Solution {
public:
    string removeKdigits(string num, int k) {
        vector<int> S;//把vector当作栈,因为可以遍历
        string res="";//保存最终结果的字符串
        int num_len=num.length(); 
        for(int i=0;i<num_len;i++)
        {
            int number=num[i]-'0';
            while(S.size()!=0&&number<S[S.size()-1]&&k>0)
            {
                S.pop_back();
                k--;
            }
            if(S.size()!=0||number!=0)
                S.push_back(number);
        } 
        while(S.size()!=0&&k>0)//剩下的序列都是递增的
        {
                S.pop_back();
                k--;
        }
        for(int i=0;i<S.size();i++)
        {
           res.append(1,'0'+S[i]);
        }
        
        if(res=="")
            res="0";
        
        return res;
    }
};

55. 跳跃游戏

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 从位置 0 到 1 跳 1 步, 然后跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

我的思路:
从最后一个位置开始遍历,出现0之后,设该位置为i,判断是否step[i-1]>1,step[i-2]>2,…直到出现另外一个step[i-m]=0,重新开始新一轮判断,若之前一轮所有判断都不成立,那么直接得出结论,不能,若都成立继续判断,直到第一个位置。
另外一个思路:
在这里插入图片描述在这里插入图片描述

class Solution {
public:
    bool canJump(vector<int>& nums) {
        vector<int> index;
        for(int i=0;i<nums.size();i++)
        {
            index.push_back(nums[i]+i);
        }
        int jump=0;
        int max_index=index[0];
        
        while(jump<nums.size()&&jump<=max_index)
        {
            max_index=max(max_index,index[jump]);
            jump++;
        }
        if(jump==nums.size())
            return true;
        return false;
    }
};

45. 跳跃游戏 II

给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

 思考:
 何时跳跃才是最合适的?
 贪心规律:在到达某点之前若一直不能跳跃,发现从该点不能跳到更远的地方了,在这之前肯定有次必要的跳跃!
 结论:在无法到达更远的地方时,在这之前应该跳到一个可以到达更远位置的位置!

在这里插入图片描述

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size()<2)
            return 0;
        int current_max_index=nums[0];//当前可达到的最远位置
        int pre_max_max_index=nums[0];//遍历各个位置过程中,可达到的最远位置
        int jump_min=1;
        for(int i=1;i<nums.size();i++)
        {
            if(i>current_max_index)//若无法向前移动了,才进行跳跃(即超出当前位置可达的最远位置)
            {
                jump_min++;
                current_max_index=pre_max_max_index;
            }
            if(pre_max_max_index<nums[i]+i)
            {
                pre_max_max_index=nums[i]+i;
            }
        }
        return jump_min;
    }
};

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.在击穿这个气球的同时,尽可能将其他气球击穿。(贪心)
在这里插入图片描述

bool cmp(const pair<int,int>&a,const pair<int,int>&b)
{
      return a.first<b.first;
}
    
class Solution {
public:
    
    int findMinArrowShots(vector<pair<int, int>>& points) {
        if(points.size()==0)
            return 0;
        
        sort(points.begin(),points.end(),cmp);//对左端点进行排序
        
        int shoot_num=1;//弓箭手的数量
        int shoot_begin=points[0].first;
        int shoot_end=points[0].second;
        
        for(int i=1;i<points.size();++i)
        {
            if(points[i].first<=shoot_end)//气球的开始点仍在区间[begin,end]内
            {
                shoot_begin=points[i].first;
                if(shoot_end>points[i].second)
                {
                    shoot_end=points[i].second;
                }
            }else{
                shoot_num++;
                shoot_begin=points[i].first;
                shoot_end=points[i].second;
            }
        }
        return shoot_num;
    }
};

871. 最低加油次数

汽车从起点出发驶向目的地,该目的地位于出发位置东面 target 英里处。
沿途有加油站,每个 station[i] 代表一个加油站,它位于出发位置东面 station[i][0] 英里处,并且有 station[i][1] 升汽油。
假设汽车油箱的容量是无限的,其中最初有 startFuel 升燃料。它每行驶 1 英里就会用掉 1 升汽油。
当汽车到达加油站时,它可能停下来加油,将所有汽油从加油站转移到汽车中。
为了到达目的地,汽车所必要的最低加油次数是多少?如果无法到达目的地,则返回 -1 。
注意:如果汽车到达加油站时剩余燃料为 0,它仍然可以在那里加油。如果汽车到达目的地时剩余燃料为 0,仍然认为它已经到达目的地。
示例 1:
输入:target = 1, startFuel = 1, stations = []
输出:0
解释:我们可以在不加油的情况下到达目的地。
示例 2:
输入:target = 100, startFuel = 1, stations = [[10,100]]
输出:-1
解释:我们无法抵达目的地,甚至无法到达第一个加油站。
示例 3:
输入:target = 100, startFuel = 10, stations = [[10,60],[20,30],[30,30],[60,40]]
输出:2
解释:
我们出发时有 10 升燃料。
我们开车来到距起点 10 英里处的加油站,消耗 10 升燃料。将汽油从 0 升加到 60 升。
然后,我们从 10 英里处的加油站开到 60 英里处的加油站(消耗 50 升燃料),
并将汽油从 10 升加到 50 升。然后我们开车抵达目的地。
我们沿途在1两个加油站停靠,所以返回 2 。
在这里插入图片描述贪心规律:
何时加油最合适?
油用光的时候加油最合适!
在哪个加油站加油最合适?
在油量最多的加油站加油最合适!最大堆!
先尝试,把油用光,若不能到达下一个加油站,则从之前的没有加油的加油站给自己加油。
在这里插入图片描述
在这里插入图片描述在这里插入图片描述

class Solution {
public:
    int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
    int res=0;//记录加过几次油
    if(stations.size()<=0)
    {
        return target>startFuel?-1:0; 
    }
	priority_queue<int> Q;//存储油量的最大堆
	
	//stations.push_back(vector<int>(2,0));//将终点作为一个停靠点
	vector<int> end = { 0,0 };
	stations.push_back(end);
	sort(stations.begin(), stations.end(), std::less<std::vector<int>>());//将停靠点至终点距离从大到小排序

	vector<int> dist;
	dist.push_back(0);
	for (int i = 1; i < stations.size(); i++)
	{
		dist.push_back(stations[i][0] - stations[i-1][0]);
	}
	for (int i = 0; i < stations.size(); i++)
	{
		//int dist=target-stations[i][0];
		while (!Q.empty() && startFuel < dist[i])
		{
			startFuel += Q.top();
			Q.pop();
			res++;
		}
		if (Q.empty() && startFuel < dist[i])
			return -1;
		startFuel -= dist[i];
		target -= dist[i];
		Q.push(stations[i][1]);
	}
	if (target == 0)
		return res;
	else {
		while (!Q.empty() && startFuel < target)
		{
			startFuel += Q.top();
			Q.pop();
			res++;
		}
        if (startFuel < target)
			return -1;
		return res;
	}
        
}
};
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值