利用广度优先或模拟解决米诺骨牌

简介: 利用广度优先或模拟解决米诺骨牌

题目

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.length

1 <= n <= 105

dominoes[i] 为 ‘L’、‘R’ 或 ‘.’

![(https://ucc.alicdn.com/images/user-upload-01/7ddeab69c86f43ee9c3a704d603a85f4.png#pic_center)

初始想法

将LR放到栈中。

如果遇到两个L 则两个L之间的全部L ,出栈入栈
栈顶L,新遇到R 不处理,新元素入栈
如果遇到两个R 两个R之间全部R,出栈入栈
栈顶R 新遇到L RL之间离R近的R,离L近的L,相等的不边,出栈入栈

结束时,栈有如下几种情况

LR
L
R

分情况讨论过于复杂,放弃。

可行的方案

时间复杂度: O(n) ,两轮循环时间复杂度都是O(n)。

分析

向左倒 右边必须有牌向左倒,且不被向右倒的牌挡住
向右倒 左边必须有牌向右倒,且不被向左倒的牌挡住
左右倒 距离左倒的近,左倒;距离右倒的近,右倒;距离相等,直立

变量解释

vRight 距离最近的向右倒的牌的索引
iLeft 距离最近向左倒的牌的索引

核心代码

class Solution {
public:
  string pushDominoes(string dominoes) {
    m_c = dominoes.length();
    vector<int> vRight(m_c, -1);
    {
      int iRight = -1;
      for (int i = 0; i < m_c; i++)
      {
        if ('R' == dominoes[i])
        {
          iRight = i;
        }
        if ('L' == dominoes[i])
        {
          iRight = -1;
        }
        vRight[i] = iRight;
      }
    }
    int iLeft = -1;
    string s(m_c, '.');
    for (int i = m_c - 1; i >= 0; i--)
    {
      if ('L' == dominoes[i])
      {
        iLeft = i;
      }
      if ('R' == dominoes[i])
      {
        iLeft = -1;
      }     
      if (iLeft >= 0)
      {
        if (vRight[i] >= 0)
        {
          int tmp = (iLeft - i) - (i - vRight[i]);
          if (tmp > 0)
          {
            s[i] = 'R';
          }
          else if (tmp < 0)
          {
            s[i] = 'L';
          }
        }
        else
        {
          s[i] = 'L';
        }
      }
      else if(vRight[i] >= 0)
      {
        s[i] = 'R';
      }
    }
    return s;
  }
  int m_c;
};

测试用例

template <class T>
void Assert(const T& t1, const T& t2)
{
  assert(t1 == t2);
}
template <class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
  if (v1.size() != v2.size())
  {
    assert(false);
    return;
  }
  for (int i = 0; i < v1.size(); i++)
  {
    Assert(v1[i], v2[i]);
  }
}
int main()
{
  string dominoes;
  string res;
  {
    Solution slu;
    dominoes = "...";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("..."));
  }
  {
    Solution slu;
    dominoes = ".L.";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("LL."));
  }
  {
    Solution slu;
    dominoes = ".R.";
    res = slu.pushDominoes(dominoes);
    Assert(res, string(".RR"));
  }
  {
    Solution slu;
    dominoes = "R";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("R"));
  }
  {
    Solution slu;
    dominoes = "L";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("L"));
  }
  {
    Solution slu;
    dominoes = ".";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("."));
  }
  {
    Solution slu;   
    dominoes = "RR.L";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("RR.L"));
  }
  {
    Solution slu;
    dominoes = ".L.R...LR..L..";
    res = slu.pushDominoes(dominoes);
    Assert(res, string("LL.RR.LLRRLL.."));
  }
  //CConsole::Out(res);
}

深度优先搜索

分析

时间复杂度: O(n) ,每个元素最多出队入队一次。

变量解释

que 记录当前回合左到或右倒的元素索引
mIndexValue 键:当前回合受到力的牌,值:当前回合受到力。1,向左的力;0,没受到力或同时受到左右的力。-1,受到向右的力。

注意: 力值能让没倒的牌倒下,无法让倒下的牌转变方向。

if ('.' != dominoes[it.first])
      {
        continue;
      }

代码

class Solution {
public:
  string pushDominoes(string dominoes) {
    m_c = dominoes.length();
    std::queue<int> que;
    for (int i = 0; i < m_c; i++)
    {
      if ('.' != dominoes[i])
      {
        que.emplace(i);
      }
    }
    while (que.size())
    {
      unordered_map<int, int> mIndexValue;
      while (que.size())
      {
        const int inx = que.front();
        que.pop();
        if ('L' == dominoes[inx])
        {
          if (inx > 0)
          {
            mIndexValue[inx - 1]++;
          }
        }
        else 
        {
          if (inx + 1 < m_c)
          {
            mIndexValue[inx + 1]--;
          }
        }
      }
      for (const auto& it : mIndexValue)
      {
        if ('.' != dominoes[it.first])
        {
          continue;
        }
        if (1 == it.second)
        {
          dominoes[it.first] = 'L';
          que.emplace(it.first);
        }
        if (-1 == it.second)
        {
          dominoes[it.first] = 'R';
          que.emplace(it.first);
        }
      }
    }
    return dominoes;
  }
  int m_c;
};

模拟

分析

时间复杂度😮(n)。

枚举所有连续的直立牌,根据两边的受力方向分情况讨论。为了简化问题:可以想象在最左边增加L,最右边增加R。

LL 全部左倒
LR 全部不边
RL 左边右倒,右边左倒
RR 全部右倒

代码

