【备战秋招】每日一题:2023.03.21-阿里OD(第二题)-最大化01串中1的个数

为了更好的阅读体检,可以查看我的算法学习博客
在线评测链接:P1101

题目内容

曾经有一个小镇,镇上的居民都信奉一位神秘的数学家。这位数学家声名远扬,因为他曾经提出了一个关于二进制串的问题,而这个问题一直困扰着小镇上的居民。问题如下:

有一串由 0 0 0 1 1 1组成的字符串,现在可以进行若干次如下操作:选择两个相邻的字符,将它们同时取反。例如,可以将 00 00 00变成 11 11 11,也可以将 10 10 10变成 01 01 01。请你求出最大化 1 1 1字符数量的最小操作次数。

输入描述

一个长度不超过 200000 200000 200000的、仅由’ 1 1 1’和’ 0 0 0组成的字符串。

输出描述

一个整数,代表最小的操作次数。

样例

输入

010

输出

2
样例2

输入

111

输出

0

说明
无论怎么操作, 1 1 1的数量最大值也只能是 3 3 3,因此无需操作。

思路

贪心

观察

我们发现每次翻转只会使得 01 01 01 1 1 1的个数变化偶数个。所以如果 0 0 0的个数是奇数,那么 0 0 0最后必定剩下 1 1 1个。如果是偶数个,那么 0 0 0可以全部变为 1 1 1.

做法

对于偶数个 0 0 0.我们发现从第一个 0 0 0开始,不断让相邻的 0 0 0合并是最优解。

对于奇数个 0 0 0,我们发现需要空出 1 1 1 0 0 0不操作。那么我们可以通过这个 0 0 0将整个字符串分为 s t r 1 str1 str1+‘ 0 0 0’+ s t r 2 str2 str2.其中 s t r 1 str1 str1 s t r 2 str2 str2的0的数量都要保证是偶数个。这些要求满足后,我们可以求出 s t r 1 str1 str1 s t r 2 str2 str2的操作次数,然后 01 01 01串的操作次数就是这两个操作次数的和。

类似题目推荐

还是一道贪心题.

LeetCode

LeetCode上的贪心题,代码随想录总结的非常好了,见 贪心 - 代码随想录

CodeFun2000

P1075 拼多多-2023.3.12-飞机大战

P1176 2023.04.08-华为od-第三题-最多等和不相交连续子序列

P1211 塔子大厂真题模拟赛-第一题-魔法石(Ⅰ)

P1024 百度 2022.9.13-01反转

P1070 2023.3.7-百度-第一题-最小化k序列平均值和

2023 美团贪心专栏

P1137 美团 2023.04.01-第一题-整理

P1077 美团 2023.3.11-第一题-字符串修改

P1235. 美团 2023.04.15-实习-第一题-字符串前缀

P1089 美团 2023.3.18.10点-第三题-塔子哥的回文串

代码

CPP

#include<bits/stdc++.h>
using namespace std;
int zeroPos[200005];
int lastSum[200005];
int cnt = 0;
int main(){
    string str;
    cin >> str;
    for(int i = 0; i < str.length(); i++){
        if(str[i] == '0')zeroPos[++cnt] = i;//找到0的位置
    }
    if(cnt % 2 == 0){//如果是偶数
        int sum = 0;
        for(int i = 1; i <= cnt; i+=2){//直接合并相邻的
            sum += zeroPos[i + 1] - zeroPos[i];
        }
        cout << sum << endl;
    }else{
        lastSum[cnt + 1] = 0;
        for(int i = cnt - 1; i >= 1; i-=2){//后缀和来维护str2的操作次数
            lastSum[i] = lastSum[i + 2] + zeroPos[i + 1] - zeroPos[i];
        }
        int sum = (int)1e9;//答案,初始化最大值
        int pre = 0;//前缀和
        for(int i = 1; i <= cnt; i+=2){
            sum = min(sum, pre + lastSum[i + 1]);//求出最小的str1+str2
            pre += zeroPos[i + 1] - zeroPos[i];
        }
        cout << sum << endl;//输出
    }
}

python

s = input() # 输入字符串s
n = len(s) # 获取字符串长度n
cnt = 0 # 统计0的个数的计数器初始化为0
zeroPos = [0] * (n + 5) # 用于存储0的位置,初始化数组大小比字符串长度多5
lastSum = [0] * (n + 5) # 初始化数组大小比字符串长度多5,用于计算后缀和
for i in range(n):
    if s[i] == '0': # 找到值为0的位置
        cnt += 1
        zeroPos[cnt] = i
if cnt % 2 == 0: # 如果0的个数是偶数
    s = 0 
    for i in range(1, cnt+1, 2): # 直接将相邻的0合并
        s += zeroPos[i + 1] - zeroPos[i]
    print(s) # 输出操作次数
else:
    lastSum[cnt + 1] = 0 # 后缀和的初始值为0
    for i in range(cnt - 1, 0, -2): # 倒序遍历0的位置
        lastSum[i] = lastSum[i + 2] + zeroPos[i + 1] - zeroPos[i] # 使用后缀和来维护字符串str2的操作次数
    s = 1000000000 # 将s初始化为一个大数
    p = 0 # 前缀和初始值为0
    for i in range(1, cnt + 1, 2): # 遍历每一组相邻的0
        s = min(s, p + lastSum[i + 1]) # 取上一步更新的s和当前str1+str2操作次数的最小值
        p += zeroPos[i + 1] - zeroPos[i] # 更新前缀和
    print(s) # 输出最小操作次数

