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

千问 LeetCode 2953. 统计完全子字符串 Java实现

这道题是一道经典的“分组循环 + 定长滑动窗口”问题。我们需要同时满足两个条件,直接暴力求解会非常复杂,因此核心思路是将两个条件拆分开来独立处理。

题目解析
一个子字符串是“完全”的,需要满足:
1. 字符频率条件:子字符串中每个出现的字符,都恰好出现 k 次。
2. 相邻字符条件:子字符串中任意两个相邻字符,在字母表中的顺序至多相差 2。

核心思路
1. 分组循环(处理相邻字符条件):
由于条件2限制了相邻字符的差值,我们可以遍历整个字符串,每当遇到两个相邻字符的差值超过 2 时,就将字符串切分开。这样,原字符串就被分成了若干个独立的“块”,我们只需要在每个独立的块内部去统计满足条件1的子字符串即可。

2. 定长滑动窗口(处理字符频率条件):
对于每一个独立的块,如果其中包含了 x 种不同的字符,那么满足条件1的子字符串长度一定是 x * k。
因为字符只有 26 个小写字母,所以我们可以枚举子字符串中包含的字符种类数 m(从 1 到 26)。对于每一个 m,窗口的固定长度就是 windowLen = m * k。我们使用滑动窗口在该块内滑动,检查窗口内的每个字符是否都恰好出现了 k 次。

Java 代码实现

class Solution {
public int countCompleteSubstrings(String word, int k) {
int n = word.length();
int totalRes = 0;

// 1. 分组循环:根据相邻字符差值 > 2 来切分字符串
for (int i = 0; i < n; ) {
int start = i;
i++;
// 只要相邻字符满足条件,就不断向后扩展当前组
while (i < n && Math.abs(word.charAt(i) - word.charAt(i - 1)) <= 2) {
i++;
}
// 对切分出来的子串 word[start, i) 单独计算满足条件1的子字符串数量
totalRes += countInSubstring(word.substring(start, i), k);
}

return totalRes;
}

// 2. 在独立的子串中,使用定长滑动窗口统计完全子字符串
private int countInSubstring(String s, int k) {
int len = s.length();
int res = 0;

// 枚举窗口内包含的字符种类数 m (1 到 26)
for (int m = 1; m <= 26 && m * k <= len; m++) {
int windowLen = m * k;
int[] freq = new int[26]; // 记录窗口内各字符的频率

// 滑动窗口遍历子串 s
for (int right = 0; right < len; right++) {
// 字符进入窗口
freq[s.charAt(right) - 'a']++;

// 计算当前窗口的左边界
int left = right + 1 - windowLen;

// 当窗口大小达到 windowLen 时开始判断
if (left >= 0) {
// 检查窗口内所有字符是否都恰好出现 k 次
boolean isValid = true;
for (int count : freq) {
// 如果某个字符出现过,但次数不等于 k,则不合法
if (count > 0 && count != k) {
isValid = false;
break;
}
}
if (isValid) {
res++;
}

// 字符滑出窗口,为下一次循环做准备
freq[s.charAt(left) - 'a']--;
}
}
}
return res;
}
}

复杂度分析
* 时间复杂度:O(26 * n),即 O(n)。其中 n 是字符串 word 的长度。我们遍历字符串进行分组是 O(n),在 countInSubstring 方法中,虽然有两层循环,但外层枚举字符种类最多 26 次,内层滑动窗口遍历子串,每个字符最多被访问常数次。
* 空间复杂度:O(1)。我们使用了一个固定大小为 26 的数组 freq 来记录字符频率,以及 substring 产生的临时空间(在 Java 新版本中 substring 会拷贝字符数组,但长度受限于 n,且通常视为常数级或 O(n) 的临时开销,整体空间复杂度非常低)。

http://www.cnnetsun.cn/news/2717359.html

相关文章:

  • Havenlon 的共同治理哲学:Owner 不应该天然拥有最终执行权
  • 从质检到金融风控:假设检验的7个真实业务场景拆解(含Python/R代码片段)
  • 如何快速掌握通达信金融数据:mootdx新手的完整入门指南
  • 紧急升级通知:Lindy v2.8.3已修复3个高危资源漂移漏洞——你的自动化流水线是否仍在裸奔?
  • 腾讯云杀疯了:大模型降价 97.5%,小玩家正在出局
  • yuzu模拟器下载安装全攻略:告别卡顿的终极优化指南
  • 抖音批量下载神器:5分钟学会保存所有精彩内容
  • 避开重映射的坑:雅特力AT32F413 TMR3通道2输出PWM的另一种配置思路(附完整代码)
  • 告别定位失败!Selenium处理shadowDOM的两种“抄近道”方法(含Chrome DevTools技巧)
  • 推挽变换器的基本结构
  • 免费提取文字软件保姆级指南:2026年最推荐的5种方法一看就会
  • 半导体与机器人行业利润大增:是真实需求驱动,还是短期扰动?
  • 麒麟V10 SP3/SP2系统yum源配置保姆级教程(附官方源地址与常见错误排查)
  • 3分钟解锁所有加密音乐:Unlock-Music终极免费解决方案
  • Win10/Win11升级后C盘少了10个G?教你彻底清理“以前的Windows安装”并释放空间
  • 搜索进入 Agentic 智能体时代,内容要能 “被 AI 直接用”
  • 别再硬编码了!用PFC2D 5.0模拟滑坡,这份参数调试与结果分析指南请收好
  • SpaceX拟6月纳斯达克上市,估值1.75 - 2万亿美元,AI与星链业务暗藏哪些风险?
  • 鸣潮自动化终极指南:3大场景解锁智能挂机新体验
  • ComfyUI-VideoHelperSuite:视频处理中的零除错误防御与智能帧选择技术
  • 洛雪音乐音源完整配置指南:5步打造你的专属高品质音乐库
  • 基于Arduino与步进电机的桌面摩天轮DIY:从机械结构到编程控制
  • 别再死记硬背公式了!用‘辗转相除法’手把手带你搞定GCD和LCM(附Java代码实战)
  • 逆推思维:找到达成目标的最短路线
  • 5分钟快速上手!MediaCrawler跨平台数据采集工具终极指南
  • DIY超级英雄控制台:从自闪LED到Arduino的创客实践
  • 低代码平台 表单设计器 unione form editor 功能组件 —— 按钮组件
  • 树莓派与Phidgets改造万圣节装饰:超声波感应与继电器控制实战
  • 【文档检索提效】实战指南:用 LangChain + FAISS 搭建你的本地 API 文档问答机器人
  • 从GitOps到ModelOps:AI工具注册整合的终极范式迁移(附开源可落地图谱v2.3)