【问题标题】:NxM Matrix TraversalNxM 矩阵遍历
【发布时间】:2017-08-14 14:40:36
【问题描述】:

我正在努力解决以下问题的算法:

给定一个只有两个可能值“X”和“O”的矩形矩阵。 值“X”总是以矩形岛的形式出现,这些岛 总是按行和按列由至少一行“O”分隔。 计算给定矩阵中岛屿的数量。

我已经生成了一个解决方案,但是我的循环没有遍历整个矩阵。我尝试在 i 循环中将 j 初始化为 0,但这会引发错误。我是这个主题的新手,我真的很想了解为什么我的代码不起作用。

def matrix(input)
row = input.length
col = input[0].length
counter = 0
i = 0
j = 0
while i < row do 
  while j < col do 
  if input[i][j] == "X"
    if i == 0 || input[i-1][j] == "O" && j = 0 || input[i][j-1] == "O"
      counter += 1
    end 
  end 
  j += 1
 end
i += 1
end
  p counter
end 

matrix(
[
  ["X", "X", "O", "X"],
  ["X", "X", "O", "X"],
  ["O", "O", "X", "O"],
  ["O", "O", "X", "O"],
  ["O", "O", "O", "X"],
  ["O", "O", "O", "X"]
]
)
# expected output is 4

这不是家庭作业。我正在练习数据结构和算法。原问题可以找到here

【问题讨论】:

  • 它不会通过完整的矩阵,因为j 值在第一次i 迭代期间达到4,一旦达到该值,while j &lt; col 就不再执行(所以基本上第二个while 只执行一次)。你能看出为什么会这样吗?提示:检查在哪里你定义j
  • @Gerry 所以如果 j-while 循环从未被执行,那么我需要在该行之前初始化 j=0。这就是我的假设......
  • @Gerry 我尝试了以下操作,输出 def matrix(input) row = input.length col = input[0].length counter = 0 i = 0 while i
  • 您假设正确:j 迭代列,因此您必须为每一行(即每个 i 迭代)重置它(即 j = 0)。现在你得到了8,因为if i == 0 || input[i-1][j] == "O" &amp;&amp; j == 0 || input[i][j-1] == "O" 中有一些逻辑off,尝试打印ij 的输出,每次将1 添加到counter 以发现您添加到counter。注意input[i-1][j]input[i][j-1],它们的行为可能与您的预期不同(即input[-1] 将返回最后一行)。
  • 同样j = 0 应该是j == 0

标签: ruby matrix traversal


【解决方案1】:

算法

我们得到一个数组input,由称为“行”的大小相等的数组组成。问题中给出的例子如下。

input = [
  ["X", "X", "O", "X"],
  ["X", "X", "O", "X"],
  ["O", "O", "X", "O"],
  ["O", "O", "X", "O"],
  ["O", "O", "O", "X"],
  ["O", "O", "O", "X"]
]

为方便起见,我将元素 input[3][2] #=&gt; "X" 称为 row 3column 2 中的元素(尽管 Ruby 没有二维数组或具有行和列的数组)。

第一步是构造一个数组groups,每个元素(一个“组”)由一个元素组成,其中一行中的一个“X”的索引:

groups
  #=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #    [[2, 2]], [[3, 2]], [[4, 3]], [[5, 3]]] 

我们现在考虑groups ([[5, 3]]) 的最后一个元素,并询问它的任何元素(只有一个,[5, 3])是否与任何其他组中的元素在同一个岛中。我们发现它与[[4, 3]] 组中的[4, 3] 位于同一个岛上(因为元素在同一列中,相隔一行)。因此,我们删除最后一个组并将其所有元素(这里只是一个)添加到组[[4, 3]]。我们现在有:

groups
  #=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #    [[2, 2]], [[3, 2]], [[4, 3], [5, 3]]] 

我们现在对现在的最后一组 [[4, 3], [5, 3]] 重复该过程。我们必须确定该组中的任何一个元素是否与其他每个组中的任何元素在同一个岛上。它们不是1。因此,我们确定了第一个岛,由位置 [4, 3][5, 3] 组成。

初始化islands = []后,我们进行如下操作:

islands << groups.pop

现在

groups
  #=>#=> [[[0, 0]], [[0, 1]], [[0, 3]], [[1, 0]], [[1, 1]], [[1, 3]],
  #       [[2, 2]], [[3, 2]]] 
islands
  #=> [[[4, 3], [5, 3]]]

我们继续这样直到groups 为空,此时islands 的每个元素都是一个岛。

代码

def find_islands(input)
  groups = input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' } }
  islands = []
  while groups.any?
    last_group = groups.pop
    idx = groups.each_index.find { |idx| same_island?(groups[idx], last_group) }
    if idx.nil?
      islands << last_group
    else
      groups[idx] += last_group
    end
  end
  islands.map(&:sort)
end

def same_island?(group1, group2)
  group1.product(group2).any? { |(i1,j1),(i2,j2)|
  ((i1==i2) && (j1-j2).abs == 1) || ((j1==j2) && (i1-i2).abs == 1) }
end

示例

对于上面给出的数组input,我们得到以下岛屿数组。

find_islands(input)
  #=> [[[4, 3], [5, 3]],
  #    [[2, 2], [3, 2]],
  #    [[0, 3], [1, 3]],
  #    [[0, 0], [0, 1], [1, 0], [1, 1]]]

说明

毫无疑问,对于没有经验的 Rubiest 来说,学习理解我所介绍的两种方法的工作原理有很多好处。需要熟悉以下方法:

groups的初始计算

如果这是一个单独的方法(不是一个坏主意),我们将编写以下内容。

