Post

Dynamic Programming VI

Best Team With No Conflicts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
public int bestTeamScore(int[] scores, int[] ages) {
    int n = ages.length;
    Integer[] indices = new Integer[n];
    for (int i = 0; i < n; i++) {
        indices[i] = i;
    }

    Arrays.sort(indices, (i, j) -> ages[i] == ages[j] ? scores[i] - scores[j] : ages[i] - ages[j]);

    // dp[i]: max score if the i-th player is chosen and all the other players are between 0 and (i - 1)
    int[] dp = new int[n];
    int max = dp[0] = scores[indices[0]];
    for (int i = 1; i < n; i++) {
       dp[i] = scores[indices[i]];
       for (int j = 0; j < i; j++) {
           // age[indices[j]] <= age[indices[i]],
           // so we can always choose the younger player
           // if s/he has a lower score
           if (scores[indices[j]] <= scores[indices[i]]) {
               dp[i] = Math.max(dp[i], scores[indices[i]] + dp[j]);
           }  
       }
       max = Math.max(dp[i], max);
    }
    return max;
}

Alternative representation:

1
2
3
4
5
6
7
8
int[][] candidate = new int[n][2];
       
for (int i = 0; i < n; i++) {
    candidate[i][0] = ages[i];
    candidate[i][1] = scores[i];
}

Arrays.sort(candidate, (a, b) -> a[0] == b[0] ? a[1] - b[1] : a[0] - b[0]);

Minimum Time to Make Array Sum At Most x

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public int minimumTime(List<Integer> nums1, List<Integer> nums2, int x) {
    int n = nums1.size();
    Integer[] indices = new Integer[n];
    for (int i = 0; i < n; i++) {
        indices[i] = i;
    }

    // Each nums1[i] is set to 0 at most once.
    // After t seconds, if we don't do the operation, then the sum would be sum2 * t + sum1.
    // With that operation at each second, The total sum of added nums2 elements is:
    //   nums2[i_0] * (t - 1) + nums2[i_1] * (t - 2) + ... + nums2[i_t] * 0
    // So, it's optimal to follow the ascending order of nums2
    Arrays.sort(indices, Comparator.comparingInt(i -> nums2.get(i)));

    // dp[i][j]: deducted value at j-th second with numbers chosen from sorted_nums1[0 ... i]
    int[] dp = new int[n + 1];
    for (int i = 0; i < n; i++) {
        for (int j = i + 1; j > 0; j--) {
            // dp[i][j] = Math.max(dp[i - 1][j] + dp[i - 1][j - 1] + sorted_nums1[i] + sorted_nums2[i] * j)
            // sorted_nums1[i] + sorted_nums2[i] * j is the deducted value if we choose i
            dp[j] = Math.max(dp[j], dp[j - 1] + j * nums2.get(indices[i]) + nums1.get(indices[i]));
        }
    }

    int sum1 = (int)nums1.stream().mapToInt(i -> i).sum(), sum2 = (int)nums2.stream().mapToInt(i -> i).sum();
    for (int i = 0; i <= n; i++) {
        if (sum2 * i + sum1 - dp[i] <= x) {
            return i;
        }
    }
    return -1;
}

Maximum Height by Stacking Cuboids

1
2
3
4
5
6
7
8
9
10
11
nt n = cuboids.length, max = 0;
int[] dp = new int[n];
for (int j = 0; j < n; j++) {
    dp[j] = cuboids[j][2];
    for (int i = 0; i < j; i++) {
        if (cuboids[i][0] >= cuboids[j][0] && cuboids[i][1] >= cuboids[j][1] && cuboids[i][2] >= cuboids[j][2]) {
            dp[j] = Math.max(dp[j], dp[i] + cuboids[j][2]);
        }
    }
    max = Math.max(max, dp[j]);
}

Build Array Where You Can Find The Maximum Exactly K Comparisons

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
private static final int MOD = (int)1e9 + 7;

public int numOfArrays(int n, int m, int k) {
    // dp[a][b][c]: max element == b
    long[][][] dp = new long[n + 1][m + 1][k + 1];

    // all one's
    for (int b = 1; b <= m; b++) {
        dp[1][b][1] = 1;
    }

    for (int a = 1; a <= n; a++) {
        for (int b = 1; b <= m; b++) {
            for (int c = 1; c <= k; c++) {
                long sum = 0;

                // dp[a][b][c] += b * dp[a - 1][b][c]
                // appends any element from [1, b] to the end of every array
                sum = (sum + b * dp[a - 1][b][c]) % MOD;

                // dp[a][b][c] += dp[a - 1][1][c - 1] + dp[a - 1][2][c - 1] + ... + dp[a - 1][b - 1][c - 1]
                // appends the element "b" to the end of every array
                for (int j = 1; j < b; j++) {
                    sum = (sum + dp[a - 1][j][c - 1]) % MOD;
                }

                dp[a][b][c] = (dp[a][b][c] + sum) % MOD;
            }
        }
    }

    long count = 0;
    for (int b = 1; b <= m; b++) {
        count = (count + dp[n][b][k]) % MOD;
    }

    return (int)count;
}

