三、贪心算法(小象)

455.分发饼干

思考:首先要知道,如果一个大饼干给了一个小胃口的孩子,那么下面一个大胃王就没得吃了。所以第一步要把两个数组排序,然后,如果一个饼干不能满足最小胃口的那个孩子,那么剩下的孩子也不用喂了,都吃不饱;直接拿下一块饼干。如果一个孩子可以用更小的饼干满足,就不要把更大的饼干给他,因为可以保留更大的饼干给需求更大的孩子(贪心!)

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
         sort(g.begin(),g.end());
         sort(s.begin(),s.end());
        int cookies =0 ; int children = 0; 
        //要么饼干都分配完了,要么孩子都有饼干吃了。
        while(cookies < s.size() && children < g.size()){
            if(s[cookies] >= g[children]){
                children++;
            }
            cookies ++; //因为孩子的胃口和饼干已经排序,
            //所以如果当前饼干能满足孩子则给他,+1(表示这个饼干也用过了)
            //不能满足的话,那么肯定也不能再给下一个孩子了,
            //因为这个孩子都吃不饱,下个胃口更大也没法吃了,所以也要+1
        }
        return children;
    }
};

376.摆动序列

思考:相邻两个元素的差值是相反的,想到了函数的每一个区间,斜率k1 ×  k2 < 0,如果有连续几个都是相同增长趋势时候[10,13,15],那么下一个数字要减去其中的一个,根据概率可知,只有前一个区间的数越大,那么他们差值为负的概率就越大。

使用状态机使得他们之间的变换更明显。

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if(nums.size() <2 )
            return nums.size();
        static const int BEGIN = 0;
         static const int UP = 1;
         static const int DOWN = 2;
        int STATE = BEGIN;
        int max_length = 1;
        for(int i = 1 ; i< nums.size();i++){
            switch(STATE){
            case BEGIN:
            if(nums[i]>nums[i-1]){
                STATE = UP;
                max_length++;
            }
            else if(nums[i]<nums[i-1]){
                 STATE = DOWN;
                max_length++;
            }
            break;
            case UP:
            if(nums[i]<nums[i-1]){
                 STATE = DOWN;
                max_length++;
        }
            break;
            case DOWN:
            if(nums[i]>nums[i-1]){
                 STATE = UP;
                max_length++;
         }
            break;
            }
        }
        return max_length;
    }
};

402.移掉K位数字

问题:

1 当所有数字都扫描完成后,K仍然 > 0,怎么处理?例如:num = 12345 ,k= 3。

2 当数字中 有0出现 时候,应该有怎么样的特殊处理?例如:num = 102000,k=1 

3.如何将最后结果存储为字符串并返回

思考:使得移除其中的K位数字,其实满足规律,如果越接近高位的,数字越大就把他删掉,可以用栈来实现。对于这个数字_字符串的每一位,首先要把字符串给转变成数: number = num[i] - '0';对于每一位数字,其实有两个操作:1、若它比栈顶的数要小且删除的数字k还没删完且栈里面还有元素(对呀!要是你本来都是空栈还怎么删除啊!) 2 、这个number比人家小,把人家踢出去了,自己肯定要进栈啊。

还有几个问题:边缘情况的考虑,如果要进栈的数字不为0,那直接进栈没问题;若进栈的数字为0,只要看看栈是否为空,若不为空就直接加进去没问题。如果最后结果仍然""(正好删完了或者本来就是空) ,就直接返回"0"。

class Solution {
public:
    string removeKdigits(string num, int k) {
        vector<int> s ;
        string result = "";
        for(int i = 0; i < num.size();i++){
            int number = num[i] - '0';
            while(k >0 && s.size()!= 0&& s.back() > number){
                s.pop_back();
                k--;
            }
            if(number || !s.empty()){
                s.push_back(number);
            }
        }
            while(!s.empty()&& k > 0){
                s.pop_back();
                k--;
            }       
            for( auto n : s){
                result.append(1,'0'+ n);
            }
            if(result == ""){
                result = "0";
        }
        return result;
    }
};

55.跳跃问题

思考:贪心算法,判断是否能够到达最后一个位置,把问题从第0个位置开始考虑,0位置max_jump = 2 ,所以 0 可以最远跳到2,当然也可以选择跳一步,到达1位置,然后又发现1位置可以跳跃3步,所以可以到达2,3,4位置。

所以,第一步,把每个位置可以到达的最远位置jump_index写出来[2,4,3,4,8]

第二步,我们其实只需要考虑从每个位置出发可以到达的最远位置就可以,以及在这个区间,每个位置可以到达的最远位置。举例子就是,0位置,可以跳到最远是2,然后,0位置还可以跳到1位置,同时又发现1位置可以跳到的最远位置是4,此时最远距离就是4。以此类推,我们只要找到这个从当前位置到最远位置,这个区间能到达的更远位置,如果有,就更换能到达的最远位置。当然控制循环的条件 1 :首先你的跳跃位置jump不可能跳出数组的最远位置;2 其次在这个区间内的跳跃,位置肯定也是在区间内(区间就是它在这个位置能跳跃到的最远位置)。

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

45.跳跃问题II

拓展:可以把跳跃变换的路径写出来吗?

