title: Break a Palindrome slug: break-a-palindrome difficulty: medium leetcode_id: 1328 leetcode_url: https://leetcode.com/problems/break-a-palindrome/ categories: - strings patterns: - greedy function_signature: "def break_palindrome(palindrome: str) -> str:" test_cases: visible: - input: { palindrome: "abccba" } expected: "aaccba" - input: { palindrome: "a" } expected: "" hidden: - input: { palindrome: "aa" } expected: "ab" - input: { palindrome: "aba" } expected: "abb" - input: { palindrome: "aaa" } expected: "aab" - input: { palindrome: "aaaa" } expected: "aaab" - input: { palindrome: "bab" } expected: "aab" - input: { palindrome: "abba" } expected: "aaba" - input: { palindrome: "zyzyz" } expected: "ayzyz" description: | Given a palindromic string of lowercase English letters `palindrome`, replace **exactly one** character with any lowercase English letter so that the resulting string is **not** a palindrome and that it is the **lexicographically smallest** one possible. Return *the resulting string. If there is no way to replace a character to make it not a palindrome, return an **empty string***. A string `a` is lexicographically smaller than a string `b` (of the same length) if in the first position where `a` and `b` differ, `a` has a character strictly smaller than the corresponding character in `b`. For example, `"abcc"` is lexicographically smaller than `"abcd"` because the first position they differ is at the fourth character, and `'c'` is smaller than `'d'`. constraints: | - `1 <= palindrome.length <= 1000` - `palindrome` consists of only lowercase English letters. examples: - input: 'palindrome = "abccba"' output: '"aaccba"' explanation: 'There are many ways to make "abccba" not a palindrome, such as "zbccba", "aaccba", and "abacba". Of all the ways, "aaccba" is the lexicographically smallest.' - input: 'palindrome = "a"' output: '""' explanation: 'There is no way to replace a single character to make "a" not a palindrome, so return an empty string.' explanation: intuition: | Think of a palindrome as a mirror: the first half reflects onto the second half. To break this symmetry, we need to change exactly one character. The key insight is understanding what makes a string "lexicographically smallest." We want the earliest characters to be as small as possible. Since `'a'` is the smallest lowercase letter, our goal is to place an `'a'` as early as possible in the string. Here's the greedy strategy: scan from the left side of the string. If we find any character that is **not** `'a'`, we can change it to `'a'` to make the string lexicographically smaller. But there's a catch: in a palindrome, changing a character in the first half automatically affects the symmetry, which is exactly what we want. However, we must be careful with the **middle character** in odd-length palindromes. Changing only the middle character keeps the string as a palindrome (since it mirrors to itself). So we skip the middle character when looking for a character to replace. What if all characters in the first half are already `'a'`? Then we can't make the string smaller by changing anything in the first half. Our fallback is to change the **last character** to `'b'` — this breaks the palindrome while keeping the result as small as possible. approach: | We use a **Greedy Single-Pass Approach**: **Step 1: Handle the edge case** - If the string has length `1`, return an empty string — there's no way to replace a single character and make a single-character string non-palindromic   **Step 2: Scan the first half of the string** - Iterate through indices `0` to `n // 2 - 1` (exclusive of the middle in odd-length strings) - For each character, check if it's **not** `'a'` - If we find a non-`'a'` character, replace it with `'a'` and return the result immediately - This guarantees the lexicographically smallest result because we change the leftmost possible character to the smallest possible value   **Step 3: Fallback — change the last character** - If all characters in the first half are `'a'`, we cannot make the string smaller by changing them - Change the **last character** to `'b'` - This breaks the palindrome (since the first character remains `'a'`) while resulting in the smallest possible string   The greedy approach works because we always prioritize changes that affect the leftmost positions first, and we always choose the smallest possible replacement character. common_pitfalls: - title: Forgetting the Single Character Edge Case description: | A string of length `1` like `"a"` is always a palindrome no matter what character it contains. Changing `"a"` to `"b"` still gives you a palindrome (any single character is a palindrome by definition). You must check for this case at the start and return an empty string. wrong_approach: "Trying to process single-character strings normally" correct_approach: "Return empty string immediately if length is 1" - title: Changing the Middle Character in Odd-Length Strings description: | In a palindrome like `"aba"`, the middle character `'b'` mirrors to itself. If you change it to `'a'`, you get `"aaa"` — still a palindrome! The middle character of an odd-length palindrome doesn't affect the palindrome property when changed alone. Only scan up to `n // 2` (exclusive) to avoid this trap. wrong_approach: "Scanning the entire first half including middle character" correct_approach: "Only scan indices 0 to n // 2 - 1" - title: Replacing 'a' with 'a' description: | If you find an `'a'` and "replace" it with `'a'`, you haven't actually changed anything, and the string remains a palindrome. Only replace characters that are **not** `'a'`. If a character is already `'a'`, skip it and continue scanning. wrong_approach: "Replacing the first character regardless of its value" correct_approach: "Only replace if the character is not 'a'" - title: Not Handling All-'a' Palindromes description: | For a string like `"aaa"` or `"aaaa"`, every character in the first half is already `'a'`. You can't make the string lexicographically smaller by changing any of them. The solution is to change the **last** character to `'b'`. This breaks the palindrome (first is `'a'`, last is `'b'`) and gives the smallest possible result. wrong_approach: "Giving up or returning empty string for all-'a' palindromes" correct_approach: "Change the last character to 'b' as a fallback" key_takeaways: - "**Greedy leftmost replacement**: To minimise a string lexicographically, make changes as early (leftmost) as possible with the smallest possible character" - "**Palindrome symmetry**: Only the first half of a palindrome needs to be checked — the second half is a mirror" - "**Edge cases matter**: Single-character strings and middle characters in odd-length strings require special handling" - "**Fallback strategy**: When the primary approach doesn't apply (all `'a'`s), have a clear secondary strategy (change last to `'b'`)" time_complexity: "O(n). We scan at most half the string once, where `n` is the length of the palindrome." space_complexity: "O(n). We create a new string to return the result (strings are immutable in Python)." solutions: - approach_name: Greedy Single Pass is_optimal: true code: | def break_palindrome(palindrome: str) -> str: n = len(palindrome) # Single character palindromes cannot be broken if n == 1: return "" # Convert to list for easy character replacement chars = list(palindrome) # Scan the first half (excluding middle for odd-length) for i in range(n // 2): # Found a character that's not 'a'? Replace it with 'a' if chars[i] != 'a': chars[i] = 'a' return ''.join(chars) # All characters in first half are 'a' # Change the last character to 'b' to break palindrome chars[-1] = 'b' return ''.join(chars) explanation: | **Time Complexity:** O(n) — Single pass through at most half the string. **Space Complexity:** O(n) — We create a list copy of the string to modify it. The greedy strategy ensures we always find the lexicographically smallest result: we try to place an `'a'` as early as possible, and only fall back to changing the last character when necessary. - approach_name: Two-Case Analysis is_optimal: true code: | def break_palindrome(palindrome: str) -> str: n = len(palindrome) # Cannot break a single-character palindrome if n == 1: return "" # Case 1: Find first non-'a' in the first half half = n // 2 for i in range(half): if palindrome[i] != 'a': # Replace it with 'a' for smallest result return palindrome[:i] + 'a' + palindrome[i+1:] # Case 2: All chars in first half are 'a' # Replace last char with 'b' return palindrome[:-1] + 'b' explanation: | **Time Complexity:** O(n) — Single pass through at most half the string. **Space Complexity:** O(n) — String concatenation creates new strings. This version uses string slicing instead of converting to a list. Both approaches are equivalent in complexity, but this one may be slightly more Pythonic when working with small strings.