Jump Game
Finding the Maximum Subarray Sum
The Maximum Subarray Problem is a classic algorithmic challenge that frequently appears in coding interviews and real-world applications. The goal is to identify the contiguous subarray within a given array of integers that has the largest sum.
This article provides a clear and efficient approach to solve the problem using Kadane’s Algorithm.
Problem Description
You are given an integer array, nums, where each element nums[i] represents the maximum jump length you can take from that index. Starting from index 0, your goal is to determine whether it is possible to reach the last index.
Explanation:
The subarray [4,-2,2,1,-1,4] has the largest sum 8.
Constraints:
- 1 <= nums.length <= 1000
- 0 <= nums[i] <= 1000
Solution Approach: Greedy Algorithm
The key to solving this problem lies in determining the farthest index you can reach at any given point. Using a greedy algorithm,
we can iteratively update the farthest reachable index and check if it eventually encompasses the last index.
Steps
Initialize the Farthest Reachable Index:
Start with farthest = 0.Iterate Through the Array:
For each index i:- If i is greater than farthest, it means you cannot progress further, so return false.
- Update farthest to the maximum of its current value and i + nums[i].
Check Final Reach:
If you finish the loop and farthest is greater than or equal to the last index, return true.
There are mainly Four approach to solve this problem –
- Recursion
- Dynamic Programming (Top-Down)
- Dynamic Programming (Bottom-Up)
- Greedy
1. Recursion
- Time complexity: O(n!)
- Space complexity: O(n)
class Solution: def canJump(self, nums: List[int]) -> bool: def dfs(i): if i == len(nums) - 1: return True end = min(len(nums) - 1, i + nums[i]) for j in range(i + 1, end + 1): if dfs(j): return True return False return dfs(0)
class Solution { public: bool canJump(vector& nums) { return dfs(nums, 0); } private: bool dfs(vector & nums, int i) { if (i == nums.size() - 1) { return true; } int end = min((int)nums.size() - 1, i + nums[i]); for (int j = i + 1; j <= end; ++j) { if (dfs(nums, j)) { return true; } } return false; } };
public class Solution { public boolean canJump(int[] nums) { return dfs(nums, 0); } private boolean dfs(int[] nums, int i) { if (i == nums.length - 1) { return true; } int end = Math.min(nums.length - 1, i + nums[i]); for (int j = i + 1; j <= end; j++) { if (dfs(nums, j)) { return true; } } return false; } }
class Solution { /** * @param {string} num1 * @param {string} num2 * @return {string} */ multiply(num1, num2) { if (num1 === "0" || num2 === "0") return "0"; if (num1.length < num2.length) { return this.multiply(num2, num1); } let res = ""; let zero = 0; for (let i = num2.length - 1; i >= 0; i--) { const cur = this.mul(num1, num2[i], zero); res = this.add(res, cur); zero++; } return res; } /** * @param {string} s * @param {Character} d * @param {number} zero * @return {string} */ mul(s, d, zero) { let i = s.length - 1; let carry = 0; const digit = Number(d); let cur = ""; while (i >= 0 || carry) { const n = i >= 0 ? Number(s[i]) : 0; const prod = n * digit + carry; cur = (prod % 10) + cur; carry = Math.floor(prod / 10); i--; } return cur + "0".repeat(zero); } /** * @param {string} num1 * @param {string} num2 * @return {string} */ add(num1, num2) { let i = num1.length - 1, j = num2.length - 1, carry = 0; let res = ""; while (i >= 0 || j >= 0 || carry) { const n1 = i >= 0 ? Number(num1[i]) : 0; const n2 = j >= 0 ? Number(num2[j]) : 0; const total = n1 + n2 + carry; res = (total % 10) + res; carry = Math.floor(total / 10); i--; j--; } return res; } }
2. Dynamic Programming (Top-Down)
Time & Space Complexity
- Time complexity: O(n^2)
- Space complexity: O(n)
Where m is the length of the array queries and n is the length of the array intervals.
class Solution { public: bool canJump(vector& nums) { unordered_map memo; return dfs(nums, 0, memo); } private: bool dfs(vector & nums, int i, unordered_map & memo) { if (memo.count(i)) { return memo[i]; } if (i == nums.size() - 1) { return true; } if (nums[i] == 0) { return false; } int end = min((int)nums.size(), i + nums[i] + 1); for (int j = i + 1; j < end; j++) { if (dfs(nums, j, memo)) { memo[i] = true; return true; } } memo[i] = false; return false; } };
public class Solution { public boolean canJump(int[] nums) { Mapmemo = new HashMap<>(); return dfs(nums, 0, memo); } private boolean dfs(int[] nums, int i, Map memo) { if (memo.containsKey(i)) { return memo.get(i); } if (i == nums.length - 1) { return true; } if (nums[i] == 0) { return false; } int end = Math.min(nums.length, i + nums[i] + 1); for (int j = i + 1; j < end; j++) { if (dfs(nums, j, memo)) { memo.put(i, true); return true; } } memo.put(i, false); return false; } }
class Solution: def canJump(self, nums: List[int]) -> bool: memo = {} def dfs(i): if i in memo: return memo[i] if i == len(nums) - 1: return True if nums[i] == 0: return False end = min(len(nums), i + nums[i] + 1) for j in range(i + 1, end): if dfs(j): memo[i] = True return True memo[i] = False return False return dfs(0)
class Solution { /** * @param {number[]} nums * @return {boolean} */ canJump(nums) { const memo = new Map(); const dfs = (i) => { if (memo.has(i)) { return memo.get(i); } if (i == nums.length - 1) { return true; } if (nums[i] === 0) { return false; } const end = Math.min(nums.length - 1, i + nums[i]); for (let j = i + 1; j <= end; j++) { if (dfs(j)) { memo.set(i, true); return true; } } memo.set(i, false); return false; } return dfs(0); } }
3. Dynamic Programming (Buttom-Up)
Time & Space Complexity
- Time complexity: O(n^2)
- Space complexity: O(n)
class Solution { public: bool canJump(vector& nums) { int n = nums.size(); vector dp(n, false); dp[n - 1] = true; for (int i = n - 2; i >= 0; i--) { int end = min((int)nums.size(), i + nums[i] + 1); for (int j = i + 1; j < end; j++) { if (dp[j]) { dp[i] = true; break; } } } return dp[0]; } };
public class Solution { public boolean canJump(int[] nums) { int n = nums.length; boolean[] dp = new boolean[n]; dp[n - 1] = true; for (int i = n - 2; i >= 0; i--) { int end = Math.min(nums.length, i + nums[i] + 1); for (int j = i + 1; j < end; j++) { if (dp[j]) { dp[i] = true; break; } } } return dp[0]; } }
class Solution: def canJump(self, nums: List[int]) -> bool: n = len(nums) dp = [False] * n dp[-1] = True for i in range(n - 2, -1, -1): end = min(n, i + nums[i] + 1) for j in range(i + 1, end): if dp[j]: dp[i] = True break return dp[0]
class Solution { /** * @param {number[]} nums * @return {boolean} */ canJump(nums) { const n = nums.length; const dp = new Array(n).fill(false); dp[n - 1] = true; for (let i = n - 2; i >= 0; i--) { const end = Math.min(nums.length, i + nums[i] + 1); for (let j = i + 1; j < end; j++) { if (dp[j]) { dp[i] = true; break; } } } return dp[0]; } }
4. Greedy
Time & Space Complexity
- Time complexity: O(n^2)
- Space complexity: O(n)
Where m is the length of the array queries and n is the length of the array intervals.
class Solution { public: bool canJump(vector& nums) { int goal = nums.size() - 1; for (int i = nums.size() - 2; i >= 0; i--) { if (i + nums[i] >= goal) { goal = i; } } return goal == 0; } };
public class Solution { public boolean canJump(int[] nums) { int goal = nums.length - 1; for (int i = nums.length - 2; i >= 0; i--) { if (i + nums[i] >= goal) { goal = i; } } return goal == 0; } }
class Solution: def canJump(self, nums: List[int]) -> bool: goal = len(nums) - 1 for i in range(len(nums) - 2, -1, -1): if i + nums[i] >= goal: goal = i return goal == 0
class Solution { /** * @param {number[]} nums * @return {boolean} */ canJump(nums) { let goal = nums.length - 1; for (let i = nums.length - 2; i >= 0; i--) { if (i + nums[i] >= goal) { goal = i; } } return goal === 0; } }