我正在查看一些面试问题,我偶然发现了这个:
有一个 mxn 数组。数组中的块用 1 表示,0 表示没有块。您应该找到数组中的对象数。一个对象只不过是一组水平和/或垂直连接的块。
例如
0 1 0 0
0 1 0 0
0 1 1 0
0 0 0 0
0 1 1 0
答案:这个数组中有 2 个对象。L 形对象和最后一行的对象。
我无法想出一个可以捕捉“u”(如下所示)形状的算法。我应该如何处理这个?
0 1 0 1
0 1 0 1
0 1 1 1
0 0 0 0
0 1 1 0
我正在查看一些面试问题,我偶然发现了这个:
有一个 mxn 数组。数组中的块用 1 表示,0 表示没有块。您应该找到数组中的对象数。一个对象只不过是一组水平和/或垂直连接的块。
例如
0 1 0 0
0 1 0 0
0 1 1 0
0 0 0 0
0 1 1 0
答案:这个数组中有 2 个对象。L 形对象和最后一行的对象。
我无法想出一个可以捕捉“u”(如下所示)形状的算法。我应该如何处理这个?
0 1 0 1
0 1 0 1
0 1 1 1
0 0 0 0
0 1 1 0
一种方法是使用Flood Fill。该算法将是这样的:
for row in block_array:
for block in row:
if BLOCK IS A ONE and BLOCK NOT VISITED:
FLOOD_FILL starting from BLOCK
您将标记在洪水填充过程中访问过的项目,并从那里跟踪形状。
这适用于 C#
static void Main()
{
int[][] array = { new int[] { 0, 1, 0, 1 }, new int[] { 0, 1, 0, 1 }, new int[] { 0, 1, 1, 1 }, new int[] { 0, 0, 0, 0 }, new int[] { 0, 1, 1, 0 } };
Console.WriteLine(GetNumber(array));
Console.ReadKey();
}
static int GetNumber(int[][] array)
{
int objects = 0;
for (int i = 0; i < array.Length; i++)
for (int j = 0; j < array[i].Length; j++)
if (ClearObjects(array, i, j))
objects++;
return objects;
}
static bool ClearObjects(int[][] array, int x, int y)
{
if (x < 0 || y < 0 || x >= array.Length || y >= array[x].Length) return false;
if (array[x][y] == 1)
{
array[x][y] = 0;
ClearObjects(array, x - 1, y);
ClearObjects(array, x + 1, y);
ClearObjects(array, x, y - 1);
ClearObjects(array, x, y + 1);
return true;
}
return false;
}
我会使用不相交集(连接组件)。
开始时,每个值为 1 的 (i,j) 矩阵元素本身就是一个元素集。
然后你可以迭代每个矩阵元素,对于每个元素 (i,j) 你应该加入每个相邻的位置集 {(i+1,j),(i-1,j),(i,j+1),( i,j-1)} 到 (i,j) 如果其值为 1,则设置。
最后,不同集合的数量就是对象的数量。
我会使用不相交集数据结构(也称为联合查找)。
简而言之:对于每个连接的组件,使用每个元素的单个链接作为“父”指针来构建“反向树”。跟随父指针最终将找到树的根,用于组件标识(因为它对于连接组件的每个成员都是相同的)。要合并相邻组件,请将一个组件的根设为另一个组件的父级(不再是根,因为它现在有父级)。
两个简单的优化使这个数据结构非常高效。一种是,让所有根查询“折叠”它们的路径以直接指向根——这样,下一个查询只需要一步。另一种是,始终使用两棵树的“更深”作为新根;这需要为每个根保持一个“等级”分数。
此外,为了更有效地评估邻居,您可以考虑逐行预处理您的输入。这样,1
同一行上的 's 的连续片段可以作为单个连接组件开始生命,并且您可以根据邻居标准有效地扫描前一行的片段。
我的两美分(斜线)算法:
1. List only the 1's.
2. Group (collect connected ones).
在哈斯克尔:
import Data.List (elemIndices, delete)
example1 =
[[0,1,0,0]
,[0,1,0,0]
,[0,1,1,0]
,[0,0,0,0]
,[0,1,1,0]]
example2 =
[[0,1,0,1]
,[0,1,0,1]
,[0,1,1,1]
,[0,0,0,0]
,[0,1,1,0]]
objects a ws = solve (mapIndexes a) [] where
mapIndexes s =
concatMap (\(y,xs)-> map (\x->(y,x)) xs) $ zip [0..] (map (elemIndices s) ws)
areConnected (y,x) (y',x') =
(y == y' && abs (x-x') == 1) || (x == x' && abs (y-y') == 1)
solve [] r = r
solve (x:xs) r =
let r' = solve' xs [x]
in solve (foldr delete xs r') (r':r)
solve' vs r =
let ys = filter (\y -> any (areConnected y) r) vs
in if null ys then r else solve' (foldr delete vs ys) (ys ++ r)
输出:
*Main> objects 1 example1
[[(4,2),(4,1)],[(2,2),(2,1),(1,1),(0,1)]]
(0.01 secs, 1085360 bytes)
*Main> objects 1 example2
[[(4,2),(4,1)],[(0,3),(1,3),(2,3),(2,2),(2,1),(1,1),(0,1)]]
(0.01 secs, 1613356 bytes)
为什么不只查看给定块的所有相邻单元格?从其中包含 1 的某个单元格开始,跟踪您之前访问过的单元格,并继续查看相邻的单元格,直到您再也找不到 1 为止。然后移动到您尚未查看的单元格并重复该过程。
像这样的东西应该工作: