寻找字符串中连续出现次数最多的字符:算法解析与实战

你好!作为一名经常与数据处理打交道的开发者,我发现在实际的编码工作或技术面试中,字符串处理是一个永恒的话题。今天,我们将深入探讨一个经典且非常实用的字符串算法问题:如何在给定的字符串中,找到连续出现次数最多的字符

这个问题看似简单,但它非常能考察我们对循环控制、边界条件处理以及算法效率优化的理解。别担心,我们会像在日常代码审查(Code Review)中一样,从最基础的思路开始,一步步剖析并优化代码,直到找到最优解。

核心问题定义

首先,让我们明确一下我们的目标。给定一个字符串 s,我们需要找到其中连续重复次数最多的那个字符。

这里有一个非常关键的点需要注意:我们要找的是“单次连续重复”的最大长度,而不是字符在字符串中出现的总次数。

为了让你更直观地理解,让我们看两个例子:

示例 1:

> 输入: s = "geeekk"

> 输出: e

> 解释: 在这里,虽然 INLINECODE5991426e 也出现了多次,但字符 INLINECODE5c78436b 是连续出现了 3 次(eee),这是当前字符串中最大的连续重复长度。

示例 2:

> 输入: s = "aaaabbcbbb"

> 输出: a

> 解释: 字符 INLINECODE03ff9d78 连续出现了 4 次(INLINECODE91e96716),而 INLINECODE50fb1855 虽然在字符串中总共有 5 个,但它们被 INLINECODEe11a2c03 隔开了,最长的一段只有 2 个。因此,答案是 a

理解了这一点后,我们就不会掉入“统计字符频率”的陷阱里了。接下来,让我们看看如何解决这个问题。

方法一:朴素思路(嵌套循环)

在寻找最高效的解决方案之前,让我们先尝试用最直观的方式解决这个问题。这通常被称为“暴力解法”或“朴素解法”。

#### 算法思路

我们的核心思路是:对于字符串中的每一个字符,都以此为起点,向后遍历,直到遇到一个不同的字符为止。

这就好比你在排队买票,你想知道当前最长的一队连续穿同样颜色衣服的人有多长。你可能会从头走到尾,每遇到一个人,就数数他和后面几个人穿的衣服颜色是一样的。

具体步骤如下:

  • 初始化 INLINECODEb7f37384(记录最大连续次数)为 0,INLINECODE1990385d(记录结果字符)为字符串的第一个字符。
  • 使用一个外层循环 i 遍历字符串的每一个字符。
  • 对于每个 INLINECODE02426597,使用内层循环 INLINECODEdc93745d 从 INLINECODEb39e51ed 开始向后移动,只要 INLINECODE1687a53d,计数器 cnt 就加 1。
  • 一旦内层循环遇到不同的字符或到达字符串末尾,我们就比较当前的 INLINECODE5c433d9b 是否大于 INLINECODE5057cd66。如果是,就更新 INLINECODE2fb713f9 和 INLINECODE3862b3b9。

#### 代码实现

让我们看看这段代码是如何用 C++、Java、Python 和 JavaScript 实现的。我特意保留了详细的注释,帮助你理解每一行的作用。

C++ 实现

// C++ program to find the maximum consecutive
// repeating character in given string
#include
using namespace std;

// 返回字符串中连续重复次数最多的字符
char maxRepeating(string str) {
    int n = str.length();
    // 记录最大连续数量
    int maxCnt = 0;
    // 记录对应的字符,默认设为第一个字符
    char res = str[0];
    
    // 外层循环:遍历字符串中的每一个字符
    for (int i = 0; i < n; i++) {
        // 当前字符的连续计数器
        int cnt = 0;
        
        // 内层循环:从当前字符开始,向后检查连续性
        for (int j = i; j  maxCnt) {
            maxCnt = cnt;
            res = str[i];
        }
    }
    
    return res;
}

