Talentd
FleetCode
Make and follow sheets.Sign in

Minimum Changes to Make K Semi-palindromes solution and explanation

HardDynamic ProgrammingStringTwo Pointers7 min read35.3% acceptanceUpdated 9 Apr 2026
2911. Minimum Changes to Make K Semi-palindromes | Weekly Leetcode 368codingMohan · 30:26 · 1K viewsWatch on YouTube
1 more video on this problem
  1. 2911. Minimum Changes to Make K Semi-palindromes | Weekly Leetcode 368codingMohan · 30:26 · 1K views
  2. 2911. Minimum Changes to Make K Semi-palindromes (Leetcode Hard)Programming Live with Larry · 1:09:36 · 292 views

The problem

Given a string s and an integer k, partition s into k substrings such that the letter changes needed to make each substring a semi-palindrome are minimized.

Return the minimum number of letter changes required.

A semi-palindrome is a special type of string that can be divided into palindromes based on a repeating pattern. To check if a string is a semi-palindrome:​

  1. Choose a positive divisor d of the string's length. d can range from 1 up to, but not including, the string's length. For a string of length 1, it does not have a valid divisor as per this definition, since the only divisor is its length, which is not allowed.
  2. For a given divisor d, divide the string into groups where each group contains characters from the string that follow a repeating pattern of length d. Specifically, the first group consists of characters at positions 1, 1 + d, 1 + 2d, and so on; the second group includes characters at positions 2, 2 + d, 2 + 2d, etc.
  3. The string is considered a semi-palindrome if each of these groups forms a palindrome.

Consider the string "abcabc":

  • The length of "abcabc" is 6. Valid divisors are 1, 2, and 3.
  • For d = 1: The entire string "abcabc" forms one group. Not a palindrome.
  • For d = 2:
    • Group 1 (positions 1, 3, 5): "acb"
    • Group 2 (positions 2, 4, 6): "bac"
    • Neither group forms a palindrome.
  • For d = 3:
    • Group 1 (positions 1, 4): "aa"
    • Group 2 (positions 2, 5): "bb"
    • Group 3 (positions 3, 6): "cc"
    • All groups form palindromes. Therefore, "abcabc" is a semi-palindrome.

Example 1:

Input: s = "abcac", k = 2

Output: 1

Explanation: Divide s into "ab" and "cac". "cac" is already semi-palindrome. Change "ab" to "aa", it becomes semi-palindrome with d = 1.

Example 2:

Input: s = "abcdef", k = 2

Output: 2

Explanation: Divide s into substrings "abc" and "def". Each needs one change to become semi-palindrome.

Example 3:

Input: s = "aabbaa", k = 3

Output: 0

Explanation: Divide s into substrings "aa", "bb" and "aa". All are already semi-palindromes.

Constraints:

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

Approach overview

Problem Overview: You are given a string s and an integer k. Split the string into k non-empty substrings such that every substring becomes a semi-palindrome. A semi-palindrome allows periodic symmetry: characters spaced by a fixed divisor mirror each other. The goal is to minimize the number of character changes required.

Approach 1: Dynamic Programming with Semi‑Palindrome Cost Precomputation (O(n^3) time, O(n^2) space)

The key challenge is evaluating how many edits are required to convert any substring s[i..j] into a semi‑palindrome. Precompute this cost for all substrings. For each length L, enumerate its divisors d. Treat indices with step d as independent sequences and check palindrome mismatches using a two pointers style comparison inside each group. Store the minimum change count in cost[i][j]. After preprocessing, apply dynamic programming where dp[i][p] represents the minimum cost to split the first i characters into p valid semi‑palindromes. Transition by iterating previous split points j and adding cost[j][i-1]. This approach systematically explores all partitions while reusing precomputed substring costs.

Approach 2: Greedy Partitioning with Preprocessing (O(n^3) time, O(n^2) space)

This strategy still relies on preprocessing substring costs but reduces DP state complexity by making locally optimal partition choices. First compute the edit cost for each substring using divisor grouping and mismatch counting over the string. Then iterate across the string and choose segment boundaries that minimize incremental change cost while ensuring exactly k segments remain feasible. The greedy step prioritizes substrings with the smallest conversion cost and adjusts boundaries when future segments would become impossible. While it often performs well in practice, correctness relies on careful feasibility checks for remaining characters.

Recommended for interviews: The dynamic programming approach with substring cost preprocessing is the expected solution. Brute force partitioning demonstrates understanding but quickly becomes exponential. Precomputing semi‑palindrome costs and combining it with DP shows strong problem decomposition and optimization skills, which interviewers typically look for in hard string DP problems.

Approach 1: Dynamic Programming

Use dynamic programming to solve this problem by calculating the minimum changes needed to convert each substring into a semi-palindrome. This involves breaking the problem into subproblems where each subproblem calculates the minimum changes required for a semi-palindrome for every substring division at different indices.

#include <iostream>
#include <vector>
#include <climits>
#include <cstring>

using namespace std;

int min(int a, int b) {
    return a < b ? a : b;
}

