算法题 推多米诺 模拟、队列与黑魔法

本文介绍了一种解决多米诺骨牌推倒问题的算法,通过队列数据结构和双指针方法避免了模拟过程中的超时问题。文章首先尝试了模拟法但因复杂度过高导致超时,然后分析问题本质,提出使用队列来处理‘L’和‘R’牌的匹配,详细描述了队列操作和双指针的运用,最后给出了优化后的解决方案。此外,还展示了两种失败的尝试,包括模拟法和一种简化处理的‘黑魔法’方法。
摘要由CSDN通过智能技术生成

推多米诺 模拟、队列与黑魔法

题目

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

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

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

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

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

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

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

来源:力扣(LeetCode)838.push-dominoes

示例:dominoes

输入:dominoes = ".L.R...LR..L.."
输出:"LL.RR.LLRRLL.."

思路

一开始想用模拟法,但笔者太菜了写出来的东西超时了😭,后文细说。分析此题,处理难点其实在于左边的牌向右倒,右边的牌向左倒时,如何处理这两个牌之间的牌。这个问题可以抽象为”R“与”L“牌匹配的问题,联想到数据结构课程学习栈时讨论过的表达式求值问题和括号匹配问题。但上述问题只是为了解决算术问题,在出入栈时经过运算元素的顺序在输出层面已经无关紧要。而本题本质上是一个字符串处理问题,对元素的顺序有要求,使用栈会导致局部反转,比较麻烦,于是决定使用队列解决问题。

队列算法描述如下:

1)从左向右遍历骨牌,如果牌是立着的或是右倾则入队;

2)如果牌是左倾的则出队,在队列有牌的情况下,右倾的牌也出队;

但这里的出队不能为简单的进进出出,出队前需要将队列中的牌进行一定的处理:

1)如果队首是直立的牌,左倾牌进来则全部向左倒,右倾的牌来则直接出队;

2)如果队首是右倾的牌,右倾的牌来则先将队列右倒,左倾的牌来则对着倒。

如何对着倒也是一个棘手的问题,这里使用双指针的方法:

1)队首队尾分别设置一个指针;

2)两个指针相向而行,首指针指到的往右,尾指针往左

3)两指针相遇则停止。

实现

那么如何实现队列呢,python的字符串是不可变变量,不可直接修改,需要转化为列表。列表随易修改,但不好控制其在有队列的情况下进进出出。于是笔者使用”伪队列“的方法,使用队列的思想,但实质上是使用队首队尾两个指针的双指针法,数据结构如下:

queue = [0,0,'']

前两个元素分别为头尾指针,最后一个表示队列的状态。回顾思路,出队时具体要采取什么样的动作需要考虑最开始入队时的元素情况。“L”只会触发出队,故队列状态有“”空队列,“.”直立牌队列,“R”右倾队列三种状态。

需要注意的是,在原字符串尾部没有“L”或者“R”的情况下,队列无法被触发出队动作,特别是“R”队列的情况,牌全部往右倒,需要单独在遍历后清空队列:

class Solution:
 def pushDominoes(self, dominoes: str) -> str:
  queue = [0,0,''] # 初始化队列
  dominoes = list(dominoes) # 列表化便于修改
  for i in range(len(dominoes)):
   if queue[-1] == '':
    if dominoes[i] == '.':
     queue = [i,i,'.']
    elif dominoes[i] == 'R':
     queue = [i,i,'R']
   elif queue[-1] == '.':
    if dominoes[i] == '.':
     queue[1] = i
    elif dominoes[i] == 'R':
     queue = [i,i,'R']
    elif dominoes[i] == 'L':
     queue[-1] = ''
     for j in range(queue[0],queue[1]+1):
      dominoes[j] = 'L'
   elif queue[-1] == 'R':
    if dominoes[i] == 'R':
     for j in range(queue[0], i):
      dominoes[j] = 'R'
     queue = [i,i,'R']
    elif dominoes[i] == '.':
     queue[1] = i
    elif dominoes[i] == 'L':
     queue[-1] = ''
     rcur, lcur = queue[0], i
     while rcur<lcur:
      dominoes[rcur], dominoes[lcur] = 'R', 'L'
      rcur+=1; lcur-=1
  if queue[-1] == 'R': # 确保所有元素已经出队
   queue[-1] = ''
   for i in range(queue[0],len(dominoes)):
    dominoes[i] = 'R'
  return ''.join(dominoes)

一个失败的模拟

循环太多,超时,写成💩山了,没得救了,博君一笑:

class Solution:
 def pushDominoes(self, dominoes: str) -> str:
    dominoes = list(dominoes)
    rcur, lcur = -1, -1
    rl_set = []
    no_dot = []
    for i in range(len(dominoes)):
      if dominoes[i] == 'R' :
        rcur = i
      elif dominoes[i] == 'L' and rcur != -1:
        lcur = i
        rl_set.append((rcur,lcur))
        rcur, lcur = -1, -1
      i += 1
    for rl in rl_set:
      rcur, lcur = rl
      no_dot.extend([i for i in range(rcur, lcur+1)])
      while rcur<lcur:
        dominoes[rcur] = 'R'
        dominoes[lcur] = 'L'
        rcur+=1
        lcur-=1
    p = 0
    for i in range(len(dominoes)): 
      if i in no_dot:
        p = 0
        continue
      if dominoes[i] == 'L':
        p = 0
        for j in range(i-1,-1,-1):
          if dominoes[j] == '.':
            dominoes[j] = 'L'
          else:
            break
      elif dominoes[i] == 'R':
        p = 1
      elif p == 1 and dominoes[i] == '.' :
        dominoes[i] = 'R'
    return ''.join(dominoes)

黑魔法

凡是字符串处理的题就少不了黑魔法,离谱,author:[太阳家的猫]:

class Solution:
 def pushDominoes(self, dominoes: str) -> str:
  od = ""
  while dominoes != od:
   od = dominoes
   dominoes = dominoes.replace("R.L", "T")
   dominoes = dominoes.replace(".L", "LL")
   dominoes = dominoes.replace("R.", "RR")
   dominoes = dominoes.replace("T", "R.L")
  return dominoes
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值