web-dev-qa-db-ja.com

O(n)時間で配列の終わりに到達するための最小ジャンプ数を見つける方法

質問

各要素がその要素から転送できる最大ステップ数を表す整数の配列が与えられます。配列の最後(最初の要素から開始)に到達するための最小ジャンプ数を返す関数を記述します。要素が0の場合、その要素を移動できません。

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

ダイナミックプログラミングアプローチ から他の線形アプローチへの複数の方法が見つかりました。時間的に線形といわれるアプローチが理解できません。 [〜#〜]ここ[〜#〜] は、線形アプローチが提案されているリンクです。

全然理解できません。私が理解できたことは、著者が貪欲なアプローチをして、終わりに到達するかどうかを確認することを提案しているということです。

20
Walt

サイトで提案されたソリューションの時間の複雑さは、アレイを1回だけ繰り返すため、線形です。アルゴリズムは、いくつかの巧妙なトリックを使用して、提案されたソリューションの内部反復を回避します。

変数maxReachは常に、配列内の到達可能な最大位置を格納します。 jumpは、その位置に到達するために必要なジャンプの量を格納します。 stepは、実行可能なステップ数を格納します(最初の配列位置のステップ数で初期化されます)

反復中に、上記の値は次のように更新されます。

最初に、配列の最後に達したかどうかをテストします。この場合、jump変数を返すだけです。

次に、到達可能な最大位置を更新します。これは、maxReachと_i+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配列内の最大要素を使用し、_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];
} 
_
19
Niels Billen

パーティーには何年も遅れますが、ここに私にとって意味のある別の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

これは別の線形解です。コードはリートコードリンクで提案されているものよりも長くなっていますが、理解しやすいと思います。これは、2つの観察に基づいています。_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に最大で1つの要素が追加されるためです。そのため、すべてのtoDeleteリストのすべての要素を処理するには線形時間が必要です。

  2. min値は増加することしかできません。これが、内側のwhileループが合計で最大n回の繰り返しを行う理由です。

  3. 外側のforループは明らかにnを繰り返します。したがって、時間の複雑さは線形です。

1
kraskevich

単純な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])
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
0
Ishaan Sharma

上記の問題の貪欲なアプローチに関する基本的な直感は次のとおりです。残りはコード要件です。

与えられた配列は入力です: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-よく言った。時々、私たちはタイムトラベラーだと感じます。

0
Sid Ray

さて、O(n)アルゴに頭を巻くのにかなりの時間がかかりました。ロジックをできるだけ簡単に説明しようと思います。

配列内の各 "i"で、その値を使用してcurrentFarthest値が何であるかがわかります。currentEnd値まで到達できます。また、currentEnd値に到達するたびに、ジャンプを実行してcurrentEndを更新する時間がわかります。 currentFarthest。

下の画像が役立つかもしれません: enter image description here

0
fight_club