Prefix sum:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// dp[a][b][c]: max element == b
long[][][] dp = new long[n + 1][m + 1][k + 1];
// prefix sum
long[][][] p = new long[n + 1][m + 1][k + 1];

// all one's
for (int b = 1; b <= m; b++) {
    dp[1][b][1] = 1;
    p[1][b][1] = p[1][b - 1][1] + 1;
}

for (int a = 1; a <= n; a++) {
    for (int b = 1; b <= m; b++) {
        for (int c = 1; c <= k; c++) {
            long sum = 0;

            // dp[a][b][c] += b * dp[a - 1][b][c]
            // appends any element from [1, b] to the end of every array
            sum = (sum + b * dp[a - 1][b][c]) % MOD;

            // dp[a][b][c] += dp[a - 1][1][c - 1] + dp[a - 1][2][c - 1] + ... + dp[a - 1][b - 1][c - 1]
            // appends the element "b" to the end of every array
            sum = (sum + p[a - 1][b - 1][c - 1]) % MOD;

            dp[a][b][c] = (dp[a][b][c] + sum) % MOD;
            p[a][b][c] = (dp[a][b][c] + p[a][b - 1][c]) % MOD;
        }
    }
}

Number of Ways to Rearrange Sticks With K Sticks Visible

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final int MOD = (int)1e9 + 7;

public int rearrangeSticks(int n, int k) {
    long[][] dp = new long[n + 1][k + 1];
    dp[0][0] = 1;

    for (int i = 1; i <= n; i++) {
        for (int j = 1; j <= k; j++) {
            // f(n - 1, k - 1): rightmost is the longest
            // f(n - 1, k): rightmost is not the longest, and there are (n - 1) possibilities
            dp[i][j] = (dp[i - 1][j - 1] + (i - 1) * dp[i - 1][j] % MOD) % MOD;
        }
    }
    return (int)dp[n][k];
}

Number of Music Playlists

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
private static final int MOD = (int)1e9 + 7;

public int numMusicPlaylists(int n, int goal, int k) {
    long[][] dp = new long[goal + 1][n + 1];
    dp[0][0] = 1;

    for (int i = 1; i <= goal; i++) {
        for (int j = 1; j <= n; j++) {
            // the last song is new
            dp[i][j] = (dp[i - 1][j - 1] * (n - (j - 1))) % MOD;

            // the last song is old
            // the songs from (j - k) to (j - 1) cannot be chosen
            if (j > k) {
                dp[i][j] = (dp[i][j] + (dp[i - 1][j] * (j - k)) % MOD) % MOD;
            }
        }
    }
    return (int)dp[goal][n];
}

Frog Jump

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public boolean canCross(int[] stones) {
    // stone : set of jump sizes which lead to the stone
    HashMap<Integer, Set<Integer>> map = new HashMap<>();
    for (int s : stones) {
        map.put(s, new HashSet<>());
    }

    map.get(0).add(0);
    for (int s : stones) {
        for (int k : map.get(s)) {
            // finds all stones that can be reached from the current stone
            for (int step = k - 1; step <= k + 1; step++) {
                if (step > 0 && map.containsKey(s + step)) {
                    map.get(s + step).add(step);
                }
            }
        }
    }
    return !map.get(stones[stones.length - 1]).isEmpty();
}