int main() {
    string s = "aaaabbaaccde";
    cout << "最大连续重复字符是: " << maxRepeating(s);
    return 0;
}

Java 实现

// Java program to find the maximum consecutive
// repeating character in given string
class GfG {

    // Function to find out the maximum repeating
    // character in given string
    static char maxRepeating(String str) {
        int n = str.length();
        int maxCnt = 0;
        char res = str.charAt(0);

        // Find the maximum repeating character
        // starting from str[i]
        for (int i = 0; i < n; i++) {
            int cnt = 0;
            for (int j = i; j  maxCnt) {
                maxCnt = cnt;
                res = str.charAt(i);
            }
        }

        return res;
    }

    public static void main(String[] args) {
        String s = "aaaabbaaccde";
        System.out.println("最大连续重复字符是: " + maxRepeating(s));
    }
}

Python 实现

# Python program to find the maximum consecutive
# repeating character in given string

def maxRepeating(str):
    n = len(str)
    max_cnt = 0
    res = str[0]

    # 遍历字符串中的每个字符
    for i in range(n):
        cnt = 0
        # 向后检查连续相同的字符
        for j in range(i, n):
            if str[i] != str[j]:
                break
            cnt += 1

        # 更新最大值和结果字符
        if cnt > max_cnt:
            max_cnt = cnt
            res = str[i]

    return res

if __name__ == "__main__":
    s = "aaaabbaaccde"
    print(f"最大连续重复字符是: {maxRepeating(s)}")

JavaScript 实现

// JavaScript program to find the maximum consecutive
// repeating character in given string

function maxRepeating(str) {
    let n = str.length;
    let maxCnt = 0;
    let res = str[0];

    // 遍历字符串
    for (let i = 0; i < n; i++) {
        let cnt = 0;
        // 内层循环检查连续性
        for (let j = i; j  maxCnt) {
            maxCnt = cnt;
            res = str[i];
        }
    }

    return res;
}

// 测试代码
let s = "aaaabbaaccde";
console.log("最大连续重复字符是: " + maxRepeating(s));

#### 性能分析

虽然朴素方法逻辑清晰,但在处理大规模数据时可能并不高效。让我们分析一下它的复杂度:

  • 时间复杂度:O(n²)。为什么是平方级?因为对于每一个字符(外层循环 n 次),我们可能都要遍历剩下的所有字符(内层循环最坏情况下也是 n 次)。当字符串非常长时,这会导致明显的性能瓶颈。
  • 空间复杂度:O(1)。这点做得很好,我们只使用了几个变量来存储计数和结果,没有使用额外的存储空间。

方法二:优化的单次遍历

既然嵌套循环带来了 O(n²) 的开销,我们能不能想办法只遍历一次字符串就得到答案呢?答案是肯定的。

#### 优化思路

让我们换个角度思考。如果我们正在从头到尾阅读字符串,当我们看到一个字符时,我们只需要关心它跟前一个字符是否相同。

想象一下你在流水线上工作:

  • 你手里拿着当前产品(字符)。
  • 你看它跟上一个产品是不是一样的。
  • 如果一样,你就把当前的计数器加 1。
  • 如果不一样,说明上一个“连续批次”结束了。你需要结算一下:刚才那个批次的数量是不是创了新高?如果是,记录下来。然后,重置计数器,开始统计新产品的数量。

这种思路把“以 i 为中心向右扩散”改成了“线性扫描并记录状态”,效率大大提升。

#### 关键步骤

  • 遍历:使用一个循环从头到尾遍历字符串(从索引 1 开始,而不是 0,因为我们总是比较 INLINECODEe5b184f9 和 INLINECODE23688a90)。
  • 比较:如果 INLINECODE21589a94,说明当前字符是前一个字符的延续,INLINECODEb55dbad0 加 1。
  • 重置与结算:如果 s[i] != s[i-1],说明连续性被打断。

