如何在O(n)时间内找到到达数组末尾的最小跳转次数

时间:2015-01-09 10:17:59

标签: java arrays algorithm

  

问题

     

给定一个整数数组,其中每个元素表示可以从该元素向前进行的最大步数。   写一个函数来返回到达的最小跳跃次数   数组的结尾(从第一个元素开始)。如果元素是   0,然后无法移动该元素。

     

实施例

     

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

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

我根本无法理解。我能理解的是,作者建议做一个贪婪的方法,看看我们是否达到了目的......如果不是那么回溯?

11 个答案:

答案 0 :(得分:15)

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

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

在迭代期间,上述值更新如下:

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

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

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

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

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 = 1 + 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 = 1 + 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 = 1 + 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 = 1 + 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 untill we reach the end of the array.

我的次优算法在O(nk)时间内工作,n数组中的元素数量,k数组中的最大元素,并使用内部循环{{1} }。通过以下算法可以避免这种循环。

<强>代码

array[i]

答案 1 :(得分:2)

参加聚会的时间已经很晚了,但这是另一个对我有用的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);
}

答案 2 :(得分:1)

这是另一种线性解决方案。代码比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次迭代。因此,时间复杂度是线性的。

答案 3 :(得分:1)

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

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

首先,请记住问题说明中的内容:(n)可以从该索引跳至最大位置,但是您可以可以进行短跳A[i]i的跳转,因此从A[i]>1跳转的最短序列可能是短跳转,而不是每个索引所允许的跳转。这很重要,因为您将看到该算法永远不会考虑那些较小的跳跃或其位置明确

第二,将您提到的算法赋予自身“绳索”(i=0)到末尾并消耗掉该绳索(steps = maxReach - i;)的算法有助于您进行思考。当它尝试通过数组前进时。

第三,请注意,该算法跟踪可能是 a 最短一部分的特定索引steps--;从输入数组i的开始到结尾的顺序。实际上,该算法仅在变量“ {从绳子用完”(从前一根绳子用完了)起起才增加变量A(为其自身提供一条新的绳子),以便可以在主循环中继续迭代以“尝试”到最后。

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

  1. 在每个位置jump向前移动穿过阵列时,保持“可到达的距离”(maxReach。请注意,如果当时已经很清楚到达该新位置 需要需要更多的时间,则每个位置 even 都会更新此数量”如果您没有最短的路径实际访问该元素,则超过您之前给自己的步数(即,用完了)甚至。这些更新的目的是确定下一跳可能到达的距离,以便一旦耗尽当前跳线就可以给自己提供足够多的绳索。

  2. 帐户,表示必须跳转i)的最低个数如果您想继续遍历数组以到达末尾,请执行以下操作,因为上一条绳子的绳子(jumps)用完了。


您链接的算法,以供参考:

steps

答案 4 :(得分:0)

以下是关于上述问题的基本直觉,贪婪的方法和休息是代码要求。

给定数组是输入: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 :(得分:0)

简单的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 :(得分:0)

好的,我花了很多时间将脑袋缠在O(n)算法上,我将尝试以最简单的方式来解释逻辑:

在数组中的每个“ i”上,您都知道该值是什么是currentFarthest值,并且您可以达到currentEnd值,而且只要您击中currentEnd值,就可以知道当前时间来进行跳转并用currentFarthest更新currentEnd。

下面的图片可能会有所帮助:enter image description here

答案 7 :(得分:0)

我已经用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 :(得分:0)

具有最佳解释的另一个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;
}


答案 9 :(得分:0)

以防万一您需要为贪婪方法编写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)

答案 10 :(得分:-1)

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]);
}