Stone Game V

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// O(n ^ 3)
public int stoneGameV(int[] stoneValue) {
    int n = stoneValue.length;
    int[] p = new int[n + 1];
    for (int i = 0; i < n; i++) {
        p[i + 1] = p[i] + stoneValue[i];
    }

    int[][] dp = new int[n][n];
    for (int len = 2; len <= n; len++) {
        for (int i = 0; i + len - 1 < n; i++) {
            int max = 0;
            for (int j = i; j + 1 <= i + len - 1; j++) {
                // [i, j] and [j + 1, i + len - 1]
                int left = p[j + 1] - p[i];
                int right = p[i + len] - p[j + 1];
                if (left == right) {
                    max = Math.max(max, left + dp[i][j]);
                    max = Math.max(max, right + dp[j + 1][i + len - 1]);
                } else if (left < right) {
                    max = Math.max(max, left + dp[i][j]);
                } else {
                    max = Math.max(max, right + dp[j + 1][i + len - 1]);
                }
            }
            dp[i][i + len - 1] = max;
        }
    }
    return dp[0][n - 1];
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
// O(n ^ 2)
public int stoneGameV(int[] stoneValue) {
    int n = stoneValue.length;
    int[][] dp = new int[n][n], max = new int[n][n];

    // i <= j
    // max[i][j]: max(dp[i][i] + sum[i...i], dp[i][i + 1] + sum[i...(i + 1)], ..., dp[i][j] + sum[i...j]), i.e. left
    // max[j][i]: max(dp[i][j] + sum[i...j], dp[i + 1][j] + sum[(i + 1)...j], ..., dp[j][j] + sum[j...j]), i.e. right
    for (int i = 0; i < n; i++) {
        max[i][i] = stoneValue[i];
    }

    for (int j = 1; j < n; j++) {
        int mid = j, sum = stoneValue[j], rightHalf = 0;
        for (int i = j - 1; i >= 0; i--) {
            // sum(stoneValue[i, j])
            sum += stoneValue[i];

            // finds the index mid in the range [i, j]
            // if stoneValue[mid] is added to right half
            // then left half < right half
            while ((rightHalf + stoneValue[mid]) * 2 <= sum) {
                rightHalf += stoneValue[mid--];
            }

            // left remains
            // - if right half == left half, stoneValue[mid] is not added to right half
            //   so left half = max[i][mid]
            // - else, left half < right half, stoneValue[mid] is added to right half
            //   - mid == i means left is stoneValue[i], so Alice gets zero
            //   - else left half = max[i][mid - 1]
            dp[i][j] = rightHalf * 2 == sum ? max[i][mid] : (mid == i ? 0 : max[i][mid - 1]);

            // right remains
            // - if right half == left half, stoneValue[mid] is not added to right half
            //   so right half = max[j][mid + 1]
            // - else, left half > right half, stoneValue[mid] is not added to right half
            //   - mid == j means right is stoneValue[j], so Alice gets zero
            //   - else right half = max[j][mid + 1]
            dp[i][j] = Math.max(dp[i][j], mid == j ? 0 : max[j][mid + 1]);

            max[i][j] = Math.max(max[i][j - 1], dp[i][j] + sum);
            max[j][i] = Math.max(max[j][i + 1], dp[i][j] + sum);
        }
    }
    return dp[0][n - 1];
}
1
2
3
4
5
6
7
8
// matrix[i][j] can be replaced by two arrays instead
left[i][j] = max(sum[i][k] + dp[i][k])
right[i][j] = max(sum[k][j] + dp[k][j])

left[i][j] = max(left[i][j-1], sum[i][j] + dp[i][j])
right[i][j] = max(right[i+1][j], sum[i][j] + dp[i][j])

dp[i][j] = max(left[i][mid], right[mid + 1][j])

Paint House III

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private static final int MAX = (int)1e6 + 1;

public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
    // dp[i][j][k]: min cost where we have j neighborhood in the first i houses
    //   and the i-th house is painted with the color k
    int[][][] dp = new int[m + 1][target + 1][n];

    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= target; j++) {
            Arrays.fill(dp[i][j], MAX);
        }
    }

    for (int k = 0; k < n; k++) {
        dp[0][0][k] = 0;
    }

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= Math.min(target, i); j++) {
            for (int k = 0; k < n; k++) {
                // the current house is houses[i - 1]
                // skips if it's painted and the painted color is not (k + 1)
                if (houses[i - 1] != 0 && k != houses[i - 1] - 1) {
                    continue;
                }

                // compares the current house with previous house
                int sameNeighborhood = dp[i - 1][j][k];

                int diffNeighborhood = MAX;
                for (int prevK = 0; prevK < n; prevK++) {
                    if (prevK != k) {
                        diffNeighborhood = Math.min(diffNeighborhood, dp[i - 1][j - 1][prevK]);
                    }
                }

                // paints the current house only if it's not pained yet
                int paintCost = cost[i - 1][k] * (houses[i - 1] == 0 ? 1 : 0);
                dp[i][j][k] = Math.min(sameNeighborhood, diffNeighborhood) + paintCost;
            }
        }
    }

    int min = MAX;
    for (int k = 0; k < n; k++) {
        min = Math.min(min, dp[m][target][k]);
    }

    return min == MAX ? -1 : min;
}

Make the XOR of All Segments Equal to Zero

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
private static final int MAX = (int)1e6 + 1;

public int minCost(int[] houses, int[][] cost, int m, int n, int target) {
    // dp[i][j][k]: min cost where we have j neighborhood in the first i houses
    //   and the i-th house is painted with the color k
    int[][][] dp = new int[m + 1][target + 1][n];

    for (int i = 0; i <= m; i++) {
        for (int j = 0; j <= target; j++) {
            Arrays.fill(dp[i][j], MAX);
        }
    }

    for (int k = 0; k < n; k++) {
        dp[0][0][k] = 0;
    }

    for (int i = 1; i <= m; i++) {
        for (int j = 1; j <= Math.min(target, i); j++) {
            for (int k = 0; k < n; k++) {
                // the current house is houses[i - 1]
                // skips if it's painted and the painted color is not (k + 1)
                if (houses[i - 1] != 0 && k != houses[i - 1] - 1) {
                    continue;
                }

                // compares the current house with previous house
                int sameNeighborhood = dp[i - 1][j][k];

                int diffNeighborhood = MAX;
                for (int prevK = 0; prevK < n; prevK++) {
                    if (prevK != k) {
                        diffNeighborhood = Math.min(diffNeighborhood, dp[i - 1][j - 1][prevK]);
                    }
                }

                // paints the current house only if it's not pained yet
                int paintCost = cost[i - 1][k] * (houses[i - 1] == 0 ? 1 : 0);
                dp[i][j][k] = Math.min(sameNeighborhood, diffNeighborhood) + paintCost;
            }
        }
    }

    int min = MAX;
    for (int k = 0; k < n; k++) {
        min = Math.min(min, dp[m][target][k]);
    }

    return min == MAX ? -1 : min;
}

