力扣每日一题0221-推多米诺

366 阅读4分钟

「这是我参与2022首次更文挑战的第22天,活动详情查看:2022首次更文挑战」。

n 张多米诺骨牌排成一行,将每张多米诺骨牌垂直竖立。在开始时,同时把一些多米诺骨牌向左或向右推。

每过一秒,倒向左边的多米诺骨牌会推动其左侧相邻的多米诺骨牌。同样地,倒向右边的多米诺骨牌也会推动竖立在其右侧的相邻多米诺骨牌。

如果一张垂直竖立的多米诺骨牌的两侧同时有多米诺骨牌倒下时,由于受力平衡, 该骨牌仍然保持不变。

就这个问题而言,我们会认为一张正在倒下的多米诺骨牌不会对其它正在倒下或已经倒下的多米诺骨牌施加额外的力。

给你一个字符串 dominoes 表示这一行多米诺骨牌的初始状态,其中:

  • dominoes[i] = 'L',表示第 i 张多米诺骨牌被推向左侧,
  • dominoes[i] = 'R',表示第 i 张多米诺骨牌被推向右侧,
  • dominoes[i] = '.',表示没有推动第 i 张多米诺骨牌。

示例 1:

输入:dominoes = "RR.L"
输出:"RR.L"
解释:第一张多米诺骨牌没有给第二张施加额外的力。

广度优先搜索

当时间为 0 时,部分骨牌会受到一个初始的向左或向右的力而翻倒。过了 1 秒后,这些翻倒的骨牌会对其周围的骨牌施加一个力。具体表现为:

  • 向左翻倒的骨牌,如果它有直立的左边紧邻的骨牌,则会对该直立的骨牌施加一个向左的力。
  • 向右翻倒的骨牌,如果它有直立的右边紧邻的骨牌,则会对该直立的骨牌施加一个向右的力。

接下去需要分析这些 1 秒时受力的骨牌的状态。如果仅受到单侧的力,它们会倒向单侧;如果受到两个力,则会保持平衡。再过 1 秒后,这些新翻倒的骨牌又会对其他直立的骨牌施加力,而不会对正在翻倒或已经翻倒的骨牌施加力。

这样的思路类似于广度优先搜索。我们用一个队列 qqq 模拟搜索的顺序;数组 time 记录骨牌翻倒或者确定不翻倒的时间,翻倒的骨牌不会对正在翻倒或者已经翻倒的骨牌施加力;数组 force 记录骨牌受到的力,骨牌仅在受到单侧的力时会翻倒。

var pushDominoes = function (dominoes) {
  let finish = false
  let len = dominoes.length
  let arr = dominoes.split("")
  // while (finish == false) {
  for (let i = 0; i < len; i++) {
    if (arr[i] == "r") arr[i] = "R"
    if (arr[i] == "l") arr[i] = "L"
  }
  finish = true
  for (let i = 0; i < len; i++) {
    if (arr[i] == ".") {
      if (arr[i - 1] && arr[i - 1] == "R" && arr[i + 1] != "L") {
        arr[i] = "r"
        finish = false
      }
      if (arr[i - 1] != "R" && arr[i + 1] && arr[i + 1] == "L") {
        arr[i] = "l"
        finish = false
      }
    }
  }
  // }
  for (let i = 0; i < len; i++) {
    if (arr[i] == "r") arr[i] = "R"
    if (arr[i] == "l") arr[i] = "L"
  }
  console.log(arr.join(""));
};

模拟

我们可以枚举所有连续的没有被推动的骨牌,根据这段骨牌的两边骨牌(如果有的话)的推倒方向决定这段骨牌的最终状态:

  • 如果两边的骨牌同向,那么这段连续的竖立骨牌会倒向同一方向。
  • 如果两边的骨牌同向,那么这段连续的竖立骨牌会倒向同一方向。
  • 如果两边的骨牌相反,那么这段骨牌会保持竖立。

特别地,如果左侧没有被推倒的骨牌,则假设存在一块向左倒的骨牌;如果右侧没有被推倒的骨牌,则假设存在一块向右倒的骨牌。这样的假设不会破坏骨牌的最终状态,并且边界情况也可以落入到上述三种情况中。

我们可以使用两个指针 iii 和 jjj 来枚举所有连续的没有被推动的骨牌,left 和 right 表示两边骨牌的推倒方向。根据上述三种情况来计算骨牌的最终状态。

var pushDominoes = function(dominoes) {
    const s = [...dominoes];
    let n = s.length, i = 0;
    let left = 'L';
    while (i < n) {
        let j = i;
        while (j < n && s[j] == '.') { // 找到一段连续的没有被推动的骨牌
            j++;
        }
        const right = j < n ? s[j] : 'R';
        if (left === right) { // 方向相同,那么这些竖立骨牌也会倒向同一方向
            while (i < j) {
                s[i++] = right;
            }
        } else if (left === 'R' && right === 'L') { // 方向相对,那么就从两侧向中间倒
            let k = j - 1;
            while (i < k) {
                s[i++] = 'R';
                s[k--] = 'L';
            }
        }
        left = right;
        i = j + 1;
    }
    return s.join('');
};