如何在2D数组中搜索从左到右和从上到下排序的数字?


90

最近有人问我这个面试问题,我很好奇这将是一个好的解决方案。

假设我得到了一个二维数组,其中数组中的所有数字从左到右以及从上到下按递增顺序排列。

搜索和确定目标编号是否在数组中的最佳方法是什么?

现在,我的第一个倾向是利用二进制搜索,因为我的数据已排序。我可以确定在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]]
Matthieu M.

Answers:


115

这是一个简单的方法:

  1. 从左下角开始。
  2. 如果目标小于该值,那么它必须在我们之上,因此将其上移一个
  3. 否则,我们知道目标不能在该列中,因此向右移动一个
  4. 转到2。

对于NxM数组,它在中运行O(N+M)。我认为很难做得更好。:)


编辑:很多很好的讨论。我说的是上面的一般情况;显然,如果N还是M 小,你可以使用一个二进制搜索方法来做到这一点的东西接近对数时间。

对于好奇的人,以下是一些详细信息:

历史

这个简单的算法称为鞍背搜索。它已经存在了一段时间,当时是最佳选择N == M。一些参考:

但是,当时N < M,直觉表明二进制搜索应比O(N+M):做得更好:例如,当时N == 1,纯二进制搜索将以对数而非线性时间运行。

最坏情况下

理查德·伯德(Richard Bird)在2006年的一篇论文中检验了这种直觉,即二进制搜索可以改进Saddleback算法:

Bird使用一种非常不寻常的对话技术向我们显示,为此N <= M,该问题的下限为Ω(N * log(M/N))。这个界限是有道理的,因为它在时给出了线性性能,N == M而当时给出了对数性能N == 1

矩形阵列的算法

使用逐行二进制搜索的一种方法如下所示:

  1. 从一个矩形数组开始,其中N < M。假设N是行,M是列。
  2. 对中间行进行二进制搜索value。如果找到它,就完成了。
  3. 否则,我们会找到一对相邻的数字sg,其中s < value < g
  4. 上方和左侧的数字矩形s小于value,因此我们可以消除它。
  5. 下方和右侧的矩形g大于value,因此我们可以将其消除。
  6. 对于剩余的两个矩形,请转到步骤(2)。

就最坏情况下的复杂性而言,该算法确实log(M)可以消除一半的可能解决方案,然后在两个较小的问题上递归调用两次。我们确实必须log(M)为每行重复一个较小的版本,但是如果行数比列数小,那么能够以对数时间消除所有这些列就变得很有价值

这给算法带来了复杂性T(N,M) = log(M) + 2 * T(M/2, N/2),Bird证明了O(N * log(M/N))

克雷格·吉德尼(Craig Gidney)发布的另一种方法描述了一种与上述方法类似的算法:它使用的步长每次检查一行M/N。他的分析表明,这也可以O(N * log(M/N))提高性能。

性能比较

Big-O分析很好,但是这些方法在实践中效果如何?下图检查了越来越多的“正方形”阵列的四种算法:

算法性能与矩形性

(“朴素”算法仅搜索数组的每个元素。上面介绍了“递归”算法。“混合”算法是Gidney算法的实现。对于每个数组大小,通过将每个算法在固定集合上计时来衡量性能) 1,000,000个随机生成的数组。)

一些值得注意的要点:

  • 不出所料,“二进制搜索”算法在矩形阵列上提供最佳性能,而Saddleback算法在方形阵列上运行最佳。
  • 对于1维数组,Saddleback算法的性能比“朴素”算法差,这可能是因为它对每个项目都进行了多次比较。
  • “二进制搜索”算法对正方形数组的性能影响可能是由于运行重复的二进制搜索的开销所致。

摘要