Count Increasing Quadruplets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public long countQuadruplets(int[] nums) {
    int n = nums.length;
    // dp[j]: count of all valid triplets (i, j, k) so that i < j < k and nums[i] < nums[k] < nums[j]
    long[] dp = new long[n];
    long count = 0;
    for (int l = 0; l < n; l++) {
        // count of "l"s that can possibly be "k"s for newer "l"s
        int kCandidates = 0;
        for (int j = 0; j < l; j++) {
            if (nums[l] > nums[j]) {
                // "132" -> "1324": nums[j] => "3", nums[l] => "4"
                count += dp[j];
                kCandidates++;
            } else if (nums[l] < nums[j]) {
                // ("k candidate", j, l) becomes a valid triple
                dp[j] += kCandidates;
            }
        }
    }
    return count;
}

Another solution is by prefix sum.

Fractional DP

Minimum Skips to Arrive at Meeting On Time

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minSkips(int[] dist, int speed, int hoursBefore) {
    int n = dist.length;
    // dp[i][j]: minimum arriving time * speed when we have travelled i roads and skipped j rests
    long[][] dp = new long[n + 1][n + 1];

    for (int j = 0; j <= n; j++) {
        for (int i = 0; i < n; i++) {
            // no skip, ceil
            dp[i + 1][j] = (dp[i][j] + dist[i] + speed - 1) / speed * speed;

            // skips current rest at i-th road
            if (j > 0) {
                dp[i + 1][j] = Math.min(dp[i + 1][j], dist[i] + dp[i][j - 1]);
            }
        }

        // min skips to arrive with time <= hoursBefore
        if (dp[n][j] <= speed * hoursBefore) {
            return j;
        }
    }
    return -1;
}

Map

Tallest Billboard

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public int tallestBillboard(int[] rods) {
    // dp[i]: pair (a, b) with max a and b - a == i > 0
    Map<Integer, Integer> dp = new HashMap<>(), tmp;
    dp.put(0, 0);

    for (int r : rods) {
        tmp = new HashMap<>(dp);
        for (int d : tmp.keySet()) {
            // Case 1: put r to the long side
            // ---- v ----|-- d --|--- r ---|
            // ---- v ----|
            // dp[d + r] = max(dp[d + r], v)
            dp.put(d + r, Math.max(dp.getOrDefault(r + d, 0), tmp.get(d)));

            // Case 2: put r to the short side
            // ---- v ----|-- d --|
            // ---- v ----|--- r ---|
            // dp[r - d] = max(dp[r - d], v + d)
            // or
            // ---- v ----|-- d --|
            // ---- v ----|- r -|
            // dp[d - r] = max(dp[d - r], v + r)
            //
            // in summary,
            // dp[abs(d - r)] = max(dp[abs[d - r]], v + min(d, r))
            dp.put(Math.abs(d - r), Math.max(dp.getOrDefault(Math.abs(d - r), 0), tmp.get(d) + Math.min(d, r)));
        }
    }
    return dp.get(0);
}

Stickers to Spell Word

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
private Map<String, Integer> memo = new HashMap<>();
private int[][] countMap;

public int minStickers(String[] stickers, String target) {
    int n = stickers.length;
    this.countMap = new int[n][26];

    for (int i = 0; i < n; i++) {
        for (char c : stickers[i].toCharArray()) {
            countMap[i][c - 'a']++;
        }
    }

    memo.put("", 0);
    return dfs(target);
}

private int dfs(String target) {
    if (memo.containsKey(target)) {
        return memo.get(target);
    }

    int[] t = new int[26];
    for (char c : target.toCharArray()) {
        t[c - 'a']++;
    }

    int min = Integer.MAX_VALUE;
    StringBuilder sb = new StringBuilder();
    for (int[] s : countMap) {
        // the sticker has to contain the first character of target
        if (s[target.charAt(0) - 'a'] > 0) {
            // builds the string = (target - sticker)
            // the string is sorted
            for (int i = 0; i < 26; i++) {
                sb.append(String.valueOf((char)('a' + i)).repeat(Math.max(0, t[i] - s[i])));
            }

            int tmp = dfs(sb.toString());
            if (tmp != -1) {
                min = Math.min(min, tmp + 1);
            }
        }
        sb.setLength(0);
    }

    if (min == Integer.MAX_VALUE) {
        min = -1;
    }
    memo.put(target, min);
    return min;
}