* 首先,检查刚才的 INLINECODEc35da2b7 是否大于 INLINECODE44acdb23。如果是,更新结果。

* 然后,将 INLINECODE21f4e9f1 重置为 1(因为当前字符 INLINECODE1c7729f3 开始了一个新的潜在连续段,它自己占 1 次)。

#### 代码实现(优化版)

这里我只展示 C++ 代码,你可以尝试用同样的逻辑修改上面的 Java 或 Python 代码,这是一个很好的练习。

// 优化后的 C++ 程序:使用单次遍历查找最大连续重复字符
#include 
#include 
using namespace std;

char maxRepeating(string str) {
    int n = str.length();
    if (n == 0) return ‘\0‘; // 处理空字符串的边界情况

    int maxCount = 0;      // 全局最大连续计数
    int count = 0;         // 当前连续字符的计数
    char res = str[0];     // 结果字符

    for (int i = 0; i  maxCount) {
                maxCount = count;
                res = str[i - 1]; // 更新结果为上一段的字符
            }
            // 重置计数器,开始计算新字符
            count = 1;
        }
    }

    // 循环结束后,不要忘记检查最后一段连续字符!
    // 因为循环可能结束时恰好处于最长连续段中,没有遇到“不同字符”来触发更新
    if (count > maxCount) {
        res = str[n - 1];
    }

    return res;
}

int main() {
    string s = "aaaabbaaccde";
    cout << "最大连续重复字符是: " << maxRepeating(s) << endl;
    return 0;
}

#### 优化后的性能分析

  • 时间复杂度:O(n)。我们只遍历了字符串一次。无论字符串有多长,我们都只需要常数倍的遍历时间。这在处理海量数据(如读取日志文件分析)时至关重要。
  • 空间复杂度:O(1)。依然只使用了有限的几个变量。

实战应用与最佳实践

在实战中,这个问题有很多应用场景。比如,在数据压缩(Run-Length Encoding)中,我们需要先找到连续重复的字符块才能进行编码。再比如,在基因序列分析中,科学家可能对最长的连续 DNA 片段感兴趣。

#### 常见错误提示

  • 忽略循环后的最后一次检查:在方法二中,很多初学者容易犯的错误是只在内层 INLINECODE71aa8d65 更新结果。如果最长的一段连续字符恰好位于字符串的末尾,循环结束前没有遇到不同的字符来触发 INLINECODEc7274667 分支,这个结果就不会被记录下来。所以,循环结束后的额外检查是必须的。
  • 索引越界:记得在比较 INLINECODE0ef0daf0 和 INLINECODE549c95b4 时,确保循环从 INLINECODE069a2646 开始,或者在逻辑中处理 INLINECODE61645ed5 的情况,否则会访问到 str[-1] 导致未定义行为。
  • 题目理解偏差:正如我在开头强调的,千万别把题目理解成“统计出现频率最高的字符”。例如在 "abbbbbba" 中,频率最高的是 ‘b‘(8次),但连续出现的最大长度也是 8。但如果题目是 "ababa",频率最高的还是 ‘a‘ 或 ‘b‘(3次),但它们的最大连续长度只有 1。

总结

通过这篇深度解析,我们从朴素的双重循环出发,将其优化为高效的线性扫描算法。在这个过程中,我们不仅学会了如何解决这个问题,更重要的是掌握了状态机思维——即如何在遍历过程中维护当前的“状态”(当前计数和最大计数)。

希望这次分享对你有帮助。下次当你遇到字符串处理问题时,不妨先思考一下:“我能不能只遍历一次就搞定?” 这种思维习惯往往能帮你写出更优雅、更高效的代码。

你可以尝试复制上面的代码,自己修改输入字符串,或者尝试实现不同语言的版本,加深对算法的理解。祝编码愉快!

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。如需转载,请注明文章出处豆丁博客和来源网址。https://shluqu.cn/30283.html
点赞
0.00 平均评分 (0% 分数) - 0