思考:同样的,如果数组为空,或者只有一个节点,那就不用跳跃,直接返回0;如果超过1个节点,把每个位置可以到达的最远位置jump_index写出来[2,4,3,4,8]。我们要尽可能少跳跃,那么从0的位置开始,最好的办法就是只跳跃一步,那么从0最远可以跳跃到2,这是一个可以跳跃的区间,因为这里告诉肯定可以跳跃到末尾的位置,所以我们只要把jump从0位置遍历到末尾位置。接下来就变成要从每一个跳跃区间内找寻一个最大可达位置,设置一个pre_max_index和current_max_index ,jump从0一直走,走到这个区间末尾,都不跳,那么你走到末尾+1的位置,肯定是发生了跳跃,不然你不可能超过这个区间,所以当jump>current_max_index ,jumpMin++,并且此时跳跃的最大位置变成了在这个区间内可达的最大位置,比如0,1,...,i -1 ,找到max(index[0],...,index[i-1]),把他标记为现在能走到的最远位置。

class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size()<2)
            return 0;
        vector<int> max_index ;
        for(int i = 0;i<nums.size();i++){
            max_index.push_back(i+nums[i]);
        }
        int current_max_index = nums[0];
        int pre_max_index = nums[0];
        int jumpMin = 1;
        for(int i =1 ; i< nums.size();i++){
            if( i > current_max_index){
                jumpMin++;
                current_max_index = pre_max_index;
            }
            if(pre_max_index < max_index[i] ){
                pre_max_index = max_index[i];
            }
        }
        return jumpMin;
    }
};

452.用最少数量的箭引爆气球

思考:气球在平面上是横向分布的,判断用最少的箭去射穿所有的气球,实际上是贪心问题,要考虑一支箭怎么去射穿更多的气球,假设把气球都排序了,那么第一个气球的直径区域是[1,6],所以在这个区域射一箭都可以射穿,然而我们要用尽可能少的箭去射,所以若第二个气球直径区域[2,8],那么怎么用一支箭去射穿两个气球呢?肯定从[2,6]去射就可以了。

这里就有两个问题:1、什么样的气球可以一起射穿?答案是比如第i个气球,第i+1个能不能射穿?要看i+1个气球的直径起始坐标在不在i个气球的直径坐标区域之内;2、若多个气球一起射,射箭的区域变化吗?肯定变化的,[1,6],[2,8],两个气球,若一起射,那么射的区域就要从[1,6] -> [2,6],然后看后者的直径终止坐标,8,8比6大,所以只能在[2,6]射,若是第二个气球[2,5],则只能在[2,5]这个区域射箭了。依次类推,当然若气球的直径起始坐标不在区域内,就只能再多用一支箭,同时更新新的射击区域。

#inlcude <algorithm>
#inlcude <vector>
bool cmp(pair<int, int>a,pair<int, int>b){
        return a.first < b.first;
    }
class Solution {
public:
    int findMinArrowShots(vector<pair<int, int>>& points) {
        if(points.empty()){
            return 0;
        }
         int shootNum = 1;
        sort(points.begin(),points.end(),cmp);
        //pair 直接调用first 而不是当做函数
        int shoot_min = points[0].first;
        int shoot_max = points[0].second;
        for(int i =1 ;i <points.size();i++){
            //如何判断能不能一箭双雕,主要要看这个气球在不在射击区间
            //气球的最左边只要比射击区间最远处小 就可以射中
            //这里因为做了排序 所以排除了 气球反而在射击区间左边
            if(points[i].first <= shoot_max){
                shoot_min = points[i].first;
            if(points[i].second<shoot_max){
                shoot_max = points[i].second;
                }
            }
            else{
                shootNum++;
                shoot_min = points[i].first;
                shoot_max = points[i].second;
            }
        }
        return shootNum;
    }
};

思考:这个题和跳跃II的问题不一样的在于,不确定能不能开到终点,并且这个不像跳跃问题,跨越了某个点就不能回头(说的不准确),比如:车在S点加油了,可以一直跑到C点,之后1KM,但是这并不妨碍A ,B两点也可以加油。解题的思路,用一个最大堆来存储沿路走过加油站的可以加油量,在某一个位置不能接续前进的时候,从最大堆中调用可以加的最大油量,看看能不能撑到下一个加油站,不能就继续加,直到能撑到下一个加油站(也就是说只要加的油能够完成当前的这段距离),如果油量的最大堆空了,说明已经不能加了,肯定到不了终点。

这里有问题其实我没想清楚?如果你传入的P= 0 /*初始油量*/,并且根据代码所写,while (!Q.empty() && P < dis) 此时可以加油的最大堆不是空么?而且P < dis(- - ! 发现不是啊!第一个站不就是<30,16>,距离为0 啊!)...所以没问题啊。直接进行下面的循环P -= dis;L = 30;Q.push(16);...好吧!

//pair 调用第一个数,直接first,而不是函数调用first()
bool cmp(const pair<int,int> &a ,const pair<int,int> &b){
	return a.first > b.first;//到终点的距离,从大到小才是逆序。
}

int get_min_refule(int L, int P ,vector<pair<int,int>> &stop){
	priority_queue<int> Q;
	int refule_times = 0;
	sort(stop.begin(),stop.end(),cmp);
	for(size_t i = 0; i<stop.size();i++){
		int dis = L - stop[i].first;
		while(!Q.empty() && P < dis){
			P += Q.top();
			Q.pop();
			refule_times++;
		}
		if(Q.empty()&&P < dis){
			return -1;
		}
		P -= dis;
		L  = stop[i].first;
		Q.push(stop[i].second);
	}
}

猜你喜欢

转载自blog.csdn.net/qq_34269988/article/details/83755250