阿里巴巴 算法岗笔试真题【小红的01串操作】多语言题解

小红的01串操作(C++/Py/Java /Js/Go)题解

阿里算法岗 0523笔试 第三题

题目内容

小红定义一个仅由字符 '000' 和 '111' 组成的字符串(简称 010101 串)的权值为:

  • 仅由 '111' 构成的非空连续子串的数量。
    现给定一长为 nnn 的 010101 串,小红会对其进行 qqq 次操作,每次操作选取 010101 串的一位将其反置(若原字符为 '111',反置后为 '000';若原字符为 '000',反置后为 '111')。
    你需要计算每次操作后 010101 串的权值是多少。
    【名词解释】 连续子串:从原字符串中,连续的选择一段字符(可以全选、可以不选)得到的新字符串。

输入描述

第一行输入两个整数 n,q (1≤n≤2×105,1≤q≤2×105)n,q\ (1 \le n \le 2 \times 10^5, 1 \le q \le 2 \times 10^5)n,q (1≤n≤2×105,1≤q≤2×105)。

第二行输入一个长为 nnn 的 010101 串。

之后 qqq 行,每行输入一个整数 k (1≤k≤n)k\ (1 \le k \le n)k (1≤k≤n) 代表一次操作,将 010101 串的第 kkk 位反置。

输出描述

输出 qqq 行,每行包含一个整数,代表此次操作后 010101 串的权值。

样例1

输入

复制代码
5 2
10010
2
3

输出

复制代码
4
10

题解

思路

逻辑分析

  1. 对于长度为len的连续1子串,可以贡献的权重为len * (len + 1) / 2
  2. 初始可以预处理每个位置所处连续子串的长度,至于1 0反转本质上是1连续子串的中断和连接。利用1的特性是可以计算出中断或连接之后增加/减少的权重。
  3. 按照2逻辑计算即可。

C++

cpp 复制代码
#include<bits/stdc++.h>
using namespace std;

int main() {
    ios_base::sync_with_stdio(false);
    cin.tie(nullptr);
    
    int n,q;
    cin >> n >> q;
    string s;
    cin >> s;
    
    // i所处连续1数量
    vector<int> oneLen(n, 0);
    long weight = 0;
    
    // 计算初始权值
    for (int i = 0; i < n;) {
        if (s[i] == '1') {
            int j = i;
            while (j < n && s[j] == '1') {
                j++;
            }
            // 连续1的长度
            int oneLength = j - i;
            weight += (long)oneLength * (oneLength + 1) /2;
            for (int k = i; k < j; k++) {
                oneLen[k] = oneLength;
            }
            i = j;
        } else {
            i++;
        }
    }
    
    while (q--) {
        int pos;
        cin >> pos;
        // 转换为0-base
        pos -= 1;
        int leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
        int rightOne = (pos < n - 1) ? oneLen[pos+1] : 0;        
        if (s[pos] == '0') {
            // 只会新增一个1子串
            if (leftOne == 0 &&  rightOne == 0) {
                weight += 1;
                oneLen[pos] = 1;
            } else if (leftOne > 0 && rightOne > 0) {
                // 先减去左右贡献再加上 连接后的 贡献
                weight -= ((long) leftOne * (leftOne + 1) / 2 + (long) rightOne * (rightOne + 1) / 2);
                int newOneLen = leftOne + rightOne + 1;
                weight += (long)newOneLen * (newOneLen + 1) / 2;
                for (int i = pos - leftOne; i <= pos + rightOne; i++) {
                    oneLen[i] = newOneLen;
                }
            // 只有单边存在1    
            } else {
                int oldOneLen = max(leftOne, rightOne);
                int newOneLen = oldOneLen + 1;
                weight += newOneLen;
                if (leftOne > 0) {
                    for (int i = pos - leftOne; i <= pos; i++) {
                        oneLen[i] = newOneLen;
                    }
                } else {
                    for (int i = pos; i <= pos + rightOne; i++) {
                        oneLen[i] = newOneLen;
                    }
                }
            }
            s[pos] = '1';
        } else {
            if (leftOne == 0 && rightOne == 0) {
                weight -= 1;
                oneLen[pos] = 0;
            // 中断连续1
            } else if (leftOne > 0 && rightOne > 0) {
                weight -= (long)leftOne * (leftOne + 1) / 2;
                // 左侧1终止 右侧终止1位置
                int leftPos = pos, rightPos = pos;
                while (leftPos - 1 > 0 && s[leftPos - 1] == '1') {
                    leftPos--;
                }
                while (rightPos + 1 < n && s[rightPos + 1] =='1') {
                    rightPos++;
                }
                int newRighOneLen = rightPos - pos;
                int newLeftOneLen =  pos - leftPos;
                weight += (long)newLeftOneLen * (newLeftOneLen + 1) / 2 + (long)newRighOneLen * (newRighOneLen + 1) / 2;
                for(int i = leftPos; i< pos; i++) {
                     oneLen[i] = newLeftOneLen;
                }
                for (int i = pos + 1; i <= rightPos; i++) {
                    oneLen[i] = newRighOneLen;
                }
                oneLen[pos] = 0;
            // 位于连续1的首或者尾    
            } else {
                weight -= max(leftOne, rightOne);
                int newOneLen = max(leftOne, rightOne) - 1;
                if (leftOne > 1) {
                    for (int i = pos - newOneLen; i < pos; i++) {
                        oneLen[i] = newOneLen;
                    }
                } else {
                    for (int i = pos + 1; i <= pos + newOneLen; i++) {
                        oneLen[i] = newOneLen;
                    }
                }
                oneLen[pos] = 0;
            }
            s[pos] = '0'; 
        }
        cout << weight << endl;
    }
    return 0;
}

