笔记88:LeetCode_134_加油站

news2025/1/16 23:57:54

前言:


前言1:这个题的题目条件给的不太严谨,题目描述中说“如果存在解,则保证它是唯一的”,通过我的实践,我发现这句话的意思其实是本题的所有样例只有两种情况,无解/有唯一解;而不可能存在多个解的情况;

注:可以使用样例【gas=(10,10,10,10,10)    cost=(1,1,1,1,1)】来验证,这个样例每个节点都可以作为起点,即样例存在多个解,但是运行后LeetCode会报错;

a

a

前言2:这个题在代码随想录上的答案也没有写清楚他的思路,而且答案提供的暴力解法超过了时间复杂度,我提供一下我的暴力解法,并将用自己的语言讲清楚我做这道题时的思路;


a

a

a

a

a

方法1:暴力求解

注:这个题看似使用暴力很简单,但是暴力其实也有一些小坑,如果不想清楚,很难直接过;


思路1:很明显暴力一定是两个循环嵌套起来的,外层使用for循环选择起点,内层则使用while循环从该起点开始一直向后开车,验证能否从新回到起点;

思路2:外层循环很好写,但是我们需要考虑一下内层循环怎么写,选择什么条件来跳出while循环呢?我定义了变量sum_gas来代表从起点走到某一个节点时邮箱里的剩余油量,那么选择【sum_gas < 0】作为跳出条件是绝对没问题的,即如果走到当前节点时邮箱里的油为负,说明选择的起点到不了这个节点,也就无法完成闭环操作;

样例1图解

初始版本代码:这个版本的代码逻辑上完全没有问题,样例都过了,但是提交运行超时了;

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        //自己尝试:暴力求解

        for(int i = 0; i < gas.size(); i++) {
            if(gas[i] == 0) continue;                       //剪枝:如果起点加油站没有,那车辆根本无法行使
            int ptr = i;                                    //选择起点为ptr
            int sum_gas = gas[ptr] - cost[ptr];             //以ptr为当前出发站点,如果往后行驶一站,车的剩余油量
            
            while(sum_gas >= 0) {
                ptr = (ptr + 1) % gas.size();               //切换当前出发站点为ptr
                if(ptr == i) return i;
                sum_gas = sum_gas + gas[ptr] - cost[ptr];   //以ptr为当前出发站点,如果往后行驶一站,车的剩余油量
            }
        }

        return -1;
    }
};

思路3:但是我现在还是不太甘心放弃使用暴力搜索,那么我想是否可以通过剪枝操作来优化一点点代码的时间复杂度?使得可以通过提交?如果要剪枝,那就只有两种情况可以剪枝,因为通过这个题的描述我们可知,这个题只有两种情况,要么无解,要么有唯一解;所以我们如何可以在还没有满足【sum_gas < 0】这个条件的时候,就可以提前知道选择当前的这个起点,到底是无解,还是有唯一解?

思路4:我们考虑一种特殊情况,即【sum_gas == 0】这种情况;如果起点到当前节点的sum_gas == 0,那么只可能有两种情况,要么无解,要么有多个解;

看下图进行分析:如果从起点x到节点z的sum_gas == 0,那么我们针对节点y进行思考;如果从节点z开始前进,那么有两种可能:

  • 节点z无法到达节点x:则代表以节点x为起点,是不可能重新回到起点的,即无解;
  • 节点z可以到达节点x:则节点x和节点z作为起点,都可以重新回到本身的位置,即解不唯一;

思路5:所以我们不用看到【sum_gas < 0】才跳出while循环,而可以看到【sum_gas <= 0】就跳出循环;这样就节省了时间复杂度;但是有一个特殊情况,如果从起点回到起点的过程中sum_gas == 0,那么这个时候在起点的前一个点会得到sum_gas == 0;

