【问题标题】:How to find minimum number of jumps to reach the end of the array in O(n) time如何在 O(n) 时间内找到到达数组末尾的最小跳转次数
【发布时间】:2015-03-07 15:27:03
【问题描述】:

问题

给定一个整数数组,其中每个元素表示可以从该元素向前执行的最大步数。 编写一个函数来返回到达 数组的结尾(从第一个元素开始)。如果一个元素是 0,则无法通过该元素。

示例

输入:arr[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}
输出:3 (1-> 3 -> 8 ->9)

找到从Dynamic Programming approach 到其他线性方法的多种方法。我无法理解所谓的线性时间方法。 HERE 是提出线性方法的链接。

我完全无法理解。我能理解的是作者建议做一个贪婪的方法,看看我们是否到达终点..如果没有,那么回溯?

【问题讨论】:

  • 我不明白解决方案.. 为什么不是 (1-> 3 -> 9)?

标签: java arrays algorithm


【解决方案1】:

网站上提出的解决方案的时间复杂度是线性的,因为您只对数组进行一次迭代。该算法通过使用一些巧妙的技巧避免了我提出的解决方案的内部迭代。

变量maxReach 始终存储数组中的最大可达位置。 jump 存储到达该位置所需的跳跃量。 step 存储了我们仍然可以采取的步数(并使用第一个数组位置的步数进行初始化)

在迭代过程中,上述值更新如下:

首先我们测试我们是否已经到达数组的末尾,在这种情况下我们只需要返回jump变量。

接下来我们更新最大可达位置。这等于maxReachi+A[i] 的最大值(我们可以从当前位置采取的步数)。

我们用了一个步骤来获取当前索引,因此必须减少 steps