二进制搜索的巧妙使用可以为O(N * log(M/N)矩形和正方形阵列提供性能。在O(N + M)“马鞍”的算法简单得多,但患有性能下降阵列变得越来越矩形。


6
将二元搜索应用于对角线走,您将获得O(logN)或O(logM)中的较高者。
阿努拉格

3
@Anurag-我认为复杂性不是很好。二进制搜索将为您提供一个良好的起点,但您必须始终沿一个维度或另一个维度行走,而且在最坏的情况下,您仍然可以从一个角开始并在另一个角处结束。
Jeffrey L Whitledge,2010年

1
如果N = 1且M = 1000000,我可以做得比O(N + M)好,那么另一种解决方案是在每行中应用二进制搜索,这会带来O(N * log(M)),其中N <M较小的常数。
卢卡·拉恩

1
我使用您的方法和二进制搜索方法进行了一些测试,并在此处发布了结果。似乎之字形方法是最好的,除非我未能正确生成这两种方法的最坏情况。
The111 2013年

1
很好的使用参考!但是,当M==N我们想要O(N)复杂性时,就不要O(N*log(N/N))因为后者为零。正确的“统一”界线是O(N*(log(M/N)+1))when N<=M
hardmath 2014年

35

此问题需要Θ(b lg(t))时间,位置b = min(w,h)t=b/max(w,h)。我将在此博客文章中讨论解决方案。

下界

对手可以通过将算法Ω(b lg(t))限制在主要对角线上来强制算法进行查询:

对手使用主要对角线

图例:白色单元格是较小的项目,灰色单元格是较大的项目,黄色单元格是较小或相等的项目,橙色单元格是较大或相等的项目。对手迫使解决方案是算法最后查询的黄色或橙色单元格。

请注意,有b独立的大小排序列表t,要求Ω(b lg(t))完全消除查询。

算法

  1. (假设不失一般性w >= h
  2. 将目标项目与t有效区域右上角左侧的单元格进行 比较
    • 如果单元格的项目匹配,则返回当前位置。
    • 如果单元格的项目小于目标项目,请t使用二进制搜索消除行中剩余的单元格。如果在执行此操作时找到匹配的项目,请返回其位置。
    • 否则,单元格的项目大于目标项目,从而消除了t短列。
  3. 如果没有有效区域,返回失败
  4. 转到步骤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=1O(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;
}

1
有趣,可能部分超出了我的脑海。我不熟悉这种复杂性分析的“对手”风格。对手实际上是在搜索时以某种方式动态地更改数组,还是他只是在最坏情况下搜索时遇到的厄运的名字?
The111 2013年

2
@ The111运气不好等于某人选择了一条不违反迄今为止所见事物的道路,因此这两个定义的作用相同。我实际上很难找到专门针对计算复杂性来解释该技术的链接……我认为这是一个更为知名的想法。
Craig Gidney 2013年

由于log(1)= 0,因此应将复杂度估算值指定为,O(b*(lg(t)+1))而不是O(b*lg(t))。不错的写作,特别是。在显示“最坏情况”界限时引起对“对抗技术”的关注。
hardmath 2014年

@hardmath我在回答中提到了这一点。我澄清了一下。
Craig Gidney 2014年

17

对于这个问题,我将采用分而治之的策略,类似于您所建议的,但是细节有所不同。

这将是对矩阵子范围的递归搜索。

在每个步骤中,选择范围中间的一个元素。如果找到的值是您要寻找的值,那么您就完成了。

否则,如果找到的值小于您要寻找的值,那么您知道它不在当前位置的上方和左侧。因此,递归搜索两个子范围:当前位置下方的所有内容(排他性),以及当前位置上方或上方的所有内容(排他性)。

否则,(找到的值大于您要寻找的值),您知道它不在当前位置下方和右侧的象限中。因此,递归搜索两个子范围:当前位置左侧的所有内容(排他性地),以及当前列或右侧一列中当前位置上方的所有内容(排他性地)。

和八大兵,你找到了。

请注意,每个递归调用仅处理当前子范围,而不处理(例如)当前位置上方的所有行。只是那些在当前子范围内。

这是给您的一些伪代码:

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

3
@Rex Kerr-看起来像O(log(N)),因为这是普通的二进制搜索,但是请注意,每个级别可能有两个递归调用。这意味着它比普通对数要差得多。我认为最坏的情况不会比O(M + N)更好,因为有可能必须搜索每一行或每一列。我猜想这个算法在很多值上都可以胜过最坏的情况。最好的部分是它可以并行化,因为这是最近硬件的发展方向。
杰弗里·惠特里奇

1
@JLW:它是O(log(N))-但实际上是O(log_(4/3)(N ^ 2))或类似的东西。请参阅下面的Svante答案。您的答案实际上是相同的(如果您以我认为的方式表示递归)。
Rex Kerr

1
@Svante-子数组不重叠。在第一种选择中,它们没有共同的y元素。在第二种选择中,它们没有共同的x元素。
Jeffrey L Whitledge,2010年

1
我不确定这是否是对数的。我使用近似递归关系T(0)= 1,T(A)= T(A / 2)+ T(A / 4)+ 1计算复杂度,其中A是搜索区域,最后得到T( A)= O(Fib(lg(A))),大约为O(A ^ 0.7),比O(n + m)差O(A ^ 0.5)。也许我犯了一些愚蠢的错误,但是看起来该算法浪费大量时间沿毫无结果的分支走。
克雷格·吉德尼

6

到目前为止,给出的两个主要答案似乎是O(log N)“ ZigZag方法”和“ O(N+M)二进制搜索”方法。我以为我会做一些测试,比较这两种方法与各种设置。详细信息如下:

在每个测试中,数组的大小为N x N平方,N介于125到8000之间(我的JVM堆可以处理的最大数组)。对于每个数组大小,我在数组中选择一个随机位置放置一个2。然后,我将一个3可能的所有位置放在2的右边和下面,然后用填充数组的其余部分1。一些较早的评论者似乎认为,这种类型的设置会给两种算法带来最坏的运行时间。对于每种阵列大小,我为2个(搜索目标)选择了100个不同的随机位置并进行了测试。我记录了每种算法的平均运行时间和最坏情况的运行时间。因为在Java中获得毫秒级的读数太快了,而且因为我不信任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
+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

当然,这也会扩展到更大的数组。这意味着该答案是最佳的。

更新:正如Jeffrey L Whitledge指出的那样,它仅是最佳的,因为它是运行时间与输入数据大小(作为单个变量)的渐近下限。可以将在两个数组维度上被视为二元函数的运行时间缩短。


您尚未证明该答案是最佳的。例如,考虑一个十个交叉且向下一百万个的数组,其中第五行包含的值都高于目标值。在那种情况下,所提出的算法将在接近目标之前进行线性搜索最多999,995个值。像我的分叉算法只会在接近目标之前搜索18个值。在所有其他情况下,它的性能(渐近地)都不会比所提出的算法差。
Jeffrey L Whitledge,2010年

@Jeffrey:对于悲观案例,这是问题的下限。您可以针对良好的输入进行优化,但是存在无法比线性更好的输入。
拉斐尔·道吉德(RafałDowgird)2010年

是的,确实存在输入不能比线性更好的输入。在这种情况下,我的算法将执行线性搜索。但也有其他的输入,你可以做的方式优于线性。因此,所提出的解决方案不是最优的,因为它总是进行线性搜索。
Jeffrey L Whitledge,2010年

这表明算法必须花费BigOmega(min(n,m))时间,而不是BigOmega(n + m)。这就是为什么在一个维度明显较小时可以做得更好的原因。例如,如果您知道只有1行,则可以用对数时间解决问题。我认为最佳算法将花费时间O(min(n + m,n lg m,m lg n))。
Craig Gidney 2010年

相应地更新了答案。
拉斐尔·道吉德(RafałDowgird)2010年

4

我认为这是答案,它适用于任何排序的矩阵

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));
    }
}

