838. 推多米诺 :「BFS」&「预处理 + 双指针」

简介: 838. 推多米诺 :「BFS」&「预处理 + 双指针」

题目描述



这是 LeetCode 上的 838. 推多米诺 ,难度为 中等


Tag : 「BFS」、「双指针」


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


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


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


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


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


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


返回表示最终状态的字符串。


示例 1:


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


示例 2:


网络异常,图片无法展示
|


输入:dominoes = ".L.R...LR..L.."
输出:"LL.RR.LLRRLL.."
复制代码


提示:


  • n==dominoes.lengthn == dominoes.lengthn==dominoes.length
  • 1<=n<=1051 <= n <= 10^51<=n<=105
  • dominoes[i]dominoes[i]dominoes[i]'L'R''.'


BFS



推倒骨牌是一个行为传递的过程,可以使用 BFS 来进行模拟。


起始将所有不为 . 的骨牌以 (loc,time,dire)(loc, time, dire)(loc,time,dire) 三元组的形式进行入队,三元组所代表的含义为「位置为 loclocloc 的骨牌在 timetimetime 时刻受到一个方向为 dirediredire 的力」,然后进行常规的 BFS 即可。


在受力(入队)时,我们尝试修改骨牌的状态,同时为了解决「一个骨牌同时受到左右推力时,维持站立状态不变」的问题,我们需要在尝试修改骨牌状态后,额外记录下该骨牌的状态修改时间,如果在同一时间内,一块骨牌受力两次(只能是来自左右两个方向的力),需要将该骨牌恢复成竖立状态。


代码:


class Solution {
    public String pushDominoes(String dominoes) {
        char[] cs = dominoes.toCharArray();
        int n = cs.length;
        int[] g = new int[n];
        Deque<int[]> d = new ArrayDeque<>();
        for (int i = 0; i < n; i++) {
            if (cs[i] == '.') continue;
            int dire = cs[i] == 'L' ? -1 : 1;
            d.add(new int[]{i, 1, dire});
            g[i] = 1;
        }
        while (!d.isEmpty()) {
            int[] info = d.pollFirst();
            int loc = info[0], time = info[1], dire = info[2];
            int ne = loc + dire;
            if (cs[loc] == '.' || (ne < 0 || ne >= n)) continue;
            if (g[ne] == 0) { // 首次受力
                d.addLast(new int[]{ne, time + 1, dire});
                g[ne] = time + 1;
                cs[ne] = dire == -1 ? 'L' : 'R';
            } else if (g[ne] == time + 1) { // 多次受力
                cs[ne] = '.';
            }
        }
        return String.valueOf(cs);
    }
}
复制代码


  • 时间复杂度:O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n)


预处理 + 双指针



我们知道,如果一块原本竖立的骨牌最终倒下,必然是「受到来自左侧向右的力」或者「受到来自右侧向左的力」


基于此,我们可以创建两个二维数组 lr 分别存储每个位置 iii 的左侧和右侧的受力情况,每个的 l[i]l[i]l[i]r[i]r[i]r[i] 分别存储「左侧」和「右侧」的最近受力点下标,以及该力的方向


然后枚举所有 dominoes[i]dominoes[i]dominoes[i]. 的位置,获取其左侧的最近受力点 loc1 和受力方向 dire1,以及其右侧的最近受力点 loc2 和受力方向 dire2,并进行分情况讨论即可。


根据左右侧受力情况修改骨牌状态可通过「双指针」实现。


一些细节:为了避免每个样例都 new 大数组,可以使用 static 优化 lr 的创建。


代码:


