[贪心_7] 最优除法 | 跳跃游戏 II | 加油站

目录

1.最优除法

题解

[2.跳跃游戏 II](#2.跳跃游戏 II)

题解

3.加油站

题解


利用 单调性,可以实现 区间跳跃

1.最优除法

链接: 553. 最优除法

给定一正整数数组nums nums 中的相邻整数将进行浮点除法。

  • 例如,nums = [2,3,4],我们将求表达式的值 "2/3/4"

但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。你需要找出怎么添加括号,以便计算后的表达式的值为最大值。

以字符串格式返回具有最大值的对应表达式。

**注意:**你的表达式不应该包含多余的括号。

示例 1:

复制代码
输入: [1000,100,10,2]
输出: "1000/(100/10/2)"
解释: 1000/(100/10/2) = 1000/((100/10)/2) = 200
但是,以下加粗的括号 "1000/((100/10)/2)" 是冗余的,
因为他们并不影响操作的优先级,所以你需要返回 "1000/(100/10/2)"。

其他用例:
1000/(100/10)/2 = 50
1000/(100/(10/2)) = 50
1000/100/10/2 = 0.5
1000/100/(10/2) = 2

给定一正整数数组 nums,nums 中的相邻整数将进行浮点除法。

例如,nums = [2,3,4],我们将求表达式的值 "2/3/4"。

但是,你可以在任意位置添加任意数目的括号,来改变算数的优先级。

  • 你需要找出怎么添加括号,以便计算后的表达式的值为最大值。
  • 题意其实就是数组给一堆数,数中间其实都有除法的,此时让我们任意位置添加一些括号,使表达式的值为最大值,最后返回的是以字符串格式返回具有最大值的对应表达式。
  • 注意:你的表达式不应该包含多余的括号。

原本括号里面顺序是3/4/5,现在多添加一个括号还是(3/4)/5。

题解

举一个例子来提取我们的贪心策略

这个时候我们要明白一点,在这个表达式中添加括号最终都会转换为 x/y 的形式。

也就是从这些数中挑一些放在分子上,一些数放在分母上,

我们最终要的是 x/y 是最大的,一个分数要最大,要么就是分子变大,要么就是分子变小。

  • 接下来我们设计表达式就是尽可能让分子大,分母小。
  • 这就是我们的贪心方向。

还有一点无论在表达式哪里填上括号,a都是在分子上,b都是在分母上

  • 所以a和b这两个无法通过添加括号去改变的a/b。所以最终a一定在分子上,b一定在分母上。
  • 现在可供我们选择的就是c、d、e、f,为了使最终结果最大,我们就想无脑的把c、d、e、f和a一起放在分子上。

这里可以用贪心优化就是因为这个值乘起来是越来越大的

贪心策略:除了前两个数以外,其余的数全部放在分子即可

  • 如何实现贪心策略呢?
    这里我们用小学就学过的知识,负负得正。除除得正。因此我们仅需在b之前填一个(,f后填一个)
  • 括号里面得除法全都因为括号外面得除法变成除除得正
复制代码
class Solution {
public:
    string optimalDivision(vector<int>& nums) 
    {
        string str;
        if(nums.size()==1) return to_string(nums[0]);
        if(nums.size()==2)
        {
            str+=to_string(nums[0]);
            str+='/';
            str+=to_string(nums[1]);
            return str;
        }
        for(int i=0;i<nums.size();i++)
        {
            str+=to_string(nums[i]);
            str+='/';
            if(i==0) str+='(';
            if(i==nums.size()-1) 
            {
                str.pop_back();
                str+=')';
            }
        }
        return str;
    }
};

2.跳跃游戏 II

链接: 45. 跳跃游戏 II

给定一个长度为 n0 索引 整数数组 nums。初始位置为 nums[0]

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]

示例 1:

