LeetCode-in-Java

2911. Minimum Changes to Make K Semi-palindromes

Hard

Given a string s and an integer k, partition s into k substrings such that the sum of the number of letter changes required to turn each substring into a semi-palindrome is minimized.

Return an integer denoting the minimum number of letter changes required.

Notes

Example 1:

Input: s = “abcac”, k = 2

Output: 1

Explanation: We can divide s into substrings “ab” and “cac”. The string “cac” is already a semi-palindrome. If we change “ab” to “aa”, it becomes a semi-palindrome with d = 1. It can be shown that there is no way to divide the string “abcac” into two semi-palindrome substrings. Therefore, the answer would be at least 1.

Example 2:

Input: s = “abcdef”, k = 2

Output: 2

Explanation: We can divide it into substrings “abc” and “def”. Each of the substrings “abc” and “def” requires one change to become a semi-palindrome, so we need 2 changes in total to make all substrings semi-palindrome. It can be shown that we cannot divide the given string into two substrings in a way that it would require less than 2 changes.

Example 3:

Input: s = “aabbaa”, k = 3

Output: 0

Explanation: We can divide it into substrings “aa”, “bb” and “aa”. The strings “aa” and “bb” are already semi-palindromes. Thus, the answer is zero.

Constraints:

Solution

public class Solution {
    private static final int INF = 200;
    private final Divisor[] divisors = getDivisors();
    private char[] cs;
    private int[][] cost;
    private int[][] dp;

    public int minimumChanges(String s, int k) {
        cs = s.toCharArray();
        int n = cs.length;
        cost = new int[n - 1][n + 1];
        dp = new int[n + 1][k + 1];
        return calc(n, k) - k;
    }

    private int calc(int i, int k) {
        if (k == 1) {
            return change(0, i);
        }
        if (dp[i][k] > 0) {
            return dp[i][k];
        }
        int min = INF;
        for (int j = (k - 1) * 2; j < i - 1; ++j) {
            min = Math.min(min, calc(j, k - 1) + change(j, i));
        }
        dp[i][k] = min;
        return min;
    }

    private int change(int start, int end) {
        if (cost[start][end] > 0) {
            return cost[start][end];
        }
        int min = INF;
        for (Divisor divisor = divisors[end - start]; divisor != null; divisor = divisor.next) {
            int d = divisor.value;
            int count = 0;
            for (int i = 0; i < d; ++i) {
                int left = start + i;
                int right = end - d + i;
                while (left + d <= right) {
                    if (cs[left] != cs[right]) {
                        count++;
                    }
                    left += d;
                    right -= d;
                }
            }
            if (count < min) {
                min = count;
            }
        }
        cost[start][end] = min + 1;
        return min + 1;
    }

    private Divisor[] getDivisors() {
        Divisor[] list = new Divisor[200 + 1];
        for (int d = 1; d < 200; ++d) {
            for (int len = d + d; len < 200 + 1; len += d) {
                list[len] = new Divisor(d, list[len]);
            }
        }
        return list;
    }

    private static class Divisor {
        int value;
        Divisor next;

        Divisor(int divisor, Divisor next) {
            this.value = divisor;
            this.next = next;
        }
    }
}