class Solution {
    static int N = 100010;
    static int[][] l = new int[N][2], r = new int[N][2];
    public String pushDominoes(String dominoes) {
        char[] cs = dominoes.toCharArray();
        int n = cs.length;
        for (int i = 0, j = -1; i < n; i++) {
            if (cs[i] != '.') j = i;
            l[i] = new int[]{j, j != -1 ? cs[j] : '.'};
        }
        for (int i = n - 1, j = -1; i >= 0; i--) {
            if (cs[i] != '.') j = i;
            r[i] = new int[]{j, j != -1 ? cs[j] : '.'};
        }
        for (int i = 0; i < n; ) {
            if (cs[i] != '.' && ++i >= 0) continue;
            int j = i;
            while (j < n && cs[j] == '.') j++;
            j--;
            int[] a = l[i], b = r[j];
            int loc1 = a[0], dire1 = a[1], loc2 = b[0], dire2 = b[1];
            if (loc1 == -1 && loc2 == -1) { // 两侧无力
            } else if (loc1 == -1) { // 只有右侧有力,且力的方向向左
                if (dire2 == 'L') update(cs, i, j, 'L', 'L');
            } else if (loc2 == -1) { // 只有左侧有力,且力的方向向右
                if (dire1 == 'R') update(cs, i, j, 'R', 'R');
            } else { // 两侧有力,且两力方向「不同时」反向
                if (!(dire1 == 'L' && dire2 == 'R')) update(cs, i, j, (char)dire1, (char)dire2);
            }
            i = j + 1;
        }
        return String.valueOf(cs);
    }
    void update(char[] cs, int l, int r, char c1, char c2) {
        for (int p = l, q = r; p <= q; p++, q--) {
            if (p == q && c1 != c2) continue;
            cs[p] = c1; cs[q] = c2;
        }
    }
}
复制代码


  • 时间复杂度:预处理 lr 的复杂度为 O(n)O(n)O(n);构造答案复杂度为 O(n)O(n)O(n)。整体复杂度为 O(n)O(n)O(n)
  • 空间复杂度:O(n)O(n)O(n)


最后



这是我们「刷穿 LeetCode」系列文章的第 No.838 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
算法
[leetcode] 推多米诺 双指针
一开始想多了,像成了真实生活中的那种会叠加的状态,就比如"RRL"中,左边的两个"R"会让第三个"L"向右边倾斜,直接用 前缀和 进行操作,但是发现示例1都无法通过,所以说是错的 正确的想法是,每一个暂未确定状态的’.‘都由这个字符两侧最相近的字符确定 “R…R” 一定是 “RRRRRRRR”,"L…L"同理 而对于: “L…R"一定是"L…R” 而对于: "R…L"会变成"RRR(.)LLL"两侧往中间挤的情况下,要记得中间的长度是奇数还是偶数进行确定是否有’.’
90 0
[leetcode] 推多米诺 双指针
|
5月前
|
C语言
指针进阶(C语言终)
指针进阶(C语言终)
|
1月前
|
C语言
无头链表二级指针方式实现(C语言描述)
本文介绍了如何在C语言中使用二级指针实现无头链表,并提供了创建节点、插入、删除、查找、销毁链表等操作的函数实现,以及一个示例程序来演示这些操作。
22 0
|
2月前
|
存储 人工智能 C语言
C语言程序设计核心详解 第八章 指针超详细讲解_指针变量_二维数组指针_指向字符串指针
本文详细讲解了C语言中的指针,包括指针变量的定义与引用、指向数组及字符串的指针变量等。首先介绍了指针变量的基本概念和定义格式,随后通过多个示例展示了如何使用指针变量来操作普通变量、数组和字符串。文章还深入探讨了指向函数的指针变量以及指针数组的概念,并解释了空指针的意义和使用场景。通过丰富的代码示例和图形化展示,帮助读者更好地理解和掌握C语言中的指针知识。
|
3月前
|
C语言
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
【C初阶——指针5】鹏哥C语言系列文章,基本语法知识全面讲解——指针(5)
|
3月前
|
C语言
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
【C初阶——指针4】鹏哥C语言系列文章,基本语法知识全面讲解——指针(4)
|
3月前
|
存储 编译器 C语言
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)
【C初阶——指针3】鹏哥C语言系列文章,基本语法知识全面讲解——指针(3)
|
4月前
|
编译器 C语言
【C语言初阶】指针篇—下
【C语言初阶】指针篇—下
|
4月前
|
存储 C语言
【C语言初阶】指针篇—上
【C语言初阶】指针篇—上
|
5月前
|
搜索推荐 程序员 C语言
指针赋值与引用传递:C语言的基础知识与实践技巧
指针赋值与引用传递:C语言的基础知识与实践技巧