为了更好的阅读体检,为了更好的阅读体检,,可以查看我的算法学习博客第四题-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); });