程序员老鬼

感觉有点不对劲,全组开会不叫我,连续三次了。。。

最近看到一个网友的吐槽,真是让我忍不住笑出声:“感觉有点不对劲,全组开会不叫我,连续三次了…” 这是什么操作?

Image

程序员的世界里,最怕的就是被忽视,特别是那种感觉自己仿佛透明的时刻。

你知道吗,这种心情就像是写代码的时候,突然出现bug,debug了半天才发现原来是自己没有定义变量——明明存在,却好像被遗忘。

Image

我觉得,作为程序员,尤其是对我们这种每天跟屏幕打交道的,偶尔被“排除”这种事儿,心里多少会有点不舒服。

你想,毕竟每个人都有个小心眼,不是吗?不过这也提醒了我,做事要更“显眼”一点,毕竟你不在场,谁知道你有没有贡献呢?【备注:文末可领最新资料】。

算法题:得分最高的最小轮调

最近碰到一道有意思的算法题,题目是这样的:

给定一个字符串,要求你进行“最小轮调”操作,以此得到得分最高的字符串。
“轮调”是指将字符串的某个前缀移动到字符串的后面。举个例子,对于字符串“abcde”,将“ab”移到后面就变成“cdeab”。最小轮调指的是找到所有可能的轮调中,字典序最小的那个。

这题看似简单,实际做起来就有点“想不到”的感觉。作为程序员,我想我们都喜欢这种看起来不复杂,实际上却有点挑战的题目。今天,我就带大家一块儿理理这道题的解决思路。

首先,来个简单的例子帮助大家理清题意。假设给定字符串是"abcde"。我们可以轮调得到以下这些字符串:

abcde
bcdea
cdeab
deabc
eabcd

显然,字典序最小的字符串是"abcde"本身。问题是:如果字符串更长,手动一个一个试就不现实了。所以我们要考虑如何高效地找到字典序最小的轮调。

问题的关键点就是:如何通过某种技巧快速得到最小的轮调?大部分人都会想到:排序。没错,你可以将所有可能的轮调都生成出来,然后排序,选出最小的那个。但是,效率太低,时间复杂度会达到O(n^2 log n)。那如果字符串长度是10^5,O(n^2 log n)直接挂了。我们得考虑一种更高效的方法。

经过一番思考后,我们发现可以借助一种叫做“最小表示法”的算法来解决这个问题。最小表示法是一个非常经典的字符串算法,用来高效地找到一个字符串的最小轮调。

具体地,最小表示法可以通过“最小循环排列”来求解,这也是著名的** Booth算法**的一个应用。它的时间复杂度是O(n),比排序要高效得多。通过这种方式,我们不需要生成所有轮调,直接用算法得到最小轮调。

Booth算法的核心思想:

  1. 将字符串本身与它自己拼接成一个新的字符串 S + S。这样可以确保我们可以通过循环的方式,检查所有的轮调。
  2. 然后通过滑动窗口的方式,找到该字符串的最小轮调。

Booth算法代码实现(Java版)

public class Main {
    public static int[] minRotation(String s) {
        String doubled = s + s;  // 将字符串自己拼接
        int n = s.length();
        int[] f = new int[n * 2];  // 存储每次最小轮调的索引
        int k = 0;  // 最小轮调的开始位置

        for (int j = 1; j < n * 2; j++) {
            int i = f[j - k - 1];
            while (i != 0 && doubled.charAt(k + i) != doubled.charAt(k + j)) {
                if (doubled.charAt(k + i) > doubled.charAt(k + j)) {
                    k = j;
                }
                i = f[i - 1];
            }
            if (doubled.charAt(k + i) != doubled.charAt(k + j)) {
                if (doubled.charAt(k + i) > doubled.charAt(k + j)) {
                    k = j;
                }
                f[j - k] = 0;
            } else {
                f[j - k] = i + 1;
            }
        }
        return new int[] {k, k + n - 1};
    }

    public static void main(String[] args) {
        String s = "bcdea";
        int[] result = minRotation(s);
        System.out.println("The lexicographically smallest rotation starts at index: " + result[0]);
    }
}

代码解释

  • 我们首先将输入字符串S与它自己拼接成S + S,这样我们就可以模拟轮调了。
  • 然后我们用一个数组f来记录每个字符的匹配信息,用来保存每次找到最小轮调的开始位置。
  • 核心的逻辑是通过比较当前字符和之前的字符,来决定是否更新最小轮调的起始位置。如果当前字符更小,就更新起始位置。

这段代码运行的时间复杂度是O(n),大大提高了效率。

结果

对于字符串“bcdea”,该算法会返回最小轮调的起始位置是0,也就是说它本身就是字典序最小的轮调。你可以尝试将字符串改成其他值,验证算法的有效性。

我觉得这道题的核心就是理解最小表示法的思想。通过Booth算法,我们可以在O(n)的时间内高效地找到最小轮调,避免了暴力解法的低效。相信大家在面试或者算法学习时,遇到类似的字符串轮调题目时,可以把这个算法应用起来。虽然看起来很复杂,但理解了其中的逻辑,后面遇到这类问题就会变得相对容易了。

最后,我为大家打造了一份deepseek的入门到精通教程,完全免费:https://www.songshuhezi.com/deepseek

同时,也欢迎加入下方的交流群,一起研究deepseek的最新玩法

图片