1 Star 1 Fork 0

小龙/leetcode-master

Create your Gitee Account
Explore and code with more than 13.5 million developers,Free private repositories !:)
Sign up
文件
pics
problems
前序
周总结
0001.两数之和.md
0015.三数之和.md
0017.电话号码的字母组合.md
0018.四数之和.md
0019.删除链表的倒数第N个节点.md
0020.有效的括号.md
0024.两两交换链表中的节点.md
0027.移除元素.md
0028.实现strStr.md
0035.搜索插入位置.md
0037.解数独.md
0039.组合总和.md
0040.组合总和II.md
0045.跳跃游戏II.md
0046.全排列.md
0047.全排列II.md
0051.N皇后.md
0053.最大子序和.md
0053.最大子序和(动态规划).md
0055.跳跃游戏.md
0056.合并区间.md
0059.螺旋矩阵II.md
0062.不同路径.md
0063.不同路径II.md
0070.爬楼梯.md
0070.爬楼梯完全背包版本.md
0072.编辑距离.md
0077.组合.md
0077.组合优化.md
0078.子集.md
0090.子集II.md
0093.复原IP地址.md
0096.不同的二叉搜索树.md
0098.验证二叉搜索树.md
0101.对称二叉树.md
0102.二叉树的层序遍历.md
0104.二叉树的最大深度.md
0106.从中序与后序遍历序列构造二叉树.md
0108.将有序数组转换为二叉搜索树.md
0110.平衡二叉树.md
0111.二叉树的最小深度.md
0112.路径总和.md
0115.不同的子序列.md
0121.买卖股票的最佳时机.md
0122.买卖股票的最佳时机II.md
0122.买卖股票的最佳时机II(动态规划).md
0123.买卖股票的最佳时机III.md
0131.分割回文串.md
0134.加油站.md
0135.分发糖果.md
0139.单词拆分.md
0142.环形链表II.md
0150.逆波兰表达式求值.md
0151.翻转字符串里的单词.md
0188.买卖股票的最佳时机IV.md
0198.打家劫舍.md
0202.快乐数.md
0203.移除链表元素.md
0206.翻转链表.md
0209.长度最小的子数组.md
0213.打家劫舍II.md
0216.组合总和III.md
0222.完全二叉树的节点个数.md
0225.用队列实现栈.md
0226.翻转二叉树.md
0232.用栈实现队列.md
0235.二叉搜索树的最近公共祖先.md
0236.二叉树的最近公共祖先.md
0239.滑动窗口最大值.md
0242.有效的字母异位词.md
0257.二叉树的所有路径.md
0279.完全平方数.md
0300.最长上升子序列.md
0309.最佳买卖股票时机含冷冻期.md
0322.零钱兑换.md
0332.重新安排行程.md
0337.打家劫舍III.md
0343.整数拆分.md
0344.反转字符串.md
0347.前K个高频元素.md
0349.两个数组的交集.md
0376.摆动序列.md
0377.组合总和Ⅳ.md
0383.赎金信.md
0392.判断子序列.md
0404.左叶子之和.md
0406.根据身高重建队列.md
0416.分割等和子集.md
0435.无重叠区间.md
0450.删除二叉搜索树中的节点.md
0452.用最少数量的箭引爆气球.md
0454.四数相加II.md
0455.分发饼干.md
0459.重复的子字符串.md
0474.一和零.md
0491.递增子序列.md
0494.目标和.md
0501.二叉搜索树中的众数.md
0509.斐波那契数.md
0513.找树左下角的值.md
0516.最长回文子序列.md
0518.零钱兑换II.md
0530.二叉搜索树的最小绝对差.md
0538.把二叉搜索树转换为累加树.md
0541.反转字符串II.md
0583.两个字符串的删除操作.md
0617.合并二叉树.md
0647.回文子串.md
0654.最大二叉树.md
0669.修剪二叉搜索树.md
0674.最长连续递增序列.md
0700.二叉搜索树中的搜索.md
0701.二叉搜索树中的插入操作.md
0704.二分查找.md
0707.设计链表.md
0714.买卖股票的最佳时机含手续费.md
0714.买卖股票的最佳时机含手续费(动态规划).md
0718.最长重复子数组.md
0738.单调递增的数字.md
0739.每日温度.md
0746.使用最小花费爬楼梯.md
0763.划分字母区间.md
0860.柠檬水找零.md
0968.监控二叉树.md
0977.有序数组的平方.md
1005.K次取反后最大化的数组和.md
1035.不相交的线.md
1047.删除字符串中的所有相邻重复项.md
1049.最后一块石头的重量II.md
1143.最长公共子序列.md
O(n)的算法居然超时了,此时的n究竟是多大?.md
为了绝杀编辑距离,卡尔做了三步铺垫.md
二叉树中递归带着回溯.md
二叉树总结篇.md
二叉树理论基础.md
二叉树的统一迭代法.md
二叉树的迭代遍历.md
二叉树的递归遍历.md
关于时间复杂度,你不知道的都在这里!.md
剑指Offer05.替换空格.md
剑指Offer58-II.左旋转字符串.md
动态规划-股票问题总结篇.md
动态规划总结篇.md
动态规划理论基础.md
双指针总结.md
哈希表总结.md
哈希表理论基础.md
回溯总结.md
回溯算法去重问题的另一种写法.md
回溯算法理论基础.md
字符串总结.md
数组总结篇.md
数组理论基础.md
栈与队列总结.md
栈与队列理论基础.md
根据身高重建队列(vector原理讲解).md
算法模板.md
背包总结篇.md
背包理论基础01背包-1.md
背包理论基础01背包-2.md
背包问题理论基础多重背包.md
背包问题理论基础完全背包.md
贪心算法总结篇.md
贪心算法理论基础.md
链表总结篇.md
链表理论基础.md
面试题02.07.链表相交.md
README.md
This repository doesn't specify license. Please pay attention to the specific project description and its upstream code dependency when using it.
Clone or Download
0134.加油站.md 9.92 KB
Copy Edit Raw Blame History
yangxk201396 authored 4 years ago . Update 0134.加油站.md

