【问题标题】:find an element in a sorted matrix [duplicate]在排序矩阵中找到一个元素[重复]
【发布时间】:2011-09-29 23:47:45
【问题描述】:

问题: 给定一个矩阵,其中每一行和每一列都已排序,编写一个方法来查找其中的元素。

这是一个经典的面试问题,这是我的解决方案

boolean F(int[][] matrix, int hs, int he, int ws, int we)
{
    if (hs > he || ws > we) 
        return false; 

    int m = (hs + he) / 2; 
    int n = (ws + we) / 2;

    if (matrix[m][n] == t)
    {
        return true;
    }
    else if (matrix[m][n] < t)
    {
        // find the ele in the same row, right to [m][n]
        F(m, m, n + 1, we);

        // find the ele in the same col, upper to [m][n]
        F(m + 1, he, n, n);

        // find the ele in the area, where i>m,j>n 
        F(m + 1, he, n + 1, we);       
    } 
    else if (matrix[m][n] > t)
    {
        // very similar to previous part
    }
}

算法的运行时间为log(m) + log(n)。我正在寻找一种更高效或代码更简洁的算法。

有更多的cmets,我想出了以下代码:

// return target recurrence in the matrix
int F(int[][] m, int rs, int re, int cs, int ce, int t){
   int r1 = rs, r2 = re;
   int c1 = cs, c2 = ce;
   int r=0 , c = c1;

   while( r1 < r2 && c1 < c2 ){
   // find the last element that <= t in column c
     r  = FlastLess( r1, r2, c, t)

     if( r == -1 ) break;

     else{
       // find the first ele in the row that is >=t
       c = FfirstGreater( r, c1, c2, t);

       if( c == -1)  break;
       else{
         r2 = r; 
         c1 = c; 
       }// else    
     }// else 
   }// while
}// f

这里是函数 F1 和 F2 的链接 Find the first element in a sorted array that is greater than the target

void FlastLess(int s, int e, int t){
  int l = s, h = e;
  while( l != h ){
     int mid = (l+h)/2;
     if( mid >=  t) high = mid - 1; 
     else {
       if( high < t) low= mid + 1;
       else low = mid;
     } 
  }

 void FfirstGreater(int s, int e, int t){
  while(l < h){
    mid = (l+h)/2;
    if ( mid <=  t) low = mid+1;
    else high = mid;
  }
 }

}

【问题讨论】:

  • 我可能是错的,但二进制搜索可能会尽可能快。
  • 我不确定你的方法是否有效。想象一个矩阵,其第一行是 [0,10,20,..,90],下一行是 [1,11,21,..,91] 直到 [9,19,29,...,99 ]。在这种情况下,每一行和每一列都是有序的。现在你从 55 开始,你正在寻找 72。72 > 55 但它不在矩阵的下半部分。如果您正在寻找 19,它不在上半部分。可能我没看懂算法。我也不明白你如何有连续的“return”语句 - 这是无法访问的代码。
  • @secureFish 考虑新的答案。让我知道这是对的。
  • 仅供参考:此数据结构也称为Young tableau

标签: java arrays algorithm binary-search


【解决方案1】:

对于基于比较的算法,O(lg(m) + lg(n)) 查询是最优的。

证明

对于基于比较的查询,每个查询只能有两个结果:真或假。一个明显的扩展是,对于 N 个查询,您最多可以有 2N 个结果。因此,使用 N 个查询,您只能在最多包含 2N 个元素的矩阵中定位元素。

那么搜索一个 m x n 矩阵需要多少次查询?只需求解 N。

2N = mn
lg(2N) = lg(mn)
N = lg(m) + lg(n)

因此 lg(m) + lg(n) 查询是最优的。

非比较查询

该证明是确凿的,但仅适用于基于比较的查询。如果您以不涉及比较的方式查询矩阵,那么如果您知道值的分布,您可以获得接近恒定的时间。我不会为您提供算法,但我建议您查看Radix sort,因为它包含击败 lg(m) + lg(n ) 下限。

