LeetCode算法日记 - Day 88: 环绕字符串中唯一的子字符串

目录

[1. 环绕字符串中唯一的子字符串](#1. 环绕字符串中唯一的子字符串)

[1.1 题目解析](#1.1 题目解析)

[1.2 解法](#1.2 解法)

[1.3 代码实现](#1.3 代码实现)


1. 环绕字符串中唯一的子字符串

https://leetcode.cn/problems/unique-substrings-in-wraparound-string/description/
定义字符串 base 为一个 "abcdefghijklmnopqrstuvwxyz" 无限环绕的字符串,所以 base 看起来是这样的:

  • "...zabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcd....".

给你一个字符串 s ,请你统计并返回 s 中有多少 不同 非空子串 也在 base 中出现。

示例 1:

复制代码
输入:s = "a"
输出:1
解释:字符串 s 的子字符串 "a" 在 base 中出现。

示例 2:

复制代码
输入:s = "cac"
输出:2
解释:字符串 s 有两个子字符串 ("a", "c") 在 base 中出现。

示例 3:

复制代码
输入:s = "zab"
输出:6
解释:字符串 s 有六个子字符串 ("z", "a", "b", "za", "ab", and "zab") 在 base 中出现。

提示:

  • 1 <= s.length <= 105
  • s 由小写英文字母组成

1.1 题目解析

题目本质

统计字符串中有多少不同的连续子串符合字母表循环顺序。本质是"连续性判断 + 去重统计"问题,核心在于识别符合 base 规则的子串(按字母顺序连续,z 可接 a),并统计不重复的个数。

常规解法

最直观的想法是枚举所有子串,逐个检查是否符合连续规则。例如 s = "zab",枚举 "z"、"za"、"zab"、"a"、"ab"、"b",每个子串都检查相邻字符是否连续。使用 HashSet 去重,最后返回集合大小。

问题分析

枚举所有子串需要 O(n²) 时间,每个子串检查连续性需要 O(n),总复杂度 O(n³)。对于长度 10⁵ 的字符串会超时。更关键的是,大量子串会重复存储,例如 "abc" 包含的 "a"、"ab" 会在其他位置重复计数,HashSet 虽然去重但效率低。

思路转折

要想高效 → 必须找到去重的规律 → 观察子串特征。

关键发现:如果存在长度为 k 的连续子串以字符 'c' 结尾,那么以 'c' 结尾的所有更短的连续子串(长度 1 到 k-1)都已经被包含了。例如 "abc" 以 'c' 结尾且长度 3,则包含了 "c"(长度1)和 "bc"(长度2)。因此,只需记录每个字符结尾的最长连续长度,就能自动去重。用动态规划计算每个位置的连续长度,再用哈希表记录 26 个字母各自的最大长度,求和即得答案。

1.2 解法

算法思想: 动态规划 + 按字符去重。定义 dp[i] 表示以位置 i 结尾的最长连续子串长度,如果 s[i-1] 和 s[i] 连续(满足字母顺序或 z→a),则 dp[i] = dp[i-1] + 1,否则 dp[i] = 1。用 hash[c] 记录以字符 c 结尾的最长连续长度。最终答案是所有 hash[c] 的和,因为长度为 k 的连续串包含了 k 个不同的子串。

状态转移方程:

java 复制代码
dp[i] = dp[i-1] + 1  如果 s[i-1]+1 == s[i] 或 (s[i-1]=='z' && s[i]=='a')

dp[i] = 1            否则

hash[c] = max(hash[c], dp[i])  其中 c = s[i]

i)初始化: 创建 dp[n] 数组全部填充为 1(每个字符本身是长度为 1 的连续串),创建 hash[26] 数组记录 26 个字母的最大连续长度

**ii)计算连续长度:**从 i=1 开始遍历字符串,判断 s[i] 和 s[i-1] 是否连续:

  • 若 ch[i-1] + 1 == ch[i](正常字母顺序,如 a→b)
  • 或 ch[i-1] == 'z' && ch[i] == 'a'(循环边界)
  • 则 dp[i] = dp[i-1] + 1,表示连续长度增加

**iii)记录最大值:**遍历所有位置 i,用 hash[ch[i] - 'a'] 保存以字符 ch[i] 结尾的最长连续长度,取 Math.max(hash[ch[i]-'a'], dp[i]) 确保记录最大值

**iv)统计答案:**遍历 hash 数组(26 个字母),累加所有最大连续长度,得到不重复子串总数

