青训营刷题-30

266 阅读6分钟

30 多米诺骨牌

多米诺骨牌游戏规则非常简单,将骨牌按一定间距的尺寸排成单行,或分行排成一片。推倒第一张骨牌,其余发生连锁反应依次倒下,或形成一条长龙,或形成一幅图案。

小明觉得多米诺骨牌超级没意思,所以他想了点小花招。

小明将 n 个多米诺骨牌放在一条线上,每一块都垂直竖立。他同时将一些骨牌向左或向右推倒。注意:不会出现连续向左或者向右推的情况。每过一秒,被推向左边或右边的骨牌会将左边或右边的相邻骨牌推倒。当一个骨牌,其左边倒向它的骨牌数目与其右边倒向它的骨牌数目相等时,由于力的平衡,该骨牌将依然保持竖立。

给定小明最初推骨牌的方向,求出最后依然保持竖立的骨牌数目和位置。

输入格式

输入数据包含以下部分:

  1. 第一行:一个整数 n(1 ≤ n ≤ 3000),表示这行多米诺骨牌的数目。

  2. 第二行:一个长度为 n 的字符串,字符串的第 i 个字符意义如下:

    • "L":第 i 个骨牌将被向左推倒。
    • "R":第 i 个骨牌将被向右推倒。
    • ".":第 i 个骨牌不会被推倒。

限制条件

  • 1 ≤ n ≤ 3000
  • 字符串只包含字符 'L''R''.'

输出格式

  • 第一行:一个整数,表示保持竖立的骨牌数目。
  • 如果保持竖立的骨牌数目不为 0,则在第二行输出这些骨牌的位置,位置从 1n,每两个数之间用一个空格隔开,注意最后一个数后面没有空格。

示例

示例 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. 第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)不会再推倒其他骨牌。
    3. 第3秒

      • 被推倒的骨牌在位置6(R)和位置11(L)由于力量平衡,保持竖立。
  • 最终状态:位置3、6、13、14的骨牌保持竖立。

  • 总计:4个骨牌保持竖立。

示例 2

输入

5
R....

输出

0

说明

  • 初始状态R . . . .

    位置编号:1 2 3 4 5

  • 推倒过程

    1. 第1秒

      • 向右推倒的骨牌在位置1(R)会将位置2(.)推倒向右。
    2. 第2秒

      • 被推倒的骨牌在位置2(R)会将位置3(.)推倒向右。
    3. 第3秒

      • 被推倒的骨牌在位置3(R)会将位置4(.)推倒向右。
    4. 第4秒

      • 被推倒的骨牌在位置4(R)会将位置5(.)推倒向右。
    5. 第5秒

      • 被推倒的骨牌在位置5(R)没有相邻骨牌可推。
  • 最终状态:所有骨牌都被推倒。

  • 总计:0个骨牌保持竖立。

示例 3

输入

1
.

输出

1
1

说明

  • 初始状态.

    位置编号:1

  • 推倒过程

    • 没有骨牌被推倒。
  • 最终状态:位置1的骨牌保持竖立。

  • 总计:1个骨牌保持竖立。

解题思路

本问题需要模拟多米诺骨牌的倒下过程,并确定哪些骨牌最终保持竖立。每个骨牌可能会被左边或右边的骨牌推倒,也可能因受到来自左右两边的力量相等而保持竖立。为了高效地解决这个问题,可以采用以下步骤:

1. 初始化

  • 创建两个数组 leftTimerightTime,分别记录每个骨牌被左侧和右侧推倒的最短时间。
  • 初始化这两个数组为一个足够大的值(如 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),用于存储 leftTimerightTime 数组。

算法实现

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"));
    }
}