You are given a 0-indexed integer array nums
and an integer k
.
You are initially standing at index 0
. In one move, you can jump at most k
steps forward without going outside the boundaries of the array. That is, you can jump from index i
to any index in the range [i + 1, min(n - 1, i + k)]
inclusive.
You want to reach the last index of the array (index n - 1
). Your score is the sum of all nums[j]
for each index j
you visited in the array.
Return the maximum score you can get.
Example 1:
Input: nums = [1,-1,-2,4,-7,3], k = 2
Output: 7
Explanation: You can choose your jumps forming the subsequence [1,-1,4,3] (underlined above). The sum is 7.
Example 2:
Input: nums = [10,-5,-2,4,0,3], k = 3
Output: 17
Explanation: You can choose your jumps forming the subsequence [10,4,3] (underlined above). The sum is 17.
Example 3:
Input: nums = [1,-5,-20,4,-1,3,-6,-3], k = 2
Output: 0
Constraints:
-
1 <= nums.length, k <= 10^5
-10^4 <= nums[i] <= 10^4
Think about solving this problem backward: if we are at the last position, then of all the positions that can reach the last position in 1 jump, we should pick the position that yields the maximum score. This means we need to solve F(last position - 1), F(last position - 2),....... F(last position - K). To solve F(last position - 1), we need to solve F(last position - 2),....... F(last position - K), F(last position - K - 1). It is clear that we have overlapping subproblems, so we also use dynamic programming.
Solution 1. O(N * K) DP
dp[i]: the max score we can get at position i;
Answer is dp[N - 1].
Solution 2. O(N * logN) DP with max heap
The bottle neck in solution 1 is that we need to repeatedly find the max dp value among the previous K values. We can speed this up by using a max heap.
The max heap's entry stores both dp value and its position. When querying the max dp value, we first pop all the values that are out of the K-sized window. Then we pick a valid max value to update the current dp[i]. The runtime is O(N * logN) since we iterate from 0 to N - 1 and for each position it is added to then popped out of the max heap at most once.
class Solution { public int maxResult(int[] nums, int k) { int n = nums.length; int[] dp = new int[n]; PriorityQueue<int[]> q = new PriorityQueue<>(Comparator.comparingInt(e->-e[0])); for(int i = 0; i < n; i++) { while(q.size() > 0 && q.peek()[1] + k < i) { q.poll(); } dp[i] = nums[i] + (q.size() > 0 ? q.peek()[0] : 0); q.add(new int[]{dp[i], i}); } return dp[n - 1]; } }
Solution 3. O(N) DP with double ended queue
Can we do better than O(N * logN)? Think about what happens when the current dp[i] is bigger than dp[i - 1]. Since dp[i] appears after dp[i - 1] and is better than dp[i - 1], this means we can ignore dp[i - 1] for the rest of our dp iteration. In general, we can safely discard all previous <= dp values. We still need to keep previous > dp values until they are out of the K-sized window. This calls for a double ended queue. The head has the current max dp value. We remove out of window positions from the head of the dq and remove <= dp values and add newly computed dp value at the end of the dq.
class Solution { public int maxResult(int[] nums, int k) { int n = nums.length; int[] dp = new int[n]; ArrayDeque<Integer> q = new ArrayDeque<>(); for(int i = 0; i < n; i++) { while(q.size() > 0 && q.peekFirst() < i - k) { q.removeFirst(); } dp[i] = nums[i] + (q.size() > 0 ? dp[q.peekFirst()] : 0); while(q.size() > 0 && dp[q.peekLast()] <= dp[i]) { q.removeLast(); } q.addLast(i); } return dp[n - 1]; } }