Hard

题目描述

给你两个长度为 n 的字符串 s 和 target,都由小写英文字母组成。

返回字典序最小的字符串,该字符串既是 s 的回文排列,又严格大于 target。如果不存在这样的排列,返回空字符串。

示例 1:

输入:s = "baba", target = "abba"
输出:"baab"
解释:
s 的回文排列(按字典序)有 "abba" 和 "baab"。
严格大于 target 的字典序最小排列是 "baab"。

示例 2:

输入:s = "baba", target = "bbaa"
输出:""
解释:
s 的回文排列(按字典序)有 "abba" 和 "baab"。
它们都不严格大于 target。因此答案是 ""。

示例 3:

输入:s = "abc", target = "abb"
输出:""
解释:
s 没有回文排列。因此答案是 ""。

示例 4:

输入:s = "aac", target = "abb"
输出:"aca"
解释:
s 的唯一回文排列是 "aca"。
"aca" 严格大于 target。因此答案是 "aca"。

约束:

  • 1 <= n == s.length == target.length <= 300
  • starget 只包含小写英文字母

解题思路

这道题需要找到字符串 s 的所有可能回文排列中,字典序最小且严格大于 target 的那一个。

解题思路:

  1. 回文排列的可能性检查:首先统计字符串 s 中每个字符的出现次数。对于回文字符串,最多只能有一个字符出现奇数次(作为中心字符)。

  2. 构造策略:回文字符串可以通过构造前半部分然后镜像得到。我们需要:

    • 确定前半部分的长度:(n + 1) / 2
    • 如果字符串长度为奇数,中间位置需要特殊处理
    • 贪心地选择每个位置的字符,使得结果尽可能小但要大于 target
  3. 核心算法

    • 首先尝试构造一个与 target 前半部分相同的回文字符串
    • 如果无法构造或构造出来不大于 target,则需要在某个位置增大字符
    • 从右到左找到第一个可以增大的位置,增大后其右侧位置都用最小可能的字符填充
  4. 实现细节

    • 使用字符计数数组管理可用字符
    • 对于每个位置,尝试使用最小的可用字符
    • 构造完前半部分后镜像得到完整回文串
    • 验证结果是否严格大于 target

算法的关键在于正确处理字符分配和回文约束,同时确保字典序最小且大于目标字符串。

代码实现