java

java 复制代码
import java.io.*;
import java.util.*;

public class Main {

    public static void main(String[] args) throws Exception {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

        String[] first = br.readLine().split(" ");
        int n = Integer.parseInt(first[0]);
        int q = Integer.parseInt(first[1]);

        StringBuilder s = new StringBuilder(br.readLine());

        // i所处连续1数量
        int[] oneLen = new int[n];
        long weight = 0;

        // 计算初始权值
        for (int i = 0; i < n; ) {
            if (s.charAt(i) == '1') {
                int j = i;
                while (j < n && s.charAt(j) == '1') {
                    j++;
                }

                // 连续1的长度
                int oneLength = j - i;
                weight += (long) oneLength * (oneLength + 1) / 2;

                for (int k = i; k < j; k++) {
                    oneLen[k] = oneLength;
                }

                i = j;
            } else {
                i++;
            }
        }

        StringBuilder ans = new StringBuilder();

        while (q-- > 0) {
            int pos = Integer.parseInt(br.readLine());

            // 转换为0-base
            pos--;

            int leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
            int rightOne = (pos < n - 1) ? oneLen[pos + 1] : 0;

            if (s.charAt(pos) == '0') {

                // 只会新增一个1子串
                if (leftOne == 0 && rightOne == 0) {
                    weight += 1;
                    oneLen[pos] = 1;

                } else if (leftOne > 0 && rightOne > 0) {

                    // 先减去左右贡献再加上 连接后的 贡献
                    weight -= (long) leftOne * (leftOne + 1) / 2;
                    weight -= (long) rightOne * (rightOne + 1) / 2;

                    int newOneLen = leftOne + rightOne + 1;
                    weight += (long) newOneLen * (newOneLen + 1) / 2;

                    for (int i = pos - leftOne; i <= pos + rightOne; i++) {
                        oneLen[i] = newOneLen;
                    }

                    // 只有单边存在1
                } else {

                    int oldOneLen = Math.max(leftOne, rightOne);
                    int newOneLen = oldOneLen + 1;

                    weight += newOneLen;

                    if (leftOne > 0) {
                        for (int i = pos - leftOne; i <= pos; i++) {
                            oneLen[i] = newOneLen;
                        }
                    } else {
                        for (int i = pos; i <= pos + rightOne; i++) {
                            oneLen[i] = newOneLen;
                        }
                    }
                }

                s.setCharAt(pos, '1');

            } else {

                if (leftOne == 0 && rightOne == 0) {

                    weight -= 1;
                    oneLen[pos] = 0;

                    // 中断连续1
                } else if (leftOne > 0 && rightOne > 0) {

                    weight -= (long) oneLen[pos] * (oneLen[pos] + 1) / 2;

                    // 左侧1终止 右侧终止1位置
                    int leftPos = pos;
                    int rightPos = pos;

                    while (leftPos - 1 >= 0 && s.charAt(leftPos - 1) == '1') {
                        leftPos--;
                    }

                    while (rightPos + 1 < n && s.charAt(rightPos + 1) == '1') {
                        rightPos++;
                    }

                    int newRightOneLen = rightPos - pos;
                    int newLeftOneLen = pos - leftPos;

                    weight += (long) newLeftOneLen * (newLeftOneLen + 1) / 2;
                    weight += (long) newRightOneLen * (newRightOneLen + 1) / 2;

                    for (int i = leftPos; i < pos; i++) {
                        oneLen[i] = newLeftOneLen;
                    }

                    for (int i = pos + 1; i <= rightPos; i++) {
                        oneLen[i] = newRightOneLen;
                    }

                    oneLen[pos] = 0;

                    // 位于连续1的首或者尾
                } else {

                    weight -= Math.max(leftOne, rightOne);

                    int newOneLen = Math.max(leftOne, rightOne) - 1;

                    if (leftOne > 1) {
                        for (int i = pos - newOneLen; i < pos; i++) {
                            oneLen[i] = newOneLen;
                        }
                    } else {
                        for (int i = pos + 1; i <= pos + newOneLen; i++) {
                            oneLen[i] = newOneLen;
                        }
                    }

                    oneLen[pos] = 0;
                }

                s.setCharAt(pos, '0');
            }

            ans.append(weight).append('\n');
        }

        System.out.print(ans);
    }
}

