笔记88:LeetCode_134_加油站

前言:


前言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;
    }
};

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【Spring】认识 Spring AOP

认识 Spring AOP 1.什么是 AOP2.AOP 中的概念3.用 AOP 方式管理日志3.1 编写 AOP 日志注解类3.2 编写控制器用于测试 1.什么是 AOP AOP&#xff08;Aspect Oriented Program&#xff0c;面向切面编程&#xff09;把业务功能分为核心、非核心两部分。 核心业务功能&#xff1a…

tcpdump源码分析

进入tcpdump.c&#xff08;函数入口&#xff09;之前&#xff0c;先看一些头文件netdissect.h里定义了一个数据结构struct netdissect_options来描述tcdpump支持的所有参数动作&#xff0c;每一个参数有对应的flag, 在tcpdump 的main 里面&#xff0c; 会根据用户的传入的参数来…

构建高效的在线培训机构CRM应用架构实践

在当今数字化时代&#xff0c;在线培训已成为教育行业的重要趋势之一。为了提供更好的学习体验和管理服务&#xff0c;在线培训机构需要构建高效的CRM&#xff08;Customer Relationship Management&#xff09;应用架构。本文将探讨在线培训机构CRM应用架构的设计与实践。 一、…

力扣周赛398题解

特殊数组Ⅰ 如果数组的每一对相邻元素都是两个奇偶性不同的数字&#xff0c;则该数组被认为是一个 特殊数组 。 Aging 有一个整数数组 nums。如果 nums 是一个 特殊数组 &#xff0c;返回 true&#xff0c;否则返回 false。 示例 1&#xff1a; 输入&#xff1a;nums [1] …

数据结构和算法|排序算法系列(二)|冒泡排序

首先需要你对排序算法的评价维度和一个理想排序算法应该是什么样的有一个基本的认知&#xff1a; 《Hello算法之排序算法》 主要内容来自&#xff1a;Hello算法11.3 冒泡排序 我觉得冒泡排序非常有意思&#xff0c;也非常简单&#xff0c;就是不停地交换相邻的元素即可&#…

代码随想录算法训练营第四天| 24. 两两交换链表中的节点、19.删除链表的倒数第N个节点 、 面试题 02.07. 链表相交、142.环形链表II

24. 两两交换链表中的节点 题目链接&#xff1a; 24. 两两交换链表中的节点 文档讲解&#xff1a;代码随想录 状态&#xff1a;没做出来&#xff0c;没有正确更新头节点&#xff0c;因为head和cur共享引用&#xff0c;会随着cur的移动&#xff0c;丢失之前存放的节点 错误代码&…

腾讯发布ELLA:为扩散模型注入LLM能力,提升复杂场景的图像生成,准确率超90%

前言 近年来&#xff0c;基于扩散模型的文本到图像生成技术取得了显著进步&#xff0c;能够生成高质量、逼真的图像。然而&#xff0c;大多数扩散模型仍然使用CLIP作为文本编码器&#xff0c;这限制了它们理解复杂提示的能力&#xff0c;例如包含多个物体、详细属性、复杂关系…

摄像头应用测试

作者简介&#xff1a; 一个平凡而乐于分享的小比特&#xff0c;中南民族大学通信工程专业研究生在读&#xff0c;研究方向无线联邦学习 擅长领域&#xff1a;驱动开发&#xff0c;嵌入式软件开发&#xff0c;BSP开发 作者主页&#xff1a;一个平凡而乐于分享的小比特的个人主页…

MySQL(一) 库和表的基础操作

1. 数据库基础 1.1 什么是数据库 存储数据用文件就可以了&#xff0c;为什么还要弄个数据库? 文件保存数据有以下几个缺点&#xff1a; 文件的安全性问题文件不利于数据查询和管理文件不利于存储海量数据文件在程序中控制不方便 数据库存储介质&#xff1a;磁盘内存 为了解…

学 C/C++ 具体能干什么?

学习 C 和 C 后&#xff0c;你可以从事许多不同的工作和项目&#xff0c;这两种语言以其高性能和低级控制而闻名&#xff0c;特别适合以下几个领域&#xff1a; 1. 系统编程 C 和 C 是系统编程的首选语言&#xff0c;适用于操作系统、驱动程序和嵌入式系统开发。 操作系统开发…

