题目解析:多米诺骨牌均衡状态问题 | 豆包MarsCode AI刷题

79 阅读4分钟

题目解析:多米诺骨牌均衡状态问题

问题描述 小S玩起了多米诺骨牌,他排列了一行骨牌,并可能将某些骨牌向左或向右推倒。随着骨牌连锁反应的进行,一些骨牌可能因为左右两侧受力平衡而保持竖立。现在小S想要知道在所有动作完成后,哪些骨牌保持竖立。

给定一个表示骨牌初始状态的字符串,其中:

  • "L" 表示该位置的骨牌将向左倒。
  • "R" 表示该位置的骨牌将向右倒。
  • "." 表示该位置的骨牌初始时保持竖立。

模拟整个骨牌倒下的过程,求出最终仍然保持竖立的骨牌的数目和位置。

测试样例

  • 样例1:

    • 输入:num = 14,data = ".L.R...LR..L.."
    • 输出:'4:3,6,13,14'
  • 样例2:

    • 输入:num = 5,data = "R...."
    • 输出:'0'
  • 样例3:

    • 输入:num = 1,data = "."
    • 输出:'1:1'

问题解析

骨牌的倒下受其左右两侧推力的影响:

  1. 初始状态

    • "L" 表示骨牌会向左倒。
    • "R" 表示骨牌会向右倒。
    • "." 表示骨牌初始时竖立。
  2. 推力传播规则

    • 右推力逐步向右衰减,每移动一格推力减一,直到衰减为零。
    • 左推力逐步向左衰减,每移动一格推力减一,直到衰减为零。
  3. 最终状态

    • 如果某骨牌受到的左右推力相等,则保持竖立。
    • 如果左推力大于右推力,骨牌向左倒。
    • 如果右推力大于左推力,骨牌向右倒。
  4. 输出

    • 保持竖立骨牌的数量和位置。

解题思路

1. 核心思想

通过两次扫描数组计算每个骨牌的净受力:

  • 从左到右:记录右推力的传播情况。
  • 从右到左:记录左推力的传播情况,并用减法更新净受力。

最终根据每个位置的净受力值决定骨牌的状态。

2. 实现步骤
  1. 从左到右计算右推力

    • 遇到 "R" 时,设置初始推力值为数组长度(相当于无限远的作用力)。
    • 遇到 "L"".",推力逐步衰减。
    • 将右推力叠加到受力数组 forces
  2. 从右到左计算左推力

    • 遇到 "L" 时,设置初始推力值为数组长度。
    • 遇到 "R"".",推力逐步衰减。
    • forces 中减去左推力,形成净受力。
  3. 确定最终状态

    • 根据 forces[i] 的值更新骨牌状态:
      • forces[i] == 0:竖立。
      • forces[i] > 0:向右倒。
      • forces[i] < 0:向左倒。
  4. 统计结果

    • 遍历最终状态,统计竖立骨牌的位置。

代码实现

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static String solution(int num, String data) {
        // 保存最终状态的字符数组
        char[] result = data.toCharArray();
        int[] forces = new int[num]; // 用于计算每个位置的受力

        // 从左向右扫描,计算右推力
        int force = 0;
        for (int i = 0; i < num; i++) {
            if (data.charAt(i) == 'R') {
                force = num; // 初始推力
            } else if (data.charAt(i) == 'L') {
                force = 0; // 左推力中断右推力
            } else {
                force = Math.max(force - 1, 0); // 推力逐步衰减
            }
            forces[i] += force; // 累加推力
        }

        // 从右向左扫描,计算左推力
        force = 0;
        for (int i = num - 1; i >= 0; i--) {
            if (data.charAt(i) == 'L') {
                force = num; // 初始推力
            } else if (data.charAt(i) == 'R') {
                force = 0; // 右推力中断左推力
            } else {
                force = Math.max(force - 1, 0); // 推力逐步衰减
            }
            forces[i] -= force; // 累减推力
        }

        // 确定最终状态
        List<Integer> uprightPositions = new ArrayList<>();
        for (int i = 0; i < num; i++) {
            if (forces[i] == 0) {
                result[i] = '.';
                uprightPositions.add(i + 1); // 记录位置(从 1 开始)
            } else if (forces[i] > 0) {
                result[i] = 'R';
            } else {
                result[i] = 'L';
            }
        }

        // 返回结果
        if (uprightPositions.isEmpty()) {
            return "0";
        }
        StringBuilder sb = new StringBuilder();
        sb.append(uprightPositions.size()).append(":");
        for (int pos : uprightPositions) {
            sb.append(pos).append(",");
        }
        sb.setLength(sb.length() - 1); // 去掉最后一个逗号
        return sb.toString();
    }

    public static void main(String[] args) {
        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"));
    }
}

时间和空间复杂度

  1. 时间复杂度
    • 两次扫描字符串,共 O(n)O(n)
  2. 空间复杂度
    • 额外使用了长度为 n 的数组存储推力,空间复杂度为 O(n)O(n)

思考与优化

  • 本解法已实现线性复杂度,适合处理较大规模数据。
  • 对于特殊输入(如全部为 "." 或单个骨牌),结果正确性已覆盖。
  • 可进一步优化输出格式生成逻辑,但对总体效率影响不大。

通过这道题,我们深入理解了如何使用两次遍历结合力的传播规则来模拟复杂状态变化,并通过数组记录中间状态,有效简化了解题过程。这种方法同样可以扩展到其他物理模拟类问题中,如重力作用下物体的移动等。