python

python 复制代码
n, q = map(int, input().split())
s = list(input())

# i所处连续1数量
one_len = [0] * n
weight = 0

# 计算初始权值
i = 0
while i < n:
    if s[i] == '1':
        j = i
        while j < n and s[j] == '1':
            j += 1

        # 连续1的长度
        one_length = j - i
        weight += one_length * (one_length + 1) // 2

        for k in range(i, j):
            one_len[k] = one_length

        i = j
    else:
        i += 1

for _ in range(q):

    pos = int(input())

    # 转换为0-base
    pos -= 1

    left_one = one_len[pos - 1] if pos > 0 else 0
    right_one = one_len[pos + 1] if pos < n - 1 else 0

    if s[pos] == '0':

        # 只会新增一个1子串
        if left_one == 0 and right_one == 0:
            weight += 1
            one_len[pos] = 1

        elif left_one > 0 and right_one > 0:

            # 先减去左右贡献再加上 连接后的 贡献
            weight -= left_one * (left_one + 1) // 2
            weight -= right_one * (right_one + 1) // 2

            new_one_len = left_one + right_one + 1
            weight += new_one_len * (new_one_len + 1) // 2

            for i in range(pos - left_one, pos + right_one + 1):
                one_len[i] = new_one_len

        # 只有单边存在1
        else:

            old_one_len = max(left_one, right_one)
            new_one_len = old_one_len + 1

            weight += new_one_len

            if left_one > 0:
                for i in range(pos - left_one, pos + 1):
                    one_len[i] = new_one_len
            else:
                for i in range(pos, pos + right_one + 1):
                    one_len[i] = new_one_len

        s[pos] = '1'

    else:

        if left_one == 0 and right_one == 0:
            weight -= 1
            one_len[pos] = 0

        # 中断连续1
        elif left_one > 0 and right_one > 0:

            weight -= one_len[pos] * (one_len[pos] + 1) // 2

            # 左侧1终止 右侧终止1位置
            left_pos = pos
            right_pos = pos

            while left_pos - 1 >= 0 and s[left_pos - 1] == '1':
                left_pos -= 1

            while right_pos + 1 < n and s[right_pos + 1] == '1':
                right_pos += 1

            new_right_one_len = right_pos - pos
            new_left_one_len = pos - left_pos

            weight += new_left_one_len * (new_left_one_len + 1) // 2
            weight += new_right_one_len * (new_right_one_len + 1) // 2

            for i in range(left_pos, pos):
                one_len[i] = new_left_one_len

            for i in range(pos + 1, right_pos + 1):
                one_len[i] = new_right_one_len

            one_len[pos] = 0

        # 位于连续1的首或者尾
        else:

            weight -= max(left_one, right_one)

            new_one_len = max(left_one, right_one) - 1

            if left_one > 1:
                for i in range(pos - new_one_len, pos):
                    one_len[i] = new_one_len
            else:
                for i in range(pos + 1, pos + new_one_len + 1):
                    one_len[i] = new_one_len

            one_len[pos] = 0

        s[pos] = '0'

    print(weight)

