当前位置: 首页 > news >正文

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

目录

1.最优除法

题解

2.跳跃游戏 II

题解

3.加油站

题解


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

1.最优除法

链接: 553. 最优除法

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

  • 例如,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好位置,这里有一个最大跳跃长度,那每次跳跃的时候就非常贪心的跳最长跳跃长度。但是这种贪心是错的。


2. 动态规划

这个模型无非就是从左往右的一个模型,其实就是动规里面非常常规的线性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;}
};

相关文章:

  • Unity | AmplifyShaderEditor插件基础(第三集:颜色的计算)
  • 高效DCDC电源芯片在运动控制器中的应用:设计考量、性能评估与可靠性分析
  • TortoiseGit使用图解
  • Linux进程学习【基本认知】
  • echarts坐标轴数值,生成的数值是0,100,200,300...,怎么不设置min和max的情况下,让坐标轴的数值相隔200
  • TestBrain开源程序是一款集使用AI(如deepseek)大模型自动生成测试用例、和测试用例评审、RAG知识库管理的web平台系统
  • 常见网络安全攻击类型深度剖析(三):DDoS攻击——分类、攻击机制及企业级防御策略
  • 《深入理解计算机系统》阅读笔记之第十一章 网络编程
  • React.memo 和 useMemo
  • 【金仓数据库征文】- 深耕国产数据库优化,筑牢用户体验新高度
  • python源码打包为可执行的exe文件
  • jQuery AJAX、Axios与Fetch
  • Java实现加密(七)国密SM2算法的签名和验签(附商用密码检测相关国家标准/国密标准下载)
  • 基于ssm的音乐播放平台管理系统(源码+数据库)
  • Android开发,实现底部弹出菜单
  • 高等数学第二章---导数与微分(2.1~2.3)
  • 求职意向商务/BD简历模板
  • 通讯的基础概念:涵盖串行通信、并行通信、TCP、UDP、Socket 等关键概念和技术
  • [AI Workflow] 基于多语种知识库的 Dify Workflow 构建与优化实践
  • 统计术语学习
  • 精准滴灌“种企业”,苏南强县常熟新的进阶密码
  • “五一”假期云南铁路预计发送旅客超330万人次
  • 政治局会议:优化存量商品房收购政策,持续巩固房地产市场稳定态势
  • 解放日报头版:外资汽车产业链布局上海步伐明显加快
  • 神二十成功对接空间站
  • 国防部:菲挑衅滋事违背地区国家共同利益