欢迎大家参与本项目,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!

134. 加油站

题目链接:https://leetcode-cn.com/problems/gas-station/

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

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

如果你可以绕环路行驶一周,则返回出发时加油站的编号,否则返回 -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 可为起始索引。

示例 2: 输入: gas = [2,3,4] cost = [3,4,3]

输出: -1 解释: 你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。 我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油 开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油 开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油 你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。 因此,无论怎样,你都不可能绕环路行驶一周。

暴力方法

暴力的方法很明显就是O(n^2)的,遍历每一个加油站为起点的情况,模拟一圈。

如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个起点是ok的。

暴力的方法思路比较简单,但代码写起来也不是很容易,关键是要模拟跑一圈的过程。

for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while!

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        for (int i = 0; i < cost.size(); i++) {
            int rest = gas[i] - cost[i]; // 记录剩余油量
            int index = (i + 1) % cost.size();
            while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈
                rest += gas[index] - cost[index];
                index = (index + 1) % cost.size();
            }
            // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
            if (rest >= 0 && index == i) return i;
        }
        return -1;
    }
};
  • 时间复杂度O(n^2)
  • 空间复杂度O(n)

C++暴力解法在leetcode上提交也可以过。

贪心算法(方法一)

直接从全局进行贪心选择,情况如下:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的

  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。

  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能这个负数填平,能把这个负数填平的节点就是出发节点。

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int min = INT_MAX; // 从起点出发,油箱里的油量最小值
        for (int i = 0; i < gas.size(); i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            if (curSum < min) {
                min = curSum;
            }
        }
        if (curSum < 0) return -1;  // 情况1
        if (min >= 0) return 0;     // 情况2
                                    // 情况3
        for (int i = gas.size() - 1; i >= 0; i--) {
            int rest = gas[i] - cost[i];
            min += rest;
            if (min >= 0) {
                return i;
            }
        }
        return -1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