复制代码
输入: nums = [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
     从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。

示例 2:

复制代码
输入: nums = [2,3,0,1,4]
输出: 2

给定一个长度为 n 的 0 索引整数数组 nums。初始位置为 nums[0]。

  • 每个元素 nums[i] 表示从索引 i 向前跳转的最大长度。
  • 换句话说,如果你在 nums[i] 处,你可以跳转到任意 nums[i + j] 处。

这句话特别重要的地方就是 任意

  • 下面举个例子,刚开始在0号位置最大跳跃长度是3,可以跳到下标3的位置。
    你可以跳转到任意 nums[i + j] 处,这句话意思
  • eg. nums[i]里面存的3是最大跳跃长度,你可以选择跳3步、跳2步、跳1步。

返回到达 nums[n - 1] 的最小跳跃次数。生成的测试用例可以到达 nums[n - 1]。

题解

  1. 贪心(X)

刚开始处于0好位置,这里有一个最大跳跃长度,那每次跳跃的时候就非常贪心的跳最长跳跃长度。但是这种贪心是错的。

  1. 动态规划

这个模型无非就是从左往右的一个模型,其实就是动规里面非常常规的线性dp问题。

1.状态表示

  • dp[i] 表示:从 0 位置开始,到达 i 位置的时候最小跳跃次数

2.状态转移方程

根据最近一步划分情况:

  • 能够到 i 位置的前提是要满足 nums[j] + j >= i,说明能够从 j 位置到达 i 位置,那到达 j 位置的最小跳跃次数 在 加上 从 j 到 i 这一跳跃次,就是到达 i 位置最小跳跃次数,j的位置有很多
  • 因此外面要求dp[i]的最小值。

3.初始化

  • dp[0] = 0 初始就在0位置
  • 然后要取dp[i]的最小值,因此0位置之后可以初始化 INT_MAX

4.填表顺序

  • 从左往右

5.返回值

  • dp[i] 表示:从 0 位置开始,到达 i 位置的时候最小跳跃次数

  • 我们要的是到底n-1位置的最小跳跃次数,因此返回dp[n-1]

    class Solution {
    public:
    int jump(vector<int>& nums)
    {
    int n=nums.size();
    if(n==1) return 0;
    vector<int> dp(n,0x3f3f3f3f);
    dp[0]=0;
    for(int i=1;i<n;i++)
    {
    for(int j=0;j<=i-1;j++)
    {
    if(j+nums[j]>=i)
    dp[i]=min(dp[i],dp[j]+1);
    //交给 计算机
    //j-->i 次数要+1
    }
    }
    return dp[n-1];
    }
    };

虽然可以通过,但是实际复杂度是O(N^2)

  1. 类似于层序遍历的过程

刚开始在2这个位置,此时起跳可以跳到3和1的位置。

  • 2这里可以表示第1次起跳的位置,3和1表示第2次起跳的位置。
  • 通过第2次起跳的位置,我们可以得到第3从起跳的位置,然后把重叠的删除,这里其实也有一点小贪心,如果能从第2次的1起跳,那为什么还要从第3次重叠的1起跳呢?
  • 跳跃次数更多了。所以只有1和4是第三次起跳的位置。
  • 同理从第3次起跳位置,我们可以得到第4次起跳位置,
  • 你会发现这里类似于层序遍历,每次都能知道起跳的左端点和右端点,然后遍历这一层的时候,又能找到下一层的左端点和右端点。

只要发现更新出来下一次起跳位置能够覆盖到n-1位置的时候就停止,因为次数已经可以跳到最后一个位置了

如何实现呢?

  • 我们仅需搞两个指针,left指向当前起跳的左端点,right指向当前起跳的右端点
  • 把这个区间遍历一遍就可以找到下一个起跳区间
  • 其中找左端点很好找就是right + 1,找右端点就是在遍历的过程中,拿着nums[i] + i 找到其中的最大值就是右端点。

在这个遍历过程中,我们仅需遍历一次就行了,所以时间复杂度是O(N)

复制代码
class Solution {
public:
    int jump(vector<int>& nums) 
    {
        int ret=0,n=nums.size();
        int left=0,right=0;
        while(left<=right)
        {
            if(right>=n-1) return ret;
            int tmp=right;
            for(int i=left;i<=tmp;i++)
            {
                right=max(right,nums[i]+i);
            }
            left=tmp+1;
            ret++;
        }
        return -1;
    }
};

不要忘了,还要处理跳不到的情况

  • while(left<=right)
  • return -1;

3.加油站

链接: 134. 加油站

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

你有一辆油箱容量无限的的汽车,从第i个加油站开往第i+1个加油站需要消耗汽油 cost[i]升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gascost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

复制代码
输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

选择某个加油站为出发点,环绕一周看是否能回到出发点。

如果可以就返回对应的下标,不能就返回-1。

  • 初始时油箱是空的,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i],油箱容量是无限的。
  • 假设从3位置出发,刚开始油箱为空,此时可以补充1升汽油,但是需要3升汽油才能到下一个位置。所以这个位置不可以,同理4和5都不可以。
  • 可以从1位置出发 ,从这里可以补充4升汽油,仅需消耗1升就可以到下一个位置,到下一个位置还剩下3升
  • 到2的位置,补充5升,现在共有8升,消耗2升,到下一个位置还有6升

然后补充1升,消耗3升,到下一个位置还剩4,在补充2升,消耗4升,到下一个位置还剩2升,在补充3升,消耗5升,到出发点正好剩下0,可以到达,返回3。

题解

解法一:暴力解法 -> 枚举

首先可以想到一个优化,仅需考虑g和c的差就可以了,比如第一个位置会加1升油,消耗3升油,它们的差就是从这个加油站获得的净收益。

如果从负开始走绝对是走不到下一个位置的,所以肯定会选择净收益为正的作为出发点。