改进版本代码:改动一共两处

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        //自己尝试:暴力求解
        
        for(int i = 0; i < gas.size(); i++) {
            if(gas[i] == 0) continue;                       //剪枝:如果起点加油站没有,那车辆根本无法行使
            int ptr = i;                                    //选择起点为ptr
            int sum_gas = gas[ptr] - cost[ptr];             //以ptr为当前出发站点,如果往后行驶一站,车的剩余油量
            
            while(sum_gas > 0) {
                ptr = (ptr + 1) % gas.size();               //切换当前出发站点为ptr
                if(ptr == i) return i;
                sum_gas = sum_gas + gas[ptr] - cost[ptr];   //以ptr为当前出发站点,如果往后行驶一站,车的剩余油量
            }
            //剪枝:特殊情况 -- 从起点回到起点后车辆剩余油量为0
            if(sum_gas == 0 && ptr == (i + gas.size() - 1) % gas.size()) return i;
        }

        return -1;
    }
};

a

a

a

a

a

方法2:贪心算法

注:使用暴力算法时间复杂度为O(n^{2}),还是太高很容易过不了提交,有没有其他的算法可以将时间复杂度降到O(n)或者O(logn)之类的;


样例1图解:

思路1:我们知道对于节点x,如果他的【gas[x] - cost[x] <= 0】,那么他一定不能作为起点;小于零的情况不用多说,等于零的情况在上面讨论过,要么是无解要么是多个解;所以只要节点x的【gas[x] - cost[x] <= 0】,就意味着这个节点不可以作为起点;只有【gas[x] - cost[x] > 0】的节点才有作为起点的潜质;

思路2:那么对于一串节点的temp_sum值,如果temp_sum小于等于0,是否也意味着这一串节点都是不能作为起点的呢?答案是肯定的;如下如示例,从节点x到节点n的temp_sum为0,那么意味着所有的节点都不可作为起点;

  • 本身作为起点的节点x和其中【gas[] - cost[] <= 0】的节点y和节点m,自不必说肯定不能作为起点;
  • 而节点z满足【gas[z] - cost[z] > 0】却也不能作为起点,是因为本身从节点x到节点z的【temp_sum > 0】,再加之节点z本身z满足【gas[z] - cost[z] > 0】,就这样两个正数相加,都不能突破节点m的封锁,导致在节点n处仍有【temp_sum = 0】,那么如果直接将节点z作为起点,那情况肯定更糟糕,更到达不了节点n了;

思路3:因此我们可以只用一个for循环,遍历所有的点,且每个点只访问一次,如果在这个点处获得的【temp_sum <= 0】,那么就意味着到当前位置的所有节点都不能作为起点;只能再遍历后面的节点,寻找具有成为起点潜质的新节点;

思路4:那如何判断一个节点可以作为起点,且可以回到本身?那么就需要遍历数组到头的时候,有【sum >= 0】,即可知道当前起点是满足答案约束的;在我的代码中,特殊情况(从第一个节点到最后一个节点的sum = 0)已经被包含进去了;

贪心算法代码:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        //自己尝试:贪心算法

        int start = 0;      //起点位置(即temp_sum的开头元素)
        int sum = 0;        //到当前节点时的剩余油量
        int temp_sum = 0;   //从起点到当前位置时的剩余油量

        for(int i = 0; i < gas.size(); i++) {
            sum = sum + gas[i] - cost[i];
            temp_sum = temp_sum + gas[i] - cost[i];
            if(temp_sum <= 0) {
                temp_sum = 0;
                start = i + 1;
            }
        }

        if(sum >= 0) return (start % gas.size());
        return -1;
    }
};

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1682757.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

医院是自建档案室还是档案寄存好呢

医院可以选择自建档案室或档案寄存&#xff0c;具体选择取决于医院的需求和资源。 自建档案室意味着医院会拥有自己的档案空间和设施&#xff0c;可以更方便地管理和保管档案。这种方式可以确保医院对档案的访问和控制有更多的自主权&#xff0c;同时也能够根据医院的需求进行档…

5月23日学习记录

