Post

Dynamic Programming (Top-down)

Divide and conquer + Memoization.

Maximum Value of K Coins From Piles

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private Integer[][] memo;

public int maxValueOfCoins(List<List<Integer>> piles, int k) {
    this.memo = new Integer[piles.size() + 1][k + 1];
    return dfs(piles, 0, k);
}

private int dfs(List<List<Integer>> piles, int i, int k) {
    if (k == 0 || i == piles.size()) {
        return 0;
    }

    if (memo[i][k] != null) {
        return memo[i][k];
    }

    int total = 0, max = dfs(piles, i + 1, k);
    for (int j = 0; j < Math.min(piles.get(i).size(), k); j++) {
        total += piles.get(i).get(j);
        max = Math.max(max, total + dfs(piles, i + 1, k - j - 1));
    }
    return memo[i][k] = max;
}

Largest Sum of Averages

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
private double[][] memo;
private double[] sum;

public double largestSumOfAverages(int[] nums, int k) {
    int n = nums.length;
    memo = new double[n + 1][k + 1];
    sum = new double[n + 1];

    for (int i = 0; i < n; i++) {
        sum[i + 1] = sum[i] + nums[i];
    }
    return largestSumOfAverages(nums, n, k);
}

private double largestSumOfAverages(int[] nums, int end, int k) {
    if (memo[end][k] != 0) {
        return memo[end][k];
    }

    if (k == 1) {
        memo[end][1] = sum[end] / end;
        return memo[end][1];
    }

    // "at most k groups" is equivalent to "exact k groups"
    // so we don't need to consider largestSumOfAverages(nums, end, k - 1)
    //
    // see https://en.wikipedia.org/wiki/Mediant_(mathematics)#Properties
    double max = 0;
    for (int i = end - 1; i >= k - 1; i--) {
        max = Math.max(max, (sum[end] - sum[i]) / (end - i) + largestSumOfAverages(nums, i, k - 1));
    }
    memo[end][k] = max;
    return max;
}

The result of each loop can be written to memo directly:

1
2
3
4
for (int i = end - 1; i >= k - 1; i--) {
    memo[end][k] = Math.max(memo[end][k], (sum[end] - sum[i]) / (end - i) + largestSumOfAverages(nums, i, k - 1));
}
return memo[end][k];

Bottom-up DP can be derived in the following way:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public double largestSumOfAverages(int[] nums, int k) {
    int n = nums.length;

    double[] sum = new double[n + 1];
    for (int i = 0; i < n; i++) {
        sum[i + 1] = sum[i] + nums[i];
    }

    double[][] dp = new double[n + 1][k + 1];
    for (int i = 1; i <= n; i++) {
        dp[i][1] = sum[i] / i;
    }

    for (int m = 2; m <= k; m++) {
        for (int i = 1; i <= n; i++) {
            for (int j = 0; j < i; j++) {
                dp[i][m] = Math.max(dp[i][m], (sum[i] - sum[j]) / (i - j) + dp[j][m - 1]);
            }
        }
    }

    return dp[n][k];
}
1
2
3
4
5
6
[0.0,0.0,0.0,0.0]
[0.0,9.0,9.0,9.0]
[0.0,5.0,10.0,10.0]
[0.0,4.0,10.5,12.0]
[0.0,3.75,11.0,13.5]
[0.0,4.8,12.75,20.0]

1D:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public double largestSumOfAverages(int[] nums, int k) {
    int n = nums.length;

    double[] sum = new double[n + 1];
    for (int i = 0; i < n; i++) {
        sum[i + 1] = sum[i] + nums[i];
    }

    double[] dp = new double[n + 1];
    for (int i = 1; i <= n; i++) {
        dp[i] = sum[i] / i;
    }

    for (int m = 2; m <= k; m++) {
        for (int i = n; i > 0; i--) {
            for (int j = 0; j < i; j++) {
                dp[i] = Math.max(dp[i], (sum[i] - sum[j]) / (i - j) + dp[j]);
            }
        }
    }

    return dp[n];
}

DP scans array from right to left:

Maximum Score from Performing Multiplication Operations

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public int maximumScore(int[] nums, int[] multipliers) {
    int n = nums.length, m = multipliers.length;
    // dp[i][j]: left of array elements has index i, and j is the index of multipliers 
    int[][] dp = new int[m + 1][m + 1];

    for (int i = m - 1; i >= 0; i--) {
        for (int j = m - 1; j >= 0; j--) {
            // len = n - j
            // right = left + len - 1
            int right = i + (n - j) - 1;
            if (right >= 0 && right < n) {
                // chooses start or end
                dp[i][j] = Math.max(dp[i + 1][j + 1] + nums[i] * multipliers[j], dp[i][j + 1] + nums[right] * multipliers[j]);
            }
        }
    }
    return dp[0][0];
}

Minimum Score Triangulation of Polygon

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
private int[][] memo;

public int minScoreTriangulation(int[] values) {
    int n = values.length;
    memo = new int[n][n];
    return minScore(values, 0, n - 1);
}

private int minScore(int[] values, int start, int end) {
    if (start + 1 == end) {
        return 0;
    }

    if (memo[start][end] > 0) {
        return memo[start][end];
    }

    int min = Integer.MAX_VALUE;
    for (int i = start + 1; i < end; i++) {
        int sum = values[start] * values[end] * values[i];
        sum += minScore(values, start, i);
        sum += minScore(values, i, end);
        min = Math.min(min, sum);
    }
    return memo[start][end] = min;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
public int minScoreTriangulation(int[] values) {
    int n = values.length;
    int[][] dp = new int[n][n];
    for (int i = n - 1; i >= 0; i--) {
        for (int j = i + 2; j < n; j++) {
            dp[i][j] = Integer.MAX_VALUE;
            for (int k = i + 1; k < j; k++) {
                dp[i][j] = Math.min(dp[i][j], dp[i][k] + dp[k][j] + values[i] * values[j] * values[k]);
            }
        }
    }
    return dp[0][n - 1];
}
1
2
3
4
5
    for (int j = 2; j < n; j++) {
        for (int i = j - 2; i >= 0; i--) {

        }
    }

Minimum Total Distance Traveled

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
private Long[][][] memo;

public long minimumTotalDistance(List<Integer> robot, int[][] factory) {
    int n = robot.size(), m = factory.length;
    // memo[i][j][k]: the cost to fix robot[i...] at factory[j] which has already fixed k robots
    this.memo = new Long[n + 1][m + 1][n + 1];

    Collections.sort(robot);
    Arrays.sort(factory, Comparator.comparingInt(f -> f[0]));

    return helper(robot, factory, 0, 0, 0);
}

public long helper(List<Integer> robot, int[][] factory, int i, int j, int k) {
    // all robots are fixed
    if (i == robot.size()) {
        return 0;
    }

    // no more factory to fix the remaining robots
    if (j == factory.length) {
        return Long.MAX_VALUE;
    }

    if (memo[i][j][k] != null) {
        return memo[i][j][k];
    }

    // skips current factory
    long res1 = helper(robot, factory, i, j + 1, 0);

    // fixes current robot at current factory
    long res2 = Long.MAX_VALUE;
    if (factory[j][1] > k) {
        long val = helper(robot, factory, i + 1, j, k + 1);
        if (val != Long.MAX_VALUE) {
            res2 = val + Math.abs(robot.get(i) - factory[j][0]);
        }
    }
    return memo[i][j][k] = Math.min(res1, res2);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public long minimumTotalDistance(List<Integer> robot, int[][] factory) {
    int n = robot.size(), m = factory.length;
    long[] dp = new long[n + 1];
    Arrays.fill(dp, (long)1e12);  // > 100 * 2 * 10 ^ 9
    dp[n] = 0;

    Collections.sort(robot);
    Arrays.sort(factory, Comparator.comparingInt(f -> f[0]));

    for (int j = m - 1; j >= 0; j--) {
        for (int i = 0; i < n; i++) {
            long d = 0;
            for (int k = 1; k <= Math.min(factory[j][1], n - i); k++) {
                // the (i + k - 1)-th robot is fixed by current factory
                d += Math.abs(robot.get(i + k - 1) - factory[j][0]);
                dp[i] = Math.min(dp[i], dp[i + k] + d);
            }
        }
    }
    return dp[0];
}
This post is licensed under CC BY 4.0 by the author.