其实我不认为这种方式是贪心算法,因为没有找出局部最优,而是直接从全局最优的角度上思考问题

但这种解法又说不出是什么方法,这就是一个从全局角度选取最优解的模拟操作。

所以对于本解法是贪心,我持保留意见!

但不管怎么说,解法毕竟还是巧妙的,不用过于执着于其名字称呼。

贪心算法(方法二)

可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,起始位置从i+1算起,再从0计算curSum。

如图: 134.加油站

那么为什么一旦[i,j] 区间和为负数,起始位置就可以是j+1呢,j+1后面就不会出现更大的负数?

如果出现更大的负数,就是更新j,那么起始位置又变成新的j+1了。

而且j之前出现了多少负数,j后面就会出现多少正数,因为耗油总和是大于零的(前提我们已经确定了一定可以跑完全程)。

那么局部最优:当前累加rest[j]的和curSum一旦小于0,起始位置至少要是j+1,因为从j开始一定不行。全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优,找不出反例,试试贪心!

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for (int i = 0; i < gas.size(); i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {   // 当前累加rest[i]和 curSum一旦小于0
                start = i + 1;  // 起始位置更新为i+1
                curSum = 0;     // curSum从0开始
            }
        }
        if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
        return start;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

说这种解法为贪心算法,才是是有理有据的,因为全局最优解是根据局部最优推导出来的

总结

对于本题首先给出了暴力解法,暴力解法模拟跑一圈的过程其实比较考验代码技巧的,要对while使用的很熟练。

然后给出了两种贪心算法,对于第一种贪心方法,其实我认为就是一种直接从全局选取最优的模拟操作,思路还是好巧妙的,值得学习一下。

对于第二种贪心方法,才真正体现出贪心的精髓,用局部最优可以推出全局最优,进而求得起始位置。

其他语言版本

Java:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int sum = 0;
        int min = 0;
        for (int i = 0; i < gas.length; i++) {
            sum += (gas[i] - cost[i]);
            min = Math.min(sum, min);
        }

        if (sum < 0) return -1;
        if (min >= 0) return 0;

        for (int i = gas.length - 1; i > 0; i--) {
            min += (gas[i] - cost[i]);
            if (min >= 0) return i;
        }

        return -1;
    }
}

Python:

class Solution:
    def canCompleteCircuit(self, gas: List[int], cost: List[int]) -> int:
        start = 0
        curSum = 0
        totalSum = 0
        for i in range(len(gas)):
            curSum += gas[i] - cost[i]
            totalSum += gas[i] - cost[i]
            if curSum < 0:
                curSum = 0
                start = i + 1
        if totalSum < 0: return -1
        return start

Go:

func canCompleteCircuit(gas []int, cost []int) int {
	curSum := 0
	totalSum := 0
	start := 0
	for i := 0; i < len(gas); i++ {
		curSum += gas[i] - cost[i]
		totalSum += gas[i] - cost[i]
		if curSum < 0 {
			start = i+1
			curSum = 0
		}
	}
	if totalSum < 0 {
		return -1
	}
	return start
}

Javascript:

var canCompleteCircuit = function(gas, cost) {
    const gasLen = gas.length
    let start = 0
    let curSum = 0
    let totalSum = 0

    for(let i = 0; i < gasLen; i++) {
        curSum += gas[i] - cost[i]
        totalSum += gas[i] - cost[i]
        if(curSum < 0) {
            curSum = 0
            start = i + 1
        }
    }

    if(totalSum < 0) return -1

    return start
};

Loading...
马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
1
https://gitee.com/buleli/leetcode-master.git
git@gitee.com:buleli/leetcode-master.git
buleli
leetcode-master
leetcode-master
master

Search