为了更好的阅读体检,为了更好的阅读体检,,可以查看我的算法学习博客第四题-01串的代价
在线评测链接:P1248
题目内容
塔子哥是一个喜欢研究密码的人,他经常在网上寻找各种有趣的密码挑战。他最近发现了一个神秘的网站,网站上只有一个输入框和一个提交按钮,没有任何提示。塔子哥好奇地输入了一些内容,发现网站会返回一个长度为 n 的 01 串,只包含字符 0 和 1 的串。塔子哥觉得这个串一定是一个密码的线索,他想要破解它。
塔子哥仔细观察了这个串,发现它里面隐藏着一些规律,比如有些位置的字符总是相同的,有些位置的字符总是相反的。塔子哥猜测这些规律可能是密码的组成部分,他想要把它们都保留下来,并且删除掉无关的字符。但是,塔子哥不想删除太多的字符,也不想保留太长的串。所以,他想知道,在保证删除一个前缀和一个后缀的情况下(前缀和后缀都可以为空),即保留一个原串的连续子串(可以为空),他需要最小化以下代价:
-
被删除的字符
1的个数; -
剩下的子串的字符
0的个数。
塔子哥会给你总共若干次询问,每次询问他会告诉你网站返回给他的 01 串。你需要帮助塔子哥判断,在每次询问中,他需要输出的最小代价是多少。
输入描述
一个长度为的 01 字符串。
输出描述
一个整数,表示最小的操作代价。
样例1
输入
101110110
输出
2
样例解释
删除前两个字符和最后一个字符,代价为 1 ;
保留下来的字符中有一个 0 ,代价为 1 ,故代价之和为 2 。
样例2
输入
1010110001
输出
3
思路
思维 + 贪心
由于可以删除一个前缀和一个后缀,故可以这么考虑: 枚举分界点 i,[1,i] 中删除一个前缀,[i+1,n] 中删除一个后缀。取两部分代价之和的最小值。
具体删除的步骤:
-
如果遇到一个 1 ,则不删除,记录下来。
-
如果遇到一个 0 ,对于删除到这个 0 的代价,和保留这个 0 的代价,两者取 min ,对于保留这个 0 的代价,就是 +1 ,对于删除到这个 0 的代价,就是将目前所有存储的 1 都删除。
时间复杂度:O(n)
类似题目推荐
LeetCode
LeetCode上的贪心题,代码随想录总结的非常好了,见 贪心 - 代码随想录
Codefun2000
-
P1091. 米哈游 2023.03.19-第一题-交换字符
-
P1235. 百度 2023.04.15-实习-第一题-字符串前缀
-
P1005. 腾讯 2022.10.16-汽车
-
P1137 美团 2023.04.01-第一题-整理
-
P1077 美团 2023.3.11-第一题-字符串修改
-
P1024 百度 2022.9.13-01反转
-
P1089 美团 2023.3.18.10点-第三题-塔子哥的回文串
代码
CPP
#include <bits/stdc++.h>
using namespace std;
int main()
{
string s;
cin >> s;
int n = s.size();
vector<int> suf(n + 1);
suf[n] = 0;
int one = 0, val = 0;
// 预处理出 suf[i] ,suf[i] 表示 [i, n - 1] 这个串删除后缀后的最小代价
for (int i = n - 1; i >= 0; --i) {
// 如果是 0
if (s[i] == '0') {
if (one > 0) {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
one -= 1;
val += 1;
}
// else {
// 如果不存在 1,这个 0 可以直接删
// }
} else {
// 如果遇到的是 1,则先不删除,保留下来,和之后遇到的 0 相抵
one += 1;
}
suf[i] = val;
}
one = val = 0;
int ans = suf[0];
// 枚举 i 为分界点
// 1. 在 [0, i] 这个串删除前缀的最小值,动态维护
// 2. 在 [i+1, n-1] 这个串删除后缀的最小值suf[i+1],已经预处理
for (int i = 0; i < n; ++i) {
if (s[i] == '0') {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
if (one > 0) {
one -= 1;
val += 1;
}
} else {
one += 1;
}
ans = min(ans, suf[i + 1] + val);
}
cout << ans << "\n";
return 0;
}
python
s = input() n = len(s) suf = [0] * (n + 1) one, val = 0, 0 # 预处理出 suf[i] ,suf[i] 表示 [i, n - 1] 这个串删除后缀后的最小代价 for i in range(n - 1, -1, -1): # 如果是 0 if s[i] == '0': if one > 0: # 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量, # 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。 one -= 1 val += 1 # else: # 如果不存在 1,这个 0 可以直接删 else: # 如果遇到的是 1,则先不删除,保留下来,和之后遇到的 0 相抵 one += 1 suf[i] = val one, val = 0, 0 ans = suf[0] # 枚举 i 为分界点 # 1. 在 [0, i] 这个串删除前缀的最小值,动态维护 # 2. 在 [i+1, n-1] 这个串删除后缀的最小值suf[i+1],已经预处理 for i in range(n): if s[i] == '0': # 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量, # 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。 if one > 0: one -= 1 val += 1 else: one += 1 ans = min(ans, suf[i + 1] + val) print(ans)
Java
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
String s = scanner.next();
int n = s.length();
int[] suf = new int[n + 1];
suf[n] = 0;
int one = 0, val = 0;
// 预处理出 suf[i] ,suf[i] 表示 [i, n - 1] 这个串删除后缀后的最小代价
for (int i = n - 1; i >= 0; --i) {
// 如果是 0
if (s.charAt(i) == '0') {
if (one > 0) {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
one -= 1;
val += 1;
}
// else {
// 如果不存在 1,这个 0 可以直接删
// }
} else {
// 如果遇到的是 1,则先不删除,保留下来,和之后遇到的 0 相抵
one += 1;
}
suf[i] = val;
}
one = val = 0;
int ans = suf[0];
// 枚举 i 为分界点
// 1. 在 [0, i] 这个串删除前缀的最小值,动态维护
// 2. 在 [i+1, n-1] 这个串删除后缀的最小值suf[i+1],已经预处理
for (int i = 0; i < n; ++i) {
if (s.charAt(i) == '0') {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
if (one > 0) {
one -= 1;
val += 1;
}
} else {
one += 1;
}
ans = Math.min(ans, suf[i + 1] + val);
}
System.out.println(ans);
}
}
Go
package main
import "fmt"
func main() {
var s string
fmt.Scanln(&s)
n := len(s)
suf := make([]int, n+1)
suf[n] = 0
one := 0
val := 0
// 预处理出 suf[i] ,suf[i] 表示 [i, n - 1] 这个串删除后缀后的最小代价
for i := n - 1; i >= 0; i-- {
if s[i] == '0' {
// 如果是 0
if one > 0 {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
one -= 1
val += 1
}
// else {
// 如果不存在 1,这个 0 可以直接删
// }
} else {
// 如果遇到的是 1,则先不删除,保留下来,和之后遇到的 0 相抵
one += 1
}
suf[i] = val
}
one = 0
val = 0
ans := suf[0]
// 枚举 i 为分界点
// 1. 在 [0, i] 这个串删除前缀的最小值,动态维护
// 2. 在 [i+1, n-1] 这个串删除后缀的最小值suf[i+1],已经预处理
for i := 0; i < n; i++ {
if s[i] == '0' {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
if one > 0 {
one -= 1
val += 1
}
} else {
one += 1
}
ans = min(ans, val+suf[i+1])
}
fmt.Println(ans)
}
func min(x, y int) int {
if x < y {
return x
}
return y
}
Js
process.stdin.resume();
process.stdin.setEncoding('utf-8');
let input = '';
process.stdin.on('data', (data) => {
input += data;
return;
});
process.stdin.on('end', () => {
const s = input.trim();
const n = s.length;
const suf = new Array(n + 1);
suf[n] = 0;
let one = 0, val = 0;
// 预处理出 suf[i] ,suf[i] 表示 [i, n - 1] 这个串删除后缀后的最小代价
for (let i = n - 1; i >= 0; --i) {
// 如果是 0
if (s[i] === '0') {
if (one > 0) {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
one -= 1;
val += 1;
}
// else {
// 如果不存在 1,这个 0 可以直接删
// }
} else {
// 如果遇到的是 1,则先不删除,保留下来,和之后遇到的 0 相抵
one += 1;
}
suf[i] = val;
}
one = val = 0;
let ans = suf[0];
// 枚举 i 为分界点
// 1. 在 [0, i] 这个串删除前缀的最小值,动态维护
// 2. 在 [i+1, n-1] 这个串删除后缀的最小值suf[i+1],已经预处理
for (let i = 0; i < n; ++i) {
if (s[i] === '0') {
// 如果存在 1,就要保留这个 0 了,但是要维护一个 one 的数量,
// 如果 one 的数量为 0 后,下次遇到 0 就不如将他们都删了。
if (one > 0) {
one -= 1;
val += 1;
}
} else {
one += 1;
}
ans = Math.min(ans, suf[i + 1] + val);
}
console.log(ans);
});















![[C++刷题之旅]反转链表](https://img-blog.csdnimg.cn/b51033cc0360463f80e1dd2c2e0ba3ef.png)