javascript

js 复制代码
const readline = require('readline');

const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout
});

const input = [];

rl.on('line', line => {
    input.push(line);
});

rl.on('close', () => {
    let idx = 0;

    const [n, q] = input[idx++].split(" ").map(Number);
    const s = input[idx++].split("");

    // i所处连续1数量
    const oneLen = new Array(n).fill(0);
    let weight = 0;

    // 计算初始权值
    for (let i = 0; i < n;) {
        if (s[i] === '1') {
            let j = i;
            while (j < n && s[j] === '1') {
                j++;
            }

            // 连续1的长度
            const oneLength = j - i;
            weight += oneLength * (oneLength + 1) / 2;

            for (let k = i; k < j; k++) {
                oneLen[k] = oneLength;
            }

            i = j;
        } else {
            i++;
        }
    }

    const ans = [];

    for (let t = 0; t < q; t++) {
        let pos = Number(input[idx++]);

        // 转换为0-base
        pos--;

        const leftOne = (pos > 0) ? oneLen[pos - 1] : 0;
        const rightOne = (pos < n - 1) ? oneLen[pos + 1] : 0;

        if (s[pos] === '0') {

            // 只会新增一个1子串
            if (leftOne === 0 && rightOne === 0) {
                weight += 1;
                oneLen[pos] = 1;

            } else if (leftOne > 0 && rightOne > 0) {

                // 先减去左右贡献再加上 连接后的 贡献
                weight -= leftOne * (leftOne + 1) / 2;
                weight -= rightOne * (rightOne + 1) / 2;

                const newOneLen = leftOne + rightOne + 1;
                weight += newOneLen * (newOneLen + 1) / 2;

                for (let i = pos - leftOne; i <= pos + rightOne; i++) {
                    oneLen[i] = newOneLen;
                }

                // 只有单边存在1
            } else {

                const oldOneLen = Math.max(leftOne, rightOne);
                const newOneLen = oldOneLen + 1;

                weight += newOneLen;

                if (leftOne > 0) {
                    for (let i = pos - leftOne; i <= pos; i++) {
                        oneLen[i] = newOneLen;
                    }
                } else {
                    for (let i = pos; i <= pos + rightOne; i++) {
                        oneLen[i] = newOneLen;
                    }
                }
            }

            s[pos] = '1';

        } else {

            if (leftOne === 0 && rightOne === 0) {

                weight -= 1;
                oneLen[pos] = 0;

                // 中断连续1
            } else if (leftOne > 0 && rightOne > 0) {

                weight -= oneLen[pos] * (oneLen[pos] + 1) / 2;

                // 左侧1终止 右侧终止1位置
                let leftPos = pos;
                let rightPos = pos;

                while (leftPos - 1 >= 0 && s[leftPos - 1] === '1') {
                    leftPos--;
                }

                while (rightPos + 1 < n && s[rightPos + 1] === '1') {
                    rightPos++;
                }

                const newRightOneLen = rightPos - pos;
                const newLeftOneLen = pos - leftPos;

                weight += newLeftOneLen * (newLeftOneLen + 1) / 2;
                weight += newRightOneLen * (newRightOneLen + 1) / 2;

                for (let i = leftPos; i < pos; i++) {
                    oneLen[i] = newLeftOneLen;
                }

                for (let i = pos + 1; i <= rightPos; i++) {
                    oneLen[i] = newRightOneLen;
                }

                oneLen[pos] = 0;

                // 位于连续1的首或者尾
            } else {

                weight -= Math.max(leftOne, rightOne);

                const newOneLen = Math.max(leftOne, rightOne) - 1;

                if (leftOne > 1) {
                    for (let i = pos - newOneLen; i < pos; i++) {
                        oneLen[i] = newOneLen;
                    }
                } else {
                    for (let i = pos + 1; i <= pos + newOneLen; i++) {
                        oneLen[i] = newOneLen;
                    }
                }

                oneLen[pos] = 0;
            }

            s[pos] = '0';
        }

        ans.push(weight);
    }

    console.log(ans.join("\n"));
});