Minimum Distance to Type a Word Using Two Fingers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int minimumDistance(String word) {
    // distance is the total distance we get with right finger
    int distance = 0, save = 0;
    // dp[i]: the max distance that can be saved if left finger ends at character i
    int[] dp = new int[26];
    for (int i = 0; i < word.length() - 1; i++) {
        int curr = word.charAt(i) - 'A', next = word.charAt(i + 1) - 'A';
        for (int prev = 0; prev < 26; prev++) {
            // moves right finger from curr to next
            // or moves left finger from prev to next
            dp[curr] = Math.max(dp[curr], dp[prev] + cost(curr, next) - cost(prev, next));
        }
        save = Math.max(save, dp[curr]);

        // now right finger is at next, left finger is at curr
        distance += cost(curr, next);
    }
    return distance - save;
}

private int cost(int a, int b) {
    return Math.abs(a / 6 - b / 6) + Math.abs(a % 6 - b % 6);
}

First Day Where You Have Been in All the Rooms

1
2
3
4
5
6
7
8
9
10
11
12
13
14
private static final int MOD = (int)1e9 + 7;

public int firstDayBeenInAllRooms(int[] nextVisit) {
    int n = nextVisit.length;
    long[] dp = new long[n];
    for (int i = 1; i < n; i++) {
        // 0 -> (i - 1): dp[i - 1]
        // (i - 1) -> nextVisit[i - 1]: 1
        // nextVisit[i - 1] -> (i - 1): dp[i - 1] - dp[nextVisit[i - 1]]
        // (i - 1) -> i: 1
        dp[i] = (2 * dp[i - 1] - dp[nextVisit[i - 1]] + 2 + MOD) % MOD;
    }
    return (int)dp[n - 1];
}

Choose Numbers From Two Arrays in Range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
private static final int MOD = (int)1e9 + 7;

public int countSubranges(int[] nums1, int[] nums2) {
    // dp[i]: number of ways to sum to i
    Map<Integer, Integer> dp = new HashMap<>(), dp2;

    int count = 0;
    for (int i = 0; i < nums1.length; i++) {
        dp2 = new HashMap<>();
        dp2.put(nums1[i], 1);
        // negates nums2 elements
        // the goal is to find the number of different ranges that sum to 0
        dp2.put(-nums2[i], dp2.getOrDefault(-nums2[i], 0) + 1);

        for (var e : dp.entrySet()) {
            int k = e.getKey(), v = e.getValue();
            // picks nums1[i]
            dp2.put(k + nums1[i], (dp2.getOrDefault(k + nums1[i], 0) + v) % MOD);
            // picks -nums2[i]
            dp2.put(k - nums2[i], (dp2.getOrDefault(k - nums2[i], 0) + v) % MOD);
        }

        count = (count + dp2.getOrDefault(0, 0)) % MOD;
        dp = dp2;
    }
    return count;
}

Minimum Total Space Wasted With K Resizing Operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public int minSpaceWastedKResizing(int[] nums, int k) {
    int n = nums.length, max = 0, sum = 0;
    int[][] dp = new int[n][k + 1];
    for (int i = n - 1; i >= 0; i--) {
        max = Math.max(max, nums[i]);
        sum += nums[i];
        dp[i][0] = max * (n - i) - sum;
    }

    for (int m = 1; m <= k; m++) {
        for (int i = n - 1; i >= 0; i--) {
            dp[i][m] = dp[i][m - 1];
            max = sum = 0;

            // resizes at i
            // finds the wasted space in [i, j)
            for (int j = i + 1; j < n; j++) {
                max = Math.max(max, nums[j - 1]);
                sum += nums[j - 1];
                dp[i][m] = Math.min(dp[i][m], dp[j][m - 1] + max * (j - i) - sum);
            }
        }
    }
    return dp[0][k];
}

Minimum White Tiles After Covering With Carpets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private static final int MAX_LENGTH = 1000;

public int minimumWhiteTiles(String floor, int numCarpets, int carpetLen) {
    int n = floor.length();
    // dp[i][j]: covers the first i tiles with j carpets
    int[][] dp = new int[n + 1][numCarpets + 1];
    for (int i = 1; i <= n; i++) {
        for (int j = 0; j <= numCarpets; j++) {
            int skip = dp[i - 1][j] + floor.charAt(i - 1) - '0';
            int cover = j > 0 ? dp[Math.max(i - carpetLen, 0)][j - 1] : MAX_LENGTH;
            dp[i][j] = Math.min(cover, skip);
        }
    }
    return dp[n][numCarpets];
}

Minimum Time to Finish the Race

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// f * r ^ (x - 1) >= changeTime
// if f == 1 and r == 2 (minimum)
// x >= 18
// i.e. it's better to change tire if the successiev laps >= 18
private static final int NUM_LAPS_TO_CHANGE_TIRE = 18;
// numLaps * max_time_per_lap
// = numLaps * (max_f + changeTime)
// = 2e8
private static final int MAX_TOTAL_TIME = (int)2e8;