[CSAWQual 2019]Unagi 涉及&#xff1a;xxe漏洞&#xff0c;外来编码xml绕过 打开环境&#xff0c;发现存在文件上传 简单上传一个php 毫无疑问上传失败&#xff0c;说是存在waf&#xff0c;绕过waf才能上传&#xff0c;点击here看看 xml编码&#xff0c;可能存在xxe漏洞&…

Java并发: 锁和同步

在Java并发: 面临的挑战那一篇中我们提到锁和同步是实现并发安全(可见性/原子性)的方法之一。这一章我们来讲讲Java中的锁和同步的各种工具&#xff0c;包括: LockSupportAbstractQueuedSynchronizerJava内置的锁实现 1. LockSupport LockSupport是基于Unsafe的park/unpark实…

57. UE5 RPG 处理AI敌人转向以及拾取物品的问题

在上一篇文章中&#xff0c;我们实现了使用AI行为树控制敌人进行移动&#xff0c;它们可以一直跟随玩家&#xff0c;虽然现在还未实现攻击。但在移动过程中&#xff0c;我发现了有两个问题&#xff0c;第一个是敌人转向的时候很僵硬&#xff0c;可以说是瞬间转向的&#xff0c;…

鸿蒙开发接口应用程序包管理:【ApplicationInfo】

ApplicationInfo 说明&#xff1a; 本模块首批接口从API version 7 开始支持。后续版本的新增接口&#xff0c;采用上角标单独标记接口的起始版本。 开发前请熟悉鸿蒙开发指导文档&#xff1a; gitee.com/li-shizhen-skin/harmony-os/blob/master/README.md点击或者复制转到。…

vue3结合element-plus之如何优雅的使用表单组件

背景 在日常开发中,我们会经常使用 element-ui 或者是 antdesign 或者是 element-plus 等组件库实现搜索功能 这里就需要用到相关表单组件 下面就以 element-plus 为例,我们实现一个搜索功能的组件,并且随着屏幕尺寸的变化,其布局也会跟随变化 最终大致效果如下: 这里…

MySQL之Schema与数据类型优化(三)

Schema与数据类型优化 BLOB和TEXT类型 BLOB和TEXT都是为存储很大的数据而设计的字符串数据类型&#xff0c;分别采用二进制和字符方式存储。 实际上它们分别属于两组不同的数据类型家族:字符类型是TINYTEXT&#xff0c;SMALLTEXT,TEXT&#xff0c;MEDIUMTEXT&#xff0c;LONG…

成都爱尔周进院长提醒当双眼度数差距过大,我们该做些什么

每个人的用眼方式、用眼习惯且两只眼睛“天生条件”不一定相同&#xff0c;当发生近视&#xff0c;双眼近视程度也就可能不同&#xff0c;双眼度数必然会变得不一样。当双眼度数产生差异&#xff0c;尤其是当双眼度数差别过大时会引发哪些问题&#xff1f; 双眼度数不一致&…

Qt快速入门到熟练(电子相册项目(二))

上一节我们成功实现了创建项目的向导界面的开发&#xff0c;这节我们继续去实现电子相册的其他功能。 新建DirTreeWidget类 还记得在Qt快速入门到熟练(电子相册项目(一))-CSDN博客里面&#xff0c;我们是在QDockWidget中添加了一个treeWidget作为以后显示目录树的空间…

OpenHarmony集成OCR三方库实现文字提取

1. 简介 Tesseract(Apache 2.0 License)是一个可以进行图像OCR识别的C库&#xff0c;可以跨平台运行 。本样例基于Tesseract库进行适配&#xff0c;使其可以运行在OpenAtom OpenHarmony&#xff08;以下简称“OpenHarmony”&#xff09;上&#xff0c;并新增N-API接口供上层应…

[算法] 优先算法(二): 双指针算法(下)