我们这道题其实特别像是一道模拟的题,任意枚举一个位置看看从这个位置能不能绕一圈会回来就可以。

如果不能就去枚举下一个位置。

所以我们的暴力策略就很简单:

  1. (预处理) 计算出 每一步的 diff
  2. 依次枚举所有的起点
  3. 从起点开始,模拟一遍加油的流程即可

虽然策略很简单,但是要注意这里是有环的,所以写代码的时候要考虑如何从最后一个位置回到0位置。

  • diff [-2, -2, -2, 3, 3]

我们的贪心就是根据暴力优化来的,所以先搞定暴力的代码。然后优化的时候仅需加一句代码,就能将时间复杂度从O(N^2)变成O(N)

如何实现暴力的代码?

  • 这里我们主要考虑就是如何从最后一个位置回到第一个位置,其实这里两层for循环就可以搞定

  • 我们创建一个变量step,用这个变量表示从 i 往后走了多少步,step变化是从 0 ~ n - 1。

  • 然后 (i + step)% n (数组大小) 就可以从最后一个位置回到第一个位置。

    class Solution {
    public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
    {
    int n=gas.size();
    vector<int> diff(n);
    for(int i=0;i<n;i++)
    {
    diff[i]=gas[i]-cost[i];
    }
    for(int i=0;i<n;i++)
    {
    bool check=false;
    if(diff[i]>=0)
    {
    check=true;
    int sum=diff[i];
    for(int step=1;step<n;step++)
    {
    int curr=(i+step)%n;
    sum+=diff[curr];
    if(sum<0)
    {
    check=false;
    break;
    }
    }
    }
    if(check) return i;
    }
    return -1;
    }
    };

解法二:优化 -> 找规律(贪心)

diff表示g-c的差,我们的暴力解法是依次固定一个位置为起点,从这个起点开始模拟加油流程,其实就是把净收益加一下。

  • 如果发现从a加到f小于0了,说明从f这个位置开始就不能往后走了,所以从a为起来最多能到f这个位置。这里有一个等式。
    我们的暴力是枚举下一个起点然后在走。然后我们这里也有个不等式,
  • 我们要想从a走到b,一定是a>=0的,从a加到f < 0,现在第二个不等式又少了a,那更是< 0
  • 同理从c为起点也是越不过f的,a + b >= 0才能到c,等式少了a+b,那更小于0
    所以说发现有一个起点点都跑不到某个位置,那中间的都不用在考虑了,不用在枚举了。直接让 i 指针更新到 五角星 后面的一个位置,也就是 i = i + step

我们最差会遍历数组两遍,假设还是以a为起点,发现到h走不到了,下一个位置就是i,最差我们绕回去在遍历一遍再走到h位置,相当于遍历了数组两遍

  • 然后接下来更新 i 的时候 是 i + step + 1 此时就已经越界了。

  • 所以最差遍历数组两遍,时间复杂度O(N)

    class Solution {
    public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost)
    {
    int n=gas.size();
    vector<int> diff(n);
    for(int i=0;i<n;i++)
    {
    diff[i]=gas[i]-cost[i];
    }
    for(int i=0;i<n;i++)
    {
    int step=0;
    int sum=0;

    复制代码
                  for(;step<n;++step)
                  {
                      int curr=(i+step)%n;
                      sum+=diff[curr];
                      if(sum<0)
                      {
                          break;
                      }
                  }
              
              if(sum>=0) return i;
              i+=step;
          }
          return -1;
      }

    };

相关推荐
知来者逆23 分钟前
计算机视觉——速度与精度的完美结合的实时目标检测算法RF-DETR详解
图像处理·人工智能·深度学习·算法·目标检测·计算机视觉·rf-detr
阿让啊28 分钟前
C语言中操作字节的某一位
c语言·开发语言·数据结构·单片机·算法
এ᭄画画的北北28 分钟前
力扣-160.相交链表
算法·leetcode·链表
草莓啵啵~1 小时前
搜索二叉树-key的搜索模型
数据结构·c++
爱研究的小陈1 小时前
Day 3:数学基础回顾——线性代数与概率论在AI中的核心作用
算法
渭雨轻尘_学习计算机ing1 小时前
二叉树的最大宽度计算
算法·面试
丶Darling.2 小时前
26考研 | 王道 | 数据结构 | 第八章 排序
数据结构·考研·排序算法
BB_CC_DD2 小时前
四. 以Annoy算法建树的方式聚类清洗图像数据集,一次建树,无限次聚类搜索,提升聚类搜索效率。(附完整代码)
深度学习·算法·聚类
我也不曾来过13 小时前
list底层原理
数据结构·c++·list
梁下轻语的秋缘3 小时前
每日c/c++题 备战蓝桥杯 ([洛谷 P1226] 快速幂求模题解)
c++·算法·蓝桥杯