Java

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String s = sc.next();
        int n = s.length();
        int cnt = 0; // 统计0的个数的计数器初始化为0
        int[] zeroPos = new int[n + 5]; // 用于存储0的位置,初始化数组大小比字符串长度多5
        int[] lastSum = new int[n + 5]; // 初始化数组大小比字符串长度多5,用于计算后缀和

        for (int i = 0; i < n; i++) {
            if (s.charAt(i) == '0') { // 找到值为0的位置
                cnt++;
                zeroPos[cnt] = i;
            }
        }

        int res = 0; // 定义返回结果
        if (cnt % 2 == 0) { // 如果0的个数是偶数
            for (int i = 1; i <= cnt; i += 2) { // 直接将相邻的0合并
                res += zeroPos[i + 1] - zeroPos[i];
            }
        } else { // 如果0的个数是奇数
            lastSum[cnt + 1] = 0; // 后缀和的初始值为0
            for (int i = cnt - 1; i >= 1; i -= 2) { // 倒序遍历0的位置
                lastSum[i] = lastSum[i + 2] + zeroPos[i + 1] - zeroPos[i]; // 使用后缀和来维护字符串str2的操作次数
            }

            int minOp = Integer.MAX_VALUE; // 将s初始化为一个大数
            int prefixSum = 0; // 前缀和初始值为0
            for (int i = 1; i <= cnt; i += 2) { // 遍历每一组相邻的0
                minOp = Math.min(minOp, prefixSum + lastSum[i + 1]); // 取上一步更新的s和当前str1+str2操作次数的最小值
                prefixSum += zeroPos[i + 1] - zeroPos[i]; // 更新前缀和
            }
            res = minOp; // 更新返回结果
        }
        System.out.println(res); // 输出最小操作次数
    }
}

Go

package main

import "fmt"

func main() {
	var s string
	fmt.Scan(&s)        // 输入字符串s
	n := len(s)         // 获取字符串长度n
	cnt := 0            // 统计0的个数的计数器初始化为0
	zeroPos := make([]int, n+5) // 用于存储0的位置,初始化数组大小比字符串长度多5
	lastSum := make([]int, n+5) // 初始化数组大小比字符串长度多5,用于计算后缀和
	for i := 0; i < n; i++ {
		if s[i] == '0' { // 找到值为0的位置
			cnt++
			zeroPos[cnt] = i
		}
	}
	if cnt % 2 == 0 { // 如果0的个数是偶数
		s := 0
		for i := 1; i < cnt+1; i += 2 { // 直接将相邻的0合并
			s += zeroPos[i + 1] - zeroPos[i]
		}
		fmt.Println(s) // 输出操作次数
	} else {
		lastSum[cnt + 1] = 0 // 后缀和的初始值为0
		for i := cnt - 1; i > 0; i -= 2 { // 倒序遍历0的位置
			lastSum[i] = lastSum[i + 2] + zeroPos[i + 1] - zeroPos[i] // 使用后缀和来维护字符串str2的操作次数
		}
		s := 1000000000 // 将s初始化为一个大数
		p := 0 // 前缀和初始值为0
		for i := 1; i < cnt+1; i += 2 { // 遍历每一组相邻的0
			s = min(s, p + lastSum[i + 1]) // 取上一步更新的s和当前str1+str2操作次数的最小值
			p += zeroPos[i + 1] - zeroPos[i] // 更新前缀和
		}
		fmt.Println(s) // 输出最小操作次数
	}
}

// 求最小值函数
func min(a, b int) int {
	if a < b {
		return a
	}
	return b
}

Js

let input = ''; // 初始化输入字符串
process.stdin.resume(); // 恢复标准输入流
process.stdin.setEncoding('utf-8'); // 设置输入流编码

process.stdin.on('data', (data) => { // 当有输入时将其保存在input中
  input += data;
});

process.stdin.on('end', () => { // 当结束输入后执行下面代码
  const lines = input.trim().split('\n'); // 按行分割输入进行处理
  const s = lines[0]; // 获取字符串s
  const n = s.length; // 获取字符串长度n
  let cnt = 0; // 统计0的个数的计数器初始化为0
  let zeroPos = new Array(n + 5).fill(0); // 用于存储0的位置,初始化数组大小比字符串长度多5
  let lastSum = new Array(n + 5).fill(0); // 初始化数组大小比字符串长度多5,用于计算后缀和

  for(let i=0; i<n; i++){ // 找到值为0的位置
    if(s[i] == '0'){ 
      cnt += 1; // 记录0的个数加1
      zeroPos[cnt] = i; // 记录第cnt个0的位置
    }
  }

  if(cnt % 2 == 0){ // 如果0的个数是偶数
    let ans = 0; // 初始化操作次数为0
    for(let i=1; i<=cnt; i+=2){ // 直接将相邻的0合并
      ans += zeroPos[i+1] - zeroPos[i]; // 计算操作次数
    }
    console.log(ans); // 输出操作次数
  }
  else{
    lastSum[cnt+1] = 0; // 后缀和的初始值为0
    for(let i=cnt-1; i>0; i-=2){ // 倒序遍历0的位置
      lastSum[i] = lastSum[i+2] + zeroPos[i+1] - zeroPos[i]; // 使用后缀和来维护字符串str2的操作次数
    }
    let ans = 1000000000; // 将s初始化为一个大数
    let p = 0; // 前缀和初始值为0
    for(let i=1; i<=cnt; i+=2){ // 遍历每一组相邻的0
      ans = Math.min(ans, p + lastSum[i+1]); // 取上一步更新的s和当前str1+str2操作次数的最小值
      p += zeroPos[i+1] - zeroPos[i]; // 更新前缀和
    }
    console.log(ans); // 输出最小操作次数
  }
});

题目内容均收集自互联网,如如若此项内容侵犯了原著者的合法权益,可联系我: (CSDN网站注册用户名: 塔子哥学算法) 进行删除。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

塔子哥学算法

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值