目录
一:基础知识
局部最优->全局最优 eg. 一堆钞票中拿走十张,需要总金额达到最大。 解:每次拿最大的钱
解题步骤:无固定模板 看是否能举反例,若不能则是贪心 (说白了就是常识性的推导)
二:题目
1.分饼干
饼干充分(满足最接近胃口的) 分给小孩,且饼干不剩,小孩可以没有吃饱。(虽然有小胃口喂小饼干,可是我觉得下面这个好理解一些)
贪心:大胃口喂大饼干,充分利用饼干尺寸,喂好每一个胃口
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int i,j; //i是饼干 j是胃口
int coun=0;
for(i=s.size()-1,j=g.size()-1,j>=0;j--)
{
if(s[i]>g[j]&&i>=0)//大胃口满足大饼干
{
count++;
i--; //i已经满足了最好的胃口,则进行下一个的挑选
}
}
return count;
}
};
(ps:其实我觉得c++的排序这样好用一些,同时算长度直接用 数组名 . size() 不用再去用c里面的strlen 和string.h )
2.摆动数列(难)
首先需要明白一点,摆动数列的意思是两数值之差 正负交替出现所组成的子数列
分析很难想到,看Car老师l的解法,有三种情况:单调中有平波(2种画法),波峰是平波(2种画法),数列的首尾有平波,最后构成了代码如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
int curdiff=0;
int prediff=0;
int result=1;//计数最后一个数值是巅峰,计为1个;
int i;
for(i=0;i<nums.size()-1;i++)
{
curdiff=nums[i+1]-nums[i];
if((prediff>=0&&curdiff<0)||(prediff<=0&&curdiff>0))
//符合上面的三种情况
//可以亲自画图,尤其是首,我们假设它是平波,尾已经在result里面+1初始化了
{
result++;
prediff=curdiff;//只有当条件符合时,才能移动pre,否则就是平波(curdiff==0)
}
}
return result;
}
};
必要时画图理解
3.最大子序列的和(要多思考)
题目:给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
子数组 是数组中的一个连续部分。
示例 1:
输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。
示例 2:输入:nums = [1]
输出:1
示例 3:输入:nums = [5,4,-1,7,8]
输出:23
刚开始的时候没理解到题意,以为和上面一道题相似,但是看了题解,才发现自己很菜,连题都没有读清楚,所以越简单的题,越要耐心看。
首先可以考虑暴力解法,一次一次慢慢循环遍历
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result=INT32_MIN;
int count=0;
int i,j;
for(i=0;i<nums.size();i++)
{
count=0;//注意清零
for(j=i;j<nums.size();j++)//注意j=i的意思,是从头i开始遍历
{
count+=nums[j];
result=result<count?count:result;//不断判断最新的和
}
}
return result;
}
};
这个暴力,运行时间有点超时了,所以试试贪心。可能由于我蒟蒻(juruo)所以对于贪心算法只有看题解了。当连续和出现负数时,停止继续相加,
“遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和”。
不断调整最大子序和区间的起始位置(暴力)=找到每个数相加的count值不小于0(只要大于0就满足和相加)
终止位置是通过不断比较count和result(前一个count的值)的大小,确定最大的和
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int result=INT32_MIN;//初始化一个较小的值
int count=0;
int i;
for(i=0;i<nums.size();i++)
{
count+=nums[i];
if(count>result)
//取区间累计的最大值(相当于不断确定最大子序终止位置)
result=count;
//这个是不断的更新result的值,便于后面count相加时,能大于之前的和
if(count<=0)
//当子序列开始为负数或0时,重置起始位置,防止子序列和一直为负数
count=0;
}
return result;
}
};
贪心:就在于当和为负数时(负数只会拉低和),不在进行相加,转换至下一组数。(应该时这个意思)
误区:如果输入用例都是-1,或者 都是负数,这个贪心算法跑出来的结果是0。× 错误
解答:由于定义了INT32_MIN, 则后面一定会去更新result的值为新值。
4.买卖股票的最佳时机||
贪心:最终利润是可以分解成每两天的利润,我们就搜集每两天的正利润(感觉有点不可思议这样思考)
class Solution {
public:
int maxProfit(vector<int>& prices) {
int i,count=0,result=0;
for(i=1;i<prices.size();i++)//注意这是从1开始
//或者是for(i=0;i<prices.size()-1;i++)
// count=prices[i+1]-prices[i];//利润序列比股票序列少一天
{
count=prices[i]-prices[i-1];
if(count>0)//保证利润为正
{
result=result+count;
}
}
return result;
}
};
//或者是Carl老师写的这个更简洁
class Solution {
public:
int maxProfit(vector<int>& prices) {
int result = 0;
for (int i = 1; i < prices.size(); i++) {
result += max(prices[i] - prices[i - 1], 0);//c++中与0之间的比较取最大值
}
return result;
}
};
5.跳跃游戏
给定一个非负整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个位置。
示例 1:
- 输入: [2,3,1,1,4]
- 输出: true
- 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
- 输入: [3,2,1,0,4]
- 输出: false
- 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
由于一个数能够跳无数种可能,不断的取寻找覆盖的最大范围,考虑每个数是否能否到达终点
难点:如何表示它跳过去后的数值,这个地方用数组的下标来表示。
class Solution {
public:
bool canJump(vector<int>& nums) {
int cover=0;
if(nums.size()==1)return true;
for(int i=0;i<=cover;i++)//注意<=
{
cover=max(i+nums[i],cover);
if(cover>=nums.size()-1) return true;
}
return false;
}
};
6.跳跃游戏||
示例:
- 输入: [2,3,1,1,4]
- 输出: 2
- 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明: 假设你总是可以到达数组的最后一个位置。
与上一道区别:是否能到达终点 最少走几步到终点
这道题设置了curdistance和nextdistance两个新的变量记录最大范围。
贪心:找到当前位置的最大覆盖范围,一步尽可能的走完,找到最少的步数
普遍判断到最后一个数的情况:
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size()==1)return 0;
int curDistance=0;//记录当前这个数的覆盖范围
int ans=0;//记录次数
int nextDistance=0;
int i;
for(i=0;i<nums.size();i++)
{
nextDistance=max(nums[i]+i,nextDistance);
if(i==curDistance)//保证一步尽量走完到最大步数
//如果到了该值覆盖的最大范围,则进行如下操作,否则继续执行next值的最大范围判断,直到到达该值的最大范围。(始终保证该值的最大范围用完)
{
if(curDistance<nums.size()-1)//判断现在的覆盖范围是否超出数
{
ans++;
curDistance=nextDistance;//重新赋值范围
if(nextDistance>=nums.size()-1) break;
}
}
}
return ans;
}
};
优化代码:停留在倒数第二位上判断,由于题目中有这样一段话:假设你总是可以到达数组的最后一个位置。(这说明倒数第二个数总能到达,也就是数组中倒数第二个数没有0)
//这个方法多少有点不理解,不知道为什么
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size()==1)return 0;
int curDistance=0;//记录当前这个数的覆盖范围
int ans=0;//记录次数
int nextDistance=0;
int i;
for(i=0;i<nums.size()-1;i++)//区别
{
nextDistance=max(nums[i]+i,nextDistance);
if(i==curDistance)
//如果到了该值覆盖的最大范围,则进行如下操作,否则继续执行next值的最大范围判断,直到到达该值的最大范围。(始终保证该值的最大范围用完)
{
ans++;
curDistance=nextDistance;//重新赋值范围
}
}
return ans;
}
};
这两道类似的题可以动笔罗列情况更好理解
7.K次取反后最大化的数组和(简单)
贪心:负数变正数 最小数变负数
class Solution {
static bool cmp(int a, int b) {
return abs(a) > abs(b);//c++的函数还不是很会哦
}
public:
int largestSumAfterKNegations(vector<int>& A, int K) {
sort(A.begin(), A.end(), cmp); // 将所有数的绝对值进行从大到小排序
for(int i=0;i<A.size();i++)
{
if(A[i]<0&&K>0)
{
A[i]=A[i]*(-1);//转化为负数的方法
K--;
}
}
if(K%2==1)A[A.size()-1]*=-1;//若负数转化玩还有k,则进行最小数转化为负数
//问题:为什么要这样用K,k一定只用一次吗。
//答:题中所述可以重复利用一个值,所以k为偶数时,变来变去等于原值(不改变原值的情况下把k的次数用完),所以只计算k为奇数
int result=0;
for(int a:A) result+=a;
return result;
}
};