【讨论】:

    【解决方案2】:

    这是我会尝试的。给定m by n 矩阵A,将值X 与条目A(m/2,n/2) 进行比较(必要时使用楼层)。

    如果A(m/2,n/2) == X,完成。

    如果A(m/2,n/2) &lt; X,则需要检查 3 个较小的矩阵:

    A(1:m/2, 1:n/2) 
    A(1:m/2, n/2+1:n) 
    A(m/2+1:m, 1:n/2) 
    

    如果 A(m/2,n/2) &gt; X, ,则需要检查 3 个较小的矩阵:

    A(m/2:m, n/2:n) 
    A(1:m/2, n/2+1:n) 
    A(m/2+1:m, 1:n/2) 
    

    您可以通过将值与相应矩阵中的最小值(左上角的值)进行比较来消除其中的两个(并非总是如此)。然后你递归地尝试在每个剩余的矩阵中找到值。

    这个复杂度大约是O((n*m)^0.8)


    行列排序矩阵是Young tableau 的特例。我在谷歌上搜索了一个 Young 画面并找到了 this article which gives 3 nice approaches(第一个(也是最差的)是我上面描述的那个)。

    【讨论】:

    • (题外话)嘿@PengOne,你看过this question吗?这似乎是您可能能够弄清楚的那种事情:-)。
    【解决方案3】:

    你的算法可能是 O(log m + log n),但它也给出了错误的答案。

    假设您在以下矩阵中搜索“4”(其中左上角为 row=0,col=0):

    0 1 4
    1 2 5
    2 3 6
    

    您的算法首先查看中心的2。由于4 大于2,您继续搜索同一行(不存在)、同一列(不存在)和右下角(不存在)。哎呀。

    对每一行和每一列都进行排序的约束实际上很弱。特别是,沿对角线的元素可以按任意顺序排列。

    我认为正确的方法是对第一个 和最后一个 列进行二进制搜索,以缩小可能行的范围。然后对这些行的第一行和最后一行进行二进制搜索以缩小可能的列。以此类推。

    不知道如何分析这个的性能...

    【讨论】:

      【解决方案4】:

      从矩阵的左下角开始。然后向右走,直到找到确切的数字(完成),或者直到找到更大的数字。

      然后你在矩阵中向上移动直到你找到确切的数字(完成),或者直到你找到一个太小的数字。

      然后您再次向右移动,... 以此类推,直到找到数字或到达矩阵的右侧或顶部。

      以下图片包含一些示例,使用 Excel 表格,以绿色显示目标编号,并以黄色显示所遵循的路径。

      在最后一个示例中,我们查找 207,它不在矩阵中:

      这只是算法。编码留给你作为练习:-)

      编辑:从底行开始时,二分搜索可能会提供更好的起点。对于算法的其余部分,它可能无关紧要。

      【讨论】:

      • 您说的是从矩阵的底部左侧,如图所示。我认为我们仍然可以使用二进制搜索来加快速度。这是我的想法。对于列,我们找到第一个等于或大于目标的元素;如果失败,我们移动到同一行的右边,仍然寻找第一个等于/大于目标的元素;如果是,则返回同一列并找到小于目标的第一个元素;如果是,向右移动并继续之前的算法,如果不是,我们可能会退出。它仍然可以是 log(m)、log(n)。顺便说一句,我非常喜欢你的图表
      • 非常好。但是您应该编辑答案以从“左下角”而不是“右下角”开始...我想知道这种方法是否最佳?即使你在整个算法中使用了二分搜索,你也可能只是“走对角线”,所以它无助于最坏情况下的性能。
      • @Nemo,纠正了错字,谢谢。这种方法可能不是最优的,但最坏的情况是 O(n+m)。 OP的问题包含他声称更快的算法,但根据您的说法是不正确的。可能一些额外的二进制搜索可以提高性能。
      • 为了优化,我认为你也可以从左上角开始。此外,一种更好的可视化方法是,假设左上角是 BST 的根,左右子元素是下和右元素,那么您可以继续这种方式。
      • @logic_max:这不起作用,因为“左右孩子”可以按任意顺序排列。同样,沿对角线的元素完全未排序。所以我相信最优算法至少需要 O(对角线长度)。
      【解决方案5】:

      我相信您的算法没有您认为的时间限制。

      要查看这一点,为简单起见,我们假设您的网格是一个 n x n 正方形(我们称其为 m 大小)。如果在这种情况下我可以得出与 O(log n) 不同的时间界限,我可以争辩说你所拥有的不应该是正确的。

      请注意,在最坏的情况下,您对大小为 (n / 2) x (n / 2) = m / 4 的问题进行 3 次递归调用。这意味着我们有递归

      T(1) = 1
      T(m) = 3T(m / 4) + O(1)
      

      使用Master Theorem,此函数的运行时间为 O(mlog43) = O(n2 log4 3) = O(n log49) &approx; O(n1.5849625)。这是 ω(log n + log m);也就是说,它严格地渐近更大。

      正如许多其他人所发布的那样,有几种众所周知的算法在 O(m + n) 中运行,这些算法基于每一步都朝着正确的方向走一步。因此,撇开正确性不谈,我不建议使用您发布的算法。

      【讨论】:

        【解决方案6】:

        阅读以前的 cmets 我想出了这个算法。它基本上假设从右上角开始,矩阵可以用作带有一些“循环”的BST(我们不关心这个循环)。

        1 4 9
        5 6 10
        
             9
            /  \
           4   10
          / \  /
         1   6
          \ /
           5
        

        此算法与在 BST 中搜索相同,并且非常容易理解。最坏情况下的运行时间是 O(n + m)。

        public static boolean search( int[][] matrix, int value )
        {   
            int rows = matrix.length;
            int columns = matrix[0].length;
        
            int i = 0;
            int j = columns - 1;
        
            while( i < rows
                   && j >= 0 )
            {   
                if( matrix[i][j] == value )
                {
                    System.out.println( "Found at " + i + " " + j );
                    return true;
                }
        
                if( matrix[i][j] < value )
                {
                    j--;
                }
                else
                {
                    i++;
                }
            }
        
            return false;
        }
        

        【讨论】:

        • 对我不起作用:(
        【解决方案7】:
        boolean FindElem(int[][] mat, int elem, int M, int N) {
         int row = 0;
         int col = N-1;
         while (row < M && col >= 0) {
           if (mat[row][col] == elem) {
             return true;
           } else if (mat[row][col] > elem) {
             col--;
           } else {
             row++;
           }
         }
           return false;
        }
        

        【讨论】:

          【解决方案8】:

          二维数组中的给定元素(a[n][m])水平和垂直增加。所以对于给定的问题,我们需要首先找到元素的索引。因此,如果我们能以更快的方式找到元素,那么我们就可以优化解决方案。问题是我们如何以有效的方式找到它。一种方法是取矩阵的中间元素并用它检查给定的元素

          如果给定元素小于中间元素,那么我们的解决方案在于矩阵 a[0][0] 到 a[n/2][m/2] 因为右边和下面的所有元素都大于中间元素(因为给定元素小于中间元素),因此我们将搜索空间从 a[n][m] 减少到 a[n/2][m/2],这是原始大小的四分之一。

          如果给定元素大于中间元素,那么我们的解决方案不在于矩阵 a[0][0] 到 a[n/2][m/2],因为左侧和上方的所有元素都小于中间(因为给定元素大于中间元素),所以我们的搜索空间是总数组减去 a[0][0] 到 a[n/2][m/2],它是原始大小的四分之三。总数组减去 a[0][0] 到 a[n/2][m/2] 的意思是,将有三个带有数组索引的递归调用

          --------->a[0][m/2](start index) to a[n/2][m](end index)  
          --------->a[n/2][0](start index) to a[n][m/2](end index)
          --------->a[n/2][m/2](start index) to a[n][m](end index)
          

          现在根据我们的搜索空间递归调用相同的函数。

          我们函数的时间复杂度如下。注意:在时间函数中,n 表示元素的总数,但不是提到的行数。n=(no_of_rows)*(no_of_columns)

                          _________________T(n/4)  if given element is less than middle of the array.
                         / 
                        /
          T(n)==========------------------- 1 if n=1 (if element found)
                        \
                         \_________________3T(n/4) if given element is greater than middle element of array
          

          所以超时功能会

          T(n)=3T(n/4) 或 T(n)=T(n/4)

          In worst case T(n)=3T(n/4)
                         T(n)=3{3T(n/4)}
                         T(n)=3power(i)T(n/(4)poweri)     equation------> (1) 
          

          但是 T(1)=1(猜测给定的元素是在数组中找到的)

          so  n/(4power(i))=1
          ====> n=2power(2*i)
          ====> n=2power(2*i)
          Talking log to base 2 on both sides (log[n])/2=i ====> i=log(sqrt(n))
          

          代入方程 1 我们得到

          T(n)=3power(log[sqrt(n)])
          T(n)∈ θ( nlog sqrt(3) )..
          

          据我所知,这是进行最少比较次数的算法。

          【讨论】:

            【解决方案9】:

            JavaScript 解决方案:

            //start from the top right corner
            //if value = el, element is found
            //if value < el, move to the next row, element can't be in that row since row is sorted
            //if value > el, move to the previous column, element can't be in that column since column is sorted
            
            function find(matrix, el) {
            
              var row = 0; //first row
              var col = matrix[0].length - 1; //last column
            
              while (row < matrix.length && col >= 0) {
                if (matrix[row][col] === el) { //element is found
                  return true;
                } else if (matrix[row][col] < el) {
                  row++; //move to the next row
                } else {
                  col--; //move to the previous column
                }
              }
            
              return false;
            
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 2017-03-13
              • 1970-01-01
              • 2015-04-17
              • 2014-10-19
              • 1970-01-01
              • 1970-01-01
              • 2021-09-19
              相关资源
              最近更新 更多