30 多米诺骨牌
多米诺骨牌游戏规则非常简单,将骨牌按一定间距的尺寸排成单行,或分行排成一片。推倒第一张骨牌,其余发生连锁反应依次倒下,或形成一条长龙,或形成一幅图案。
小明觉得多米诺骨牌超级没意思,所以他想了点小花招。
小明将 n 个多米诺骨牌放在一条线上,每一块都垂直竖立。他同时将一些骨牌向左或向右推倒。注意:不会出现连续向左或者向右推的情况。每过一秒,被推向左边或右边的骨牌会将左边或右边的相邻骨牌推倒。当一个骨牌,其左边倒向它的骨牌数目与其右边倒向它的骨牌数目相等时,由于力的平衡,该骨牌将依然保持竖立。
给定小明最初推骨牌的方向,求出最后依然保持竖立的骨牌数目和位置。
输入格式
输入数据包含以下部分:
-
第一行:一个整数
n(1 ≤ n ≤ 3000),表示这行多米诺骨牌的数目。 -
第二行:一个长度为
n的字符串,字符串的第i个字符意义如下:"L":第i个骨牌将被向左推倒。"R":第i个骨牌将被向右推倒。".":第i个骨牌不会被推倒。
限制条件:
1 ≤ n ≤ 3000- 字符串只包含字符
'L'、'R'和'.'
输出格式
- 第一行:一个整数,表示保持竖立的骨牌数目。
- 如果保持竖立的骨牌数目不为
0,则在第二行输出这些骨牌的位置,位置从1到n,每两个数之间用一个空格隔开,注意最后一个数后面没有空格。
示例
示例 1
输入
14
.L.R...LR..L..
输出
4
3 6 13 14
说明
-
初始状态:
. L . R . . . L R . . L . .位置编号:1 2 3 4 5 6 7 8 9 10 11 12 13 14
-
推倒过程:
-
第1秒:
- 向左推倒的骨牌在位置2(
L)会将位置1(.)推倒向左。 - 向右推倒的骨牌在位置4(
R)会将位置5(.)推倒向右。 - 向左推倒的骨牌在位置8(
L)会将位置7(.)推倒向左。 - 向右推倒的骨牌在位置9(
R)会将位置10(.)推倒向右。 - 向左推倒的骨牌在位置12(
L)会将位置11(.)推倒向左。
- 向左推倒的骨牌在位置2(
-
第2秒:
- 被推倒的骨牌在位置1(
L)没有相邻骨牌可推。 - 被推倒的骨牌在位置5(
R)会将位置6(.)推倒向右。 - 被推倒的骨牌在位置7(
L)会将位置6(R)推倒向左和右,这两种力相互抵消,位置6保持竖立。 - 被推倒的骨牌在位置10(
R)会将位置11(L)推倒向右和左,这两种力相互抵消,位置11保持竖立。 - 被推倒的骨牌在位置11(
L)不会再推倒其他骨牌。
- 被推倒的骨牌在位置1(
-
第3秒:
- 被推倒的骨牌在位置6(
R)和位置11(L)由于力量平衡,保持竖立。
- 被推倒的骨牌在位置6(
-
-
最终状态:位置3、6、13、14的骨牌保持竖立。
-
总计:4个骨牌保持竖立。
示例 2
输入
5
R....
输出
0
说明
-
初始状态:
R . . . .位置编号:1 2 3 4 5
-
推倒过程:
-
第1秒:
- 向右推倒的骨牌在位置1(
R)会将位置2(.)推倒向右。
- 向右推倒的骨牌在位置1(
-
第2秒:
- 被推倒的骨牌在位置2(
R)会将位置3(.)推倒向右。
- 被推倒的骨牌在位置2(
-
第3秒:
- 被推倒的骨牌在位置3(
R)会将位置4(.)推倒向右。
- 被推倒的骨牌在位置3(
-
第4秒:
- 被推倒的骨牌在位置4(
R)会将位置5(.)推倒向右。
- 被推倒的骨牌在位置4(
-
第5秒:
- 被推倒的骨牌在位置5(
R)没有相邻骨牌可推。
- 被推倒的骨牌在位置5(
-
-
最终状态:所有骨牌都被推倒。
-
总计:0个骨牌保持竖立。
示例 3
输入
1
.
输出
1
1
说明
-
初始状态:
.位置编号:1
-
推倒过程:
- 没有骨牌被推倒。
-
最终状态:位置1的骨牌保持竖立。
-
总计:1个骨牌保持竖立。
解题思路
本问题需要模拟多米诺骨牌的倒下过程,并确定哪些骨牌最终保持竖立。每个骨牌可能会被左边或右边的骨牌推倒,也可能因受到来自左右两边的力量相等而保持竖立。为了高效地解决这个问题,可以采用以下步骤:
1. 初始化
- 创建两个数组
leftTime和rightTime,分别记录每个骨牌被左侧和右侧推倒的最短时间。 - 初始化这两个数组为一个足够大的值(如
n + 1),表示尚未被推倒。
2. 从左到右遍历,处理向右推倒的情况
- 遍历字符串,从左到右。
- 当遇到
'R'时,记录当前时间为0,表示该骨牌被立即向右推倒。 - 对于接下来的每个骨牌,如果不是
'L',时间递增,并记录该骨牌被右侧推倒的时间。 - 如果遇到
'L',则停止向右推倒的传播。
3. 从右到左遍历,处理向左推倒的情况
- 遍历字符串,从右到左。
- 当遇到
'L'时,记录当前时间为0,表示该骨牌被立即向左推倒。 - 对于接下来的每个骨牌,如果不是
'R',时间递增,并记录该骨牌被左侧推倒的时间。 - 如果遇到
'R',则停止向左推倒的传播。
4. 确定保持竖立的骨牌
-
对于每个骨牌,比较
leftTime[i]和rightTime[i]:- 如果
leftTime[i] == rightTime[i],则该骨牌保持竖立。 - 如果
leftTime[i] < rightTime[i],则骨牌向左倒下。 - 如果
rightTime[i] < leftTime[i],则骨牌向右倒下。
- 如果
5. 计算结果
- 统计保持竖立的骨牌数量,并记录其位置。
6. 复杂度分析
- 时间复杂度:
O(n),其中n是骨牌的数量。遍历字符串两次。 - 空间复杂度:
O(n),用于存储leftTime和rightTime数组。
算法实现
public class Main {
public static String solution(int num, String data) {
// Please write your code here
// 初始化leftTime和rightTime数组
int[] leftTime = new int[num];
int[] rightTime = new int[num];
int inf = num + 1;
for (int i = 0; i < num; i++) {
leftTime[i] = inf;
rightTime[i] = inf;
}
// 从左到右遍历,处理向右推的情况
int pushTime = inf;
for (int i = 0; i < num; i++) {
char c = data.charAt(i);
if (c == 'R') {
pushTime = 0;
rightTime[i] = Math.min(rightTime[i], pushTime);
} else if (c == 'L') {
pushTime = inf;
} else {
if (pushTime != inf) {
pushTime += 1;
if (pushTime < rightTime[i]) {
rightTime[i] = pushTime;
}
}
}
}
// 从右到左遍历,处理向左推的情况
pushTime = inf;
for (int i = num - 1; i >= 0; i--) {
char c = data.charAt(i);
if (c == 'L') {
pushTime = 0;
leftTime[i] = Math.min(leftTime[i], pushTime);
} else if (c == 'R') {
pushTime = inf;
} else {
if (pushTime != inf) {
pushTime += 1;
if (pushTime < leftTime[i]) {
leftTime[i] = pushTime;
}
}
}
}
// 收集保持竖立的骨牌位置
StringBuilder sb = new StringBuilder();
int count = 0;
for (int i = 0; i < num; i++) {
if (leftTime[i] == rightTime[i]) {
count++;
}
}
if (count == 0) {
return "0";
} else {
sb.append(count).append(":");
boolean first = true;
for (int i = 0; i < num; i++) {
if (leftTime[i] == rightTime[i]) {
if (!first) {
sb.append(",");
}
sb.append(i + 1); // 位置从1开始
first = false;
}
}
return sb.toString();
}
}
public static void main(String[] args) {
// You can add more test cases here
System.out.println(solution(14, ".L.R...LR..L..").equals("4:3,6,13,14"));
System.out.println(solution(5, "R....").equals("0"));
System.out.println(solution(1, ".").equals("1:1"));
}
}