为了更好的阅读体检,可以查看我的算法学习博客
在线评测链接: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
P1176 2023.04.08-华为od-第三题-最多等和不相交连续子序列
P1070 2023.3.7-百度-第一题-最小化k序列平均值和
2023 美团贪心专栏
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网站注册用户名: 塔子哥学算法) 进行删除。