zoukankan      html  css  js  c++  java
  • 134. Gas Station

    今天又是刷leetcode的一天。
    太难受了,今天第2道题就不会了,看了别人的解答才会的,记录一下吧,自己太笨了。

    题目是134. Gas Station

    这个问题如果用暴力算法去做的话是很简单的,就是从0出发到最后一个位置,以每个位置为起点试探一下,看看走一圈能不能回到自己,复杂度是O(n^2),显然,这种方法会超时的。

    于是我们需要去思考这其中是不是做了一些不必要的重复计算呢?

    我们可以根据题目的gas数组和cost数组得到一个remain数组,计算方法如下:
    remian = gas - cost

    那么这个remain数组是什么意思呢,它的每一项代表的就是从当前位置出发到达下一个位置能够剩余的汽油数量

    假设从第i个位置出发能到达最远的一个位置是j,言外之意是说最近一个无法到达的位置是(j+1)%n,其中n是gas数组的长度。

    既然从第i个位置出发没法到达第j+1个位置,那么问题来了,从第i+1,i+2....j个位置出发,能不能到达第j+1个位置呢?

    答案是不能,下面来简单证明一下:

    记从第i个结点出发到达第j个结点后剩余的油量为:ri->j
    举个例子,r1->2 = gas[1] - cost[1], r1->3 = gas[1] - cost[1] + gas[2] - cost[2]

    其实我们会发现,ri->j = remain[i] + ... + remian[j-1]

    现在从i出发能到达j,说明ri->j >= 0,同时,能到j说明也能到i+1,所以ri->i+1 >= 0
    而从i出发不能到达j+1,说明ri->j+1 < 0

    而ri->j+1 = ri->i+1 + ri+1->j+1

    说明ri->i+1 + ri+1->j+1 < 0

    故ri+1->j+1 < -ri->i+1

    故ri+1->j+1 < 0

    于是说明从i+1出发也是无法到达j+1的。

    同理,对于上面的证明过程,我们可以把i+1换成i+2,...j,我们同样可以证明,从i+2,...j出发,也依然无法达到j+1。

    这说明什么,这说明,一旦从某个位置i出发,到了最近一个到不了的位置j+1,那说明这中间的位置i,i+1,...j-1,j都不可能是起始出发的位置,我们只需要从第j+1个位置开始,再往后面寻找下一个位置。

    其实这也正好回答了我们上面的问题,就是暴力法搜索过程中为什么存在很多不必要的重复计算,所以我们再想想经过这么一顿优化之后算法的复杂度变为多少了?

    没错,算法复杂度变成O(n)了,走一遍就行。

    代码如下

    // Gas Station.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
    //
    
    #include <iostream>
    #include <vector>
    
    using namespace std;
    
    class Solution {
    public:
        int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
            int n = gas.size();
            int sum = 0, total = 0, idx = 0;
    
            for (int i = 0; i < n; i++) {
                sum += gas[i] - cost[i];
                if (sum < 0) {
                    total += sum;
                    sum = 0;
                    idx = i + 1;
                }
            }
    
            total += sum;
            if (total < 0) return -1;
            else return idx;
        }
    };
    

    稍微解释一下代码,从0号位置出发,我们每次累加当前的remian[i],如果发现到了某个位置sum小于0,那么我们就从把i+1设为起始位置,有人说为什么不是i,因为:
    r0->1 = remain[0]
    r0->2 = remain[0] + remain[1]
    r0->3 = remain[0] + remain[1] + remian[2]

    一旦发现ri->j < 0,说明remain[i] + remain[i+1] +...+ remian[j-1] < 0,说明下一个起始位置应该是j。
    反过来说,当我们sum累加到remian[i]的时候小于0,说明下一个位置是i+1

    一旦sum小于0了自然我们要重置sum为0,可是这里我们为什么还有一个total变量,你想啊,sum变量只是记录了从最近一个起始点i出发到当前位置之间的remian的累加值,
    而这个累加值在最后大于0也只能说明从i出发能到当前位置,并不能说明从i出发就能绕一圈,所以我们total变量就是记录整个一圈过程中的每个位置的remain的累加值。

    如果最后total的值大于或者等于0,说明走完一圈下来还有油量剩余或者刚好用完,如果小于0,则说明我们试探了所有可能的位置,也没能走完一圈。

    起始这个total变量你可以另外用一个O(n)的循环来计算,毕竟它就是remain[0] + ... + remain[n-1] (假设gas数组长度为n),融入到这个循环里面只是为了把复杂度从O(2n)降低到O(n)。

    只有0和1的世界是简单的
  • 相关阅读:
    数据结构笔记
    简单数学
    分析代码练习--长期目标
    C#基础--面向对象计算器
    经常喜欢看的网站
    C#基础--面向过程计算器
    C#中的五个访问修饰符
    SQLServer 游标详解
    快速产生大量顺序数字序列
    VSCode 必装的 10 个高效开发插件
  • 原文地址:https://www.cnblogs.com/nullxjx/p/14242017.html
Copyright © 2011-2022 走看看