如何在从左到右和从上到下排序的二维数组中搜索数字?
Posted
技术标签:
【中文标题】如何在从左到右和从上到下排序的二维数组中搜索数字?【英文标题】:How do I search for a number in a 2d array sorted left to right and top to bottom? 【发布时间】:2011-01-28 07:19:07 【问题描述】:我最近收到了这个面试问题,我很好奇有什么好的解决方案。
假设我有一个二维数组,其中所有 数组中的数字在增加 从左到右和从上到下的顺序 底部。
什么是最好的搜索和 确定目标号码是否在 数组?
现在,我的第一个倾向是使用二进制搜索,因为我的数据已排序。我可以确定一个数字是否在 O(log N) 时间内位于一行中。然而,让我失望的是两个方向。
我认为可能可行的另一个解决方案是从中间的某个地方开始。如果中间值小于我的目标,那么我可以确定它在矩阵的左方部分中。然后我沿对角线移动并再次检查,减小目标可能所在的正方形的大小,直到我确定目标编号为止。
有没有人有解决这个问题的好主意?
示例数组:
从左到右,从上到下排序。
1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11
【问题讨论】:
简单的问题:你是否可以拥有一个具有相同值的邻居:[[1 1][1 1]]
?
【参考方案1】:
这是一个简单的方法:
-
从左下角开始。
如果目标小于该值,它一定在我们上方,所以上移一个。
否则我们知道目标不能在该列中,所以向右移动一个。
转到 2。
对于NxM
数组,它在O(N+M)
中运行。我认为很难做得更好。 :)
编辑:很多很好的讨论。我说的是上面的一般情况;显然,如果N
或M
很小,您可以使用二进制搜索方法在接近对数时间的时间内完成此操作。
这里有一些细节,对于那些好奇的人:
历史
这个简单的算法称为Saddleback Search。它已经存在了一段时间,当N == M
时它是最佳的。一些参考资料:
但是,当N < M
时,直觉表明二分查找应该能够比O(N+M)
做得更好:例如,当N == 1
时,纯二分查找将以对数而不是线性时间运行。
最坏情况界限
Richard Bird 在 2006 年的一篇论文中检验了二进制搜索可以改进鞍背算法的直觉:
Richard S. Bird,Improving Saddleback Search: A Lesson in Algorithm Design,项目构建数学,第 82--89 页,第 4014 卷,2006 年。Bird 使用一种相当不寻常的对话技术向我们展示了对于N <= M
,这个问题的下限为Ω(N * log(M/N))
。这个界限是有意义的,因为它在N == M
时为我们提供线性性能,在N == 1
时为我们提供对数性能。
矩形数组的算法
一种使用逐行二进制搜索的方法如下所示:
-
从
N < M
所在的矩形阵列开始。假设N
是行,M
是列。
在中间行对value
进行二分搜索。如果我们找到它,我们就完成了。
否则我们会找到一对相邻的数字s
和g
,其中s < value < g
。
s
上方和左侧的数字矩形小于value
,因此我们可以消除它。
g
下方和右侧的矩形大于value
,因此我们可以消除它。
对剩余的两个矩形分别执行步骤 (2)。
就最坏情况的复杂性而言,该算法在log(M)
工作以消除一半可能的解决方案,然后在两个较小的问题上递归调用自身两次。我们确实必须为每一行重复一个较小版本的 log(M)
工作,但是如果行数与列数相比很小,那么能够在对数时间内消除所有这些列就开始了变得有价值。
这使算法的复杂度为T(N,M) = log(M) + 2 * T(M/2, N/2)
,Bird 显示为O(N * log(M/N))
。
Another approach posted by Craig Gidney 描述了一种类似于上述方法的算法:它使用M/N
的步长一次检查一行。他的分析表明,这也会导致O(N * log(M/N))
的性能。
性能比较
Big-O 分析一切都很好,但这些方法在实践中的效果如何?下面的图表检查了四种算法,用于越来越“方形”的数组:
(“朴素”算法只是搜索数组的每个元素。“递归”算法在上面进行了描述。“混合”算法是Gidney's algorithm 的实现。对于每个数组大小,通过对每个数组的计时来衡量性能算法对 1,000,000 个随机生成的数组的固定集合。)
一些值得注意的点:
正如预期的那样,“二分搜索”算法在矩形阵列上的性能最佳,而鞍背算法在方形阵列上的性能最佳。 对于一维数组,Saddleback 算法的性能比“朴素”算法差,可能是因为它对每个项目进行了多次比较。 “二分搜索”算法对方形数组的性能影响可能是由于运行重复二分搜索的开销所致。总结
巧妙地使用二分搜索可以为矩形和方形数组提供O(N * log(M/N)
性能。 O(N + M)
"saddleback" 算法要简单得多,但随着数组变得越来越矩形,性能会下降。
【讨论】:
对对角线游走应用二分查找,得到 O(logN) 或 O(logM),取其高者。 @Anurag - 我认为复杂性不太好。二分搜索会给你一个很好的起点,但你必须一直走一维或另一维,在最坏的情况下,你仍然可以从一个角落开始,在另一个角落结束。 如果 N = 1 和 M = 1000000 我可以做得比 O(N+M) 更好,所以另一种解决方案是在每一行中应用二进制搜索,这会带来 O(N*log(M)) 其中NM==N
我们想要O(N)
复杂性,而不是O(N*log(N/N))
,因为后者为零。当N<=M
时,正确的“统一”锐界是O(N*(log(M/N)+1))
。【参考方案2】:
这个问题需要Θ(b lg(t))
时间,其中b = min(w,h)
和t=b/max(w,h)
。我在this blog post讨论解决方案。
下限
攻击者可以通过将自己限制在主对角线上来强制算法进行Ω(b lg(t))
查询:
图例:白色单元格是较小的项目,灰色单元格是较大的项目,黄色单元格是较小或相等的项目,橙色单元格是较大或相等的项目。对手迫使解决方案是算法最后查询的黄色或橙色单元格。
注意有b
大小为t
的独立排序列表,需要Ω(b lg(t))
查询才能完全消除。
算法
-
(不失一般性假设
w >= h
)
将目标项目与有效区域右上角左侧的单元格t
进行比较
如果单元格的项目匹配,则返回当前位置。
如果单元格的项小于目标项,则使用二分搜索消除行中剩余的t
单元格。如果在执行此操作时找到匹配项,则返回其位置。
否则单元格的项目多于目标项目,消除t
短列。
如果没有剩余有效区域,返回失败
转到第 2 步
寻找物品:
确定一个项目不存在:
图例:白色单元格是较小的项目,灰色单元格是较大的项目,绿色单元格是相等的项目。
分析
有b*t
短列要消除。有b
长行要消除。消除长排成本O(lg(t))
时间。消除 t
短列会花费 O(1)
时间。
在最坏的情况下,我们将不得不消除每一列和每一行,这需要时间O(lg(t)*b + b*t*1/t) = O(b lg(t))
。
请注意,我假设 lg
钳制到高于 1 的结果(即 lg(x) = log_2(max(2,x))
)。这就是为什么当w=h
,意思是t=1
,我们得到O(b lg(1)) = O(b) = O(w+h)
的预期边界。
代码
public static Tuple<int, int> TryFindItemInSortedMatrix<T>(this IReadOnlyList<IReadOnlyList<T>> grid, T item, IComparer<T> comparer = null)
if (grid == null) throw new ArgumentNullException("grid");
comparer = comparer ?? Comparer<T>.Default;
// check size
var width = grid.Count;
if (width == 0) return null;
var height = grid[0].Count;
if (height < width)
var result = grid.LazyTranspose().TryFindItemInSortedMatrix(item, comparer);
if (result == null) return null;
return Tuple.Create(result.Item2, result.Item1);
// search
var minCol = 0;
var maxRow = height - 1;
var t = height / width;
while (minCol < width && maxRow >= 0)
// query the item in the minimum column, t above the maximum row
var luckyRow = Math.Max(maxRow - t, 0);
var cmpItemVsLucky = comparer.Compare(item, grid[minCol][luckyRow]);
if (cmpItemVsLucky == 0) return Tuple.Create(minCol, luckyRow);
// did we eliminate t rows from the bottom?
if (cmpItemVsLucky < 0)
maxRow = luckyRow - 1;
continue;
// we eliminated most of the current minimum column
// spend lg(t) time eliminating rest of column
var minRowInCol = luckyRow + 1;
var maxRowInCol = maxRow;
while (minRowInCol <= maxRowInCol)
var mid = minRowInCol + (maxRowInCol - minRowInCol + 1) / 2;
var cmpItemVsMid = comparer.Compare(item, grid[minCol][mid]);
if (cmpItemVsMid == 0) return Tuple.Create(minCol, mid);
if (cmpItemVsMid > 0)
minRowInCol = mid + 1;
else
maxRowInCol = mid - 1;
maxRow = mid - 1;
minCol += 1;
return null;
【讨论】:
很有趣,可能部分超出了我的想象。我不熟悉这种“对手”风格的复杂性分析。对手实际上是在您搜索时以某种方式动态地更改数组,还是他只是您在最坏情况下的搜索中遇到的坏运气的名字? @The111 厄运相当于某人选择了一条不违反目前所见事物的坏路,因此这两个定义的结果相同。实际上,我很难找到专门针对计算复杂性来解释该技术的链接……我认为这是一个更为知名的想法。 因为 log(1)=0,复杂度估计应该用O(b*(lg(t)+1))
而不是O(b*lg(t))
。写得很好,尤其是。在显示“最坏情况”界限时提醒注意“对手技术”。
@hardmath 我在答案中提到了这一点。我澄清了一点。【参考方案3】:
对于这个问题,我会使用分而治之的策略,类似于你的建议,但细节有点不同。
这将是对矩阵子范围的递归搜索。
在每一步中,选择范围中间的一个元素。如果找到的价值正是您所寻求的,那么您就完成了。
否则,如果找到的值小于您正在寻找的值,那么您就知道它不在您当前位置的上方和左侧的象限中。因此,递归搜索两个子范围:当前位置下方的所有内容(仅),以及当前位置或上方的所有内容(唯一)。
否则,(找到的值大于您正在寻找的值)您知道它不在您当前位置下方和右侧的象限中。因此递归地搜索两个子范围:当前位置左侧的所有内容(不包括),以及当前列或右侧列中当前位置上方的所有内容(不包括)。
还有 ba-da-bing,你找到了。
请注意,每个递归调用仅处理当前子范围,而不是(例如)当前位置上方的所有行。只是当前子范围内的那些。
这里有一些伪代码:
bool numberSearch(int[][] arr, int value, int minX, int maxX, int minY, int maxY)
if (minX == maxX and minY == maxY and arr[minX,minY] != value)
return false
if (arr[minX,minY] > value) return false; // Early exits if the value can't be in
if (arr[maxX,maxY] < value) return false; // this subrange at all.
int nextX = (minX + maxX) / 2
int nextY = (minY + maxY) / 2
if (arr[nextX,nextY] == value)
print nextX,nextY
return true
else if (arr[nextX,nextY] < value)
if (numberSearch(arr, value, minX, maxX, nextY + 1, maxY))
return true
return numberSearch(arr, value, nextX + 1, maxX, minY, nextY)
else
if (numberSearch(arr, value, minX, nextX - 1, minY, maxY))
return true
reutrn numberSearch(arr, value, nextX, maxX, minY, nextY)
【讨论】:
+1:这是一个 O(log(N)) 策略,因此是一个很好的订单。 @Rex Kerr - 它看起来像 O(log(N)),因为这是正常的二进制搜索,但是请注意,每个级别可能有两个递归调用。这意味着它比普通对数差得多。我不认为最坏的情况比 O(M+N) 更好,因为可能必须搜索每一行或每一列。不过,我猜这个算法可以击败很多值的最坏情况。最好的部分是它是可并行化的,因为这是硬件最近的发展方向。 @JLW:它是 O(log(N))——但实际上是 O(log_(4/3)(N^2)) 或类似的东西。请参阅下面的 Svante 的回答。您的答案实际上是相同的(如果您按照我认为的方式表示递归)。 @Svante - 子数组不重叠。在第一个选项中,它们没有共同的 y 元素。在第二个选项中,它们没有共同的 x 元素。 我不确定这是否是对数。我使用近似递归关系 T(0) = 1, T(A) = T(A/2) + T(A/4) + 1 计算了复杂度,其中 A 是搜索区域,最终得到 T( A) = O(Fib(lg(A))),大约为 O(A^0.7),比 O(A^0.5) 的 O(n+m) 差。也许我犯了一些愚蠢的错误,但看起来算法在无结果的分支上浪费了很多时间。【参考方案4】:到目前为止给出的两个主要答案似乎是O(log N)
“ZigZag 方法”和O(N+M)
Binary Search 方法。我想我会做一些测试,将这两种方法与一些不同的设置进行比较。详情如下:
在每个测试中,数组都是 N x N 平方,N 从 125 到 8000 不等(我的 JVM 堆可以处理的最大)。对于每个数组大小,我在数组中随机选择一个位置来放置一个2
。然后我在任何可能的地方(在 2 的右侧和下方)放置一个 3
,然后用 1
填充数组的其余部分。一些较早的评论者似乎认为这种类型的设置会为两种算法产生最坏情况下的运行时间。对于每个数组大小,我为 2(搜索目标)选择了 100 个不同的随机位置并运行了测试。我记录了每个算法的平均运行时间和最坏情况下的运行时间。因为它发生得太快而无法在 Java 中获得良好的 ms 读数,并且因为我不信任 Java 的 nanoTime(),所以我重复每个测试 1000 次,只是为了始终添加一个统一的偏差因子。结果如下:
ZigZag 在平均和最坏情况时间的每次测试中都击败了二进制,但是,它们或多或少都在一个数量级之内。
这是Java代码:
public class SearchSortedArray2D
static boolean findZigZag(int[][] a, int t)
int i = 0;
int j = a.length - 1;
while (i <= a.length - 1 && j >= 0)
if (a[i][j] == t) return true;
else if (a[i][j] < t) i++;
else j--;
return false;
static boolean findBinarySearch(int[][] a, int t)
return findBinarySearch(a, t, 0, 0, a.length - 1, a.length - 1);
static boolean findBinarySearch(int[][] a, int t,
int r1, int c1, int r2, int c2)
if (r1 > r2 || c1 > c2) return false;
if (r1 == r2 && c1 == c2 && a[r1][c1] != t) return false;
if (a[r1][c1] > t) return false;
if (a[r2][c2] < t) return false;
int rm = (r1 + r2) / 2;
int cm = (c1 + c2) / 2;
if (a[rm][cm] == t) return true;
else if (a[rm][cm] > t)
boolean b1 = findBinarySearch(a, t, r1, c1, r2, cm - 1);
boolean b2 = findBinarySearch(a, t, r1, cm, rm - 1, c2);
return (b1 || b2);
else
boolean b1 = findBinarySearch(a, t, r1, cm + 1, rm, c2);
boolean b2 = findBinarySearch(a, t, rm + 1, c1, r2, c2);
return (b1 || b2);
static void randomizeArray(int[][] a, int N)
int ri = (int) (Math.random() * N);
int rj = (int) (Math.random() * N);
a[ri][rj] = 2;
for (int i = 0; i < N; i++)
for (int j = 0; j < N; j++)
if (i == ri && j == rj) continue;
else if (i > ri || j > rj) a[i][j] = 3;
else a[i][j] = 1;
public static void main(String[] args)
int N = 8000;
int[][] a = new int[N][N];
int randoms = 100;
int repeats = 1000;
long start, end, duration;
long zigMin = Integer.MAX_VALUE, zigMax = Integer.MIN_VALUE;
long binMin = Integer.MAX_VALUE, binMax = Integer.MIN_VALUE;
long zigSum = 0, zigAvg;
long binSum = 0, binAvg;
for (int k = 0; k < randoms; k++)
randomizeArray(a, N);
start = System.currentTimeMillis();
for (int i = 0; i < repeats; i++) findZigZag(a, 2);
end = System.currentTimeMillis();
duration = end - start;
zigSum += duration;
zigMin = Math.min(zigMin, duration);
zigMax = Math.max(zigMax, duration);
start = System.currentTimeMillis();
for (int i = 0; i < repeats; i++) findBinarySearch(a, 2);
end = System.currentTimeMillis();
duration = end - start;
binSum += duration;
binMin = Math.min(binMin, duration);
binMax = Math.max(binMax, duration);
zigAvg = zigSum / randoms;
binAvg = binSum / randoms;
System.out.println(findZigZag(a, 2) ?
"Found via zigzag method. " : "ERROR. ");
//System.out.println("min search time: " + zigMin + "ms");
System.out.println("max search time: " + zigMax + "ms");
System.out.println("avg search time: " + zigAvg + "ms");
System.out.println();
System.out.println(findBinarySearch(a, 2) ?
"Found via binary search method. " : "ERROR. ");
//System.out.println("min search time: " + binMin + "ms");
System.out.println("max search time: " + binMax + "ms");
System.out.println("avg search time: " + binAvg + "ms");
【讨论】:
+1 是的,数据。 :) 看看这两种方法在 NxM 数组上的表现可能也很有趣,因为二进制搜索似乎在直觉上应该变得更有用,我们越接近一维情况。【参考方案5】:这是问题下限的简短证明。
你不能比线性时间做得更好(就数组维度而言,而不是元素数量)。在下面的数组中,每个标记为 *
的元素可以是 5 或 6(独立于其他元素)。因此,如果您的目标值为 6(或 5),则算法需要检查所有这些值。
1 2 3 4 *
2 3 4 * 7
3 4 * 7 8
4 * 7 8 9
* 7 8 9 10
当然,这也扩展到更大的数组。这意味着this answer 是最优的。
更新:正如 Jeffrey L Whitledge 所指出的,它仅作为运行时间与输入数据大小(作为单个变量处理)的渐近下限是最优的。可以提高在两个数组维度上视为双变量函数的运行时间。
【讨论】:
您尚未证明该答案是最佳的。例如,考虑一个跨 10 且向下 100 万的数组,其中第五行包含的值都高于目标值。在这种情况下,所提出的算法将在接近目标之前对 999,995 个值进行线性搜索。像我这样的分叉算法在接近目标之前只会搜索 18 个值。在所有其他情况下,它的性能(渐近地)不比所提出的算法差。 @Jeffrey:这是悲观情况下问题的下界。您可以针对良好的输入进行优化,但有些输入无法做得比线性更好。 是的,确实存在输入,你不能做得比线性更好。在这种情况下,我的算法执行线性搜索。但是还有其他输入可以比线性更好地方式。因此,建议的解决方案不是最优的,因为它总是进行线性搜索。 这表明算法必须花费 BigOmega(min(n,m)) 时间,而不是 BigOmega(n+m)。这就是为什么当一维明显更小时你可以做得更好。例如,如果您知道只有 1 行,您可以在对数时间内解决问题。我认为最佳算法需要时间 O(min(n+m, n lg m, m lg n))。 相应地更新了答案。【参考方案6】:我认为这是答案,它适用于任何类型的排序矩阵
bool findNum(int arr[][ARR_MAX],int xmin, int xmax, int ymin,int ymax,int key)
if (xmin > xmax || ymin > ymax || xmax < xmin || ymax < ymin) return false;
if ((xmin == xmax) && (ymin == ymax) && (arr[xmin][ymin] != key)) return false;
if (arr[xmin][ymin] > key || arr[xmax][ymax] < key) return false;
if (arr[xmin][ymin] == key || arr[xmax][ymax] == key) return true;
int xnew = (xmin + xmax)/2;
int ynew = (ymin + ymax)/2;
if (arr[xnew][ynew] == key) return true;
if (arr[xnew][ynew] < key)
if (findNum(arr,xnew+1,xmax,ymin,ymax,key))
return true;
return (findNum(arr,xmin,xmax,ynew+1,ymax,key));
else
if (findNum(arr,xmin,xnew-1,ymin,ymax,key))
return true;
return (findNum(arr,xmin,xmax,ymin,ynew-1,key));
【讨论】:
【参考方案7】:有趣的问题。考虑一下这个想法 - 创建一个边界,其中所有数字都大于您的目标,而另一个边界则所有数字都小于您的目标。如果两者之间有任何东西,那就是你的目标。
如果我在您的示例中寻找 3,我会阅读第一行直到达到 4,然后寻找大于 3 的最小相邻数字(包括对角线):
1 2 4 5 6 2 3 5 7 84 6 8 9 10 5 8 9 10 11
现在我对那些小于 3 的数字做同样的事情:
1 2 4 5 62 3 5 7 84 6 8 9 10 5 8 9 10 11
现在我问,这两个边界内有什么东西吗?如果是,它必须是 3。如果不是,那么没有 3。有点间接,因为我实际上并没有找到数字,我只是推断它必须在那里。这具有计算所有 3 的额外好处。
我在一些例子上试过这个,它似乎工作正常。
【讨论】:
不予置评的否决票?我认为这是 O(N^1/2) 因为最坏情况下的性能需要检查对角线。至少给我看一个这个方法不起作用的反例! +1:很好的解决方案...很有创意,而且它找到了所有解决方案。【参考方案8】:通过数组的对角线进行二分搜索是最好的选择。 我们可以找出元素是否小于或等于对角线上的元素。
【讨论】:
【参考方案9】:十年来我一直在面试中问这个问题,我认为只有一个人能够提出最佳算法。
我的解决方案一直是:
二分查找中间对角线,即向下向右的对角线,包含(rows.count/2, columns.count/2)
处的项目。
如果找到目标号码,则返回true。
否则,将找到两个数字(u
和 v
),使得u
小于目标,v
大于目标,v
右一下来自u
。
递归搜索u
右侧和v
顶部的子矩阵以及u
底部和v
左侧的子矩阵。
我相信这是对Nate here 给出的算法的严格改进,因为搜索对角线通常可以减少一半以上的搜索空间(如果矩阵接近正方形),而搜索行或列总是结果正好消除了一半。
这是 Swift 中的代码(可能不是非常 Swifty):
import Cocoa
class Solution
func searchMatrix(_ matrix: [[Int]], _ target: Int) -> Bool
if (matrix.isEmpty || matrix[0].isEmpty)
return false
return _searchMatrix(matrix, 0..<matrix.count, 0..<matrix[0].count, target)
func _searchMatrix(_ matrix: [[Int]], _ rows: Range<Int>, _ columns: Range<Int>, _ target: Int) -> Bool
if (rows.count == 0 || columns.count == 0)
return false
if (rows.count == 1)
return _binarySearch(matrix, rows.lowerBound, columns, target, true)
if (columns.count == 1)
return _binarySearch(matrix, columns.lowerBound, rows, target, false)
var lowerInflection = (-1, -1)
var upperInflection = (Int.max, Int.max)
var currentRows = rows
var currentColumns = columns
while (currentRows.count > 0 && currentColumns.count > 0 && upperInflection.0 > lowerInflection.0+1)
let rowMidpoint = (currentRows.upperBound + currentRows.lowerBound) / 2
let columnMidpoint = (currentColumns.upperBound + currentColumns.lowerBound) / 2
let value = matrix[rowMidpoint][columnMidpoint]
if (value == target)
return true
if (value > target)
upperInflection = (rowMidpoint, columnMidpoint)
currentRows = currentRows.lowerBound..<rowMidpoint
currentColumns = currentColumns.lowerBound..<columnMidpoint
else
lowerInflection = (rowMidpoint, columnMidpoint)
currentRows = rowMidpoint+1..<currentRows.upperBound
currentColumns = columnMidpoint+1..<currentColumns.upperBound
if (lowerInflection.0 == -1)
lowerInflection = (upperInflection.0-1, upperInflection.1-1)
else if (upperInflection.0 == Int.max)
upperInflection = (lowerInflection.0+1, lowerInflection.1+1)
return _searchMatrix(matrix, rows.lowerBound..<lowerInflection.0+1, upperInflection.1..<columns.upperBound, target) || _searchMatrix(matrix, upperInflection.0..<rows.upperBound, columns.lowerBound..<lowerInflection.1+1, target)
func _binarySearch(_ matrix: [[Int]], _ rowOrColumn: Int, _ range: Range<Int>, _ target: Int, _ searchRow : Bool) -> Bool
if (range.isEmpty)
return false
let midpoint = (range.upperBound + range.lowerBound) / 2
let value = (searchRow ? matrix[rowOrColumn][midpoint] : matrix[midpoint][rowOrColumn])
if (value == target)
return true
if (value > target)
return _binarySearch(matrix, rowOrColumn, range.lowerBound..<midpoint, target, searchRow)
else
return _binarySearch(matrix, rowOrColumn, midpoint+1..<range.upperBound, target, searchRow)
【讨论】:
【参考方案10】:A.在目标编号可能在的那些行上进行二进制搜索。
B.制作图表:通过始终采用最小的未访问邻居节点并在发现太大数字时回溯来查找数字
【讨论】:
【参考方案11】:二分搜索是最好的方法,imo。从 1/2 x 开始,1/2 y 会将其切成两半。即 5x5 的正方形类似于 x == 2 / y == 3 。我向下舍入一个值,向上舍入一个值,以便在目标值的方向上更好地定位。
为了清楚起见,下一次迭代会给你类似 x == 1 / y == 2 OR x == 3 / y == 5
【讨论】:
【参考方案12】:首先,让我们假设我们使用的是正方形。
1 2 3
2 3 4
3 4 5
1.搜索正方形
我会在对角线上使用二分搜索。目标是找到不严格低于目标数字的较小数字。
例如,假设我正在寻找4
,那么我最终会在(2,2)
找到5
。
然后,我确信如果4
在表格中,它位于(x,2)
或(2,x)
的位置,x
在[0,2]
中。好吧,这只是 2 次二进制搜索。
复杂性并不令人生畏:O(log(N))
(对长度范围 N
进行 3 次二进制搜索)
2。搜索一个矩形,幼稚的方法
当然,当N
和M
不同(带有一个矩形)时,它会变得有点复杂,考虑这种退化的情况:
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17
假设我正在寻找9
... 对角线方法仍然很好,但是对角线的定义发生了变化。这里我的对角线是[1, (5 or 6), 17]
。假设我选择了[1,5,17]
,那么我知道如果9
在表格中,那么它要么在子部分中:
5 6 7 8
6 7 8 9
10 11 12 13 14 15 16
这给了我们 2 个矩形:
5 6 7 8 10 11 12 13 14 15 16
6 7 8 9
所以我们可以递归!可能从元素较少的那个开始(尽管在这种情况下它会杀死我们)。
我应该指出,如果其中一个维度小于3
,我们不能应用对角线方法,必须使用二分查找。这里的意思是:
10 11 12 13 14 15 16
应用二分查找,未找到
对5 6 7 8
应用二分查找,未找到
对6 7 8 9
应用二分查找,未找到
这很棘手,因为要获得良好的性能,您可能需要区分几种情况,具体取决于一般形状......
3.搜索一个矩形,残酷的方法
如果我们处理一个正方形会容易得多......所以让我们把事情摆正吧。
1 2 3 4 5 6 7 8
2 3 4 5 6 7 8 9
10 11 12 13 14 15 16 17
17 . . . . . . 17
. .
. .
. .
17 . . . . . . 17
我们现在有一个正方形。
当然,我们可能不会实际创建这些行,我们可以简单地模拟它们。
def get(x,y):
if x < N and y < M: return table[x][y]
else: return table[N-1][M-1] # the max
所以它的行为就像一个正方形而不占用更多内存(以速度为代价,可能,取决于缓存......哦,好吧:p)
【讨论】:
【参考方案13】:编辑:
我误解了这个问题。正如 cmets 指出的那样,这只适用于更受限制的情况。
在像 C 这样以行优先顺序存储数据的语言中,只需将其视为大小为 n * m 的一维数组并使用二分查找。
【讨论】:
是的,为什么让它变得比它必须的更复杂。 数组未排序,因此无法对其应用 bin 搜索 这仅在每行的最后一个元素高于下一行的第一个元素时才有效,这比问题提出的限制要严格得多。 谢谢,我已经编辑了我的答案。没有仔细阅读,尤其是示例数组。【参考方案14】:我有一个递归的分治法。 一个步骤的基本思想是:我们知道左上(LU)是最小的,右下(RB)是最大的。所以给定的No(N)必须:N>=LU和N
IF N==LU and N==RB::::Element Found and Abort 返回位置/索引 如果 N>=LU 且 N=LU 且 N
我的算法是正确的我已经在我朋友的电脑上实现了。 复杂性:在最坏的情况下,每 4 次比较可用于将元素的总数推导出为四分之一。 所以我的复杂度是 1 + 4 x lg(n) + 4 但真的希望这对 O(n) 起作用
我认为我的复杂度计算有问题,如果有,请更正..
【讨论】:
【参考方案15】:最佳解决方案是从具有最小值的左上角开始。沿对角线向下向右移动,直到碰到一个其值 >= 给定元素的值的元素。如果元素的值等于给定元素的值,则返回 found 为真。
否则,从这里我们可以通过两种方式进行。
策略 1:
-
在列中向上移动并搜索给定元素,直到到达末尾。如果找到,则返回找到为 true
在行中向左移动并搜索给定元素,直到到达末尾。如果找到,则返回找到为 true
发现返回错误
策略 2: 让 i 表示行索引, j 表示我们停止的对角线元素的列索引。 (在这里,我们有 i = j,顺便说一句)。让 k = 1。
重复以下步骤直到 i-k >= 0-
搜索 a[i-k][j] 是否等于给定元素。如果是,则返回发现为真。
搜索 a[i][j-k] 是否等于给定元素。如果是,则返回发现为真。
增量k
1 2 4 5 6 2 3 5 7 8 4 6 8 9 10 5 8 9 10 11
【讨论】:
【参考方案16】:public boolean searchSortedMatrix(int arr[][] , int key , int minX , int maxX , int minY , int maxY)
// base case for recursion
if(minX > maxX || minY > maxY)
return false ;
// early fails
// array not properly intialized
if(arr==null || arr.length==0)
return false ;
// arr[0][0]> key return false
if(arr[minX][minY]>key)
return false ;
// arr[maxX][maxY]<key return false
if(arr[maxX][maxY]<key)
return false ;
//int temp1 = minX ;
//int temp2 = minY ;
int midX = (minX+maxX)/2 ;
//if(temp1==midX)midX+=1 ;
int midY = (minY+maxY)/2 ;
//if(temp2==midY)midY+=1 ;
// arr[midX][midY] = key ? then value found
if(arr[midX][midY] == key)
return true ;
// alas ! i have to keep looking
// arr[midX][midY] < key ? search right quad and bottom matrix ;
if(arr[midX][midY] < key)
if( searchSortedMatrix(arr ,key , minX,maxX , midY+1 , maxY))
return true ;
// search bottom half of matrix
if( searchSortedMatrix(arr ,key , midX+1,maxX , minY , maxY))
return true ;
// arr[midX][midY] > key ? search left quad matrix ;
else
return(searchSortedMatrix(arr , key , minX,midX-1,minY,midY-1));
return false ;
【讨论】:
【参考方案17】:我建议,将所有字符存储在 2D list
中。然后找到所需元素的索引,如果它存在于列表中。
如果不存在,则打印适当的消息,否则将行和列打印为:
row = (index/total_columns)
和 column = (index%total_columns -1)
这只会导致列表中的二进制搜索时间。
请提出任何更正建议。 :)
【讨论】:
【参考方案18】:如果 O(M log(N)) 解决方案适用于 MxN 数组 -
template <size_t n>
struct MN * get(int a[][n], int k, int M, int N)
struct MN *result = new MN;
result->m = -1;
result->n = -1;
/* Do a binary search on each row since rows (and columns too) are sorted. */
for(int i = 0; i < M; i++)
int lo = 0; int hi = N - 1;
while(lo <= hi)
int mid = lo + (hi-lo)/2;
if(k < a[i][mid]) hi = mid - 1;
else if (k > a[i][mid]) lo = mid + 1;
else
result->m = i;
result->n = mid;
return result;
return result;
Working C++ demo.
如果这不起作用或是否存在错误,请告诉我。
【讨论】:
【参考方案19】:给定一个方阵如下:
[ a b c ] [ 定义 ] [我ķķ]我们知道a c 等等。我们只有一维的保证。
查看结束元素 (c,f,k),我们可以做一种过滤:is N
让我举一个例子,其中 N = j,
1) 检查第 1 行。j
2) 检查第 2 行。j
3) 检查第 3 行。j
用 N = q 再试一次,
1) 检查第 1 行。q
2) 检查第 2 行。q
3) 检查第 3 行。q
可能有更好的解决方案,但这很容易解释.. :)
【讨论】:
【参考方案20】:由于这是一个面试问题,它似乎会引发对并行编程和 Map-reduce 算法的讨论。
见http://code.google.com/intl/de/edu/parallel/mapreduce-tutorial.html
【讨论】:
以上是关于如何在从左到右和从上到下排序的二维数组中搜索数字?的主要内容,如果未能解决你的问题,请参考以下文章
在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
杨氏矩阵(二维数组的每行从左到右是递增的,每列从上到下是递增的. 在这样的数组中查找一个数字)
题目描述 在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数