[LeetCode 1696] Jump Game VI

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;

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);
}
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();
}
}