青训营刷题算法学习心得2 | 豆包MarsCode AI 刷题

90 阅读5分钟

学习方法与心得:回文串字典序问题

在解决豆包 MarsCode AI 刷题题库中的“构造回文字符串”问题时,我遇到了一些思路上的困难。一开始我认为仅仅是将原来的字符串变化为回文字符串,然后从前到后一个一个去减小字母的字典顺序,如果是‘a’就处理下一个,拿到最大的哪一个就能得到输出,但几个错误的返回让我发现事情并没有这么简单。类似于abbabbc,他的最大字典序回文字符串并不是abcacba,而是将a改为z,最终的答案是abazabc。在豆包 MarsCode AI 的帮助下,我理解了这道题应该是将回文字符串前面一部分看做一个26进制的数字,这道题的本质是26进制数字的加减法,如此问题便迎刃而解。

一、题目解析

小C手中有一个由小写字母组成的字符串 `s`。她希望构造另一个字符串 `t`,并且这个字符串需要满足以下几个条件:

1.  `t` 由小写字母组成,且长度与 `s` 相同。
1.  `t` 是回文字符串,即从左到右与从右到左读取相同。
1.  `t` 的字典序要小于 `s`,并且在所有符合条件的字符串中字典序尽可能大。

小C想知道是否能构造出这样的字符串 `t`,输出这样的`t`。如果无法构造满足条件的字符串,则输出 `-1`
public class Main {
    public static String solution(String s) {
        // write code here
        // 将s转化为回文串

        String t = toPalindrome(s);

        // 当t为回文串时且大于chars时,更新t的字典序
        while (t.compareTo(s) >= 0) {
            t = minusOne(t);
            if (t.equals("-1")) {
                return "-1";
            }
        }

        if (t.compareTo(s) >= 0) {
            return "-1";
        }else {
            return t;
        }
    }
    // 将一个回文字符串看做26进制数,并返回其-1之后的字符串
    public static String minusOne(String s) {
        char[] chars = s.toCharArray();
        for (int i = chars.length / 2 - 1 + (chars.length % 2); i >= 0; i--) {
            if (chars[i] != 'a') {
                chars[i]--;
                chars[chars.length - 1 - i] = chars[i];
                break;
            } else {
                do {
                    chars[i] = 'z';
                    chars[chars.length - 1 - i] = chars[i];
                    i--;
                } while (i >= 0 && chars[i] == 'a');
                if (i <= 0) {
                    return "-1";
                }
            }
            i++;
        }
        return new String(chars);
    }
    
    // 将一个字符串转换为回文串
    public static String toPalindrome(String s) {
        char[] chars = s.toCharArray();
        int i = 0, j = chars.length - 1;
        while (i < j) {
            chars[j] = chars[i];
            i++;
            j--;
        }
        return new String(chars);
    }

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

这段代码解决的问题是:给定一个字符串 sss,我们需要生成一个字典序小于 sss 的回文串。如果不存在这样的回文串,则返回 -1

核心思路

  1. 回文转化:首先将 sss 转化为一个最接近 sss 的回文串(toPalindrome 方法)。
  2. 逐步减小:将这个回文串视为一个特殊的“26 进制数”,每次减去 1(minusOne 方法),同时保持回文特性。
  3. 校验与返回:如果当前回文串字典序仍然大于等于 sss,继续减小;如果不可能找到符合条件的回文串,则返回 -1

二、关键知识点总结

  1. 回文构造(toPalindrome
    通过镜像对称方式,将字符串转化为回文串。从两端向中间逐步同步字符,时间复杂度为 O(n)。

  2. 减法运算(minusOne
    将字符串看作 26 进制数,从右向左模拟减法操作,同时维持回文特性。关键点是:

    • 当中间字符为非 'a' 时,直接减一并镜像对称即可;
    • 如果中间字符为 'a',需要进位(如 'aba' 减一变为 'aaa')。

    核心逻辑

    1. 回文对称性

      • 回文字符串的特点是左右对称,因此减 1 时,不仅要修改中间部分的字符,还需要同步调整另一侧对应的位置。
      • 例如:如果修改字符串 chars[i] 的字符,必须同步修改 chars[chars.length - 1 - i],以保持回文特性。
    2. 从中心向两侧操作

      • 回文减 1 操作从中心开始向外进行,因为修改较低位(中心位置)对整体的字典序影响最小。
      • 字符串长度为奇数时,中间字符的减法影响较单一;长度为偶数时,中心偏左位置优先操作。
    3. 模拟减法

      • 如果某个位置的字符不是 'a',直接将其减 1,然后同步更新对应位置的字符即可。
      • 如果某个位置的字符是 'a',减 1 后需进位(视为从 'z' 开始),并继续向更高位递归减 1。
    4. 边界情况

      • 当所有字符都为 'a' 时,整个回文串无法再减 1,返回 "-1"
      • 例如:aaa -> -1
  3. 循环与比较
    solution 方法中,比较当前回文串与原字符串 sss 的字典序,直到找到符合条件的结果或者返回 -1

三、学习收获与心得

  1. 问题转化的巧妙性
    我学习到,将问题分解为 回文构造回文递减 的两个阶段后,大幅简化了问题复杂度。这种将复杂问题分步解决的思路可以推广到其他算法设计中。

  2. 模拟进制运算的实践
    minusOne 方法展示了如何将字符串看作进制数并进行加减法操作,且需额外处理回文特性。这让我对模拟进制运算和双指针技术的结合应用有了更深刻的理解。

  3. 代码鲁棒性与边界情况

    • 对字符串全为 'a' 的边界情况(如 'aaa'),正确返回 -1
    • 对短字符串(如 'cba')和非回文字符串(如 'abc'),能灵活处理,确保了结果的准确性。