public int minimumFinishTime(int[][] tires, int changeTime, int numLaps) {
    int n = tires.length;

    // noChange[i][j]: the total time to run j laps successively with tire i
    // j is small enough
    long[][] noChange = new long[n][NUM_LAPS_TO_CHANGE_TIRE];
    for (long[] t : noChange) {
        Arrays.fill(t, MAX_TOTAL_TIME);
    }

    for (int i = 0; i < n; i++) {
        noChange[i][1] = tires[i][0];
        // per lap
        for (int j = 2; j < NUM_LAPS_TO_CHANGE_TIRE; j++) {
            noChange[i][j] = noChange[i][j - 1] * tires[i][1];
            if (noChange[i][j] > MAX_TOTAL_TIME) {
                noChange[i][j] = MAX_TOTAL_TIME;
                break;
            }
        }

        // prefix sum
        for (int j = 2; j < NUM_LAPS_TO_CHANGE_TIRE; j++) {
            noChange[i][j] += noChange[i][j - 1];
            if (noChange[i][j] > MAX_TOTAL_TIME) {
                noChange[i][j] = MAX_TOTAL_TIME;
                break;
            }
        }
    }

    // dp[i]: the minimum time to finish i laps
    int[] dp = new int[numLaps + 1];
    Arrays.fill(dp, MAX_TOTAL_TIME);
    for (int[] tire : tires) {
        dp[1] = Math.min(dp[1], tire[0]);
    }

    for (int i = 1; i <= numLaps; i++) {
        if (i < NUM_LAPS_TO_CHANGE_TIRE) {
            // when i is small enough, the optimal solution might never change tire
            for (long[] t : noChange) {
                dp[i] = Math.min(dp[i], (int)t[i]);
            }
        }

        for (int j = i - 1; j > 0 && j >= i - NUM_LAPS_TO_CHANGE_TIRE; j--) {
            dp[i] = Math.min(dp[i], dp[j] + changeTime + dp[i - j]);
        }
    }

    return dp[numLaps];
}

The previous states of elements is stored in a map.

Longest String Chain

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public int longestStrChain(String[] words) {
    Arrays.sort(words, Comparator.comparingInt(s -> s.length()));

    Map<String, Integer> dp = new HashMap<>();
    int max = 0;
    for (String word : words) {
        int length = 0;
        for (int i = 0; i < word.length(); i++) {
            String predecessor = word.substring(0, i) + word.substring(i + 1);
            length = Math.max(length, dp.getOrDefault(predecessor, 0) + 1);
        }
        dp.put(word, length);
        max = Math.max(max, length);
    }
    return max;
}

Make Array Strictly Increasing

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public int makeArrayIncreasing(int[] arr1, int[] arr2) {
    Arrays.sort(arr2);

    // rolling dp
    // dp[i]: i is the element we choose for the current position.
    // this element can be from either arr1 or arr2.
    Map<Integer, Integer> dp = new HashMap<>();
    dp.put(-1, 0);

    for (int a1 : arr1) {
        // builds temporary dp map for i-th element of arr1
        Map<Integer, Integer> tmp = new HashMap<>();
        for (int key : dp.keySet()) {
            int val = dp.get(key);
            // option #1: no assignment for key -> a1
            if (a1 > key && (!tmp.containsKey(a1) || val < tmp.get(a1))) {
                tmp.put(a1, val);
            }

            // finds the smallest element in arr2 that's >= key
            int index = Arrays.binarySearch(arr2, key + 1);
            if (index < 0) {
                index = ~index;
            }

            // option #2: one assignment for key -> arr2[index]
            if (index < arr2.length && (!tmp.containsKey(arr2[index]) || val + 1 < tmp.get(arr2[index]))) {
                tmp.put(arr2[index], val + 1);
            }
        }
        dp = tmp;
    }

    return dp.isEmpty() ? -1 : Collections.min(dp.values());
}

Reverse

Coin Path

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public List<Integer> cheapestJump(int[] coins, int maxJump) {
    int n = coins.length;
    List<Integer> path = new ArrayList<>();
    if (coins[n - 1] < 0) {
        return path;
    }

    // dp[i]: cost from coins[i] to coins[n - 1]
    int[] dp = new int[n], next = new int[n];
    Arrays.fill(dp, Integer.MAX_VALUE);
    Arrays.fill(next, -1);

    dp[n - 1] = coins[n - 1];

    // reverse order to ensure we get the lexicographically smallest path
    for (int i = n - 2; i >= 0; i--) {
        if (coins[i] == -1) {
            continue;
        }

        for (int j = i + 1; j <= Math.min(i + maxJump, n - 1); j++) {
            // strict > guarantees lexicographical order
            if (dp[i] > dp[j] + coins[i] && dp[j] != Integer.MAX_VALUE) {
                dp[i] = dp[j] + coins[i];
                next[i] = j;
            }
        }
    }

    if (dp[0] == Integer.MAX_VALUE) {
        return path;
    }

    int index = 0;
    while (index != -1) {
        path.add(index + 1);
        index = next[index];
    }
    return path;
}

