【037】构造回文字符串算法解析 | 豆包MarsCode AI 刷题

179 阅读5分钟

前言

回文字符串在编程中是一个常见的概念,因其对称性和特殊的性质,经常出现在算法和面试题中。而本次要解决的问题,进一步增加了难度:除了要求字符串为回文外,还需要满足字典序的限制,即构造出的字符串必须小于给定字符串,并且在所有符合条件的回文字符串中字典序尽可能大。

这种问题看似复杂,但通过合理的分解和优化,我们可以在可接受的时间内得到正确的解。在本文中,我们将从问题的特点入手,详细分析解题思路,并给出高效的实现方法。

题目内容

image.png

问题分析

在这个问题中,我们需要构造一个回文字符串 t,并且要满足以下条件:

  1. 回文特性:字符串 t 是回文,即从左到右与从右到左读取相同。
  2. 字典序限制:字符串 t 的字典序严格小于给定字符串 s
  3. 尽可能大:在满足上述两个条件的情况下,t 的字典序应尽可能接近 s

回文字符串的特性

回文字符串的特点是左右对称,因此一个长度为 n 的回文字符串只需确定前半部分(n/2 个字符),后半部分可以由前半部分镜像生成。例如:

  • 对于 "abcba",确定 "abc" 后自动推导 "cba"
  • 对于 "racecar",确定 "race" 后自动推导 "ecar"

这种对称性允许我们在生成回文时只操作前半部分,从而大大简化了问题。


构造方案的核心思想

根据题目的要求,我们可以将问题分为以下几步:

  1. 构造最接近 s 的回文字符串:通过将 s 的前半部分镜像到后半部分,生成一个初始回文字符串 t
  2. 比较字典序:如果生成的回文字符串已经满足字典序小于 s,直接返回即可。
  3. 调整以满足字典序要求:如果回文 t 的字典序大于或等于 s,则需要从尾部开始调整,使其字典序严格小于 s
  4. 特殊情况处理:如果无法生成符合要求的回文字符串(例如所有字符均为 'a' 的情况),返回 -1

思路

为满足要求的回文字符串 t,可以分以下几步:


1. 构造初始回文字符串
  • 取字符串 s 的前半部分,镜像到后半部分,构造一个最接近 s 的回文字符串 t
  • 例如:输入 s = "abc",镜像得到 t = "aba"

2. 检查字典序
  • 如果构造的 t 满足 t < s,直接返回。
  • 否则,进入调整步骤。

3. 调整回文
  • 从中间向前扫描,寻找第一个大于 'a' 的字符,减小其字典序,并同步调整对称位置。
  • 调整后,将剩余字符设置为 'z' 以保持字典序尽可能大。
  • 例如:t = "cbc" 调整后为 t = "cac"

4. 特殊情况处理
  • 如果 s 中所有字符均为 'a',无法构造字典序更小的回文,返回 -1

复杂度

  • 时间复杂度:构造和调整均为 O(n)O(n)O(n),总复杂度为 O(n)O(n)O(n)。
  • 空间复杂度:O(1)O(1)O(1)。

这种思路可以保证构造的回文符合条件且字典序尽可能大。

代码实现

以下是具体实现的代码(基于Java)

public class Main {
    public static String solution(String s) {
        int n = s.length();
        char[] t = s.toCharArray();

        // Step 1: 构造最接近s的回文字符串
        for (int i = 0; i < n / 2; i++) {
            t[n - i - 1] = t[i];
        }

        // 检查生成的回文是否小于s
        if (new String(t).compareTo(s) < 0) {
            return new String(t);
        }

        // Step 2: 从尾部调整,使字典序尽可能大但仍小于s
        for (int i = (n - 1) / 2; i >= 0; i--) {
            if (t[i] > 'a') {
                t[i]--;
                t[n - i - 1] = t[i]; // 同步对称位置
                for (int j = i + 1; j < (n + 1) / 2; j++) {
                    t[j] = 'z';
                    t[n - j - 1] = 'z';
                }
                return new String(t);
            }
        }

        // 如果无法生成符合条件的回文字符串
        return "-1";
    }

    public static void main(String[] args) {
        System.out.println(solution("abc").equals("aba")); // 输出: "aba"
        System.out.println(solution("cba").equals("cac")); // 输出: "cac"
        System.out.println(solution("aaa").equals("-1"));  // 输出: "-1"
    }
}

1. 构造初始回文字符串
for (int i = 0; i < n / 2; i++) {
    t[n - i - 1] = t[i];
}
  • 作用:将 s 的前半部分镜像到后半部分,生成最接近 s 的回文字符串 t
  • 示例s = "abc",镜像后 t = "aba"

2. 检查字典序
if (new String(t).compareTo(s) < 0) {
    return new String(t);
}
  • 作用:如果生成的回文 t 满足 t < s,直接返回结果。
  • 示例s = "abc",生成的 t = "aba" 满足条件,返回 t

3. 调整回文
for (int i = (n - 1) / 2; i >= 0; i--) {
    if (t[i] > 'a') {
        t[i]--;
        t[n - i - 1] = t[i];
        for (int j = i + 1; j < (n + 1) / 2; j++) {
            t[j] = 'z';
            t[n - j - 1] = 'z';
        }
        return new String(t);
    }
}
  • 作用:当 t >= s 时,从中间向前扫描调整,找到第一个大于 'a' 的字符,将其减小,同时填充后续字符为 'z' 以最大化字典序。
  • 示例s = "cbc",调整后 t = "cac"
复杂度
  • 时间复杂度:构造和调整均为 O(n)O(n)O(n),总复杂度为 O(n)O(n)O(n)。
  • 空间复杂度:O(1)O(1)O(1)。

这种思路保证构造的回文符合条件且字典序尽可能大。