题目描述
对于一个长度为 n 的字符串 s=s0s1⋯sn−1 来说,子串的定义是从中选出两个下标 l,r (0≤l≤r≤n−1),这之间所有的字符组合起来的一个新的字符串:s′=slsl+1⋯sr 就是其中一个子串。
现在给出一个只有数字字符 0∼9 组成的数字字符串,小蓝想要知道在其所有的子串中,有多少个子串是好串。一个子串是好串,当且仅当它满足以下两个条件之一:
- 单字符子串一定是好串,即当子串长度为 1 时,它总是好串;
- 长度大于 1 时,可以拆分为两个连续非递减子串: 一个串 p=p0p1⋯pk−1 为连续非递减子串是指,对于所有 1≤i<k,满足 pi=pi−1 或 pi=pi−1+1。即数字串中的每一个数字,要么等于上一个数字,要么等于上一个数字加 1。例如
12233
、456
是连续非递减子串。
输入格式
输入一行包含一个字符串 s。
输出格式
输出一行包含一个整数表示答案,即好串的数目。
输入输出样例
输入
12258
输出
12
说明/提示
样例说明 1
- 长度为 1 的好串:
1
、2
、2
、5
、8
,共 5 个; - 长度为 2 的好串:
12
、22
、25
、58
,共 4 个; - 长度为 3 的好串:
122
、225
,共 2 个; - 长度为 4 的好串:
1225
,共 1 个;
总计 5+4+2+1=12 个。
思路:滑动窗口
对于连续的好串需要满足s[r]=s[r-1]+1或者s[r]=s[r-1]
一个子串是“好串”的条件是:
1. 长度为 1。
2. 可以拆分为 最多 两个连续的非递减子串。
关键在于条件 2。“可以拆分为最多两个连续的非递减子串”意味着这个子串内部 最多只允许出现一个“断点” 。“断点”指的是相邻两个字符 s[i-1] 和 s[i] 不满足非递减关系(即 s[i] 既不等于 s[i-1] 也不等于 s[i-1] + 1 )。
代码利用滑动窗口 [l, r] 来维护一个子串,并保证这个窗口内的子串始终最多只包含一个“断点”。
我们用一个变量来统计这个断点。
#include <bits/stdc++.h>
using namespace std;
#define int long long
const int nn = 100005; // 定义一个常量,表示字符串最大长度+缓冲区
string s;
signed main() {
cin >> s;
int cnt = 0; //断点数
int l = 0; // 滑动窗口的左边界指针,初始指向字符串开头
int ans = 0;
// s[r] 用于判断是否到达字符串末尾 (空字符 '\0')
for (int r = 0; s[r]; r++) {
// 检查新加入窗口的字符 s[r] 是否与前一个字符 s[r-1] 构成了“断点”
// r > 0 是为了防止访问 s[-1]
// 条件:s[r] 既不等于 s[r-1],也不等于 s[r-1] + 1
if (r > 0 && s[r - 1] + 1 != s[r] && s[r - 1] != s[r]) {
cnt++; // 如果是断点,+1
}
// 当窗口内的断点数量超过 1 时,需要收缩左边界
while (cnt > 1) {
l++; // 将左边界向右移动一位
// 检查即将移出窗口的那个“断点”是否发生在 l 处
// 注意:这里比较的是 s[l] 和 s[l-1] 的关系
// 如果 s[l] 和 s[l-1] 之间存在断点,那么当 l 向右移动一位后,
// 这个断点就不在窗口 [l+1, r] 内了,所以断点计数器需要减 1
// 并且需要 l > 0 (因为 l=0 时 l-1 是 -1)
if (s[l] != s[l - 1] && s[l - 1] + 1 != s[l]) {
cnt--; // 断点计数器减 1
}
}
// 到这里,窗口 [l, r] 内的断点数量保证为 0 或 1
// 这意味着所有以 r 结尾,且起始位置在 [l, r] 区间内的子串都是“好串”
// 例如,子串 s[l..r], s[l+1..r], ..., s[r..r] 都是好串
// 这些子串的数量是 r - l + 1
ans += (r - l + 1); // 将这些好串的数量累加到总答案中
}
cout << ans << '\n'; // 输出最终的好串总数
return 0;
}
和博主上篇文章思路类似,有兴趣也可以看一下哦,链接:力扣209. 长度最小的子数组-CSDN博客
感谢看到这里,喜欢博主可以点点关注,感谢!!!