Go

go 复制代码
package main

import (
	"bufio"
	"fmt"
	"os"
)

func main() {
	in := bufio.NewReader(os.Stdin)

	var n, q int
	fmt.Fscan(in, &n, &q)

	var s string
	fmt.Fscan(in, &s)

	str := []byte(s)

	// i所处连续1数量
	oneLen := make([]int, n)
	var weight int64 = 0

	// 计算初始权值
	for i := 0; i < n; {
		if str[i] == '1' {
			j := i
			for j < n && str[j] == '1' {
				j++
			}

			// 连续1的长度
			oneLength := j - i
			weight += int64(oneLength*(oneLength+1)) / 2

			for k := i; k < j; k++ {
				oneLen[k] = oneLength
			}

			i = j
		} else {
			i++
		}
	}

	for ; q > 0; q-- {

		var pos int
		fmt.Fscan(in, &pos)

		// 转换为0-base
		pos--

		leftOne := 0
		if pos > 0 {
			leftOne = oneLen[pos-1]
		}

		rightOne := 0
		if pos < n-1 {
			rightOne = oneLen[pos+1]
		}

		if str[pos] == '0' {

			// 只会新增一个1子串
			if leftOne == 0 && rightOne == 0 {

				weight++
				oneLen[pos] = 1

			} else if leftOne > 0 && rightOne > 0 {

				// 先减去左右贡献再加上 连接后的 贡献
				weight -= int64(leftOne*(leftOne+1))/2 + int64(rightOne*(rightOne+1))/2

				newOneLen := leftOne + rightOne + 1

				weight += int64(newOneLen*(newOneLen+1)) / 2

				for i := pos - leftOne; i <= pos+rightOne; i++ {
					oneLen[i] = newOneLen
				}

				// 只有单边存在1
			} else {

				oldOneLen := leftOne
				if rightOne > oldOneLen {
					oldOneLen = rightOne
				}

				newOneLen := oldOneLen + 1

				weight += int64(newOneLen)

				if leftOne > 0 {
					for i := pos - leftOne; i <= pos; i++ {
						oneLen[i] = newOneLen
					}
				} else {
					for i := pos; i <= pos+rightOne; i++ {
						oneLen[i] = newOneLen
					}
				}
			}

			str[pos] = '1'

		} else {

			if leftOne == 0 && rightOne == 0 {

				weight--
				oneLen[pos] = 0

				// 中断连续1
			} else if leftOne > 0 && rightOne > 0 {

				weight -= int64(oneLen[pos]*(oneLen[pos]+1)) / 2

				// 左侧1终止 右侧终止1位置
				leftPos := pos
				rightPos := pos

				for leftPos-1 >= 0 && str[leftPos-1] == '1' {
					leftPos--
				}

				for rightPos+1 < n && str[rightPos+1] == '1' {
					rightPos++
				}

				newRightOneLen := rightPos - pos
				newLeftOneLen := pos - leftPos

				weight += int64(newLeftOneLen*(newLeftOneLen+1))/2 +
					int64(newRightOneLen*(newRightOneLen+1))/2

				for i := leftPos; i < pos; i++ {
					oneLen[i] = newLeftOneLen
				}

				for i := pos + 1; i <= rightPos; i++ {
					oneLen[i] = newRightOneLen
				}

				oneLen[pos] = 0

				// 位于连续1的首或者尾
			} else {

				old := leftOne
				if rightOne > old {
					old = rightOne
				}

				weight -= int64(old)

				newOneLen := old - 1

				if leftOne > 1 {
					for i := pos - newOneLen; i < pos; i++ {
						oneLen[i] = newOneLen
					}
				} else {
					for i := pos + 1; i <= pos+newOneLen; i++ {
						oneLen[i] = newOneLen
					}
				}

				oneLen[pos] = 0
			}

			str[pos] = '0'
		}

		fmt.Println(weight)
	}
}