题意
用最少次数将一个密码变成强密码。
强密码的定义为:
- 长度为6到20
- 包含字母大小写与数字
- 同样的字符不能连续出现3次以及3次以上
思路
贪心,看着代码理解。
这里用的变量有点多,所以变量名随便一点。同时有些变量有复用的情况解释一下。
22行以上:
a,b,c表示数字、大写字母、小写字母是否缺失,1表示缺失,0表示存在,为1的时候就是需要添加该类型的字符,但强密码只要有不要多,所以贪心就是补充1个,
n表示password的字符个数,
j表示出现连续字符的连续长度
z 表示出现连续字符需要修改的字符个数,这里每连续3个修改一个字符就可以破坏连续。所以是j/3的累加。
22行以下:
a表示数字、大写字母、小写字母这三种类型的缺失个数,如password缺失大写字母与数字,
这里的a就会为2,a的意义就变成了需要在password上添加a个字符去补充类型。
n表示一种需要删除字符的个数的状态,
j才是表示必须要删除的字符个数。
这道题需要分类讨论,
最简单的情况,字符个数不足6个的时候至少要补充到6个,即6-n,至少补充到有3种类型,即a,不用考虑连续个数,因为你的补充字符的操作是可以打断连续的,所以max(6-n,a);
然后就是长度在6到20之间的时候,这里不考虑删除,因为删除的效果只会小于等于修改,所以答案就是max(z,a)
最后是超过20的情况
这里添加的效果就会小于修改和删除,所以不用考虑添加的操作。
因为要控制到长度在20以内,那么n-20个删除操作就是必不可少的,但删除了n-20个数字后,又会使得z中多少个字符不用去修改呢?
从26行后,n是个状态,j是必定会执行的删除操作。
如果有个字符串的一个子串是baaab,这里可以看出aaa必定会令z加一,若不执行删除操作,必须进行修改一次,但如果进行过删除操作,且a被删除了一个,那这时候的z就应该减一。这是出发点。
aaaaa要修改1个字符,想不修改就要删除3个a
aaaaaa要修改2个字符,想不修改就要删除3个a,想要只修改1个字符就要删除1个a
aaaaaaaaa要修改3个字符,想不修改就要删除6个a,想要只修改2个字符就要删除1个a,想要只修改1个字符就要删除4个a。
多观察一下规律,不难发现就是和连续字符的长度的余数相关。对应代码中的x和y,有x段连续长度为3的倍数的话,优先删除这x段的最后1个字符,那么z就可以少修改x个字符,有y段连续长度为3的倍数+1的话,次优先删除这y段的最后2个字符,那么z就可以少修改y个字符,到最后还有剩余的n,z就可以少修改n/3个字符。
代码
class Solution {
public:
int strongPasswordChecker(string password) {
int i,j,a,b,c,n,x,y,z;
a = b = c = 1;
x = y = z = 0;
n = password.size();
for(i = 0;i < n;i ++){
if(password[i]>='0'&&password[i]<='9')a = 0;
else if(password[i]>='A'&&password[i]<='Z')b = 0;
else if(password[i]>='a'&&password[i]<='z')c = 0;
j = 1;
while(i+1<n&&password[i]==password[i+1]){
j ++;
i ++;
}
if(j>2){
z += j/3;
if(j%3==0)x ++;
else if(j%3==1)y ++;
}
}
a = a+b+c;
if(n<6)return max(6-n,a);
else if(n<21) return max(z,a);
n -= 20;
j = n;
if(n<=x)return max(z-n,a)+j;
n-=x;
z-=x;
if(n<=2*y)return max(z-n/2,a)+j;
n-=2*y;
z-=y;
return max(z-n/3,a)+j;
}
};