Race Car

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public int racecar(int target) {
    // dp[i]: the length of the shortest sequence of instructions from initial speed 1 to target i
    int[] dp = new int[target + 1];

    for (int i = 1; i <= target; i++) {
        dp[i] = Integer.MAX_VALUE;

        // j is the position of the car right before the first reverse instruction
        // j = 2 ^ a1 - 1, where a1 is the number of 'A's.
        int a1 = 1, j = 1;

        // if j < i, the reverse instruction is issued before the car reaches i
        // the car is going away from the target
        // we need to wait for the second reverse instruction
        for (; j < i; j = (1 << ++a1) - 1) {
            // j - k is the position of the car right before the second reverse instruction
            // k = 2 ^ a2 - 1
            // where a2 is the number of 'A's between j and the position of the second reverse instruction
            for (int a2 = 0, k = 0; k < j; k = (1 << ++a2) - 1) {
                // +1 are 'R's
                dp[i] = Math.min(dp[i], a1 + 1 + a2 + 1 + dp[i - (j - k)]);
            }
        }

        // if j == i, no reverse instructions
        // if j > i, only one reverse instruction
        dp[i] = Math.min(dp[i], a1 + (i == j ? 0 : 1 + dp[j - i]));
    }

    return dp[target];
}

Precompute

Number of Ways to Reach a Position After Exactly k Steps

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static final int MOD = (int)1e9 + 7;
private static final int MAX_POS = 1000;

public int numberOfWays(int startPos, int endPos, int k) {
    // dp[i][j]: uses exact i steps to reach end, while distance between start (fixed to 0) and end is j
    int[][] dp = new int[MAX_POS + 1][MAX_POS + 1];
    for (int i = 1; i < dp.length; i++) {
        // one way to go distance i
        dp[i][i] = 1;
        for (int j = 0; j < i; j++) {
            dp[i][j] = (dp[i - 1][Math.abs(j - 1)] + dp[i - 1][j + 1]) % MOD;
        }
    }

    return dp[k][Math.abs(startPos - endPos)];
}

Digit Dynamic Programming

Count of Integers

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
    const int MOD = 1e9 + 7;
    // memo[index][isLowTight][isHighTight][sum]
    int memo[23][2][2][401];

    /**
     * @brief Counts the number of integers in [num1, num2] whose sum of digits <= `sum`.
     * @param index: Index of the current index. Index 0 stands for the most significant digit.
     * @param isLowTight: If all the selected digits so far are the lowest possible.
     * @param isHighTight: If all the selected digits so far are the highest possible.
     */
    int countStrings(int index, int sum, bool isLowTight, bool isHighTight, string num1, string num2) {
        if (sum < 0) {
            return 0;
        }

        if (index == num2.length()) {
            return 1;
        }

        if (memo[index][isLowTight][isHighTight][sum] >= 0) {
            return memo[index][isLowTight][isHighTight][sum];
        }

        int cnt = 0;
        // e.g. num1 == 234, index == 1
        //   If previous isLowTight is true, then the running number is 2**,
        //     if we want to keep the current digit low tight, then it has to be 3.
        //     any number lower than 3, e.g. 2, will yield 22* < 234
        //   otherwise, the running number is more flexible, e.g. 3**
        //     the current digit can be any number (as low as 0)
        // Same applies to isHighTight
        int low = isLowTight ? num1[index] - '0' : 0;
        int high = isHighTight ? num2[index] - '0' : 9;
        for (int d = low; d <= high; d++) {
            cnt = (cnt + countStrings(index + 1, sum - d, isLowTight && d == low, isHighTight && d == high, num1, num2) % MOD) % MOD;
        }
        return memo[index][isLowTight][isHighTight][sum] = cnt;
    }

public:
    int count(string num1, string num2, int min_sum, int max_sum) {
        memset(memo, -1, sizeof(memo));

        // Makes num1 and num2 equal length
        num1 = string(num2.length() - num1.length(), '0') + num1;

        // Initial value of `isTight` is `true`.
        int cnt1 = countStrings(0, max_sum, true, true, num1, num2);
        int cnt2 = countStrings(0, min_sum - 1, true, true, num1, num2);

        return (cnt1 - cnt2 + MOD) % MOD;
    }