class Solution {
public:
  string pushDominoes(string dominoes) {
    m_c = dominoes.length();
    int iPre = -1;
    for (int i = 0; i < m_c; i++)
    {
      if ('.' != dominoes[i])
      {
        Do(dominoes,iPre, i);
        iPre = i;
      }
    } 
    Do(dominoes, iPre, m_c);
    return dominoes;
  }
  void Do(string& s, int left, int right)
  {
    const char chL = (left < 0) ? 'L' : s[left];
    const char chR = (right < m_c) ? s[right] : 'R';
    if ('L' == chL)
    {
      if ('L' == chR)
      {
        for (int i = left + 1; i < right; i++)
        {
          s[i] = 'L';
        }
      }
      else
      {
        //什么都不干
      }
    }
    else
    {
      if ('L' == chR)
      {
        for (int i = 1; i <= (right - left - 1) / 2; i++)
        {
          s[left + i] = 'R';
          s[right - i] = 'L';
        }
      }
      else
      {
        for (int i = left + 1; i < right; i++)
        {
          s[i] = 'R';
        }
      }
    }
  }
  int m_c;
};

2022年12月旧版

class Solution {
public:
string pushDominoes(string dominoes) {
c = dominoes.length();
std::unordered_map mPreIndexValue;
for (int i = 0; i < c; i++)
{
const char& ch = dominoes[i];
Test(mPreIndexValue,ch, i);
}
while (mPreIndexValue.size())
{
std::unordered_map dp;
for (auto& it : mPreIndexValue)
{
if (0 == it.second)
{
continue;
}
if (‘.’ != dominoes[it.first])
{
continue;
}
dominoes[it.first] = (1 == it.second) ? ‘L’ : ‘R’;
Test(dp,dominoes[it.first], it.first);
}
dp.swap(mPreIndexValue);
}
return dominoes;
}
void Test(std::unordered_map& m,const char& ch, int i)
{
if (‘L’ == ch)
{
if (i > 0 )
{
m[i - 1]++;
}
}
else if (‘R’ == ch)
{
if (i + 1 < c)
{
m[i + 1]–;
}
}
}
int c;
};

2023年11月旧版

class Solution {
public:
string pushDominoes(string dominoes) {
int n = dominoes.size();
vector vRight(n, -1);
{
int iR = -1;
for (int i = 0; i < n; i++)
{
if (‘R’ == dominoes[i])
{
iR = i;
}
else if (‘L’ == dominoes[i])
{
iR = -1;
}
vRight[i] = iR;
}
}
int iLeft = -1;
string s(n, ‘.’);
for (int i = n - 1; i >= 0; i–)
{
if (‘L’ == dominoes[i])
{
iLeft = i;
}
else if (‘R’ == dominoes[i])
{
iLeft = -1;
}
if ((iLeft >= 0)&&(vRight[i] >= 0 ))
{
const int tmp = (iLeft - i) - (i - vRight[i]);
if (tmp > 0)
{
s[i] = ‘R’;
}
if (tmp < 0)
{
s[i] = ‘L’;
}
}
else if (iLeft >= 0)
{
s[i] = ‘L’;
}
else if (vRight[i] >= 0)
{
s[i] = ‘R’;
}
}
return s;
}
};

扩展阅读

视频课程

有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。

https://edu.csdn.net/course/detail/38771

如何你想快

速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程

https://edu.csdn.net/lecturer/6176

相关下载

想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版

https://download.csdn.net/download/he_zhidan/88348653

测试环境

操作系统:win7 开发环境: VS2019 C++17

或者 操作系统:win10 开发环境:

VS2022 C++17



相关文章
|
2月前
|
存储 算法
算法思想总结:模拟算法
算法思想总结:模拟算法
|
9月前
|
算法
广度优先遍历(BFS):逐层探索图的算法奥秘
在图论中,广度优先遍历(Breadth-First Search,BFS)是一种图遍历算法,它以一种逐层的方式探索图的节点。通过从起始节点开始,逐层向外扩展,BFS能够帮助我们解决许多与图相关的问题。
76 0
|
5月前
|
算法 测试技术 C++
利用广度优先或模拟解决米诺骨牌
利用广度优先或模拟解决米诺骨牌
|
9月前
|
数据采集 算法 C++
DFS(深度优先搜索)详解(概念讲解,图片辅助,例题解释,剪枝技巧)
DFS(深度优先搜索)详解(概念讲解,图片辅助,例题解释,剪枝技巧)
104 0
|
10月前
|
Cloud Native 算法 Go
886. 可能的二分法:图+深度优先算法
这是 力扣上的 886. 可能的二分法,难度为 中等。
|
10月前
|
数据采集 存储 算法
【基础知识】一文看懂深度优先算法和广度优先算法
图的遍历是指,从给定图中任意指定的顶点(称为初始点)出发,按照某种搜索方法沿着图的边访问图中的所有顶点,使每个顶点仅被访问一次,这个过程称为图的遍历。 我们根据访问节点的顺序与方式(根据搜索方法),可以分为广度优先(BFS)和深度优先(DFS),这是图论中两种非常重要的算法,生产上广泛用于拓扑排序,寻路(走迷宫),搜索引擎,爬虫等。
|
12月前
|
机器学习/深度学习 C++
|
12月前
|
安全 C++
【C++】二叉搜索树模拟实现
二叉搜索树也称为二叉排序树。它或者是一个空树或者是有如下性质的二叉树: • 左子树上的所有节点的值小于根节点 • 右子树上的所有节点的值大于根节点 • 不存在值相同
|
Java
数据结构,Java实现递归回溯,寻找出迷宫路线,解决迷宫问题
数据结构,Java实现递归回溯,寻找出迷宫路线,解决迷宫问题
71 0
数据结构,Java实现递归回溯,寻找出迷宫路线,解决迷宫问题
|
算法
算法设计与分析 二叉树的Morris遍历
算法设计与分析 二叉树的Morris遍历
104 0
算法设计与分析 二叉树的Morris遍历