当前位置: 首页 > news >正文

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

目录

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

1.1 题目解析

1.2 解法

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 个不同的子串。

状态转移方程:

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 代码实现

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) 空间

http://www.dtcms.com/a/545322.html

相关文章:

  • 发送 Prompt 指令:判断用户评价是好评还是差评
  • 高阶数据结构 --- 跳表Skiplist
  • Ansible模块分类与实战应用指南
  • 发送 Prompt 指令:请用一句话总结文本内容
  • 沧州网站seo创业 建网站
  • 临安市住房和建设局网站百度搜索引擎的原理
  • k8s rbac权限最小化实践
  • Javascript数据类型之类型转换
  • 销售拜访前的全面准备指南以及ABC推荐法
  • 优秀网站模板下载网站编程论文
  • 仓颉代码内联策略:性能优化的精密艺术
  • 欧瑞电机编码器引脚定义
  • 中国隧道空间分布
  • 作文网站哪个平台好wordpress超简洁主题
  • 聊城公司网站建设注册域名需要多久
  • 国外摄影网站合肥网站网站建设
  • Vue+Element 封装表格组件
  • 有向图能拓扑排序,必定无环
  • 网络:2.1加餐 - 网络命令
  • 为什么需要设置字符编码?
  • 电影网站如何做seo济南制作公司网站
  • 怎么网站排名seo乐清网络推广公司
  • 仓颉 String 内存表示:从 UTF-8 小对象到零拷贝子串的完整旅程
  • Android Studio新手开发第三十四天
  • 多维c++ vector, vector<pair<int,int>>, vector<vector<pair<int,int>>>示例
  • 【TVM 教程】自定义优化
  • 免费行情网站大全下载成品源码网站
  • 男女生做羞羞事情的网站网站域名怎样选择
  • 做政协网站软件的公司找人做网站 优帮云
  • 电力系统安全新样本:瑞数信息用“动态安全”筑起业务防线