题目描述

✅ 838. 推多米诺

题意分析

L、R 表示初始向左或向右受推的骨牌,. 表示直立。所有推力以每秒一格的速度同时传播;直立骨牌若同时受到两侧相反推力,会保持直立。已经倒下或正在倒下的骨牌不会再改变方向,求最终状态。

解法:按相邻推力之间的区间分类填充

核心思路

[!blue]

将连续的点号按两侧最近的初始 L、R 分段。这段内部没有初始推力,外侧的影响又不能穿过已经受推的端点改变它,所以中间状态只由相邻两个非点字符决定,不必逐秒模拟。

  • 两端同为 L:右端的推力向左传播,整段填 L。
  • 两端同为 R:左端的推力向右传播,整段填 R。
  • 左端为 L、右端为 R:两边都向外倒,中间保持直立。
  • 左端为 R、右端为 L:两股推力相向传播。更靠左的位置先受右推,更靠右的位置先受左推;用双指针从两侧向中间分别填 R、L。若剩下一个等距中心,两股力同时到达,保留原来的点号。

用 left、previous 保存上一推力的位置与方向,right 扫描下一个非点字符。找到右端后才填充开区间 (left, right),修改的都是已经扫描过的位置,不会把新填入的字符误当成后续初始推力。

首尾缺少一侧端点时,用下标 -1 的虚拟 L 与下标 n 的虚拟 R 统一处理。它们都向区间外侧,不会凭空产生向内推力:开头的点号仅在第一个非点字符为 L 时向左倒,末尾的点号仅在最后一个非点字符为 R 时向右倒;全是点号时则保持不变。虚拟端点只参与分类,不写入数组。

解题步骤

  1. 将输入复制成可修改字符数组,令 left = -1、previous = L。
  2. 从左到右扫描,遇到点号继续;扫描到 n 时把当前方向视为虚拟 R。
  3. 两端方向相同则填满中间区间;左 R 右 L 时从两边向中间填充;左 L 右 R 时不修改。
  4. 将当前右端保存为下一段的左端,直到末尾区间也处理完。
  5. 返回字符数组组成的字符串。

代码实现

class Solution {
    public String pushDominoes(String dominoes) {
        char[] result = dominoes.toCharArray();
        int left = -1;
        int n = result.length;
        char previous = 'L';

        for (int right = 0; right <= n; right++) {
            char current = right == n ? 'R' : result[right];

            if (current == '.') {
                continue;
            }

            if (previous == current) {
                for (int i = left + 1; i < right; i++) {
                    result[i] = current;
                }
            } else if (previous == 'R' && current == 'L') {
                int i = left + 1;
                int j = right - 1;

                while (i < j) {
                    result[i++] = 'R';
                    result[j--] = 'L';
                }
            }

            left = right;
            previous = current;
        }

        return new String(result);
    }
}
func pushDominoes(dominoes string) string {
    result := []byte(dominoes)
    left, n := -1, len(result)
    previous := byte('L')
    for right := 0; right <= n; right++ {
        current := byte('R')
        if right < n {
            current = result[right]
        }
        if current == '.' {
            continue
        }
        if previous == current {
            for i := left + 1; i < right; i++ {
                result[i] = current
            }
        } else if previous == 'R' && current == 'L' {
            i, j := left+1, right-1
            for i < j {
                result[i] = 'R'
                result[j] = 'L'
                i++
                j--
            }
        }
        left = right
        previous = current
    }
    return string(result)
}

复杂度分析

  • 时间复杂度:$O(n)$,主指针扫描一次,各段内部互不重叠,每个位置最多被填充一次。
  • 空间复杂度:$O(n)$,保存可修改的结果字符数组。

关键点总结

[!green]

四种端点关系决定每段最终状态;相向推力按到达距离决定方向,等距中心保持直立。虚拟端点用于统一首尾分支,不代表实际新增骨牌。

易错点总结

[!yellow]

  • R…L 的中间一个位置受力同时抵消,应保留 .。
  • L…R 是背向传播,中间不能填成 L 或 R。
  • 虚拟端点只参与判断,不能写入下标 -1 或 n。

相似题目

转载与许可
作者
链接 https://hgnulb.github.io/blog/2026/69521287
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!