题目:
给定一个收尾相连的字符串s,该字符串中只包含b、r、w三个字符。现在选定一个点作为分割点,从这个分割点往左开始收集字符,直到遇到第一个与已收集的字符不同的字符为止,设收集到的字符个数为left个。同样,从分割点往右开始收集字符,直到遇到与已收集的字符不同的字符为止,设收集到的字符为right个。要求返回left+right值。其中字符w可以作为b或r的任一一个字符。
例如:
s = brb 则最佳的分割点在r之后,left = 1, right = 2,最终值为3
s = brbw 则最佳的分割点在r之后,left = 1, right = 3(w作为b进行统计),最终值为4
解析:
这题最直接的解法即从字符串头开始往后遍历,没遍历到一个分割点,便分别直接统计分割点的左边和右边能够得到的字符个数,并在遍历过程中记录最大的值即可。然而这种方法存在许多重复的计算。
假设我们已经有了分割点i的left和right值,那么当我们进行到分割点i+1时,我们看看left和right会有什么变化。
1.left值的变化
(1)当s[i] == s[i-1] 或s[i] == ‘w'时,则分割点i+1的left_new = left_i + 1;
(2)当s[i] != s[i-1]且s[i] != 'w'时,我们只能从s[i]开始往前遍历,统计当前分割点的left值
2.right值的变化
(1)当s[i] == s[i+1]或s[i] == 'w'时,则分割点i+1的right_new = right_i - 1;
(2)当s[i]!=s[i+1]且s[i]!='w'时,则只能从s[i+1]开始往后遍历,统计当前分割点的right值
#include <iostream>
#include <string>
using namespace std;
int getCharCount(string& s)
{
int n = s.size();
if(n <= 2) return n;
int left = 0, right = 0; //切割点左边和右边能够收集到的字符个数
int max = INT_MIN; //能收集到最大的字符个数
int l = 0, r = 0, k = 0;
char lc = '\0', rc = '\0';
//以0作为切割点,进行初始化
l = 0;
r = 1;
while(l != r)
{
if(s[l] != 'w' && lc == '\0') lc = s[l];
else if(s[l] != 'w' && s[l] != lc) break;
left++;
l = (n+l-1)%n;
}
if(left == n-1) return n;
l = (l+1)%n; //使l指向当前切割点左边子串的最左边
while(r != l)
{
if(s[r] != 'w' && rc == '\0') rc = s[r];
else if(s[r] != 'w' && s[r] != rc) break;
right++;
r = (r+1)%n;
}
max = left+right;
k = 1;
//遍历每一个可能的切割点,求取最佳解
for(; k < n; k++)
{
//更新left值
if(s[k] == s[(n+k-1)%n] || s[k] == 'w') left = left+1;
else
{
l = (n+k-1)%n;
left = 1;
while(l != (k+1)%n && s[l] == 'w' || s[l] == s[k]){ left++; l = (n+l-1)%n;}
l = (l+1)%n; //使l指向当前切割点左边子串的最左边
}
if(left == n-1) return n;
//更新right值
if(s[k] == s[(k+1)%n] || s[k] == 'w') right = right-1;
else
{
r = (k+1)%n;
right = 0;
while(r != l && s[r] == 'w'){ r = (r+1)%n; right++;}
if(r == l) return n;
rc = s[r];
while(r != l && (s[r] == 'w' || s[r] == rc)) {r = (r+1)%n; right++;}
}
if(r == l) return n;
if(max < left+right) max = left+right;
}//for
return max;
}
int main()
{
string s;
while(cin >> s)
{
cout << getCharCount(s) << endl;
}
}