1

有趣的问题。考虑一下这个想法-创建一个边界,在该边界上所有数字均大于目标,而在另一个边界上所有数字均小于目标。如果两者之间还剩下什么,那就是您的目标。

如果我在您的示例中查找3,则在第一行中一直读到4,然后查找大于3的最小相邻数字(包括对角线):

1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11

现在,对于那些小于3的数字,我也要这样做:

1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11

现在我问,两个边界内有什么东西吗?如果是,则必须为3。如果否,则将没有3.间接排序。由于我实际上没有找到该数字,因此我推断出它一定在那里。这具有计数所有3的额外好处。

我在一些示例上进行了尝试,似乎工作正常。


没有评论的否决票?我认为这是O(N ^ 1/2),因为最坏情况下的性能需要检查对角线。至少给我看一个反例,说明这种方法不起作用!
Grembo 2010年

+1:不错的解决方案...有创造力,而且可以找到所有解决方案。
Tony Delroy

1

最好通过数组对角线进行二进制搜索。我们可以找出元素是否小于或等于对角线上的元素。


0

答:在那些目标号码可能存在的行上进行二进制搜索。

B.画一个图:寻找数字,方法是始终取最小的未访问邻居节点,并在发现太大的数字时回溯


0

二进制搜索将是最好的方法。从1/2 x开始,1/2 y会将其切成两半。IE 5x5正方形将类似于x == 2 / y == 3。我将一个值向下舍入,将一个值向上舍入到目标值的方向。

为了清楚起见,下一次迭代将为您提供x == 1 / y == 2或x == 3 / y == 5


0

好吧,首先,让我们假设我们使用的是正方形。

1 2 3
2 3 4
3 4 5

1.搜索正方形

我会在对角线上使用二进制搜索。目标是找到不严格低于目标数字的较小数字。

假设我要寻找4例如,然后我将最终定位5(2,2)

于是,我很放心,如果4是在表中,这是在任何位置(x,2)(2,x)x[0,2]。好吧,这只是2个二进制搜索。

复杂性并不令人畏惧:(O(log(N))对长度范围进行3次二进制搜索N

2.搜索矩形的简单方法

当然,当NM不同时(带有矩形),它会变得更加复杂,请考虑这种退化的情况:

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)


