问题描述
小S玩起了多米诺骨牌,他排列了一行骨牌,并可能将某些骨牌向左或向右推倒。随着骨牌连锁反应的进行,一些骨牌可能因为左右两侧受力平衡而保持竖立。现在小S想要知道在所有动作完成后,哪些骨牌保持竖立。
给定一个表示骨牌初始状态的字符串,其中:
- "L" 表示该位置的骨牌将向左倒。
- "R" 表示该位置的骨牌将向右倒。
- "." 表示该位置的骨牌初始时保持竖立。
模拟整个骨牌倒下的过程,求出最终仍然保持竖立的骨牌的数目和位置。
解题思路
不需要模拟所有动作,只需要遍历一次字符串就可以得出结果,算法复杂度为O(n)。
对于样例 ".L.R...LR..L.." ,查看其中的第四个骨牌 ".L.R...LR..L.." ,该骨牌向右推倒,其右边有向左推倒的骨牌,但并不会影响到其左边的竖立骨牌,可以看出每一个被推倒的骨牌都有阻隔作用。
将字符串分割成一个个子字符串,每个字符串都以 'L' 或 'R' 开头,并以 'L' 或 'R' 结尾,内部为若干个 '.' , 数量可以为零。在此之前可以先预处理一下字符串,在开头添加 'L' 并在结尾添加 'R', 这样不会影响结果但可以避免边界情况。比如样例 ".L.R...LR..L.." 可以先预处理成为 "L.L.R...LR..L..R",然后分割成若干子字符串: "L.L" , "L.R" , "R...L" , "LR" , "R..L" , "L..R"。
每个子字符串内部的竖立骨牌最后的状态只和边缘的字符串初始状态有关,存在四种情况:
- 当边缘两个骨牌都向左倒时,如 "L.L" ,内部的骨牌会全部倒下
- 当边缘两个骨牌都向右倒时,同上
- 当左边缘骨牌向左倒,右边缘骨牌向右倒时,如 "L..R" ,内部的骨牌会全部保持竖立
- 当左边缘骨牌向右倒,右边缘骨牌向左倒时,如 "R...L" 和 "R..L" ,如果内部有偶数个骨牌,则所有骨牌都会倒下;如果内部有奇数个骨牌,只有正中间的骨牌会保持竖立。
代码实现
std::string solution(int num, std::string data) {
std::vector<int> stand;
std::string res;
//对字符串预处理,避免边界情况
data = "L" + data + "R";
//将data划分为多个区间,每个区间由一个'L'或'R'开始并由'L'或'R'结束
int start = 0, end;
while(end < data.size()) {
for(end = start + 1; data[end] == '.'; end++);
//如果区间开头为'L'且结尾为'R',区间内的'.'骨牌均不会倒下
if(data[start] == 'L' && data[end] == 'R')
for(int i = start + 1; i < end; i++)
stand.push_back(i);
//如果区间开头为'R'且结尾为'L',区间正中间的'.'骨牌不会倒下
else if(data[start] == 'R' && data[end] == 'L' && (start+end)%2 == 0)
stand.push_back((start + end)/2);
start = end;
}
//构建返回字符串
res = std::to_string(stand.size());
if(stand.size() > 0) res += ":" + std::to_string(stand[0]);
for(int i = 1; i < stand.size(); i++)
res += "," + std::to_string(stand[i]);
return res;
}
在实际的代码中,并不需要分割字符串,只用一个start和一个end表示子字符串的开头和结尾。每次循环都从start开始找到第一个非 '.' 的字符,也就是 'L' 或 'R' ,然后对该子字符串进行分析,最后将该子字符串的结尾作为下一个子字符串的开头。由于对字符串的预处理,无需额外处理边界情况,并且索引刚好与骨牌位置对应。