易错点

  • 去重逻辑理解: 为什么 hash 存最大值就能去重?因为长度为 k 的以 'c' 结尾的连续串,自动包含了长度 1 到 k 的所有以 'c' 结尾的子串(如 "abc" 包含 "c"、"bc"、"abc")。不同位置的同一字符,保留最长的即可覆盖所有情况
  • **循环边界:**最后求和时应该循环 26 次(i < 26),而不是 n 次,因为 hash 数组长度固定为 26,循环 n 次会在 n > 26 时数组越界
  • z→a 的判断: 别忘了处理循环边界 ch[i-1] == 'z' && ch[i] == 'a',这也算连续。注意需要用括号括起来:(ch[i-1] == 'z' && ch[i] == 'a')
  • **dp 初始化:**所有 dp[i] 初始化为 1,因为单个字符本身就是长度为 1 的有效子串
  • 为什么累加 hash: hash[c] = k 表示以字符 c 结尾有 k 个不同的连续子串(长度从 1 到 k),所以直接累加 hash 值就是总数

1.3 代码实现

java 复制代码
class Solution {
    public int findSubstringInWraproundString(String s) {
        int n = s.length();
        char[] ch = s.toCharArray();
        
        // dp[i]: 以位置 i 结尾的最长连续长度
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        
        // hash[c]: 以字符 c 结尾的最长连续长度
        int[] hash = new int[26];
        
        // 计算每个位置的连续长度
        for (int i = 1; i < n; i++) {
            if (ch[i-1] + 1 == ch[i] || (ch[i-1] == 'z' && ch[i] == 'a')) {
                dp[i] = dp[i-1] + 1;
            }
        }
        
        // 记录每个字符的最大连续长度
        for (int i = 0; i < n; i++) {
            hash[ch[i] - 'a'] = Math.max(hash[ch[i] - 'a'], dp[i]);
        }
        
        // 累加所有字符的贡献
        int ret = 0;
        for (int i = 0; i < 26; i++) {
            ret += hash[i];
        }
        
        return ret;
    }
}

复杂度分析

  • **时间复杂度: O(n),**三次线性遍历:计算 dp 数组 O(n),更新 hash 数组 O(n),累加结果 O(26) = O(1)
  • **空间复杂度: O(n),**dp 数组占用 O(n) 空间,hash 数组占用 O(26) = O(1) 空间
相关推荐
黑云压城After4 小时前
vue2实现图片自定义裁剪功能(uniapp)
java·前端·javascript
B站_计算机毕业设计之家4 小时前
python电商商品评论数据分析可视化系统 爬虫 数据采集 Flask框架 NLP情感分析 LDA主题分析 Bayes评论分类(源码) ✅
大数据·hadoop·爬虫·python·算法·数据分析·1024程序员节
小白菜又菜5 小时前
Leetcode 1518. Water Bottles
算法·leetcode·职场和发展
长存祈月心5 小时前
Rust Option 与 Result深度解析
算法
zcl_19915 小时前
记一次ThreadLocal导致的生产事故
java
RoboWizard5 小时前
怎么判断我的电脑是否支持PCIe 5.0 SSD?Kingston FURY Renegade G5
java·spring·智能手机·电脑·金士顿
杭州杭州杭州6 小时前
机器学习(3)---线性算法,决策树,神经网络,支持向量机
算法·决策树·机器学习
毕设源码-钟学长6 小时前
【开题答辩全过程】以 儿童游泳预约系统为例,包含答辩的问题和答案
java·eclipse
皮皮林5516 小时前
5种接口频率监控方案实战对比,性能、成本、复杂度全解析!
java