0

编辑:

我误解了这个问题。正如评论所指出的那样,这仅在更受限的情况下有效。

在像C这样以行优先顺序存储数据的语言中,只需将其视为大小为n * m的一维数组,然后使用二进制搜索即可。


是的,为什么要使它变得比必须的复杂。
erikkallen 2010年

数组未排序,因此无法对其进行bin搜索
Miollnyr 2010年

1
仅当每行的最后一个元素高于下一行的第一个元素时,这才起作用,这比问题提出的要严格得多。
Jeffrey L Whitledge,2010年

谢谢,我已经编辑了答案。阅读不够仔细,尤其是示例数组。
休·布拉基特2010年

0

我有一个递归的分而治之解决方案。第一步的基本思想是:我们知道,左上(LU)最小,右下(RB)是最大号,因此给定的No(N)必须:N> = LU和N <= RB

如果找到N == LU并且N == RB :::: Element并中止返回位置/索引如果N> = LU且N <= RB = FALSE,则不存在并中止。如果N> = LU并且N <= RB = TRUE,则将2D数组以逻辑方式分别划分为2D数组的4个相等部分。然后对所有四个子数组应用相同的算法步骤。

我的算法是正确的,我已经在朋友的PC上实现了。复杂度:每4个比较都可以用来在最坏的情况下将元素总数减少到四分之一。因此我的复杂度为1 + 4 x lg(n)+ 4,但是我真的希望这对O起作用(n)

我认为我在计算复杂度时某处出了问题,如果是,请更正。


0

最佳解决方案是从值最小的左上角开始。沿对角线向下右移,直到您击中一个值> =给定元素值的元素。如果元素的值等于给定元素的值,则返回找到的结果为true。

否则,从这里我们可以以两种方式进行。

策略1:

  1. 在列中上移并搜索给定的元素,直到到达末尾。如果找到,则返回true
  2. 在行中向左移动并搜索给定的元素,直到到达末尾。如果找到,则返回为true
  3. 返回为假

策略2:让我表示停在对角线元素的行索引,而j表示对角元素的列索引。(这里,我有i = j,顺便说一句)。令k = 1。

  • 重复以下步骤,直到ik> = 0
    1. 搜索a [ik] [j]是否等于给定元素。如果是,则返回true。
    2. 搜索a [i] [jk]是否等于给定的元素。如果是,则返回true。
    3. 增量k

1 2 4 5 6
2 3 5 7 8
4 6 8 9 10
5 8 9 10 11


0
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 ;

}

0

我建议将所有字符存储在中2D list。然后在列表中找到所需元素的索引。

如果不存在,则打印适当的消息,否则将行和列打印为:

row = (index/total_columns)column = (index%total_columns -1)

这只会产生列表中的二进制搜索时间。

请提出任何更正。:)


0

如果对于MxN阵列,O(M log(N))解决方案可以-

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;
}

工作中的C ++演示。

请告诉我这是否行不通或存在错误。


0

在过去的十年中,我一直在采访中问这个问题,而且我认为只有一个人能够提出最佳算法。

我的解决方案一直是:

  1. 二进制搜索中间的对角线,该对角线是向下和向右延伸的对角线,其中包含位于的项(rows.count/2, columns.count/2)

  2. 如果找到目标号码,则返回true。

  3. 否则,将找到两个数字(uv),这些数字u小于目标,v大于目标,并且v是的右移一个u

  4. 递归搜索子矩阵在的右侧u和顶部,v在的底部u和左侧v

我相信这是对Nate此处给出的算法的严格改进,因为搜索对角线通常可以减少一半以上的搜索空间(如果矩阵接近于正方形),而搜索行或列总是导致消除恰好一半。

这是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)
        }
    }
}

-1

给定方阵如下:

[abc]
[def]
[ijk]

我们知道a <c,d <f,i <k。我们不知道是d <c还是d> c,等等。我们仅能保证一维。

看一下末端元素(c,f,k),我们可以做一个过滤器:N <c吗?search():next()。因此,我们在各行上进行了n次迭代,每行采用O(log(n))进行二进制搜索,或者采用O(1)进行过滤。

让我举一个例子,其中N = j,

1)检查第1行。j <c?(不,继续)

2)检查第2行。j <f?(是的,bin搜索一无所获)

3)检查第3行。j <k?(是的,bin搜索找到了它)

再次尝试N = q,

1)检查第1行。(不,继续)

2)检查第2行。(不,继续)

3)检查第3行。q <k?(不,继续)

可能有一个更好的解决方案,但这很容易解释.. :)


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.