def construct_initial_groups(input)
  input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,`j| groups << [[i,j]] if c == 'X' } }
end

Ruby 新手可能会觉得这有点不知所措。其实就是把下面的方法收紧的Ruby方式。

def construct_initial_groups(input)
  groups = []
  i = 0
  input.each do |row|
    j = 0
    row.each do |c|
      groups << [[i,j]] if c == 'X'
      j += 1
    end
    i += 1
  end
  groups
end

从这里到那里的第一步是使用方法Enumerable#each_with_index

def construct_initial_groups(input)
  groups = []
  input.each_with_index do |row,i|
    row.each_with_index do |c,j|
      groups << [[i,j]] if c == 'X'
    end
  end
  groups
end

接下来我们利用Enumerator#with_object的方法得到construct_initial_groups的上述第一种形式。

编写块变量 (|(row,i),groups|) 可能仍然令人困惑。你会学到的

enum = input.each_with_index.with_object([])
  #=> #<Enumerator: #<Enumerator: [["X", "X", "O", "X"], ["X", "X", "O", "X"],
  #     ["O", "O", "X", "O"], ["O", "O", "X", "O"], ["O", "O", "O", "X"],
  #     ["O", "O", "O", "X"]]:each_with_index>:with_object([])>

是一个枚举器,其元素是通过应用Enumerator#next方法生成的。

(row,i), groups = enum.next
  #=> [[["X", "X", "O", "X"], 0], []]

Ruby 使用 disambiguationdecomposition 为三个块变量中的每一个赋值:

row
  #=> ["X", "X", "O", "X"]
i #=> 0
groups
  #=> []

然后我们执行块计算。

row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=> ["X", "X", "O", "X"].each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=>  ["X", "X", "O", "X"]

现在

groups
  #=> [[[1, 0]], [[1, 1]], [[1, 3]]]

现在生成enum 的第二个元素并将其传递给块并执行块计算。

(row,i), groups = enum.next
  #=> [[["O", "O", "X", "O"], 2], [[[1, 0]], [[1, 1]], [[1, 3]]]]
row
  #=> ["O", "O", "X", "O"]
i #=> 2
groups
  #=> [[[1, 0]], [[1, 1]], [[1, 3]]]
row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' }
  #=> ["O", "O", "X", "O"]

现在groups 有两个元素。

groups
  #=> [[[1, 0]], [[1, 1]],
  #    [[1, 3]], [[2, 2]]]

其余的计算类似。

same_island? method

假设

group1 #=> [[0, 0], [1, 0]]
group2 #=> [[0, 1], [1, 1]]

这告诉我们[0, 0][1, 0] 在同一个岛上,[0, 1][1, 1] 在同一个岛上,但我们还不知道所有四个坐标是否都在同一个岛上。为了确定是否是这种情况,我们将查看所有坐标对,其中一个来自group1,另一个来自group2。如果我们比较[0, 0][1, 1],我们不能断定它们在同一个岛(或不同的岛)上。但是,当我们比较[0, 0][0, 1] 时,我们看到它们在同一个岛上(因为它们在相邻列中位于同一行),因此我们推断两组的所有元素都在同一个岛上。例如,我们可以将坐标从group2 移动到group1,并从进一步考虑中排除group2

现在考虑same_island? 方法对这两组执行的步骤。

group1 = [[0, 0], [1, 0]]
group2 = [[0, 1], [1, 1]]

a = group1.product(group2)
  #=> [[[0, 0], [0, 1]], [[0, 0], [1, 1]], [[1, 0], [0, 1]],
  #    [[1, 0], [1, 1]]]

(i1,j1),(i2,j2) = a.first
   #=> [[0, 0], [0, 1]]
i1 #=> 0
j1 #=> 0
i2 #=> 0
j2 #=> 1
b = i1==i2 && (j1-j2).abs == 1
   #=> true
c = j1==j2 && (i1-i2).abs == 1
   #=> false
b || c
   #=> true

已发现所考虑的第一对坐标位于同一个岛上。 (其实c的计算不会进行,因为b被发现是true。)

find_islands 已评论

为了展示一切如何组合在一起,我将在方法 find_islands 中添加 cmets。

def find_islands(input)
  groups = input.each_with_index.with_object([]) { |(row,i),groups|
    row.each_with_index { |c,j| groups << [[i,j]] if c == 'X' } }
  islands = []
  # the following is the same as: until groups.empty?
  while groups.any?
    # pop removes and returns last element of `groups`. `groups` is modified    
    last_group = groups.pop
    # iterate over indices of `groups` looking for one whose members
    # are on the same island as last_group 
    idx = groups.each_index.find { |idx| same_island?(groups[idx], last_group) }
    if idx.nil?
      # last_group is an island, so append it to islands
      islands << last_group
    else
      # groups[idx] members are found to be on the same island as last_group,
      # so add last_group to group groups[idx]
      groups[idx] += last_group
    end
  end
  # sort coordinates in each island, to improve presentation 
  islands.map(&:sort)
end

1 也就是说,没有元素groups[i,j][4, 3][5, 3] 位于同一列且相隔一列或位于同一行且相隔一列

2 模块Kernel 中的实例方法记录在Object 类中。前两段见Kernel,第二段见Object

【讨论】:

  • 感谢您花时间解释。我被困在您迭代组数组以找到“岛屿”的部分。从概念上讲,我了解您是如何获得剩余 4 个组的,我只是不太了解您是如何实现逻辑的
  • Natasha,我已经进行了一些编辑(并进行了一次更正,将 zip 替换为 product)。这是否有助于您了解这些方法的工作原理?
猜你喜欢
  • 2016-02-12
  • 2022-01-06
  • 1970-01-01
  • 2018-10-27
  • 2018-02-16
  • 2016-03-17
  • 2016-09-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多