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;
}
posted @ 2025-11-24 20:12  SDSSpJ306  阅读(14)  评论(0)    收藏  举报