如何在一个从左到右、从上到下排序的二维数组中搜索一个数字?

96

最近我遇到了这样一道面试题,我想知道一个好的解决方法。

假设有一个二维数组,数组中的所有数字从左到右、从上到下递增。

最好的方法是什么来搜索和确定目标数字是否在数组中?

现在,我的第一反应是利用二分搜索,因为我的数据已经排序。我可以在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.
21个回答

129

这里有一个简单的方法:

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

对于一个NxM数组,它的运行时间为O(N+M)。我认为很难做得更好。 :)


编辑:有很多好的讨论。我上面说的是一般情况;显然,如果NM很小,你可以使用二分搜索方法在接近对数时间内完成此操作。

以下是一些细节,供那些好奇的人参考:

历史

这个简单的算法称为Saddleback Search。它已经存在一段时间了,并且当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. 左上角的数字矩形小于 value,因此我们可以将其消除。
  5. 右下角的数字矩形大于 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))的性能。

性能比较

大O分析很好,但这些方法在实践中表现如何呢?下面的图表检查了四种算法,针对越来越“方形”的数组:

algorithm performance vs squareness

“朴素”算法只是搜索数组的每个元素。“递归”算法如上所述。“混合”算法是Gidney's algorithm的一种实现。对于每个数组大小,通过在固定的1,000,000个随机生成的数组集合上计时每个算法来衡量性能。

一些值得注意的点:

  • 如预期,“二分查找”算法在矩形数组上提供最佳性能,“Saddleback”算法在正方形数组上表现最佳。
  • 对于1-d数组,“Saddleback”算法的性能不如“朴素”算法,可能是因为它对每个项进行了多次比较。
  • “二分查找”算法在正方形数组上的性能下降可能是由于运行重复的二分查找的开销造成的。

总结

巧妙地使用二分查找可以为矩形和正方形数组提供O(N * log(M/N)的性能。 O(N + M)的“Saddleback”算法更简单,但随着数组变得越来越矩形化,性能会下降。


6
对对角行走应用二分查找,可以得到O(logN)或O(logM),取较大的那个。 - Anurag
4
@Anurag - 我认为这种复杂性并不可行。二分查找可以给你一个好的起点,但你必须在其中一个维度上一直走到底,在最坏的情况下,你仍然可能从一个角落开始,结束于另一个角落。 - Jeffrey L Whitledge
1
如果N = 1且M = 1000000,我可以做得比O(N+M)更好。因此,另一种解决方案是在每行中应用二分搜索,这将带来O(N*log(M))的时间复杂度,其中N<M,以便产生较小的常数。 - Luka Rahne
1
我使用了你的方法和二分查找法进行了一些测试,并在HERE上发布了结果。看起来,除非我没有正确生成两种方法的最坏情况,否则之字形方法是最好的。 - The111
1
很好的引用使用!然而,当 M==N 时,我们希望复杂度为 O(N),而不是 O(N*log(N/N)),因为后者为零。当 N<=M 时,正确的“统一”尖锐边界是 O(N*(log(M/N)+1)) - hardmath
显示剩余16条评论

38
这个问题需要 Θ(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

查找项目:

Finding an item

确定项目不存在:

Determining an item doesn't exist

图例:白色单元格是较小的项,灰色单元格是较大的项,绿色单元格是相等的项。

分析 需要消除 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;
}

1
有趣且可能部分超出我的理解范围。我不熟悉这种“对手”风格的复杂性分析。对手是否实际上在你搜索时以某种方式动态更改数组,还是只是给你在最坏情况下遇到的厄运起了个名字? - The111
2
@The111 倒霉相当于选择了一条违背到目前为止所见的事物的不良路径,因此这两个定义的结果是相同的。我实际上很难找到关于计算复杂性方面特定的技术解释的链接...我以为这是一个更为广为人知的概念。 - Craig Gidney
由于log(1)=0,因此复杂度估计应为O(b*(lg(t)+1)),而不是O(b*lg(t))。很好的写作,特别是要注意在展示“最坏情况”限制时使用"对手技术"。 - hardmath
@hardmath,我在答案中提到了这一点。我稍微澄清了一下。 - Craig Gidney

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

3
看起来是O(log(N)),因为这就是普通二分查找的时间复杂度。不过请注意,每一层可能会有两次递归调用,这意味着它比普通的对数时间复杂度要糟糕得多。我认为最坏情况下的时间复杂度不会比O(M+N)好,因为潜在地必须搜索每一行或每一列。不过我猜测,这种算法可以在很多值的情况下击败最坏情况。而最好的部分是它可以并行化,因为那正是硬件目前的趋势。 - Jeffrey L Whitledge
1
@JLW:它是O(log(N))——但实际上它是O(log_(4/3)(N^2))或类似的东西。请参见下面Svante的答案。如果你的意思是我所想的那样递归,那么你的答案实际上是一样的。 - Rex Kerr
1
@Svante - 子数组不重叠。在第一种情况下,它们没有共同的y元素。在第二种情况下,它们没有共同的x元素。 - Jeffrey L Whitledge
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)更糟糕。也许我犯了一些愚蠢的错误,但看起来算法浪费了很多时间在无用的分支上。 - Craig Gidney
这个算法比O(N+M)算法表现更好。在非常大的n和m值上,这个算法更加高效。在较小的值上,另一个算法可能更好。 - Henley
显示剩余15条评论