PgMP:项目集管理,哪些人适合学习?

美国项目管理协会&#xff08;PMI&#xff09;对项目集经理&#xff08;Program Manager&#xff09;的角色做出如下的定义&#xff1a; 在最少的领导/监督下&#xff0c;项目集经理PgMP负责在商业和组织目的下协调管理多个相关项目。这些项目含有跨部门、组织、地理区域…

【kubernetes】探索k8s集群中金丝雀发布后续 + 声明式资源管理yaml

目录 一、K8S常见的发布方式 1.1蓝绿发布 1.2灰度发布&#xff08;金丝雀发布&#xff09; 1.3滚动发布 二、金丝雀发布 三、声明式管理方法 3.1YAML 语法格式 3.1.1查看 api 资源版本标签 3.1.2查看资源简写 3.2YAML文件详解 3.2.1Deployment.yaml 3.2.2Pod.yaml …

国际版Tiktok抖音运营流量实战班:账号定位/作品发布/热门推送/等等-13节

课程目录 1-tiktok账号定位 1.mp4 2-tiktok作品发布技巧 1.mp4 3-tiktok数据功能如何开通 1.mp4 4-tiktok热门视频推送机制 1.mp4 5-如何发现热门视频 1.mp4 6-如何发现热门音乐 1.mp4 7-如何寻找热门标签 1.mp4 8-如何寻找垂直热门视频 1.mp4 9-如何发现热门挑战赛 1…

【C语言回顾】编译和链接

前言1. 编译2. 链接结语 上期回顾: 【C语言回顾】文件操作 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;【C语言学习】 前言 各位小伙伴大家好&#xff01;上期小编给大家讲解了C语言中的文件操作&#xff0c;接下来我们讲解一下编译和链接&#xff01; 1. 编译 预处理…

C++11 线程库

C11 线程库 一.thread类1.介绍1.框架2.构造3.赋值4.join与joinable5.id和get_id6.this_thread命名空间7.yield8.演示 二.锁类1.互斥锁1.介绍2.使用1.配合lambda来使用2.ref 2.递归锁和时间锁1.递归锁介绍2.例子3.时间锁介绍 三.RAII管理锁类1.lock_guard1.介绍2.使用3.好处与不…

AOP总结

AOP是什么 AOP是面向切面编程&#xff0c;其目的是将横切关注点从核心业务代码中分离出来&#xff0c;通过动态代理等方式&#xff0c;实现代码的增强和解耦&#xff0c;使得其具有更好的可维护性和可扩展性。 其中横切关注点是多个类或对象的公共行为&#xff0c;如事务管理…

五种独立成分分析(ICA)

代码原理及流程 代码实现了混合信号的独立成分分析&#xff08;ICA&#xff09;过程&#xff0c;主要包括以下几个步骤&#xff1a; 原始语音信号读取与显示&#xff1a;首先读入原始的两个语音信号(music.wav和man.wav)&#xff0c;并显示在图中的第一和第二个子图中。混合声…

mfc140.dll丢失原因和mfc140.dll丢失修复办法分享

mfc140.dll是与微软基础类库&#xff08;Microsoft Foundation Classes, MFC&#xff09;紧密相关的动态链接库&#xff08;DLL&#xff09;文件。MFC是微软为C开发者设计的一个应用程序框架&#xff0c;用于简化Windows应用程序的开发工作。以下是mfc140.dll文件的一些关键属性…

项目管理:敏捷实践框架

一、初识敏捷 什么是敏捷(Agile)?敏捷是思维方式。 传统开发模型 央企,国企50%-60%需求分析。整体是由文档控制的过程管理。 传统软件开发面临的问题: 交付周期长:3-6个月甚至更长沟通效果差:文档化沟通不及时按时发布低:技术债增多无法发版团队士气弱:死亡行军不关注…

如何安装虚拟机Wmware,并且在虚拟机中使用centos系统

1. 前言 大家好&#xff0c;我是jiaoxingk 本篇文章主要讲解如何安装虚拟机&#xff0c;并且在虚拟机中安装centos系统&#xff0c;让windows电脑也能够使用Linux系统 2. 虚拟机的介绍 在安装Vmware之前&#xff0c;我们先做虚拟机的介绍 虚拟机&#xff1a;通过软件虚拟出来的…