class Solution {
public:
    string lexPalindromicPermutation(string s, string target) {
        int n = s.length();
        vector<int> count(26, 0);
        
        // Count characters in s
        for (char c : s) {
            count[c - 'a']++;
        }
        
        // Check if palindrome is possible
        int oddCount = 0;
        for (int i = 0; i < 26; i++) {
            if (count[i] % 2 == 1) oddCount++;
        }
        if (oddCount > 1) return "";
        
        // Find middle character for odd length
        char middle = 0;
        if (n % 2 == 1) {
            for (int i = 0; i < 26; i++) {
                if (count[i] % 2 == 1) {
                    middle = 'a' + i;
                    count[i]--;
                    break;
                }
            }
        }
        
        // Half all counts for constructing first half
        for (int i = 0; i < 26; i++) {
            count[i] /= 2;
        }
        
        int halfLen = n / 2;
        string firstHalf = "";
        vector<int> used(26, 0);
        
        // Try to construct first half
        for (int pos = 0; pos < halfLen; pos++) {
            char targetChar = target[pos];
            bool found = false;
            
            // Try to use same character as target
            if (used[targetChar - 'a'] < count[targetChar - 'a']) {
                firstHalf += targetChar;
                used[targetChar - 'a']++;
                found = true;
            } else {
                // Find next available character greater than target[pos]
                for (char c = targetChar + 1; c <= 'z'; c++) {
                    if (used[c - 'a'] < count[c - 'a']) {
                        firstHalf += c;
                        used[c - 'a']++;
                        found = true;
                        break;
                    }
                }
            }
            
            if (!found) {
                // Backtrack and try to increment previous position
                while (pos > 0) {
                    pos--;
                    char lastChar = firstHalf[pos];
                    used[lastChar - 'a']--;
                    firstHalf.pop_back();
                    
                    // Try next character
                    bool incremented = false;
                    for (char c = lastChar + 1; c <= 'z'; c++) {
                        if (used[c - 'a'] < count[c - 'a']) {
                            firstHalf += c;
                            used[c - 'a']++;
                            incremented = true;
                            break;
                        }
                    }
                    
                    if (incremented) {
                        pos++;
                        break;
                    }
                }
                
                if (pos == 0 && firstHalf.empty()) return "";
            }
        }
        
        // Fill remaining positions with smallest available characters
        while (firstHalf.length() < halfLen) {
            for (int i = 0; i < 26; i++) {
                if (used[i] < count[i]) {
                    firstHalf += ('a' + i);
                    used[i]++;
                    break;
                }
            }
        }
        
        // Construct full palindrome
        string result = firstHalf;
        if (middle != 0) result += middle;
        reverse(firstHalf.begin(), firstHalf.end());
        result += firstHalf;
        
        return result > target ? result : "";
    }
};
class Solution:
    def lexPalindromicPermutation(self, s: str, target: str) -> str:
        n = len(s)
        count = [0] * 26
        
        # Count characters in s
        for c in s:
            count[ord(c) - ord('a')] += 1
        
        # Check if palindrome is possible
        odd_count = sum(1 for x in count if x % 2 == 1)
        if odd_count > 1:
            return ""
        
        # Find middle character for odd length
        middle = ""
        if n % 2 == 1:
            for i in range(26):
                if count[i] % 2 == 1:
                    middle = chr(ord('a') + i)
                    count[i] -= 1
                    break
        
        # Half all counts for constructing first half
        for i in range(26):
            count[i] //= 2
        
        half_len = n // 2
        first_half = []
        used = [0] * 26
        
        # Try to construct first half
        pos = 0
        while pos < half_len:
            target_char = target[pos]
            found = False
            
            # Try to use same character as target
            if used[ord(target_char) - ord('a')] < count[ord(target_char) - ord('a')]:
                first_half.append(target_char)
                used[ord(target_char) - ord('a')] += 1
                found = True
                pos += 1
            else:
                # Find next available character greater than target[pos]
                for c_ord in range(ord(target_char) + 1, ord('z') + 1):
                    c_idx = c_ord - ord('a')
                    if used[c_idx] < count[c_idx]:
                        first_half.append(chr(c_ord))
                        used[c_idx] += 1
                        found = True
                        pos += 1
                        break
            
            if not found:
                # Backtrack and try to increment previous position
                while pos > 0:
                    pos -= 1
                    last_char = first_half.pop()
                    used[ord(last_char) - ord('a')] -= 1
                    
                    # Try next character
                    incremented = False
                    for c_ord in range(ord(last_char) + 1, ord('z') + 1):
                        c_idx = c_ord - ord('a')
                        if used[c_idx] < count[c_idx]:
                            first_half.append(chr(c_ord))
                            used[c_idx] += 1
                            incremented = True
                            pos += 1
                            break
                    
                    if incremented:
                        break
                
                if pos == 0 and not first_half:
                    return ""
        
        # Fill remaining positions with smallest available characters
        while len(first_half) < half_len:
            for i in range(26):
                if used[i] < count[i]:
                    first_half.append(chr(ord('a') + i))
                    used[i] += 1
                    break
        
        # Construct full palindrome
        result = ''.join(first_half) + middle + ''.join(reversed(first_half))
        
        return result if result > target else ""
