LeetCode 467 环绕字符串中唯一的子字符串


文章目录

摘要

这道题第一眼看很容易被"子字符串""不同""无限环绕字符串"这些词劝退,但实际上它是一个典型的"计数型动态规划"问题

真正的难点不在字符串本身,而在于:

  • 怎么避免重复统计
  • 怎么在 O(n) 的复杂度里算清楚所有可能的子串数量

如果你平时做过日志切片、连续事件统计、或者字符串规则分析,这道题的解法思路会非常熟悉。

描述

题目先定义了一个"看起来很吓人"的字符串 base

复制代码
"...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd..."

它的特点其实只有一个:

  • 字母是 连续递增的
  • 'z' 后面可以接 'a'

然后问题来了:

给你一个字符串 s,统计 s 中有多少个 不同的非空子串

同时也能在 base 中出现。

注意几个关键信息:

  1. 子串是 连续的
  2. 要求 不同(不能重复算)
  3. 子串本身必须满足 字母连续 + 可 z→a

题解答案

这道题如果暴力枚举子串:

  • 子串数量是 O(n²)
  • 再去判定是否在 base 中,直接爆炸

真正高效的解法只有一句话:

记录每个字母作为结尾时,最多能形成多长的"连续环绕子串"

核心思想是:

  • 只关心 "以某个字符结尾"的最长合法子串
  • 同一个结尾字符,短的子串一定包含在长的里面
  • 所以只统计"最长的那一条"就够了

题解代码分析

下面是完整 Swift 实现,可以直接复制运行。

swift 复制代码
class Solution {
    func findSubstringInWraproundString(_ s: String) -> Int {
        let chars = Array(s)
        if chars.isEmpty { return 0 }

        // dp[i] 表示:以字符 ('a' + i) 结尾的最长连续子串长度
        var dp = [Int](repeating: 0, count: 26)

        var currentLen = 0

        for i in 0..<chars.count {
            if i > 0 && isContinuous(chars[i - 1], chars[i]) {
                currentLen += 1
            } else {
                currentLen = 1
            }

            let index = Int(chars[i].asciiValue! - Character("a").asciiValue!)
            dp[index] = max(dp[index], currentLen)
        }

        return dp.reduce(0, +)
    }

    private func isContinuous(_ a: Character, _ b: Character) -> Bool {
        let aVal = a.asciiValue!
        let bVal = b.asciiValue!
        return bVal == aVal + 1 || (a == "z" && b == "a")
    }
}

核心逻辑拆解

什么叫"连续环绕"?

合法情况只有两种:

  • 'a' -> 'b' -> 'c'
  • 'z' -> 'a'
swift 复制代码
bVal == aVal + 1 || (a == "z" && b == "a")

这就是 base 的全部规则,没有其他花样。

currentLen 在干嘛?

currentLen 表示:

当前这个字符,能接在前一个字符后面,形成多长的合法子串

比如:

复制代码
s = "zab"

扫描过程是:

  • 'z' → currentLen = 1
  • 'a' → 连续 → currentLen = 2
  • 'b' → 连续 → currentLen = 3
为什么 dp[index] = max(dp[index], currentLen)

这是整道题最关键的一步。

'b' 结尾:

  • "b" 是合法
  • "ab" 是合法
  • "zab" 也是合法

但如果你已经有了长度为 3 的 "zab"

  • 长度为 1、2 的子串 一定已经包含在里面
  • 再统计就会重复

所以我们只保留 最长的那个

示例测试及结果

示例 1

swift 复制代码
let solution = Solution()
print(solution.findSubstringInWraproundString("a"))

分析:

  • 只有一个子串 "a"
  • 合法

输出:

复制代码
1

示例 2

swift 复制代码
print(solution.findSubstringInWraproundString("cac"))

分析:

  • "c" 合法
  • "a" 合法
  • "ca" 不合法(不是连续)
  • "ac" 不合法

输出:

复制代码
2

示例 3

swift 复制代码
print(solution.findSubstringInWraproundString("zab"))

分析:

  • "z", "a", "b"
  • "za", "ab"
  • "zab"

一共 6 个。

输出:

复制代码
6

时间复杂度

整个算法只做了一次线性扫描:

复制代码
O(n)

n 是字符串 s 的长度,最大 10⁵,完全没压力。

空间复杂度

只用了一个固定长度的数组:

复制代码
dp[26]

空间复杂度:

复制代码
O(1)

总结

《环绕字符串中唯一的子字符串》是一道非常典型的"看起来复杂,其实规律极强"的题

它真正想考你的不是字符串 API,而是:

  • 能不能把"不同子串"这个问题压缩成 状态统计
  • 能不能意识到:
    同一个结尾字符,只需要记最长那条
相关推荐
Hcoco_me4 分钟前
大模型面试题49:从白话到进阶详解SFT 微调的 Loss 计算
人工智能·深度学习·神经网络·算法·机器学习·transformer·word2vec
修炼地6 分钟前
代码随想录算法训练营第五十三天 | 卡码网97. 小明逛公园(Floyd 算法)、卡码网127. 骑士的攻击(A * 算法)、最短路算法总结、图论总结
c++·算法·图论
小王和八蛋6 分钟前
负载均衡之DNS轮询
后端·算法·程序员
炽烈小老头12 分钟前
【每天学习一点算法 2026/01/07】Fizz Buzz
学习·算法
数据大魔方18 分钟前
【期货量化实战】威廉指标(WR)策略:精准捕捉超买超卖信号(Python源码)
开发语言·数据库·python·算法·github·程序员创富
why技术19 分钟前
可怕,看到一个冷血的算法。人心逐利,算法只会更聪明地逐利。
前端·后端·算法
程序员良辰22 分钟前
【面试读心术】OJ系统面试深度解析 - 从“一问三不知“到“对答如流“的蜕变
android·面试·职场和发展
程序员小远24 分钟前
接口自动化测试知识总结
自动化测试·软件测试·python·测试工具·职场和发展·测试用例·接口测试
CCPC不拿奖不改名28 分钟前
Python基础:python语言中的文件操作+面试题目
开发语言·数据结构·人工智能·python·学习·面试·职场和发展
有一个好名字29 分钟前
力扣-最大连续1的个数III
c++·算法·leetcode