Problem Statement
You are given an integer array nums
. You are initially positioned at the array's first index, and each element in the array represents your maximum jump length at that position.
Return true
if you can reach the last index, or false
otherwise.
LeetCode
Constraints
1 <= nums.length <= 10^4
0 <= nums[i] <= 10^5
1 ≤ nums.length ≤ 10^4
- The array
nums
must have at least 1 element and at most10,000
elements. - Implication:
- Avoid brute force (
O(n^2)
) – these could lead to Time Limit Exceeded (TLE) in the worst case. - Aim for
O(n)
orO(n log n)
complexity.
- Avoid brute force (
- The array
0 ≤ nums[i] ≤ 10^5
- Each element in the array (i.e
nums[i]
) represents the maximum number of indices you can jump forward from indexi
. - Examples:
- If
nums[0] = 0
, you can't move forward from that index. - If
nums[i] = 3
, you can jump toi + 1
,i + 2
, ori + 3
.
- If
- Since the jump length can be up to 100000, it might seem like we can jump very far, but we're still bound by the size of the array (which is at most 10,000).
- Even if a value is large, you can't jump beyond the array—you just need to reach the last index, not go past it.
- Each element in the array (i.e
Examples
Example 1:
Input: nums = [2, 3, 1, 1, 4]
Output: true
Explanation:
- Start at index 0, jump to index 1 (jump length = 2)
- From index 1, jump to index 4 (jump length = 3)
- You’ve reached the last index
Example 2:
Input: nums = [3, 2, 1, 0, 4]
Output: false
Explanation:
- Start at index 0, can jump to indices 1, 2, or 3
- All of those eventually hit a `0` before reaching index 4
- You cannot reach the last index
Different Approaches
1️⃣ Brute Force Approach (TLE)
Intuition:
The problem is essentially about finding if there is a way to jump from the start (index 0) to the last index in the array by making jumps. Each element in the array represents the maximum number of steps you can take from that index.
The intuition behind the recursive approach is:
- Start at index 0 and try to jump to all possible positions from that index (based on the value of the current index).
- After each jump, recursively check if you can reach the last index from the new position.
- If you find any path to the last index, return true.
- If none of the jumps can lead to the last index, return false.
Approach:
- Recursive Function:
- Start from the first index (i = 0).
- If you're already at the last index (i == nums.size() - 1), return true because you've reached the goal.
- Otherwise, for each position i, try all possible steps from 1 to nums[i] (i.e., all jumps that can be made from the current index).
- Recursively check if you can reach the last index from each of these new positions.
- If any of the recursive calls returns true, the current call will return true, signifying that a valid path exists. Otherwise, it returns false.
- Base Case:
- If you reach the last index, return true as you've successfully reached the goal.
- Termination Condition:
- If you can't make any valid jump that leads to the last index, return false.
- Overall Plan:
- Start at the first index.
- Try every possible jump and recursively check if it leads to the last index.
Code:
class Solution {
public:
// Recursive function to check if it's possible to reach the last index
bool recursive(vector<int>& nums, int i) {
// Base case: if we have reached the last index, return true
if (i == nums.size() - 1) {
return true;
}
// Try all steps from 1 to nums[i] (the number of steps we can jump from i)
bool possible = false;
for (int steps = 1; steps <= nums[i]; steps++) {
// Check if the new index is within bounds and recursively check the jump
if (i + steps < nums.size() && recursive(nums, i + steps)) {
possible = true;
}
}
// Return whether any of the jumps led to the last index
return possible;
}
// Main function to check if it's possible to jump from index 0 to the last index
bool canJump(vector<int>& nums) {
return recursive(nums, 0); // Start the recursive process from index 0
}
};
Complexity Analysis:
- Time Complexity:
O(2^n)
- The recursive approach explores all possible jumps from every index, leading to an exponential time complexity. Each index can have multiple recursive calls branching out, making it inefficient for larger input sizes.
- Worst-case scenario: Every possible jump from each index could lead to a branching path that results in exponential growth.
- Space Complexity:
O(n)
- The maximum depth of the recursion stack is n (where n is the size of the array), which leads to a space complexity of O(n).
2️⃣ Memoization
Code:
class Solution {
public:
// Memoization helper function
bool recursive(vector<int>& nums, int i, vector<int>& memo) {
// If already computed, return the result
if (memo[i] != -1) {
return memo[i] == 1;
}
// Base case: if we have reached the last index, return true
if (i == nums.size() - 1) {
return true;
}
// Try all steps from 1 to nums[i] (the number of steps we can jump from i)
bool possible = false;
for (int steps = 1; steps <= nums[i]; steps++) {
// Check if the new index is within bounds and recursively check the jump
if (i + steps < nums.size() && recursive(nums, i + steps, memo)) {
possible = true;
break; // Once we find a valid path, no need to check further
}
}
// Memoize the result for index i
memo[i] = possible ? 1 : 0;
return possible;
}
// Main function to check if it's possible to jump from index 0 to the last index
bool canJump(vector<int>& nums) {
int n = nums.size();
vector<int> memo(n, -1); // Memoization array, initialized to -1 (unvisited)
return recursive(nums, 0, memo); // Start the recursive process from index 0
}
};
Complexity Analysis:
- Time Complexity:
O(n^2)
- In worst-case for each of
n
positions, we try up ton
possible next jumps.
- In worst-case for each of
- Space Complexity:
O(n)
- For the memoization array and recursion call stack.
3️⃣ Greedy Algorithm (Optimal)
Intuition:
Keep track of the farthest position that can be reached at any point. If at an index where it's impossible to move to the next one because it's too far away, then it's impossible to get to the end, and the process must stop.
Otherwise, continue updating the farthest reachable index while moving forward. If the traversal manages to reach or pass the last index, then reaching the end is possible.
Approach:
- Start by setting a pointer to track the farthest point that can be reached from the beginning of the array.
- Iterate through each position in the array, checking if the current position exceeds the farthest point reached so far.
- If the current position is beyond the reachable point, it indicates that further progress is not possible, and reaching the end is infeasible.
- If the current position is within the reachable point, update the pointer to reflect the farthest position that can be reached from the current spot.
- If the entire array is traversed without finding an unreachable position, it confirms that the last index is reachable, thus making it possible to reach the end.
Code:
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
// To determine if last index is reachable
bool canJump(vector<int>& nums) {
// Initialize maximum index
int maxIndex = 0;
// Iterate through each index of the array
for (int i = 0; i < nums.size(); i++) {
/*If the current index
is greater than the
maximum reachable index
it means we cannot move
forward and should
return false*/
if (i > maxIndex) {
return false;
}
/* Update the maximum index that can be
reached by comparing
the current maxIndex with the sum
of the current index and
the maximum jump from that index*/
maxIndex = max(maxIndex, i + nums[i]);
}
/* If we complete the
loop, it means we
can reach the
last index*/
return true;
}
};
int main() {
vector<int> nums = {4, 3, 7, 1, 2};
cout << "Array representing maximum jump from each index: ";
for (int i = 0; i < nums.size(); i++) {
cout << nums[i] << " ";
}
cout << endl;
Solution solution;
bool ans = solution.canJump(nums);
if (ans) {
cout << "It is possible to reach the last index." << endl;
} else {
cout << "It is not possible to reach the last index." << endl;
}
return 0;
}
Complexity Analysis:
- Time Complexity:
O(N)
whereN
is the length of the array. We iterate through the input array exactly once and at each element perform constant time operations. - Space Complexity:
O(1)
no extra space used.