int palindrome_changes(const string &s, int start, int end) {
    int changes = 0;
    while (start < end) {
        if (s[start] != s[end]) {
            changes++;
        }
        start++;
        end--;
    }
    return changes;
}

int minChangesToMakeKPalindromes(const string &s, int k) {
    int n = s.length();
    vector<vector<int>> dp(n, vector<int>(k + 1, INT_MAX));

    for (int i = 0; i < n; ++i) {
        dp[i][1] = palindrome_changes(s, 0, i);
    }

    for (int i = 0; i < n; ++i) {
        for (int j = 2; j <= k; ++j) {
            for (int m = j - 1; m <= i; ++m) {
                dp[i][j] = min(dp[i][j], dp[m - 1][j - 1] + palindrome_changes(s, m, i));
            }
        }
    }

    return dp[n - 1][k];
}

int main() {
    string s = "abcac";
    int k = 2;
    cout << minChangesToMakeKPalindromes(s, k) << endl;
    return 0;
}
#include <stdio.h>
#include <string.h>
#include <limits.h>

int min(int a, int b) {
    return a < b ? a : b;
}

int palindrome_changes(char *s, int start, int end) {
    int changes = 0;
    while (start < end) {
        if (s[start] != s[end]) {
            changes++;
        }
        start++;
        end--;
    }
    return changes;
}

int minChangesToMakeKPalindromes(char *s, int n, int k) {
    int dp[n][k+1];
    memset(dp, 0, sizeof(dp));

    for (int i = 0; i < n; ++i) {
        dp[i][1] = palindrome_changes(s, 0, i);
    }
    
    for (int i = 0; i < n; ++i) {
        for (int j = 2; j <= k; ++j) {
            dp[i][j] = INT_MAX;
            for (int m = j-1; m <= i; ++m) {
                dp[i][j] = min(dp[i][j], dp[m-1][j-1] + palindrome_changes(s, m, i));
            }
        }
    }

    return dp[n-1][k];
}

int main() {
    char s[] = "abcac";
    int k = 2;
    int n = strlen(s);
    printf("%d\n", minChangesToMakeKPalindromes(s, n, k));
    return 0;
}

The C++ solution mirrors the C solution's dynamic programming approach. The dp table is constructed using nested loops to store the minimum changes for creating j semi-palindromes from the first i characters of s. It then computes the possible partitions and updates dp[i][j] by adding the palindrome changes for current partitions in the loop.

Complexity

Time Complexity: O(n^3) due to triple nested loop for each substring division
Space Complexity: O(n*k) for the dp array

Try this approach in the editor

Approach 2: Greedy with Preprocessing

This approach involves a preprocessing step to approximate the number of changes needed to convert each possible substring into a semi-palindrome. Using a greedy strategy, we then try to partition the string into k substrings while minimizing changes for each partition step.

