leetcode学习记录_贪心

贪心的特点就是不断求得局部最优解
然后用局部最优解求得全局最优解
55. 跳跃游戏

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

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

判断你是否能够到达最后一个下标。

示例 1:

输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

来源:力扣(LeetCode)
思路,每一个数据都有一个能跳跃的区间,如下图,当nums[0] = 2时,你可以跳一步,也可以跳两步
在这里插入图片描述
我们保存当前跳跃区间的末尾,然后遍历数组,找到这个区间内的能跳最远的选择,很明显,在当前区间内,从2跳到3明显是最好的选择,然后把更新end到‘3’的跳跃区间的末尾,并且再次寻找最优解,如下图红色部分

在这里插入图片描述

明显4是最好的选择,再次更新end,发现end已经超过了数组长度,那么我们就得到了答案:可以到达数组末尾
代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
		int N = nums.size(), end = 0 , maxjump = 0;
//特殊情况,如果数组长度为1,那他一开始就在终点了,不用再进行后续判断		
		if(N == 1) return true;
//不用考虑最后一位,因为只要跳到最后一位就行了		
		for(int i = 0;i<N-1;++i)
		{
//如果当前最远距离甚至都超不过我们遍历的位置,
//就说明更加不可能跳到末尾了,直接放弃吧		
			if(maxjump >= i)
			{//维护当前最远跳跃距离
				maxjump = max(maxjump , nums[i]+i);
//如果已经遍历到当前跳跃区间末尾,就利用最远条约距离更新end,然后判断end是否超过数组长度(下标)				
				if(i == end)
				{
					end = maxjump;
					if(end >= N-1) return true;
				}
			}
		}
//如果没在for里边return,就说明是到达不了末尾了,返回false		
		return flase;
    }
};

同样思路解决第二题
45. 跳跃游戏 II
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。
来源:力扣(LeetCode)

这题有一点点区别,题目确定了一定能达到数组末尾,然后要我们求最小跳跃次数,思路一模一样,求当前最优解,每更新一次end,就说明跳了一次

代码:

class Solution {
public:
    int jump(vector<int>& nums) {
        int end = 0 , maxjump = 0, count = 0 ,N = nums.size();
        for(int i=0;i<N-1;i++)
        {
            if(maxjump>=i)
            {
                maxjump = max(nums[i]+i,maxjump);
                if(i == end)
                {
                    end = maxjump;
                    count++;//每跳一次,就+1
                }
            }
        }
        return count;
    }
};


134. 加油站

在一条环路上有 N 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。
如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1。
说明:
如果题目有解,该答案即为唯一答案。
输入数组均为非空数组,且长度相同。
输入数组中的元素均为非负数。
来源:力扣(LeetCode)
先来个暴力算法:

首先确定哪些加油站可能作为起点
gas[ i ] < cost[ i ] 时,i就不可能作为起点,首先看看cost数组的定义:开往下一个加油站要花费的汽油,而gas是在改加油站获得的汽油,如果gas[ i ] < cost[ i ]就代表改变到达不了下一个加油站,所以不能做起点、

再说说算法的思路
我们从每一个可能作为起点的加油站出发,用gassum记录当前的汽油剩余值,一旦gassum < 0,就代表这个点也没救了,这时就从下一个可能的点出发,
一旦走完了一圈且没有跳出,就说明这个点可行,直接返回
而如果所有可能的点都走过了还没有返回,就说明不不管从哪个点出发都走不完一圈,return -1;

代码:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int N = gas.size();
        for(int i = 0;i<N;++i)
        {
            int gassum = gas[i]-cost[i];
            int idx = (i+1)%N;
            if(gassum>=0)
            {
                while(idx != i)
                {
                    gassum += gas[idx]-cost[idx];
                    if(gassum <0) break;
                    idx = (idx+1)%N;
                }
                if(idx == i) return i;
            }
        }
        return -1;
    }
};

贪心:

思路:
用gassum记录出发点到当前站点时得汽油剩余量,如果剩余量 < 0就说明刚开始选的那个出发点不行,就把当前站点的下一个站点作为新的出发点,并清零gassum,重新计数,> 0的话就不管,让保持出发点不变,因为根据gassum来看,这个出发点暂时还是可靠的,不管他就行了,但是其实我对这个结论还是不清不楚的,只知道这个结论确实是对的

同时,我们用一个Sum更新总的gas - cost 如果 Sum > 0,就说明这一圈还是有希望的,如果Sum < 0,就说明没希望了,怎么走都走不完一圈

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int gassum = 0;//当前汽油
        int Sum = 0;//总汽油
        int idx = 0;//出发点
        for(int i = 0;i<gas.size();++i)
        {
            gassum += gas[i]-cost[i];
            Sum += gas[i]-cost[i];
            if(gassum < 0)
            {//一旦 < 0,就更新出发点,并清零gassum
                idx = i+1;
                gassum = 0;
            }
        }
        if(Sum < 0) return -1;//如果总汽油 < 0,就说明没救了
        else return idx;//不然就返回出发点
    }
};


122. 买卖股票的最佳时机 II

给定一个数组 prices ,其中 prices[i] 是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

输入: prices = [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

来源:力扣(LeetCode)
思路:

贪心算法是把全局收获分开成每天的收获来看了

假设每天都进行买入卖出的情况下,我们只选择能获利的那天进行卖出,这样一直下去最终我们的获利就是最高的,
但是注意:
这种思维不能用于只进行一次买卖的情况,因为一次选择并不能获得真正的最大利润,如下图,稍微复杂一点的情况两者就不一样了,而这一题并没有要求只能选择一次
在这里插入图片描述
贪心算法的结果是正确的,但是它并没有模拟股票的买卖过程,要真正的过程,还是得用动态规划👉点这里


781. 森林中的兔子

森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。

返回森林中兔子的最少数量。

示例:
输入: answers = [1, 1, 2]
输出: 5
解释:
两只回答了 “1” 的兔子可能有相同的颜色,设为红色。
之后回答了 “2” 的兔子不会是红色,否则他们的回答会相互矛盾。
设回答了 “2” 的兔子为蓝色。
此外,森林中还应有另外 2 只蓝色兔子的回答没有包含在数组中。
因此森林中兔子的最少数量是 5: 3 只回答的和 2 只没有回答的。
输入: answers = [10, 10, 10]
输出: 11
来源:力扣(LeetCode)

这一题的贪心其实挺抽象的,但是也挺简单的,说说思路:

假设数组为2,2,2 最少会有多少只兔子?答案是3,为什么呢,我们假设数组里的这三个兔子都是同一种颜色,就蓝色吧,第一只说:除了我还有两只蓝色,第二只也说,第三只也这样说,大家都这么说,发现了什么,只要他们颜色一样,那第一只兔子说的另外两只就是第二只和第三只,
第二只说的就是1和3,只有把他们假设为同一种,才能获得最小数量

反过来举个例子吧,我们假设第一只兔子的蓝色,第二只是绿色,第三只是红色,这种情况下总共就有9只,三只蓝的,三只绿的,三只红的,当然不是我们想要的结果;

下面再来个特殊情况,上面的2,2,2是刚好为一组,每个只兔说的都是另外的两只兔子,假如是2,2,2,2呢?有一组(同样颜色)已经满人了,多出来的一只兔子就只能单独一个颜色,单独一个组了,一个组就是3人,所以2,2,2,2代表最少有6只兔子,两组,一组三只

同理3,3,3,3代表一组,一组4只,4,4,4,4,4也代表一组…

我们用哈希表存储每个数字出现的次数,然后算出对应的组数,向上取整,然后把组数乘上对应的组员数量,最后加在一起即可;

代码:

class Solution {
public:
    int numRabbits(vector<int>& answers) {
        unordered_map<int,int>map;
        for(int &buf:answers)
        {//存入哈希表
            ++map[buf];
        }
        int res = 0;
        for(auto&[y,x] : map)
        {//ceil是向上取整,这里也可以用(x+y)/(y+1)实现
		// y+1是组员数
            res+=ceil((double)x/(y+1))*(y+1);
        }
        return res;
    }
};


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

timathy33

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值