思路:
步骤:
代码:
class Solution {
public:
bool canJump(vector<int>& nums) {
if(nums.size() == 1) return true;//单元素数组 肯定可以到达终点
int cover = 0;
for(int i=0; i<=cover; i++)//i每次只能在cover范围内移动
{
cover = max(i+nums[i], cover);//更新最大覆盖范围
if(cover >= nums.size()-1) return true;//可以覆盖到终点
}
return false;
}
};
思路:
注意:
代码:
class Solution {
public:
int jump(vector<int>& nums) {
if(nums.size() == 1) return true;
int curdistance = 0;// 当前覆盖最远距离下标
int nextdistance = 0;// 下一步覆盖最远距离下标
int leap = 0;// 记录走的最大步数
for(int i=0; i<nums.size(); i++)
{
nextdistance = max(nums[i] + i, nextdistance);// 更新下一步覆盖最远距离下标
// 当移动下标达到了当前覆盖的最远距离下标时
if(i == curdistance)
{
// 如果当前覆盖最远距离下标不是终点 再走一步 增加覆盖范围
if(curdistance < nums.size()-1)
{
leap++;
curdistance = nextdistance;
// 下一步的覆盖范围已经可以达到终点,结束循环
if(nextdistance >= nums.size()-1) break;
}
else break;// 当前覆盖最远距到达集合终点,直接结束
}
}
return leap;
}
};
思路:
如果不考虑终点,那么移动下标只要遇到当前覆盖最远距离的下标,直接步数加一。因此只要让移动下标最远只能移动到nums.size - 2。当移动下标指向nums.size - 2时,
关键在于控制移动下标i只移动到nums.size() - 2的位置,所以移动下标只要遇到当前覆盖最远距离的下标,直接步数加一,不用考虑终点了
代码:
class Solution {
public:
//不考虑终点
int jump(vector<int>& nums)
{
int curdistance = 0;
int nextdistance = 0;
int leap = 0;
//关键 i最远指向nums.size()-1 考虑终点i最远指向nums.size()
for(int i=0; i<nums.size()-1; i++)
{
nextdistance = max(i+nums[i], nextdistance);
if(i==curdistance)//移动下标等于当前覆盖最大距离下标 需要再走一步
{
leap++;
curdistance = nextdistance;//更新当前覆盖的最远距离下标
}
}
return leap;
}
};
思路: for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while
代码: 力扣可以过用例,但是会超时
class Solution {
public:
//暴力解法
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
for(int i=0; i<cost.size(); i++)
{
int res = gas[i] - cost[i];//剩余油量
int index = (i+1) % cost.size();//行驶起点
// 模拟以i为起点行驶一圈
while(res > 0 && index != i)
{
res += gas[index] - cost[index];
index = (index + 1) % cost.size();
}
// 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
if(res >= 0 && index == i) return i;
}
return -1;
}
};
思路: 直接从全局进行贪心选择,情况如下
代码:
class Solution {
public:
//贪心算法 方法1
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int gassum = 0;
int gasmin = INT_MAX;//从起点出发,记录油箱里的油量最小值
int res = 0;//油箱剩余油量
//1.从0开始出发 累加油量 记录油箱剩余油量最小值
for(int i=0; i<gas.size(); i++)
{
res = gas[i] - cost[i];
gassum += res;
if(gassum < gasmin) gasmin = gassum;
}
//2.gas的总和小于cost总和
if(gassum < 0) return -1;
//3.油箱剩余油量最小值≥0 从0出发最后回到0
if(gasmin >= 0) return 0;
//4.油箱剩余油量最小值<0 从非0节点出发 寻找该节点
for(int i=gas.size()-1; i>=0; i--)
{
res = gas[i] - cost[i];
gasmin += res;//负数填平
if(gasmin >= 0) return i;
}
return -1;
}
};
思路
i+1后面就不会出现更大的负数? 如果出现更大的负数,就更新i,起始位置变成新的i+1。
有没有可能 [0,i] 区间 选某一个作为起点,累加到i,gassum不小于零?
gassum<0,区间和1+区间和2<0,又区间和2>0,所以区间和1<0,那么就会重新选择起始位置直到gassum不小于0,也就是图中假设位置。
代码:
class Solution {
public:
//贪心算法 方法2
int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
{
int start = 0;//起始位置 跑完一圈的位置
int gassum = 0;//当前起始位置的剩余油量 累加rest[i]和
int totalsum = 0;//跑一圈总耗油量
for(int i=0; i<gas.size(); i++)
{
gassum += gas[i] - cost[i];
totalsum += gas[i] - cost[i];
//当前累加rest[i]和 gassum<0
if(gassum < 0)
{
start = i+1;//更新起始位置 也就是可以跑完一圈的位置
gassum = 0;//剩余油量 累加rest[i]和清空 要重新计算
}
}
if(totalsum < 0) return -1;//总耗油量<0 不可能跑一圈
return start;
}
};
思路,采用了两次贪心的策略:
一次是从左到右遍历,只比较右边孩子评分比左边大的情况。
一次是从右到左遍历,只比较左边孩子评分比右边大的情况。
1.先确定右边孩子评分高于左边孩子评分情况,从前向后遍历
如果ratings[i] > ratings[i - 1],那么[i]的糖 一定要比[i - 1]的糖多一个,第i个小孩的糖果数量为candyVec[i] = candyVec[i - 1] + 1
局部最优:只要右孩子评分比左孩子大,右孩子就多一个糖果;全局最优:相邻的孩子中,评分高的右孩子获得比左边孩子更多的糖果。
2.再确定左边孩子评分高于右边孩子情况,从后向前遍历
3.为什么不能从前向后遍历呢?
4.candyVec[i]选择
代码:
class Solution {
public:
int candy(vector<int>& ratings) {
vector<int> candyVec(ratings.size(), 1);//每个孩子都有一个糖果
//从前往后
for(int i=1; i<ratings.size(); i++)//两个孩子比较
{
if(ratings[i] > ratings[i-1]) candyVec[i] = candyVec[i-1] + 1;
}
//从后往前
for(int i=ratings.size()-2; i>=0; i--)//两个孩子比较
{
if(ratings[i] > ratings[i+1]) candyVec[i] = max(candyVec[i], candyVec[i+1]+1);
}
int result = 0;
for(int c : candyVec) result += c;
return result;
}
};
有三种情况:
情况一:账单是5,直接收下
情况二:账单是10,消耗一个5,增加一个10
情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。
代码:
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
int five = 0, ten = 0;
for(int bill : bills)
{
//情况一:账单是5,直接收下
if(bill == 5) five++;
//情况二:账单是10,消耗一个5,增加一个10
if(bill == 10)
{
if(five <= 0) return false;//没有零钱找了
five--;
ten++;
}
//情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
if(bill == 20)
{
if(five > 0 && ten > 0)//有5和10的零钱
{
ten--;
five--;
}
//没有10 但有3个5
else if(five >= 3) five -= 3;
else return false;
}
}
return true;
}
};
思路:
过程:
代码:
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
if(a[0]==b[0]) return a[1] < b[1];//身高相同 k小的在前面
return a[0] > b[0];//身高高的在前面
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
//1.排序
sort(people.begin(), people.end(), cmp);
//2.数组
vector<vector<int>> que;
for(int i=0; i<people.size(); i++)
{
int position = people[i][1];//队列插入位置
que.insert(que.begin()+position, people[i]);
}
return que;
}
};
class Solution {
public:
static bool cmp(const vector<int>& a, const vector<int>& b)
{
if(a[0]==b[0]) return a[1] < b[1];//身高相同 k小的在前面
return a[0] > b[0];//身高高的在前面
}
vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
//1.排序
sort(people.begin(), people.end(), cmp);
//3.链表实现
list<vector<int>> que;
for(int i=0; i<people.size(); i++)
{
int position = people[i][1];
std::list<vector<int>>::iterator it = que.begin();//起始迭代器
//找到插入位置 链表只能依次访问
while(position--)
{
it++;
}
que.insert(it, people[i]);
}
return vector<vector<int>>(que.begin(), que.end());//转换类型
}
};
使用动态数组vector来insert很费时,如果插入元素大于预先普通数组大小,vector底部先扩容,即申请两倍于原先普通数组的大小,然后把数据拷贝到另一个更大的数组上。链表虽然没有数组访问便捷,但是插入时快很多。