def min_changes_to_k_palindromes(s: str, k: int) -> int:
    def palindrome_changes(st, en):
        return sum(1 for i in range((en - st) // 2) if s[st + i] != s[en - i - 1])

    n = len(s)
    dp = [[float('inf')] * (k + 1) for _ in range(n)]

    for i in range(n):
        dp[i][1] = palindrome_changes(0, i + 1)

    for j in range(2, k + 1):
        for i in range(n):
            for m in range(j - 1, i + 1):
                dp[i][j] = min(dp[i][j], dp[m - 1][j - 1] + palindrome_changes(m, i + 1))

    return dp[n - 1][k]

print(min_changes_to_k_palindromes('abcac', 2))  # Output: 1
class SemiPalindrome {
    public int minChanges(String s, int k) {
        int n = s.length();
        int[][] dp = new int[n][k + 1];
        for (int[] row : dp) Arrays.fill(row, Integer.MAX_VALUE);

        for (int i = 0; i < n; ++i) {
            dp[i][1] = palindromeChanges(s, 0, i);
        }

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

        return dp[n - 1][k];
    }

    private int palindromeChanges(String s, int start, int end) {
        int changes = 0;
        while (start < end) {
            if (s.charAt(start) != s.charAt(end)) {
                changes++;
            }
            start++;
            end--;
        }
        return changes;
    }

    public static void main(String[] args) {
        SemiPalindrome sp = new SemiPalindrome();
        System.out.println(sp.minChanges("abcac", 2));  // Output: 1
    }
}

In Python, this approach starts by calculating the changes necessary for each possible substring to transform into a palindrome through a helper function. These precomputed values then assist in the greedy partition strategy by effectively reducing the computation needed during the partition phase.

Complexity

Time Complexity: O(n^2 * k), largely effective due to preprocessing of palindromicity constraints

Try this approach in the editor

Approach 3: Default Approach

function minimumChanges(s: string, k: number): number {
    const n = s.length;
    const g = Array.from({ length: n + 1 }, () => Array.from({ length: n + 1 }, () => Infinity));
    const f = Array.from({ length: n + 1 }, () => Array.from({ length: k + 1 }, () => Infinity));
    f[0][0] = 0;
    for (let i = 1; i <= n; ++i) {
        for (let j = 1; j <= n; ++j) {
            const m = j - i + 1;
            for (let d = 1; d < m; ++d) {
                if (m % d === 0) {
                    let cnt = 0;
                    for (let l = 0; l < m; ++l) {
                        const r = (((m / d) | 0) - 1 - ((l / d) | 0)) * d + (l % d);
                        if (l >= r) {
                            break;
                        }
                        if (s[i - 1 + l] !== s[i - 1 + r]) {
                            ++cnt;
                        }
                    }
                    g[i][j] = Math.min(g[i][j], cnt);
                }
            }
        }
    }
    for (let i = 1; i <= n; ++i) {
        for (let j = 1; j <= k; ++j) {
            for (let h = 0; h < i - 1; ++h) {
                f[i][j] = Math.min(f[i][j], f[h][j - 1] + g[h + 1][i]);
            }
        }
    }
    return f[n][k];
}
func minimumChanges(s string, k int) int {
	n := len(s)
	g := make([][]int, n+1)
	f := make([][]int, n+1)
	const inf int = 1 << 30
	for i := range g {
		g[i] = make([]int, n+1)
		f[i] = make([]int, k+1)
		for j := range g[i] {
			g[i][j] = inf
		}
		for j := range f[i] {
			f[i][j] = inf
		}
	}
	f[0][0] = 0
	for i := 1; i <= n; i++ {
		for j := i; j <= n; j++ {
			m := j - i + 1
			for d := 1; d < m; d++ {
				if m%d == 0 {
					cnt := 0
					for l := 0; l < m; l++ {
						r := (m/d-1-l/d)*d + l%d
						if l >= r {
							break
						}
						if s[i-1+l] != s[i-1+r] {
							cnt++
						}
					}
					g[i][j] = min(g[i][j], cnt)
				}
			}
		}
	}
	for i := 1; i <= n; i++ {
		for j := 1; j <= k; j++ {
			for h := 0; h < i-1; h++ {
				f[i][j] = min(f[i][j], f[h][j-1]+g[h+1][i])
			}
		}
	}
	return f[n][k]
}
Try this approach in the editor

Complexity comparison

ApproachComplexity
Dynamic Programming

Time Complexity: O(n^3) due to triple nested loop for each substring division
Space Complexity: O(n*k) for the dp array

Greedy with Preprocessing

Time Complexity: O(n^2 * k), largely effective due to preprocessing of palindromicity constraints

Default Approach–

Complexity analysis

ApproachTimeSpaceWhen to Use
Dynamic Programming with Cost PreprocessingO(n^3)O(n^2)General case. Guarantees optimal partitioning and is the most common interview solution.
Greedy with Preprocessed CostsO(n^3)O(n^2)Useful when implementing faster heuristics in practice and when substring costs are already precomputed.
Brute Force Partition EnumerationO(2^n * n)O(n)Conceptual baseline for understanding the partitioning search space. Not practical for large inputs.

Frequently asked questions

Is Minimum Changes to Make K Semi-palindromes easy or hard?

Minimum Changes to Make K Semi-palindromes is classified as a Hard problem. It combines string processing, divisor-based pattern checks, and dynamic programming partitioning, which requires careful preprocessing and optimization to avoid exponential complexity.

Minimum Changes to Make K Semi-palindromes Python/Java solution

Python and Java implementations typically follow the same structure: precompute a cost matrix for all substrings, then run dynamic programming to split the string into k semi‑palindromes. The DP transition checks previous split points and accumulates the minimal edit cost.

How to solve Minimum Changes to Make K Semi-palindromes in O(n^3)?

Precompute a cost matrix where cost[i][j] represents the edits required to convert substring s[i..j] into a semi‑palindrome. For each substring length, test valid divisors and count mismatched mirrored characters within groups using two pointers. Then use DP where dp[i][p] stores the minimum edits needed to form p segments using the first i characters.

What is the best approach for Minimum Changes to Make K Semi-palindromes?

The most reliable approach uses dynamic programming with substring cost preprocessing. First compute the minimum edits required to convert every substring into a semi‑palindrome by checking valid divisors and counting mismatches. Then apply DP to partition the string into k segments while minimizing total cost. This runs in O(n^3) time and O(n^2) space.

Is Minimum Changes to Make K Semi-palindromes asked at Google/Amazon/Meta?

Problems involving string partitioning and dynamic programming frequently appear in interviews at companies like Google, Amazon, and Meta. Variants that combine substring preprocessing with DP are common in senior-level interviews because they test both string manipulation and optimization skills.

What data structure is used in Minimum Changes to Make K Semi-palindromes?

The core solution relies on dynamic programming tables and a 2D matrix to store substring conversion costs. String indexing and two‑pointer comparisons are used to evaluate palindrome mismatches inside divisor-based character groups.

What is the time complexity of Minimum Changes to Make K Semi-palindromes?

The optimal solution typically runs in O(n^3) time. O(n^2) substrings are evaluated, and each substring may check multiple divisor groupings to compute semi‑palindrome costs. The dynamic programming partition step also iterates possible split points, resulting in cubic complexity overall with O(n^2) memory.

Comments

Now write it yourselfClose this page and solve Minimum Changes to Make K Semi-palindromes in the editor, without looking.
Practise Minimum Changes to Make K Semi-palindromes