Question
Étant donné un tableau d'entiers où chaque élément représente le nombre maximal d'étapes pouvant être effectuées à partir de cet élément. Ecrivez une fonction pour renvoyer le nombre minimum de sauts pour atteindre la fin du tableau (à partir du premier élément). Si un élément est 0, alors ne peut pas se déplacer à travers cet élément.
Exemple
Entrée: arr [] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}
Sortie: 3 (1-> 3 -> 8 -> 9)
A trouvé plusieurs façons de approche de programmation dynamique vers d’autres approches linéaires. Je ne suis pas en mesure de comprendre l'approche dite linéaire dans le temps. ICI est le lien où une approche linéaire est proposée.
Je ne suis pas capable de le comprendre du tout. Ce que je pourrais comprendre, c’est que l’auteur suggère de faire une approche gourmande et de voir si nous arrivons à la fin… sinon, revenir en arrière?
La complexité temporelle de la solution proposée sur le site est linéaire car vous n'effectuez une itération sur le tableau qu'une seule fois. L'algorithme évite l'itération interne de la solution proposée en utilisant des astuces astucieuses.
La variable maxReach
stocke à tout moment la position maximale accessible dans le tableau. jump
enregistre le nombre de sauts nécessaires pour atteindre cette position. step
stocke le nombre d’étapes que nous pouvons encore effectuer (et est initialisé avec le nombre d’étapes à la première position du tableau)
Au cours de l'itération, les valeurs ci-dessus sont mises à jour comme suit:
Nous testons d’abord si nous avons atteint la fin du tableau, auquel cas nous devons simplement renvoyer la variable jump
.
Ensuite, nous mettons à jour la position maximale accessible. Ceci est égal au maximum de maxReach
et i+A[i]
(le nombre de pas que nous pouvons effectuer à partir de la position actuelle).
Nous avons utilisé une étape pour obtenir l'index actuel, donc steps
doit être diminué.
S'il ne reste plus d'étapes (c'est-à-dire steps=0
, nous devons avoir utilisé un saut. Par conséquent, augmentez jump
. Puisque nous savons qu'il est possible d'atteindre maxReach
, nous initialisons les étapes au nombre d'étapes nécessaires pour atteindre maxReach
à partir de la position 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;
}
}
Exemple:
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.
Mon algorithme sous-optimal qui fonctionne dans O(nk)
time avec n
le nombre d'éléments dans le tableau et k
le plus grand élément du tableau et utilise une boucle interne sur array[i]
. Cette boucle est évitée par l'algorithme ci-dessous.
Code
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];
}
Des années de retard dans la soirée, mais voici une autre solution O(n) qui me paraissait logique.
/// <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);
}
Voici une autre solution linéaire. Le code est plus long que celui suggéré dans le lien code leet, mais je pense qu'il est plus facile à comprendre. Il repose sur deux observations: le nombre d’étapes requis pour atteindre la position i + 1
n’est jamais inférieur au nombre d’étapes requis pour atteindre la position i
et chaque élément attribue sa valeur +1 au segment 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];
}
}
Analyse de complexité:
Le nombre total d'éléments dans les listes toDelete
est O(n)
. C'est le cas parce qu'à chaque position i
au plus un élément est ajouté. C'est pourquoi le traitement de tous les éléments de toutes les listes toDelete
nécessite un temps linéaire.
La valeur min
ne peut qu’augmenter. C'est pourquoi la boucle while
intérieure fait au maximum __ itérations n
au total.
La boucle for
extérieure fait évidemment des itérations n
. Ainsi, la complexité temporelle est linéaire.
Code python simple pour le nombre minimal de sauts pour atteindre le problème final.
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])
Voici l’intuition de base concernant l’approche gloutonne du problème susmentionné et les autres exigences du code.
Le tableau donné est Input: a [] = {1, 3, 5, 8, 9, 2, 6, 7, 6, 8, 9}.
Maintenant, nous partons du premier élément, c’est-à-dire i = 0 et a [i] = 1. Nous pouvons donc faire tout au plus un saut de taille 1, nous n’avons pas d’autre choix et nous faisons en sorte que cette étape se produise.
Nous sommes actuellement à i = 1 et a [i] = 3. Nous pouvons donc actuellement effectuer un saut de taille 3, mais au lieu de cela, nous considérons tous les sauts possibles que nous pouvons faire à partir de l’emplacement actuel et atteignons la distance maximale qui est comprise dans les limites (du tableau). Alors, quels sont nos choix? on peut faire un saut d'un pas, ou deux pas ou trois pas. Nous examinons donc à partir de l’emplacement actuel pour chaque saut de taille et choisissons celui qui peut nous emmener au maximum plus loin dans la matrice.
Une fois que nous avons décidé de celui auquel nous nous en tenons, nous prenons cette taille de saut et mettons à jour le nombre de sauts que nous avons réalisés jusqu’à présent, ainsi que les objectifs que nous pouvons atteindre au maximum et le nombre de pas dont nous disposons maintenant pour décider de notre prochain mouvement. Et c'est tout. C’est ainsi que finalement nous sélectionnons la meilleure option traversant linéairement le tableau. Donc, c’est l’idée de base de l’algo que vous recherchez, la prochaine consiste à la coder pour que l’algorithme fonctionne. À votre santé!
J'espère que le temps passe et trouve l'intuition utile !! :): P "Années en retard à la fête" @Vasilescu Andrei - bien dit. Parfois, j'ai l'impression que nous sommes des voyageurs temporels.