LeetCode in Kotlin

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

import kotlin.math.min

class Solution {
    private val divisors = getDivisors()
    private lateinit var cs: CharArray
    private lateinit var cost: Array<IntArray>
    private lateinit var dp: Array<IntArray>

    fun minimumChanges(s: String, k: Int): Int {
        cs = s.toCharArray()
        val n = cs.size
        cost = Array(n - 1) { IntArray(n + 1) }
        dp = Array(n + 1) { IntArray(k + 1) }
        return calc(n, k) - k
    }

    private fun calc(i: Int, k: Int): Int {
        if (k == 1) {
            return change(0, i)
        }
        if (dp[i][k] > 0) {
            return dp[i][k]
        }
        var min = INF
        for (j in (k - 1) * 2 until (i - 1)) {
            min = min(min.toDouble(), (calc(j, k - 1) + change(j, i)).toDouble()).toInt()
        }
        dp[i][k] = min
        return min
    }

    private fun change(start: Int, end: Int): Int {
        if (cost[start][end] > 0) {
            return cost[start][end]
        }
        var min = INF
        var divisor = divisors[end - start]
        while (divisor != null) {
            val d = divisor.value
            var count = 0
            for (i in 0 until d) {
                var left = start + i
                var right = end - d + i
                while (left + d <= right) {
                    if (cs[left] != cs[right]) {
                        count++
                    }
                    left += d
                    right -= d
                }
            }
            if (count < min) {
                min = count
            }
            divisor = divisor.next
        }
        cost[start][end] = min + 1
        return min + 1
    }

    private fun getDivisors(): Array<Divisor?> {
        val list = arrayOfNulls<Divisor>(200 + 1)
        for (d in 1..199) {
            var len = d + d
            while (len < 200 + 1) {
                list[len] = Divisor(d, list[len])
                len += d
            }
        }
        return list
    }

    private class Divisor(var value: Int, var next: Divisor?)
    companion object {
        private const val INF = 200
    }
}