1122字符串(游程编码)
题目
题目链接:https://atcoder.jp/contests/abc433/tasks/abc433_c
官方题解:https://atcoder.jp/contests/abc433/editorial/14653
问题描述
如果字符串 T长度为偶数,前半段都是数字a,后半段都是数字b,且b=a+1,则称其为 1122-字符串。
你被给定一个由数字组成的字符串 S 。
找出 S 中是 1122字符串的子串的数量。
如果两个子串是从不同位置提取的,即使它们作为字符串是相同的,也会被分别计数。
约束
S 是一个由数字组成的字符串,长度在 1 和 10e6 之间(包括 1 和 10e6 )。
输入
输入按照以下格式从标准输入给出:
S
输出
输出 S 中非空且是 1122-字符串的子串的数量。
样本输入 1
1122
样本输出 1
2
以下两个子字符串满足条件。
12 从 2 到 3 的 S 个字符中提取
1122从 1 到 4 的 S 个字符中提取
因此,输出 2 。
示例输入 2
7788788
示例输出 2
3
注意,如果从不同位置提取,即使字符串相同,两个子字符串也会被分别计数。
示例输入 3
2025
示例输出 3
0
可能不存在是 1122 字符串的子字符串。
示例输入 4
1112222334445556555
样本输出 4
11
题解:
注意到,1122字符串需满足前半段的数字a加1等于后半段的数字b,即b=a+1,则对于每一个1122字符串,都存在一个边界,该边界的左边是数字a,右边是数字b。所以,这道题我们可以对这个“边界值”进行穷举法。对于满足条件(即左边a右边b,且b=a+1)的边界值,统计其左边a的个数记为n,右边b的个数记为m,min(n,m)的累加值就是这道题的答案。举个栗子,对于字符串778788:
当 i = 1 , s[1] = 7 , s[2] = 7,不符合边界条件,跳过;
当 i = 2 , s[2] = 7 , s[3] = 8,符合条件,min(2,1)=1,则答案加上1;
当 i = 3 , s[3] = 8 , s[4] = 8,跳过;
当 i = 4 , s[4] = 7 , s[5] = 8,min(1,2)=1;
当 i = 5 , s[5] = 8 , s[6] = 8,跳过;
则最终答案为1+1=2。
如果我们能找到包含每个 Si 的连续序列的长度,那么这个问题就可以解决了。这里给出两个找到连续序列长度的方法。
方法一 行程长度编码
一个字符串的行程长度编码是一系列值及其连续出现次数的配对。例如,字符串 S= 778788 的行程长度编码是 (7,2) (8,1) (7,1) (8,2)。行程编码可以通过遍历一次字符串得到,其复杂度为O(n),而后在检查一次行程编码,便可以得到这道题的答案,因此整个问题可以在 O(N) 时间内解决。
代码如下:
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
vector<pair<char, int>> a;
char c=s[0]; // 记录子串的数字,初始化为s[0]
int cont=1; // 记录子串的长度,初始化为1
for (int i=1; i<s.length(); i++) {
if (s[i]==c) {
cont++; // 若和子串的数字相同,则子串长度加一
} else {
a.push_back({c,cont}); // 否则将该子串加入动态数组a
c=s[i]; // 更新为新的字符
cont=1; // 重置计数
}
}
a.push_back({c,cont}); // 加入最后一个子串
long long ans=0;
for (int i=0; i<a.size()-1; i++) {
int d1=a[i].first-'0';
int d2 =a[i + 1].first-'0';
if (d1+1==d2) { // 若后一子串的数字等于前一子串的数字加1,则答案加上它们长度的最小值
ans+=min(a[i].second,a[i+1].second);
}
}
cout<<ans<<endl;
return 0;
}
方法二 简单处理
遍历字符串从s[0]到s[n-1],若s[i]+1=s[i+1],则使j=i,k=i+1。当s[j]=s[i]时,j-1;当s[k]=s[i+1]时,k+1。最后min(i-j,k-i+1)的累加值即为答案。
虽然看起来有嵌套循环,但外层循环遍历每个位置一次,其复杂度为O(n)。内层循环的指针移动是单向的,向左扩展时,访问的字符不会被重复扫描;向右扩展时,访问的字符也不会被重复扫描。因此,所有内层循环的总运行时间也是 O(n),整体复杂度保持为 O(n)。
代码如下:
#include <bits/stdc++.h>
using namespace std;
int main() {
string s;
cin >> s;
int n = s.length();
long long ans = 0;
for (int i = 0; i < n - 1; i++) {
if (s[i] + 1 == s[i + 1]) {
int j = i;
// 向左扩展,找到连续相同数字的起始位置之前的位置
while (j >= 0 && s[j] == s[i]) {
j--;
}
int k = i + 1;
// 向右扩展,找到连续相同数字的结束位置
while (k < n - 1 && s[k] == s[k + 1]) {
k++;
}
// 左连续段长度为 i - j,右连续段长度为 k - i
ans += min(i - j, k - i);
}
}
cout << ans << endl;
return 0;
}
拓展 游程编码
基本概念
行程长度编码(Run-Length Encoding, RLE)又叫行程编码、游程编码。它是一种简单的无损数据压缩算法,它将连续的相同数据值序列(称为"游程")替换为单个数据值和该值连续出现的次数。
其基本格式如下:
原始数据:AAABBBCCDDDD
游程编码:A3B3C2D4
编码思路
· 遍历数据,记录当前字符和连续出现的次数
· 遇到不同字符时,保存前一个字符及其计数,然后重置计数
· 处理完所有数据后,保存最后一个字符及其计数
游程编码的优势
· 时间复杂度低:通常为 O(n)
· 空间效率高:对连续重复数据压缩效果好
· 实现简单:逻辑清晰,代码简洁
· 适用性广:可用于字符串、数组、图像等多种数据类型
适用场景特征
· 数据中有大量连续重复元素
· 需要统计连续区间信息
· 问题与"连续"、"重复"、"区间"等概念相关
· 需要压缩数据或简化处理
在解决算法问题时,如果发现题目涉及连续相同的元素或区间统计,就可以考虑使用游程编码来简化问题。
举例
1. 字符串压缩类问题
LeetCode 443. 压缩字符串 https://leetcode.cn/problems/string-compression/description/
class Solution {
public:
int compress(vector<char>& chars) {
int n = chars.size();
int write_idx = 0; // 写入位置指针
for (int i = 0; i < n;) {
char current_char = chars[i];
int count = 0;
// 统计连续相同字符的数量
while (i < n && chars[i] == current_char) {
count++;
i++;
}
// 写入字符
chars[write_idx++] = current_char;
// 如果计数大于1,写入数字
if (count > 1) {
string count_str = to_string(count);
for (char digit : count_str) {
chars[write_idx++] = digit;
}
}
}
return write_idx; // 返回新长度
}
};
2. 连续字符统计问题
LeetCode 830. 较大分组的位置 https://leetcode.cn/problems/positions-of-large-groups/submissions/680382706/
class Solution {
public:
vector<vector<int>> largeGroupPositions(string s) {
vector<vector<int>> result;
int n = s.length();
for (int i = 0; i < n;) {
int j = i;
while (j < n && s[j] == s[i]) {
j++;
}
if (j - i >= 3) {
result.push_back({i, j - 1});
}
i = j;
}
return result;
}
};
3. 区间合并问题
LeetCode 56. 合并区间 https://leetcode.cn/problems/merge-intervals/description/
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.empty()) return {};
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (auto& interval : intervals) {
// 类似游程编码的思路:合并连续区间
if (merged.empty() || merged.back()[1] < interval[0]) {
merged.push_back(interval);
} else {
merged.back()[1] = max(merged.back()[1], interval[1]);
}
}
return merged;
}

浙公网安备 33010602011771号