如果没有剩余步骤(即steps=0,那么我们一定使用了跳转。因此增加jump。由于我们知道有可能以某种方式到达maxReach,我们将步骤初始化为数量从i 位置到达maxReach 的步骤。

public class Solution {
    public int jump(int[] A) {
        if (A.length <= 1)
            return 0;
        int maxReach = A[0];
        int step = A[0];
        int jump = 1;
        for (int i = 1; i < A.length; i++) {
           if (i == A.length - 1)
                return jump;
            if (i + A[i] > maxReach)
                maxReach = i + A[i];
            step--;
            if (step == 0) {
                jump++;
                step = maxReach - i;
            } 
        }
        return jump;
    }
}

例子:

int A[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}
int maxReach = A[0];     // A[0]=1, so the maximum index we can reach at the moment is 1.
int step = A[0];         // A[0] = 1, the amount of steps we can still take is also 1.
int jump = 1;            // we will always need to take at least one jump.

/*************************************
 * First iteration (i=1)
 ************************************/
if (i + A[i] > maxReach) // 1+3 > 1, we can reach further now!
    maxReach = i + A[i]  // maxReach = 4, we now know that index 4 is the largest index we can reach.

step--                   // we used a step to get to this index position, so we decrease it
if (step == 0) {
    ++jump;              // we ran out of steps, this means that we have made a jump
                         // this is indeed the case, we ran out of the 1 step we started from. jump is now equal to 2.
                         // but we can continue with the 3 steps received at array position 2.
    steps = maxReach-i   // we know that by some combination of 2 jumps, we can reach  position 4.
                         // therefore in the current situation, we can minimaly take 3
                         // more steps to reach position 4 => step = 3
}

/*************************************
 * Second iteration (i=2)
 ************************************/
if (i + A[i] > maxReach) // 2+5 > 4, we can reach further now!
    maxReach = i + A[i]  // maxReach = 7, we now know that index 7 is the largest index we can reach.

step--                   // we used a step so now step = 2
if (step==0){
   // step 
}

/*************************************
 * Second iteration (i=3)
 ************************************/
if (i + A[i] > maxReach) // 3+8 > 7, we can reach further now!
    maxReach = i + A[i]  // maxReach = 11, we now know that index 11 is the largest index we can reach.

step--                   // we used a step so now step = 1
if (step==0){
   // step 
}

/*************************************
 * Third iteration (i=4)
 ************************************/
if (i + A[i] > maxReach) // 4+9 > 11, we can reach further now!
    maxReach = i + A[i]  // maxReach = 13, we now know that index 13 is the largest index we can reach.

step--                   // we used a step so now step = 0
if (step == 0) {
    ++jump;              // we ran out of steps, this means that we have made a jump.
                         // jump is now equal to 3.
    steps = maxReach-i   // there exists a combination of jumps to reach index 13, so
                         // we still have a budget of 9 steps
}


/************************************
 * remaining iterations
 ***********************************
// nothing much changes now until we reach the end of the array.

我的次优算法在O(nk) 时间内工作,n 是数组中的元素数,k 是数组中的最大元素,并使用array[i] 上的内部循环。下面的算法避免了这个循环。

代码

public static int minimum_steps(int[] array) {
    int[] min_to_end = new int[array.length];
    for (int i = array.length - 2; i >= 0; --i) {
        if (array[i] <= 0)
            min_to_end[i] = Integer.MAX_VALUE;
        else {
            int minimum = Integer.MAX_VALUE;
            for (int k = 1; k <= array[i]; ++k) {
                if (i + k < array.length)
                    minimum = Math.min(min_to_end[i+k], minimum);
                else
                    break;
            }
            min_to_end[i] = minimum + 1;
        }
    }
    return min_to_end[0];
} 

【讨论】:

  • 为什么是线性时间?
  • 我认为这不是线性时间。需要一个及时的线性解决方案。
  • 来吧。非常感谢您抽出时间回复。没有理由道歉:)。如果您对线性时间解决方案有任何想法,请告诉我。我分享的 leet 代码链接声称具有线性时间解决方案。只是我无法理解它。你能看一下吗
  • @NielsBillen 能否请您稍微解释一下您的解决方案及其复杂性
  • @NielsBillen - 函数 jump 永远不会失败(例如:A[] = {0,1}A[]={1,3,1,0,0,0,0,1},应该会失败,根据问题描述)。你不应该在循环的开头添加一个条件:if (steps == 0) return INT_MAX;,并在函数结束时添加一个返回(尽管你永远不应该到达那里):return INT_MAX;?,thnx!
【解决方案2】:

这是另一个线性解决方案。该代码比leet代码链接中建议的要长,但我认为它更容易理解。它基于两个观察结果:到达i + 1 位置所需的步数永远不会少于到达i 位置所需的步数,并且每个元素将其值+1 分配给i + 1 ... i + a[i]段。

public class Solution {
    public int jump(int[] a) {
        int n = a.length;
        // count[i] is the number of "open" segments with value i
        int[] count = new int[n];
        // the number of steps to reach the i-th position
        int[] dp = new int[n];
        Arrays.fill(dp, n);
        // toDelete[i] is the list of values of segments 
        // that close in the i-th position
        ArrayList<Integer>[] toDelete = new ArrayList[n];
        for (int i = 0; i < n; i++)
            toDelete[i] = new ArrayList<>();
        // Initially, the value is 0(for the first element).
        toDelete[0].add(0);
        int min = 0;
        count[0]++;
        for (int i = 0; i < n; i++) {
            // Finds the new minimum. It uses the fact that it cannot decrease. 
            while (min < n && count[min] == 0)
                min++;
            // If min == n, then there is no path. So we can stop.
            if (min == n)
                break;
            dp[i] = min;
            if (dp[i] + 1 < n) {
                // Creates a new segment from i + 1 to i + a[i] with dp[i] + 1 value
                count[dp[i] + 1]++;
                if (i + a[i] < n)
                    toDelete[i + a[i]].add(dp[i] + 1);
            }
            // Processes closing segments in this position.
            for (int deleted : toDelete[i])
                count[deleted]--;
        }
        return dp[n - 1];
    }
}

复杂性分析:

  1. toDelete 列表中的元素总数为 O(n)。之所以如此,是因为在每个位置 i 最多添加一个元素。这就是为什么处理所有toDelete 列表中的所有元素需要线性时间的原因。

  2. min 值只能增加。这就是为什么内部while 循环总共最多进行n 迭代。

  3. 外部for 循环显然使n 迭代。因此,时间复杂度是线性的。

【讨论】:

  • 非常感谢。您能否对代码和时间复杂度进行更多解释。当您说“到达 i + 1 位置所需的步数永远不会少于到达 i 所需的步数”时,您的意思是到达 i+1 的#steps 总是小于到达 i 的 #steps从特定位置 j .. 对吗?
  • @Walt 关于步数:意思是dp[i] &lt;= dp[i + 1]代表所有i
  • 非常感谢@ILoveCoding 添加复杂性分析。但我还不能理解算法。 :( 如果你也可以在那里找到一些 cmets。或者它是如何工作的
  • @Walt 暂时,让我们回忆一个简单的解决方案:对于每个 i,迭代 j = i + 1 ... i + a[i]:dp[j] = min(dp[ j],dp[i] + 1)。所以它创建了一个段 [i + 1, i + a[i]] 与 dp[i] + 1 值。除了以这种方式迭代,我们可以维护一组“开放”段及其值(开放意味着 k + 1
【解决方案3】:

聚会迟到了几年,但这是另一个对我有意义的 O(n) 解决方案。

/// <summary>
/// 
/// The actual problem is if it's worth not to jump to the rightmost in order to land on a value that pushes us further than if we jumped on the rightmost.
/// 
/// However , if we approach the problem from the end,  we go end to start,always jumping to the leftmost
/// 
/// with this approach , these is no point in not jumping to the leftmost from end to start , because leftmost will always be the index that has the leftmost leftmost :) , so always choosing leftmost is the fastest way to reach start
/// 
/// </summary>
/// <param name="arr"></param>
static void Jumps (int[] arr)
{
    var LeftMostReacher = new int[arr.Length];
    //let's see , for each element , how far back can it be reached from 

    LeftMostReacher[0] = -1; //the leftmost reacher of 0 is -1

    var unReachableIndex = 1; //this is the first index that hasn't been reached by anyone yet
    //we use this unReachableIndex var so each index's leftmost reacher is  the first that was able to jump to it . Once flagged by the first reacher , new reachers can't be the leftmost anymore so they check starting from unReachableIndex

    // this design insures that the inner loop never flags the same index twice , so the runtime of these two loops together is O(n)

    for (int i = 0; i < arr.Length; i++)
    {
        int maxReach = i + arr[i];

        for (; unReachableIndex <= maxReach && unReachableIndex < arr.Length; unReachableIndex++)
        {

            LeftMostReacher[unReachableIndex] = i;
        }

    }

    // we just go back from the end and then reverse the path

    int index = LeftMostReacher.Length - 1;
    var st = new Stack<int>();

    while (index != -1)
    {
        st.Push(index);
        index = LeftMostReacher[index];
    }

    while (st.Count != 0)
    {
        Console.Write(arr[st.Pop()] + "  ");
    }
    Console.WriteLine();
}
static void Main ()
{
    var nrs = new[] { 1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9 };
    Jumps(nrs);
}

【讨论】:

    【解决方案4】:

    这是关于上述问题的贪婪方法的基本直觉,剩下的就是代码要求。

    给定数组是输入:a[] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}。

    现在我们从第一个元素开始,即 i=0 和 a[i] = 1。所以看到这个我们最多可以跳跃 1 的大小,所以我们没有其他选择,所以我们做这个步骤发生。

    目前我们处于 i=1 和 a[i]=3。因此,我们目前可以进行大小为 3 的跳跃,但我们会考虑从当前位置可以进行的所有可能的跳跃,并获得在(数组的)范围内的最大距离。那么我们的选择是什么?我们可以跳1步,或2步或3步。因此,我们从当前位置调查每个大小跳跃,并选择可以使我们最大程度地进入数组的那个。

    一旦我们决定了坚持哪一个,我们就会采用该跳跃大小并更新我们迄今为止完成的跳跃次数,以及我们最多可以到达的位置以及我们现在有多少步来决定下一步行动。就是这样。这就是我们最终选择线性遍历数组的最佳选项的方式。 所以这是您可能正在寻找的算法的基本思想,接下来是对其进行编码以使算法工作。干杯!

    希望有人穿越时空发现直觉很有帮助! :) :P “派对迟到了几年”@Vasilescu Andrei - 说得好。有时我觉得我们是时间旅行者。

    【讨论】:

    • 很好的解释
    【解决方案5】:

    关于到达终点的最小跳转次数问题的简单 Python 代码。

    ar=[1, 3, 6, 3, 2, 3, 6, 8, 9, 5]
    minJumpIdx=0
    res=[0]*len(ar)
    i=1
    while(i<len(ar) and i>minJumpIdx):
        if minJumpIdx+ar[minJumpIdx]>=i:
            res[i]=res[minJumpIdx]+1
            i+=1
        else:
            minJumpIdx+=1
    if res[-1]==0:
        print(-1)
    else:
        print(res[-1])
    

    【讨论】:

      【解决方案6】:

      好的,我花了很多时间来理解 O(n) 算法,我会尽量用最简单的方式解释逻辑:

      在数组中的每个“i”处,您可以通过该值知道 currentFarthest 值是多少,您可以达到最高值,以及 currentEnd 值,每当您点击 currentEnd 值时,您就知道是时候进行跳跃了并用 currentFarthest 更新 currentEnd。

      下面的图片可能会有所帮助:

      【讨论】:

        【解决方案7】:

        我已经用 Python 做到了这一点。 使用简单术语的不太复杂的代码。这可能会对您有所帮助。

        def minJump(a):
            end=len(a)
            count=0
            i=a[0]
            tempList1=a
            while(i<=end):
                if(i==0):
                    return 0
                tempList1=a[count+1:count+i+1]
                max_index=a.index(max(tempList1))
                count+=1
                i=a[max_index]
                end=end-max_index
            return count+1
        

        【讨论】:

          【解决方案8】:
          static void minJumps(int a[] , int n)
          {
              int dp[] = new int[n];
              dp[0] = 0;  //As the min jumps needed to get to first index is zero only.
              //Fill the rest of the array with INT_MAX val so we can make math.min comparisions.
              for(int i=1;i<n;i++)
                  dp[i] = Integer.MAX_VALUE;
          
              for(int i=1;i<n;i++)
              {
                  for(int j=0;j<i;j++)
                  {   //If we have enough jumps from the position j to reach i. 
                      if(j+a[j]>=i)    
                      {   //Take the min of current stored value & jumps req to 
                        //reach i from j by getting jumps req to reach j plus 1.
                        //(Plus 1 because since we have enough jumps to reach 1 from j we 
                        //simply add 1 by taking the jumps required to reach j.)
                          dp[i] = Math.min(dp[i],dp[j]+1);
                      }
                  }
              }
          
              //If first element has zero jumps in store or if the final jumps value 
              //becomes MAX value because there's an element in between which gives zero
              //jumps.
              if(a[0]==0 || dp[n-1] == Integer.MAX_VALUE ) 
              System.out.println("-1");
          
              else System.out.println(dp[n-1]);
          }
          

          【讨论】:

            【解决方案9】:

            到目前为止,这里的许多答案都很棒,但我觉得我可以帮助解释为什么该算法是正确的以及它背后的直觉。

            我喜欢这个问题,因为这是直观的动态编程方法在O(n^2)最坏情况下运行的问题,以及贪婪的方法(激发这个问题的方法) ) 在 O(n) 最坏情况下运行(它实际上只访问数组的每个元素一次)。这个算法对我来说也有点让人想起 Dijkstra 的算法,它解决了另一个单源最短路径问题,这也是贪婪的。

            首先,请记住问题陈述中A[i] 拥有您可以从该索引跳转到的最大位置,但您可以从@ 进行更短的跳转 987654324@ 如果A[i]&gt;1,那么从i=0 开始的最短跳转序列可能是比每个索引允许的跳转更短。这一点很重要,因为您会看到算法从不考虑那些较小的跳跃或它们的位置显式

            其次,将您提到的算法想象成一种算法会有所帮助通过阵列前进。

            第三,请注意,该算法跟踪可能是 a 最短序列的特定索引 i输入数组A 的开头到结尾。实际上,算法只是在“用完绳子”(从上一个绳子)时增加变量jump(它给自己一个新绳子),这样它就可以在主循环中不断迭代“尝试” " 到达终点。

            更具体地说,要使算法正确,它需要:

            1. 跟踪从每个位置i 在阵列中向前移动时“可以到达多远” (maxReach)。请注意,如果当时已经很清楚到达该新位置 需要花费更多时间,则此数量会针对每个位置进行更新即使跳跃”,因为你超过了你之前给自己的步数(即你用完了绳子),即使如果没有最短路径实际访问该元素。这些更新的目标是确定下一次跳跃可以达到多远,以便在用尽当前跳跃时可以给自己那么多绳索。

            2. 记下最少跳跃 (jumps),如果你必须您想继续遍历数组以到达终点,因为您从前一条链中用完了绳索 (steps)。


            你链接的算法,供参考:

            public class Solution {
                public int jump(int[] A) {
                    if (A.length <= 1)
                        return 0;
                    int maxReach = A[0];
                    int step = A[0];
                    int jump = 1;
                    for (int i = 1; i < A.length; i++) {
                        if (i == A.length - 1)
                            return jump;
                        if (i + A[i] > maxReach)
                            maxReach = i + A[i];
                        step--;
                        if (step == 0) {
                            jump++;
                            step = maxReach - i;
                        }
                    }
                    return jump;
                }
            }
            

            【讨论】:

            • 我有一个问题。我们怎么知道我们可以在这个问题中使用贪婪的方法来解决其他 dp 问题。我理解该解决方案,但我无法理解它为什么起作用,因为大多数最大化或最小化这种性质的问题往往由 dp 以最佳方式解决。此外,在每次迭代中,为什么我们不能在发现当前步数不足以达到 maxreach 时立即更新步数。为什么我们要等到步数为 0?
            【解决方案10】:

            另一个 O(n) 解决方案最好的解释 以下解决方案提供了 o(n) 时间复杂度 为了解决到达数组末尾的最小跳跃, 对于每个跳转索引,我们考虑需要评估索引中对应的步长值,并使用索引值将数组划分为子部分,找出最大步数覆盖索引。

            下面的代码和解释会让你有个清晰的思路:

            在每个子数组中找出最大距离覆盖索引作为数组的第一部分,第二个数组

            
            Input Array : {1, 3, 5, 9, 6, 2, 6, 7, 6, 8, 9} -> index position starts with 0
            Steps :
            Initial step is considering the first index and incrementing the jump
            Jump = 1
            1, { 3, 5, 9, 6, 2, 6, 7, 6, 8, 9} -> 1 is considered as a first jump
            
            next step
            From the initial step there is only one step to move so
            Jump = 2
            1,3, { 5, 9, 6,2, 6, 7, 6, 8, 9} -> 1 is considered as a first jump
            
            next step
            Now we have a flexibility to choose any of {5,9,6} because of last step says we can move upto 3 steps
            Consider it as a subarray, evaluate the max distance covers with each index position
            As {5,9,6} index positions are {2,3,4} 
            so the total farther steps we can cover:
            {7,12,10} -> we can assume it as {7,12} & {10} are 2 sub arrays where left part of arrays says max distance covered with 2 steps and right side array says max steps cover with remaining values
            
            next step:
            Considering the maximum distanc covered in first array we iterate the remaining next elements
            1,3,9 {6,2, 6, 7, 6, 8, 9}
            From above step ww already visited the 4th index we continue with next 5th index as explained above
            {6,2, 6, 7, 6, 8, 9} index positions {4,5,6,7,8,9,10}
            {10,7,12,14,14,17,19}
            Max step covers here is 19 which corresponding index is 10
            

            代码

            //
            //  Created by Praveen Kanike on 07/12/20.
            //
            
            #include <iostream>
            using namespace std;
            
            // Returns minimum number of jumps
            // to reach arr[n-1] from arr[0]
            int minJumps(int arr[], int n)
            {
                // The number of jumps needed to
                // reach the starting index is 0
                if (n <= 1)
                    return 0;
            
                // Return -1 if not possible to jump
                if (arr[0] == 0)
                    return -1;
            
                // stores the number of jumps
                // necessary to reach that maximal
                // reachable position.
                int jump = 1;
                
                // stores the subarray last index
                int subArrEndIndex = arr[0];
                
                int i = 1;
                
                //maximum steps covers in first half of sub array
                int subArrFistHalfMaxSteps = 0;
                
                //maximum steps covers in second half of sub array
                int subArrSecondHalfMaxSteps =0;
                // Start traversing array
                for (i = 1; i < n;) {
                    
                    subArrEndIndex = i+subArrEndIndex;
                    // Check if we have reached the end of the array
                    if(subArrEndIndex >= n)
                        return  jump;
                    
                    int firstHalfMaxStepIndex = 0;
                    //iterate the sub array and find out the maxsteps cover index
                    for(;i<subArrEndIndex;i++)
                    {
                        int stepsCanCover = arr[i]+i;
                        if(subArrFistHalfMaxSteps < stepsCanCover)
                        {
                            subArrFistHalfMaxSteps = stepsCanCover;
                            subArrSecondHalfMaxSteps = 0;
                            firstHalfMaxStepIndex = i;
                        }
                        else if(subArrSecondHalfMaxSteps < stepsCanCover)
                        {
                            subArrSecondHalfMaxSteps = stepsCanCover;
                        }
                    }
                    if(i > subArrFistHalfMaxSteps)
                        return -1;
                    jump++;
                    //next subarray end index and so far calculated sub array max step cover value
                    subArrEndIndex = arr[firstHalfMaxStepIndex];
                    subArrFistHalfMaxSteps  = subArrSecondHalfMaxSteps;
                }
            
                return -1;
            }
            
            // Driver program to test above function
            int main()
            {
                int arr[] = {100, 3, 5, 9, 6, 2, 6, 7, 6, 8, 9};
                int size = sizeof(arr) / sizeof(int);
            
                // Calling the minJumps function
                cout << ("Minimum number of jumps to reach end is %d ",
                        minJumps(arr, size));
                return 0;
            }
            
            
            

            【讨论】:

              【解决方案11】:

              以防万一您需要为贪婪方法编写 python 解决方案,此代码将帮助您解决上述问题 :)

              def minJumps(self, arr, n):
                  #code here
                  if(n <= 1):
                      return(0)
                  if(arr[0] == 0):
                      return -1
                  maxrange, step = arr[0], arr[0]
                  jumps = 1
                  for i in range(1,n):
                      if (i == len(arr) - 1): 
                          return jumps
                      maxrange = max(maxrange, i+arr[i])
                      step -= 1
                      if(step == 0):
                          jumps += 1
                          if(i>=maxrange):
                              return -1
                          step = maxrange - i
                  return(jumps)
              

              【讨论】:

                【解决方案12】:

                这是另一种解决方案。在此解决方案中,最坏情况复杂度为 O(n),而平均情况复杂度小于 O(n)。我不知道如何进行平均情况复杂性分析。所以,我不能说出确切的平均情况复杂性。但是,是的,它比 99.22% 的 leet 代码提交要快。

                def minJumps(self, arr, n):
                    current_w=0 # current_index
                    last_w=n-1  # last_index
                    max_reach=0 # value of index upto which we have analysed array for optimum solution
                    max_jumps=arr[0]   # maximum jumps that can be taken from a current_index
                    hop=0            # total jumps
                    
                    while current_w<last_w:
                        max_jumps=arr[current_w]
                        
                        if max_jumps==0:
                            return -1
                        
                        if max_jumps==1:
                            max_reach=max_jumps+current_w
                            current_w+=1
                            
                        elif max_jumps<last_w-current_w:     # if maximum steps does not reach to last index
                            can_jump_to=arr[max_reach+1:max_jumps+current_w+1]         # subarray in which we have to search for a wall,jumping to which can take us to required solution
                            jump_to=max(range(len(can_jump_to)),key=lambda x: x+can_jump_to[x])+max_reach+1 # finding index of wall whoose definition mentioned in above comment
                            max_reach=max_jumps+current_w    #updating max_reach
                            current_w=jump_to       #updating current position
                            
                        else:
                            current_w=last_w
                            
                        hop+=1
                    return hop
                

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 2020-04-07
                  • 2020-11-25
                  • 1970-01-01
                  • 2021-08-30
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2015-10-16
                  相关资源
                  最近更新 更多