题目链接

小红的01子序列构造(easy)

题目描述

给定一个仅由字符 '0' 和 '1' 组成的字符串 ,长度为 。小红想找到一个闭区间 ,使得在子串 中,恰好存在 个严格等于 "01" 的子序列。

请你输出任意一个满足条件的区间;若不存在,则输出 -1。

解题思路

这个问题可以高效地使用**滑动窗口(双指针)**方法来解决。

首先,我们需要明确如何计算一个字符串中 "01" 子序列的数量。对于字符串中的每一个字符 '1',它能与它左边的任意一个 '0' 组成一个 "01" 子序列。因此,一个 '1' 对 "01" 子序列总数的贡献就等于它左边 '0' 的数量。整个字符串中 "01" 子序列的总数就是所有 '1' 的贡献之和。

基于这个思想,我们可以使用两个指针,,来维护一个动态的窗口 。我们还需要一些变量来维护窗口内的状态:

  1. :记录当前窗口内 '0' 的数量。
  2. :记录当前窗口内 '1' 的数量。
  3. :记录当前窗口内 "01" 子序列的总数。

算法流程如下:

  1. 初始化 ,以及三个计数器为 0。
  2. 让右指针 遍历到 ,不断扩大窗口:
    • 读入新字符
    • 如果 是 '0',则 增加 1。
    • 如果 是 '1',它将与窗口内已有的 个 '0' 形成新的 "01" 子序列,所以 增加 增加 1。
  3. 在每一步扩大窗口后,检查 是否大于 。如果是,说明当前窗口内的 "01" 子序列太多,需要从左边收缩窗口:
    • 时,持续移动左指针
      • 如果要移除的字符 是 '0',那么窗口内所有的 '1' 都失去了一个可以配对的 '0'。因此, 需要减去当前窗口内的 。同时, 减 1。
      • 如果要移除的字符 是 '1',移除它不会影响其他 "01" 子序列的构成(因为它在最左边,没有 '0' 在它前面),所以 不变。我们只需将 减 1。
    • 收缩窗口的循环结束后, 将小于或等于
  4. 在每次外层循环(移动 )的末尾,检查 是否恰好等于 。如果是,我们就找到了一个满足条件的区间 ,可以立即输出并终止程序。
  5. 如果整个过程结束后都没有找到满足条件的区间,则输出 -1。

这个算法中,左右指针都只向右移动,最多遍历一次字符串,因此总的时间复杂度为

代码

#include <iostream>
#include <vector>
#include <string>

using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(NULL);
    int n;
    long long k;
    cin >> n >> k;
    string s;
    cin >> s;

    long long current_k = 0;
    long long zeros_count = 0;
    long long ones_count = 0;
    int left = 0;

    for (int right = 0; right < n; ++right) {
        if (s[right] == '0') {
            zeros_count++;
        } else { // s[right] == '1'
            ones_count++;
            current_k += zeros_count;
        }

        while (current_k > k && left <= right) {
            if (s[left] == '0') {
                current_k -= ones_count;
                zeros_count--;
            } else { // s[left] == '1'
                ones_count--;
            }
            left++;
        }
        
        if (current_k == k) {
            cout << left + 1 << " " << right + 1 << endl;
            return 0;
        }
    }

    cout << -1 << endl;

    return 0;
}
import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        long k = sc.nextLong();
        String s = sc.next();

        long currentK = 0;
        long zerosCount = 0;
        long onesCount = 0;
        int left = 0;

        for (int right = 0; right < n; right++) {
            char c = s.charAt(right);
            if (c == '0') {
                zerosCount++;
            } else { // c == '1'
                onesCount++;
                currentK += zerosCount;
            }

            while (currentK > k && left <= right) {
                char leftChar = s.charAt(left);
                if (leftChar == '0') {
                    currentK -= onesCount;
                    zerosCount--;
                } else { // leftChar == '1'
                    onesCount--;
                }
                left++;
            }

            if (currentK == k) {
                System.out.println((left + 1) + " " + (right + 1));
                return;
            }
        }

        System.out.println(-1);
    }
}
def main():
    n, k = map(int, input().split())
    s = input().strip()

    current_k = 0
    zeros_count = 0
    ones_count = 0
    left = 0

    for right in range(n):
        if s[right] == '0':
            zeros_count += 1
        else: # s[right] == '1'
            ones_count += 1
            current_k += zeros_count

        while current_k > k and left <= right:
            if s[left] == '0':
                current_k -= ones_count
                zeros_count -= 1
            else: # s[left] == '1'
                ones_count -= 1
            left += 1

        if current_k == k:
            print(left + 1, right + 1)
            return

    print(-1)

if __name__ == "__main__":
    main()

算法及复杂度

  • 算法:滑动窗口(双指针)。
  • 时间复杂度,其中 是字符串 的长度。左右指针 都最多遍历一次字符串。
  • 空间复杂度,用于存储输入的字符串