6
到目前为止,两个最主要的答案似乎是具有争议的 O(log N) 的 "ZigZag方法" 和 O(N+M) 的二分查找方法。我想做一些测试来比较这两种方法在不同情况下的表现。以下是详细信息:数组在每个测试中都是一个 N x N 的正方形,其中 N 从 125 变化到 8000(这是我的 JVM 堆可以处理的最大值)。对于每个数组大小,我选了一个随机位置在数组中放置一个单独的 2。然后将 3 放置到可能的所有位置 (2 的右侧和下面),然后将剩余的数组用 1 填充。之前的评论者中一些人认为这种设置会导致两种算法的最坏运行时间。对于每个数组大小,我选择了 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 数组上看这两种方法的效果也可能很有趣。 - Nate Kohl

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个值就可以接近目标。在所有其他情况下,它的表现不会比所提出的算法差。(注意:此处的“linier”可能应为“linear”,但原文如此) - Jeffrey L Whitledge
这表明算法必须花费BigOmega(min(n,m))的时间,而不是BigOmega(n+m)。这就是为什么当一个维度显着较小时,你可以做得更好。例如,如果你知道只有1行,你可以在对数时间内解决问题。我认为最优算法将花费O(min(n+m, n lg m, m lg n))的时间。 - Craig Gidney
相应地更新了答案。 - Rafał Dowgird
@Rafał Dowgird,请点击以下链接:它提供了(log n)^2的解决方案。http://www.leetcode.com/2010/10/searching-2d-sorted-matrix-part-ii.html 我想知道这是否正确? - Green goblin
@Aashish 他们并没有声称这种复杂性。引用:“请注意,改进的二分法方法的最坏情况在这里尚未被证明。”只有当每个分区步骤将矩阵分成大致相等的4个部分时,才能达到这种复杂性,而该算法显然不能保证这一点。 - Rafał Dowgird
显示剩余2条评论

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

findNum() 应该被记录文档,至少需要说明“最大值”是否包含在内。我认为这里存在一些偏差错误:如果目标位于 xnewynew 中的任意一个,但不在 [xnew][ynew] 中怎么办? - greybeard

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
+1:好的解决方案...创意十足,而且很好地找到了所有的解决方案。 - Tony Delroy

1

通过数组的对角线进行二分查找是最佳选择。 我们可以找出元素是否小于或等于对角线上的元素。


1
我已经在面试中问了这个问题十年之久,我认为只有一个人能够想出最优算法。
我的解决方案一直是:
1. 二分查找中间对角线,这是包含项目的向下和向右运行的对角线,位于(rows.count/2, columns.count/2)。 2. 如果找到目标数字,则返回true。 3. 否则,会找到两个数字(u和v),其中u小于目标,v大于目标,并且v在u的右侧和下方。 4. 递归搜索u右侧和v上方的子矩阵以及u下方和v左侧的子矩阵。
我相信这比Nate here给出的算法严格得多,因为搜索对角线通常可以减少超过一半的搜索空间(如果矩阵接近正方形),而搜索行或列总是会准确消除一半。
以下是使用(可能不是非常Swifty的)Swift编写的代码:
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)
        }
    }
}

0
class Solution {
   public boolean searchMatrix(int[][] matrix, int target) {
    if(matrix == null)
        return false;
    int i=0;
    int j=0;
    int m = matrix.length;
    int n = matrix[0].length;
    boolean found = false;
    while(i<m && !found){
        while(j<n && !found){
            if(matrix[i][j] == target)
                found = true;
            if(matrix[i][j] < target)
                j++;
            else
                break;
        }
        i++;
        j=0;
    }
    return found; 
 }}

通过了 129 / 129 个测试用例。

状态: 已接受

运行时间: 39 毫秒

内存使用: 55 MB


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接