Number of Beautiful Integers in the Range

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    // memo[index][isLowTight][isHighTight][odd][even][mod][isZero]
    int memo[12][2][2][12][12][20][2];
    int k;

    int countIntegers(const string& s1, const string& s2, int index, bool isLowTight, bool isHighTight, int odd, int even, int mod, bool isZero) {
        if (index == s2.length()) {
            return !isZero && mod == 0 && odd == even;
        }

        if (memo[index][isLowTight][isHighTight][odd][even][mod][isZero] >= 0) {
            return memo[index][isLowTight][isHighTight][odd][even][mod][isZero];
        }

        int low = isLowTight ? s1[index] - '0' : 0;
        int high = isHighTight ? s2[index] - '0' : 9;
        int cnt = 0;
        for (int d = low; d <= high; d++) {
            // `isZero` means the digits at [0, index) are all zeros .
            // We don't count odds and evens for numbers with leading zeros,
            // e.g. "0032" is not a valid beautiful integer for k = 2.
            if (isZero && !d) {
                // isZero && d == 0
                //   => low == 0 && isLowTight == true
                //   => new isLowTight = (isLowTight && d == low) == true
                //
                // Since s2 has no leading zeros, the current number with isZero == true can't be high tight.
                // Therefore, new isHighTight == false.
                cnt += countIntegers(s1, s2, index + 1, true, false, odd, even, mod, true);
            } else {
                cnt += countIntegers(s1, s2, index + 1, isLowTight && d == low, isHighTight && d == high, odd + d % 2, even + 1 - d % 2, (mod * 10 + d) % k, false);
            }
        }
        return memo[index][isLowTight][isHighTight][odd][even][mod][isZero] = cnt;
    }

public:
    int numberOfBeautifulIntegers(int low, int high, int k) {
        this->k = k;

        memset(memo, -1, sizeof(memo));

        // Makes num1 and num2 equal length
        string s1 = to_string(low), s2 = to_string(high);
        s1 = string(s2.length() - s1.length(), '0') + s1;
        return countIntegers(s1, s2, 0, true, true, 0, 0, 0, true);
    }

You can process low and high separately with two calls, eliminating the need to pad low.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
    // memo[index][isTight][odd][even][mod][isZero]
    int memo[12][2][12][12][20][2];
    int k;

    int countIntegers(const string& s, int index = 0, bool isTight = true, int odd = 0, int even = 0, int mod = 0, bool isZero = true) {
        if (index == s.size()) {
            return !isZero && mod == 0 && odd == even;
        }

        if (memo[index][isTight][odd][even][mod][isZero] >= 0) {
            return memo[index][isTight][odd][even][mod][isZero];
        }

        int high = isTight ? s[index] - '0' : 9;
        int cnt = 0;
        for (int d = 0; d <= high; d++) {
            // `isZero` means the digits at [0, index) are all zeros .
            // We don't count odds and evens for numbers with leading zeros,
            // e.g. "0032" is not a valid beautiful integer for k = 2.
            if (isZero && !d) {
                // Since s has no leading zeros, the current number with isZero == true can't be high tight.
                // Therefore, new isHighTight == false.
                cnt += countIntegers(s, index + 1, false, odd, even, mod, true);
            } else {
                cnt += countIntegers(s, index + 1, isTight && d == high, odd + d % 2, even + 1 - d % 2, (mod * 10 + d) % k, false);
            }
        }
        return memo[index][isTight][odd][even][mod][isZero] = cnt;
    }

public:
    int numberOfBeautifulIntegers(int low, int high, int k) {
        this->k = k;

        memset(memo, -1, sizeof(memo));
        int cnt1 = countIntegers(to_string(high));

        memset(memo, -1, sizeof(memo));
        int cnt2 = countIntegers(to_string(low - 1));

        return cnt1 - cnt2;
    }

Count the Number of Powerful Integers

This problem can be solved by digit DP, however, there is a more efficient and smarter solution (credits to @abhik2003):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
    string s;
    int limit;

    long long countInts(const string& num) {
        // Length of prefix
        int p = num.length() - s.length();
        if (p < 0) {
            return 0;
        }

        if (p == 0) {
            return num >= s;
        }

        string suffix = num.substr(p, s.length());
        long long res = 0;
        // Iterates through nums[0...p)
        for (int i = 0; i < p; i++) {
            if (num[i] - '0' > limit) {
                // The remaining digits in the prefix (index [i, p)) can pick any number in [0, limit].
                // In this way, the built string (p + s) < num.
                return res + pow(limit + 1, p - i);
            }

            // num[i] - '0' <= limit
            // If we pick any number in [0, num[i]),
            //   the remaining digits in the prefix (index (i, p)) can pick any number in the range [0, limit].
            // If we pick num[i] (tight case),
            //   the iteration needs to go on for possibly more candidates.
            res += (num[i] - '0') * pow(limit + 1, p - i - 1);
        }

        // Code reaches here only if the prefix has been tight up to p - 1.
        // so suffix >= s is the requirement to make (p + s) <= num.
        return res + (suffix >= s);
    }

public:
    long long numberOfPowerfulInt(long long start, long long finish, int limit, string s) {
        this->limit = limit;
        this->s = s;

        return countInts(to_string(finish)) - countInts(to_string(start - 1));
    }
This post is licensed under CC BY 4.0 by the author.