题目重现:
思路介绍:
写在前面:
对于本题,我记录了我自己的做法(法一),官方做法(法二),大佬做法(法三),我自己的做法有点新意(qi guai),所以其实可以直接去看后两种...我在这里给出我的做法的正确性说明
法一:
对于本题,我们很容易抽象出来一个简单的模型,s是由A0个a,B0个b,A1个a,B1个b...一直到An个a,Bn个b."aababbab"就是2个a,1个b,1个a,2个b,1个a,1个b,这里我们可以很容易使得a序列的n和b序列的n相等,因为如果是b开始,如示例2,就是0个a,2个b,5个a,2个b;同时我们把这些个Ai,Bi放入vector<int> a_count,b_count中.
刚看见这个题,可能觉得无从下手,但是如果我们抽离出来一个简单的模型之和,反而便于观察,现在再去看这个题,会发现我们需要在A0B0...AnBn里面包含两边界的2n+1个空隙中找一个位置放上分割线 || 使得线前的Bi全部删去,线后的Ai全部删去.
简单思考一下,其实我们并不需要2*n+1个选择,我们其实只有n个选择这n个选择是插在每一组Ai || Bi之中(也就等价与找到一组Ai Bi使得删去的个数最少),我们实际上是要找到一组相邻的aa...abb...b(即AiBi),然后把i前的所有Bi-1,Bi-2...B0删去,i后面的Ai+1,Ai+2...An删去,然后使得删去的总个数最少就可以了,同样的如果我们删去除了A0以外的所有的a,那也就是选择了第一组,而同样的如果我们选择最后一组,就意味着我们要删去除了Bn以外的所有b.(注意:其实A0,Bn不会影响最后的结果)
因为如果我们插在Ai Bi || Ai+1 Bi+1之间,比起插在Ai || Bi 之间其实是多删除了一个Bi,比起插在Ai+1 Bi+1之间是多删除了一个Ai+1,完全没有必要这么做.
代码如下:
int minimumDeletions(string s) {
vector<int> a_count;
vector<int> b_count;
bool flag_a=true;
for(int i=0;i<s.size();){
int temp=0;
if(flag_a){
while(s[i]=='a') {i++; temp++;}
a_count.push_back(temp);
}else{
while(s[i]=='b') {i++; temp++;}
b_count.push_back(temp);
}
flag_a=!flag_a;
}
if(flag_a==false) b_count.push_back(0);//末尾没有b是0个,这个操作保证a_count,b_count个数相等
for(int i=a_count.size()-2;i>=0;i--){
a_count[i] += a_count[i+1];
}//把a_count里面的数换成从末尾走到该分组(包括该分组)中所有的a 可简化运算
for(int i=1;i<b_count.size();i++){
b_count[i] += b_count[i-1];
}//把b_count里面的数换成从开始走到该分组(包括该分组)中所有的b 可简化运算
if(a_count.size()==1) return 0;//防止越界访问
int min=INT_MAX;
for(int i=0;i<a_count.size();i++){
int sum=0;
if(i==0){ sum=a_count[1]; }
else if(i==a_count.size()-1){
sum=b_count[a_count.size()-2];
}else{
sum=b_count[i-1]+a_count[i+1];//其实总的只有这个式子,另外两个if是为了防止越界访问
}
if(sum<min) min=sum;
}
return min;
}
法二:
如果我们抓住每一个单词字母来看,就会发现这个题是对于s字符串的n+1个空隙(n==s.size()),找到一个插入||分隔符,使得要删去的左边的b和右边的a的个数的总和最小,min(leftb+righta)
我们只需要两遍对s的遍历即可,第一遍求出a的总数sum_a作为初始化的值,第二遍遍历s[i],如果s[i]=='a' righta--,如果s[i]=='b' leftb++
代码如下:
int minimumDeletions(string s) {
int leftb = 0, righta = 0;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'a') {
righta++;
}
}
int res = righta;
for (int i = 0; i < s.size(); i++) {
if (s[i] == 'a') {
righta--;
} else {
leftb++;
}
res = min(res, leftb + righta);
}
return res;
}
法三:动态规划
对于这个题,我自己是有想过使用动态规划的,但是不知道从哪里下手这里提供评论区的@Tammon的两种思路:
1.计算可以达到最大的长度len.返回s.size()-len.
最终是所有a在所有b后面,我们使用len_a记录最终以a结尾的最大长度,len_b记录最终以b结尾的最大长度,如果当前的s[i]=='a',len_a++,而如果当前的s[i]=='b',len_b=max(len_a,len_b)+1,加的这个1就是最后的这个('b')
int minimumDeletions(string s) {
int len_a = 0, len_b = 0, n = s.size();
for (char x : s) {
if (x == 'a') len_a ++;
else b = max(len_a, len_b) + 1;
}
return n - max(len_a, len_b);
}
2.计算要删去的最小长度 同@落霞与孤鹜亓飞
我们使用dp[i]记录当前以下标i元素结尾所要删去的最少元素,当我们的s[i]=='a'的时候,我们有两种做法,一种是与dp[i-1]联系起来,直接删除s[i]处的a,第二种是删除之前出现的所有的b--count_b,而当s[i]=='b'时,count_b++即可;
int minimumDeletions(string s) {
int ans= 0, count_b= 0;
for (int i = 0; i < s.size(); ++i) {
if (s[i] =='b') ++count_b;
else
ret = min(count_b, 1 + ans);
}
return ans;
}
动态规划是一种简化了的思想,它不要求我们直接从条件看到结论的得来,而是可以建立在假设的基础上,进一步简化了思维深度,而一个好的假设就是解题的关键,这往往和题目相关但是也会有一些通用的做法.当我们思考不出来dp做法时,可以用多用数组建立dp[i]与dp[i-1]的关系而不是直接ans=ans... 会更有利于理解.
这是自己的学习笔记,侵删.