&#x1f338;个人主页:https://blog.csdn.net/2301_80050796?spm1000.2115.3001.5343 &#x1f3f5;️热门专栏:&#x1f355; Collection与数据结构 (91平均质量分)https://blog.csdn.net/2301_80050796/category_12621348.html?spm1001.2014.3001.5482 &#x1f9c0;Java …

Rust学习心得

我分享一下一年的Rust学习经历&#xff0c;从书到代码都一网打尽。 关于新手如何学习Rust&#xff0c;我之前在Hacker News上看到了这么一篇教程&#xff1a; 这篇教程与其他教程不同的时&#xff0c;他不是一个速成教程&#xff0c;而是通过自己的学习经历&#xff0c;向需要…

mac安装两个版本谷歌浏览器;在mac运行不同版本的chrome浏览器

场景 正常情况下&#xff0c;mac上只能安装一个版本的chrome浏览器&#xff0c;即使你安装了两个版本的&#xff0c;打开老旧版本时候也会自动切换成最新版的浏览器 故本文主要解决如何下载和在mac运行不同版本的chrome浏览器 文章目录 场景一、下载1.mac本身就有一个最新版ch…

【MySQL】库的操作和表的操作

库的操作和表的操作 一、库的操作1、创建数据库(create)2、字符集和校验规则&#xff08;1&#xff09;查看系统默认字符集以及校验规则&#xff08;2&#xff09;查看数据库支持的字符集&#xff08;3&#xff09;查看数据库支持的字符集校验规则&#xff08;4&#xff09;校验…

网关路由SpringCloudGateway、nacos配置管理(热更新、动态路由)

文章目录 前言一、网关路由二、SpringCloudGateway1. 路由过滤2. 网关登录校验2.1 鉴权2.2 网关过滤器2.3 登录校验2.3.1 JWT2.3.2 登录校验过滤器 3. 微服务从网关获取用户4. 微服务之间用户信息传递 三、nacos配置管理问题引入3.1 配置共享3.1.1 在Nacos中添加共享配置3.1.2 …

Kubectl 的使用——k8s陈述式资源管理

一、kebuctl简介: kubectl 是官方的CLI命令行工具&#xff0c;用于与 apiserver 进行通信&#xff0c;将用户在命令行输入的命令&#xff0c;组织并转化为 apiserver 能识别的信息&#xff0c;进而实现管理 k8s 各种资源的一种有效途径。 对资源的增、删、查操作比较方便&…

MobaXterm下载虚拟机SSH链接超时解决(保姆级踩坑)

文章目录 为啥要用MobaXtermMobaXterm下载打开虚拟机ssh链接ssh连接失败排查linux配置windows配置 到这了&#xff0c;什么都干了&#xff0c;怎么还不成功&#xff1f; 更多相关内容可查看 在一个阳光明媚的下午&#xff0c;开启了无限踩坑的旅程 为啥要用MobaXterm 作为小编…

高性能负载均衡的分类及架构分析

如何选择与部署适合的高性能负载均衡方案&#xff1f; 当单服务器性能无法满足需求&#xff0c;高性能集群便成为提升系统处理能力的关键。其核心在于通过增加服务器数量&#xff0c;强化整体计算能力。而集群设计的挑战在于任务分配&#xff0c;因为无论在哪台服务器上执行&am…

解决脚本刷服务器导致卡顿宕机的问题

在互联网服务领域&#xff0c;自动化脚本的不当使用或恶意攻击可能会导致服务器资源被过度消耗&#xff0c;从而引发服务响应缓慢甚至系统崩溃。特别是在电商、游戏、社交平台等领域&#xff0c;这种现象尤为常见。本文将深入探讨脚本刷服的常见形式、其对服务器性能的影响&…

面向对象-----继承

前面向大家介绍了面向对象中的封装性&#xff0c;今天再来向大家介绍面向对象的继承和多态的两大特性。 1.继承 1.1 为什么需要继承&#xff1f; 在java语言中&#xff0c;我们用类来描述世间万物&#xff0c;虽然万物非常复杂&#xff0c;但总有一些共同点&#xff0c;如果…