public class Solution {
    public string LexPalindromicPermutation(string s, string target) {
        int n = s.Length;
        int[] count = new int[26];
        
        // Count characters in s
        foreach (char c in s) {
            count[c - 'a']++;
        }
        
        // Check if palindrome is possible
        int oddCount = 0;
        for (int i = 0; i < 26; i++) {
            if (count[i] % 2 == 1) oddCount++;
        }
        if (oddCount > 1) return "";
        
        // Find middle character for odd length
        char middle = '\0';
        if (n % 2 == 1) {
            for (int i = 0; i < 26; i++) {
                if (count[i] % 2 == 1) {
                    middle = (char)('a' + i);
                    count[i]--;
                    break;
                }
            }
        }
        
        // Half all counts for constructing first half
        for (int i = 0; i < 26; i++) {
            count[i] /= 2;
        }
        
        int halfLen = n / 2;
        var firstHalf = new StringBuilder();
        int[] used = new int[26];
        
        // Try to construct first half
        int pos = 0;
        while (pos < halfLen) {
            char targetChar = target[pos];
            bool found = false;
            
            // Try to use same character as target
            if (used[targetChar - 'a'] < count[targetChar - 'a']) {
                firstHalf.Append(targetChar);
                used[targetChar - 'a']++;
                found = true;
                pos++;
            } else {
                // Find next available character greater than target[pos]
                for (char c = (char)(targetChar + 1); c <= 'z'; c++) {
                    if (used[c - 'a'] < count[c - 'a']) {
                        firstHalf.Append(c);
                        used[c - 'a']++;
                        found = true;
                        pos++;
                        break;
                    }
                }
            }
            
            if (!found) {
                // Backtrack and try to increment previous position
                while (pos > 0) {
                    pos--;
                    char lastChar = firstHalf[firstHalf.Length - 1];
                    used[lastChar - 'a']--;
                    firstHalf.Length--;
                    
                    // Try next character
                    bool incremented = false;
                    for (char c = (char)(lastChar + 1); c <= 'z'; c++) {
                        if (used[c - 'a'] < count[c - 'a']) {
                            firstHalf.Append(c);
                            used[c - 'a']++;
                            incremented = true;
                            pos++;
                            break;
                        }
                    }
                    
                    if (incremented) {
                        break;
                    }
                }
                
                if (pos == 0 && firstHalf.Length == 0) return "";
            }
        }
        
        // Fill remaining positions with smallest available characters
        while (firstHalf.Length < halfLen) {
            for (int i = 0; i < 26; i++) {
                if (used[i] < count[i]) {
                    firstHalf.Append((char)('a' + i));
                    used[i]++;
                    break;
                }
            }
        }
        
        // Construct full palindrome
        string firstHalfStr = firstHalf.ToString();
        var result = new StringBuilder(firstHalfStr);
        if (middle != '\0') result.Append(middle);
        for (int i = firstHalfStr.Length - 1; i >= 0; i--) {
            result.Append(firstHalfStr[i]);
        }
        
        string finalResult = result.ToString();
        return string.Compare(finalResult, target) > 0 ? finalResult : "";
    }
}
var lexPalindromicPermutation = function(s, target) {
    const n = s.length;
    const count = new Array(26).fill(0);
    
    for (let char of s) {
        count[char.charCodeAt(0) - 97]++;
    }
    
    let oddCount = 0;
    let oddChar = '';
    for (let i = 0; i < 26; i++) {
        if (count[i] % 2 === 1) {
            oddCount++;
            oddChar = String.fromCharCode(i + 97);
        }
    }
    
    if (oddCount > 1) return "";
    
    const half = Math.floor(n / 2);
    const firstHalf = new Array(half).fill('');
    
    let idx = 0;
    for (let i = 0; i < 26; i++) {
        const char = String.fromCharCode(i + 97);
        const halfCount = Math.floor(count[i] / 2);
        for (let j = 0; j < halfCount; j++) {
            firstHalf[idx++] = char;
        }
    }
    
    function nextPermutation(arr) {
        let i = arr.length - 2;
        while (i >= 0 && arr[i] >= arr[i + 1]) i--;
        if (i < 0) return false;
        
        let j = arr.length - 1;
        while (arr[j] <= arr[i]) j--;
        
        [arr[i], arr[j]] = [arr[j], arr[i]];
        
        let left = i + 1, right = arr.length - 1;
        while (left < right) {
            [arr[left], arr[right]] = [arr[right], arr[left]];
            left++;
            right--;
        }
        return true;
    }
    
    function buildPalindrome(firstHalf) {
        let result = firstHalf.join('');
        if (oddChar) result += oddChar;
        result += firstHalf.slice().reverse().join('');
        return result;
    }
    
    let current = buildPalindrome(firstHalf);
    if (current > target) return current;
    
    do {
        if (!nextPermutation(firstHalf)) break;
        current = buildPalindrome(firstHalf);
        if (current > target) return current;
    } while (true);
    
    return "";
};

复杂度分析

指标复杂度
时间-
空间-