leetcode
leetcode 2351 ~ 2400
得到 K 个半回文串的最少修改次数

得到 K 个半回文串的最少修改次数

难度:

标签:

题目描述

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

  • A string is a palindrome if it can be read the same way from left to right and right to left.
  • A string with a length of len is considered a semi-palindrome if there exists a positive integer d such that 1 <= d < len and len % d == 0, and if we take indices that have the same modulo by d, they form a palindrome. For example, "aa", "aba", "adbgad", and, "abab" are semi-palindrome and "a", "ab", and, "abca" are not.
  • A substring is a contiguous sequence of characters within a string.

 

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:

  • 2 <= s.length <= 200
  • 1 <= k <= s.length / 2
  • s consists only of lowercase English letters.

代码结果

运行时间: 416 ms, 内存: 21.7 MB


// Problem: Similar to the above solution but using Java Streams for more concise code.

// Approach: We use a similar approach as above, utilizing streams to calculate the minimum changes needed for each substring.
// We loop through each possible division point and use streams to determine the required changes.

import java.util.*;
import java.util.stream.IntStream;

public class SolutionStream {
    public int minChangesToSemiPalindrome(String s, int k) {
        int n = s.length();
        int[][] dp = new int[k + 1][n + 1];
        for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE / 2);
        dp[0][0] = 0;
        for (int i = 1; i <= k; i++) {
            for (int j = 1; j <= n; j++) {
                dp[i][j] = IntStream.range(0, j)
                    .map(m -> dp[i - 1][m] + minChanges(s.substring(m, j)))
                    .min().orElse(Integer.MAX_VALUE / 2);
            }
        }
        return dp[k][n];
    }

    private int minChanges(String str) {
        int len = str.length();
        return (int) IntStream.range(0, len / 2)
            .filter(i -> str.charAt(i) != str.charAt(len - i - 1))
            .count();
    }
}

解释

方法:

该题解采用了动态规划和记忆化搜索的方法来解决问题。主要分为两部分: 1. **get_modify(start, end)**: 计算将子字符串 s[start:end+1] 转换成半回文串的最小修改次数。它通过枚举所有可能的 d 值来找到最优解。对于每个 d,它检查通过分组跳跃比较字符是否能形成多个小的回文字符串。 2. **dp(k, end)**: 使用动态规划来计算最终结果,dp(k, end) 表示将字符串 s[0:end+1] 分割成 k 个半回文子字符串的最小修改次数。它递归地尝试所有可能的分割点,结合 get_modify 函数结果来更新状态。 整体上,通过组合这两个函数的结果来构建全局最优解。

时间复杂度:

O(n^3 * K)

空间复杂度:

O(n^2 + n * K)

代码细节讲解

🦆
在函数`get_modify(start, end)`中,为什么将最大修改次数初始化为`length`?这是否意味着最坏情况下每个字符都需要修改?
在`get_modify`函数中,`length`代表考察的子字符串`s[start:end+1]`的长度。初始化最大修改次数为`length`是基于最坏情况的考虑,即如果无法通过任何方式的字符分组或调整来形成半回文串,那么可能需要修改每一个字符来达到半回文的条件。因此,最初设定`best`为`length`,然后通过尝试不同的分组方法寻找是否有需要更少修改的方案。如果有,`best`会被更新为较小的数值。
🦆
在枚举所有可能的`d`值时,为什么选择跳过`length % d != 0`的情况?这对于确定是否可以形成半回文串有什么特别的意义吗?
在`get_modify`函数中跳过`length % d != 0`的情况,是因为如果`length`不能被`d`整除,那么子字符串不能被平均分割成长度为`d`的多个小组。这意味着字符分组将不均匀,一些组内的字符数量会与其他组不同,从而无法通过固定的跳跃间隔进行有效的回文检查。因此,只有当`length`能被`d`整除时,才能保证所有组内的字符数量相同,这是形成多个小的回文字符串的基础。
🦆
在计算每个可能的`d`时,具体是如何通过`分组跳跃比较字符`来检查是否能形成多个小的回文字符串的?能否详细解释这个过程?
具体来说,在计算每个可能的`d`时,通过将子字符串`s[start:end+1]`分成多个长度为`d`的小组进行处理。对于每个起始位置`start_`(从0到`d-1`),在子字符串中以`start_`为起点,每隔`d`个字符取一个,这样形成一个新的字符序列。然后,检查这个序列是否能通过最小修改成为回文串。具体检查方法是,比较序列的首尾字符,逐步向中心移动,计算不匹配的字符对,这些就是需要修改的次数。这一过程重复进行,直到覆盖所有可能的起始位置`start_`。
🦆
在`dp(k, end)`函数中,为什么特别将`k==1`的情况单独处理,而不是统一使用循环逻辑来处理?
在`dp(k, end)`函数中,单独处理`k==1`的情况是为了效率和逻辑上的简化。当`k==1`时,意味着整个字符串`s[0:end+1]`需要被转换为一个半回文串,此时直接调用`get_modify(0, end)`得到转换整个子字符串的最小修改次数。这是一个基础情况,作为动态规划的边界条件。如果不单独处理,递归时会多一个不必要的内部循环,增加计算复杂度。单独处理可以更直接地解决问题,并减少不必要的计算。

相关问题