【问题标题】:Find kth largest element from a 2-d sorted array从二维排序数组中查找第 k 个最大元素
【发布时间】:2011-05-09 17:34:43
【问题描述】:

我有一个二维数组。行和列已排序。如何从二维数组中找到第k大的元素?

【问题讨论】:

  • 如果这是一个家庭作业,请用homework标签标记它。
  • “行和列已排序”是什么意思?每行的开头是在上一行的结尾之后开始,还是独立排序?
  • 这不是家庭作业。我的朋友问我这个问题。元素是独立排序的。如果取数组中的任何元素,则其上方的元素和左侧的元素始终小于该元素。
  • 很有趣,我最近在一次采访中被问到这个问题。
  • search algorithm 的可能重复项

标签: algorithm


【解决方案1】:

如果您有一个n * n 矩阵,则可以在O(n * log(n) * log(n)) 的平均时间内完成此操作。

您所做的是将矩阵分解为一系列排序数组,然后一次对所有数组进行二进制搜索。例如,假设n = 4 和从(0,0) 索引到(3,3)。我们可以将其分解为沿列向下到上升对角线的数组,然后右转完成该行。这将为我们提供以下一组排序数组:

  1. (0,0), (0,1), (0,2), (0,3), (1,3), (2,3), (3,3)
  2. (1,0), (1,1), (1,2), (2,2), (3,2)
  3. (2,0), (2,1), (3,1)
  4. (3,0)

这给了我们n矩阵中的排序列表。

所以我们需要找出k'th 元素在一组排序数组中的位置。

我们将通过二进制搜索来查找它的值。

从我们最长数组的中点开始,在这个例子中就是元素(0,3)。然后对于每个其他数组计算出有多少大于、小于或等于该值。 (您可以通过二分搜索找到它。)这让我们确定k'th 元素在分割的哪一侧。如果它与我们刚刚选择的元素匹配,我们就有了答案。否则我们可以平均丢弃每个数组的一半(有时会丢弃整个数组)并重复。

在平均O(log(n)) 操作之后,每个操作都需要花费O(n log(n)) 来执行,我们将得到答案,从而得出我上面的估计。

【讨论】:

  • 有趣!但是,运行时间很可能更难准确计算,因为数组具有不同的大小并且它们的输入不是独立的。我很好奇是否有某种中位数方案也可以消除平均值。
  • @missingno:这就是为什么我说那里可能有一个额外的因素log(n)。我们从sqrt(n) 数组开始,但我怀疑它们中的大多数会很快失效。但是第一遍是O(sqrt(n) * log(n)),所以我不会做得比这更好。
  • 能否请您发布一个真实矩阵的示例?我不明白您在第三行“假设 k=9 ...”中的意思
  • @JaviV 我把我的解释搞砸了。请查看修改后的版本,看看现在是否更有意义。
  • and repeat.(如果没有完成)那么,在接下来的迭代中,您选择什么作为中值候选? (中间=最长剩余的中位数?)
【解决方案2】:

在最小维度上进行 n 路合并。当你完成第 k 个项目时,你就完成了。

测试显示这在 k lg d 中运行,其中 d = min(rows,cols)。

【讨论】:

  • 对不起.. 我没能找到你。最小尺寸是什么意思?如果我是对的,在合并排序中我们使用分而治之的方法。我们将一个数组一分为二,并对每个数组进行归并排序。合并排序再次将数组分成 2 并按排序顺序组合。我们必须递归地这样做,直到数组长度为 1。对吗?
  • 抱歉,我的意思是只执行“合并”或合并步骤。如果您的数组的行数少于列数,请合并行。如果列数较少,请合并这些列。
  • 感谢您的回复。是的。我想这会给我答案。归并排序的复杂度为 (n log n )。有没有更简单的方法?再次感谢。
  • 由于您实际上不必执行除法部分,并且您可以在找到 k 个元素后停止,因此复杂度应该更像 (k* c Lg c) 其中 c 是较少的列数或行。有关 n 路合并算法,请参阅 stackoverflow.com/questions/5055909/algorithm-for-n-way-merge。
【解决方案3】:

假设我有如下所示的矩阵

1    2    3    4    5
6    7    8    9    10
11  12  13  14  15
16  17  18  19  20
21  22  23  24  25

当我在考虑这个问题的解决方案时,我看到第一个最大的元素总是在 (4,4)。第二大元素将在 (3,4) 或 (4,3) 中,它不能在 (4,4) 中。所以我在想是否可以根据矩阵大小和k找到第k大元素的可能位置。

假设第 k 个最大元素的可能位置集合 = f( size(matrix), k )。

但在下面发布的答案中,我找不到可以生成可能位置的简单函数 f()。

我只能从可能的位置检查元素,而不是检查所有位置的元素。

要查找比元素大的数字,我们可以使用下面的方法。

如果我想找出有多少元素大于14。无论如何,14(15)右侧和14(19,24)以下的元素以及它们之间的所有元素(20,25)都是大于 14。因为行和列已排序。然后有 2 个高于 14 的子矩阵(包括 5 和 10 )和一个低于 14 的子矩阵(包括 16、17、18、21、22、23),它们可能包含也可能不包含大于 14 的元素。所以如果我们发现和将这 3 个矩阵中大于 14 的元素数相加,我们将得到大于 14 的元素数。

对于每个可能的位置,我们可以找到矩阵中较大元素的数量。如果有k-1个较大的元素,那么当前位置的元素就是第k大的元素。

package test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class NewTest
{
    private static int matrixSize = 25;

    private static Map < Integer, List < Point > > largestEltVsPossiblePositions = new HashMap < Integer, List < Point >>();

    static
    {
        // In the initialize method, I am populating the map
        // "largestEltVsPossiblePositions" with kth largest element and its
        // possible positions. That is 1st largest element will always be in
        // (24,24) and 2nd largest element will be (23,24) and (24,23). Like
        // that I am populating the possible locations for all the nth largest
        // elements. This map we need to initialize only once.
        initialize();
    }

    private static void initialize()
    {
        for ( int i = 1; i <= matrixSize * matrixSize; i++ )
        {
            //Getting the possible locations for each number and putting in the map.
            List < Point > possiblePositions = getPossiblePositions( matrixSize, i );
            largestEltVsPossiblePositions.put( i, possiblePositions );
        }
    }

    /**
     * @param args
     */
    public static void main( String [] args )
    {
        //        int matrixSize = 5;
        //        for ( int i = 1; i <= matrixSize * matrixSize; i++ )
        //        {
        //            List < Point > possiblePositions = getPossiblePositions( matrixSize, i );
        //            System.out.println( i + " --- " + possiblePositions.size() + " - " + possiblePositions );
        //        }

        //creating a test array.
         int [][] matrix = createTestArray();

         long currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 7 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 27 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 34 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 624 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 2 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 4 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );

         currentTimeMillis = System.currentTimeMillis();
         findKthLargestElement( matrix, 310 );
         System.out.println( "Total time : " + ( System.currentTimeMillis() -
             currentTimeMillis ) );
    }

    private static int [][] createTestArray()
    {
        int [][] matrix = new int [matrixSize] [matrixSize];

        int count = 1;
        for ( int i = 0; i < matrixSize; i++ )
        {
            for ( int j = 0; j < matrixSize; j++ )
            {
                matrix[j][i] = count;
                count++ ;
            }
        }

        return matrix;
    }

    private static void findKthLargestElement( int [][] matrix, int k )
    {
        //Get all the possible positions of this kth largest element.
        List < Point > possiblePoints = largestEltVsPossiblePositions.get( k );

        //I am sorting the points in descending order of the values in them.
        Collections.sort( possiblePoints, new PointComparator( matrix ) );

        for ( Point point : possiblePoints )
        {
            //For a point, If there are exactly k-1, larger elements in the matrix, then it is the kth largest element.
            if ( ( k - 1 ) == getNoofLargerElementsThanKFromMatrix( matrix, point ) )
            {
                System.out.println( "Largest " + k + "th element in the matrix is : " + matrix[point.x][point.y]
                        + " in the co-ordinates : " + point );
                break;
            }
        }
    }

    /*
     * This method will find the elements larger than the element at the specified point from the matrix.
     */
    private static int getNoofLargerElementsThanKFromMatrix( int [][] matrix, Point point )
    {
        int sum = 0;
        // Suppose the point is (x,y). Then all the elements (x+1,y),
        // (x+2,y).... (maxRows,y), (x,y+1), (x,y+2), ... (x,maxCols) and all
        // the numbers between them(x+1,y+1), (x+2,y+1)... (maxRows,maxCols)
        // will be surely greater than the element at the point (x,y.). We are counting those element. 
        sum = ( matrixSize - point.x ) * ( matrixSize - point.y ) - 1;
        if ( point.x > 0 )
        {
            // In the above case, we were sure that all the elements in that range are greater than element at the point.
            // There is a region in the matrix where there might be elements larger than the element at the point.
            // If the point is (x,y), then the elements from (0,y+1) to
            // (x-1,maxCols), In this region there might be some elements which
            // are larger than the element we need to count those.
            sum = sum + getNumbersGreaterThanKFromUpperMatrix( matrix, point );
        }
        if ( point.x < matrix.length - 1 )
        {
            // It is same as the above case, There is another region in the
            // matrix where there might be elements larger than the element at the point.
            // If the point is (x,y), then the elements from (x+1,0) to
            // (maxRows,y-1), In this region there might be some elements which
            // are larger than the element we need to count those.
            sum = sum + getNumbersGreaterThanKFromLowerMatrix( matrix, point );
        }
        //Once we get all the elements larger than k, we can return it.
        return sum;
    }

    private static int getNumbersGreaterThanKFromUpperMatrix( int [][] matrix, Point point )
    {
        int startY = point.y;
        if ( point.y + 1 != matrix[0].length )
        {
            startY = point.y + 1;
        }
        Point matrixStart = new Point( 0, startY );
        int startX = point.x;
        if ( point.x != 0 )
        {
            startX = point.x - 1;
        }
        Point matrixEnd = new Point( startX, matrix[0].length - 1 );
        return getLargerElementsFromTheMatrix( matrix, matrixStart, matrixEnd, matrix[point.x][point.y] );
    }

    private static int getNumbersGreaterThanKFromLowerMatrix( int [][] matrix, Point point )
    {
        int startX = point.x;
        if ( point.x + 1 != matrix.length )
        {
            startX = point.x + 1;
        }
        Point matrixStart = new Point( startX, 0 );
        int startY = point.y;
        if ( point.y != 0 )
        {
            startY = point.y - 1;
        }
        Point matrixEnd = new Point( matrix.length - 1, startY );
        return getLargerElementsFromTheMatrix( matrix, matrixStart, matrixEnd, matrix[point.x][point.y] );
    }

    private static int getLargerElementsFromTheMatrix( int [][] matrix, Point matrixStart, Point matrixEnd, int elt )
    {
        //If it is a single cell matrix, just check that element in the matrix is larger than the kth element we are checking.
        if ( matrixStart.equals( matrixEnd ) )
        {
            if ( elt <= matrix[matrixStart.x][matrixStart.y] )
            {
                return 1;
            }
            else
            {
                return 0;
            }
        }
        if ( elt <= matrix[matrixStart.x][matrixStart.y] )
        {
            return ( matrixEnd.x - matrixStart.x + 1 ) * ( matrixEnd.y - matrixStart.y + 1 );
        }
        else
        {
            //Do it recursively to get all the elements larger than elt from the matrix from the startPoint to endPoint.
            int matrixStartX = matrixStart.x;
            if ( matrixStart.x + 1 <= matrixEnd.x )
            {
                matrixStartX = matrixStart.x + 1;
            }
            int matrixStartY = matrixStart.y;
            if ( matrixStart.y + 1 <= matrixEnd.y )
            {
                matrixStartY = matrixStart.y + 1;
            }
            Point newMatrixStart = new Point( matrixStartX, matrixStartY );
            int s1 = getLargerElementsFromTheMatrix( matrix, newMatrixStart, matrixEnd, elt );
            int s2 = getLargerElementsFromTheMatrix( matrix, new Point( matrixStartX, matrixStart.y ), new Point(
                    matrixEnd.x, matrixStart.y ), elt );
            int s3 = getLargerElementsFromTheMatrix( matrix, new Point( matrixStart.x, matrixStartY ), new Point(
                    matrixStart.x, matrixEnd.y ), elt );
            return s1 + s2 + s3;
        }
    }

    //For getting the possible positions of kth largest element.
    private static List < Point > getPossiblePositions( int matrixSize, int k )
    {
        List < Point > points = new ArrayList < Point >();
        k-- ;
        for ( int i = 0; i < matrixSize; i++ )
        {
            for ( int j = 0; j < matrixSize; j++ )
            {
                int minNoGreaterThanIJ = ( matrixSize - i ) * ( matrixSize - j ) - 1;
                int maxNoGreaterThanIJ = matrixSize * matrixSize - ( ( i + 1 ) * ( j + 1 ) );
                if ( minNoGreaterThanIJ <= k && maxNoGreaterThanIJ >= k )
                    points.add( new Point( i, j ) );
            }
        }
        return points;
    }
}

class Point
{
    final int x;
    final int y;

    Point( int x, int y )
    {
        this.x = x;
        this.y = y;
    }

    @Override
    public String toString()
    {
        return "(" + x + "," + y + ")";
    }

    @Override
    public int hashCode()
    {
        final int prime = 31;
        int result = 1;
        result = prime * result + x;
        result = prime * result + y;
        return result;
    }

    @Override
    public boolean equals( Object obj )
    {
        if ( this == obj )
            return true;
        if ( obj == null )
            return false;
        if ( getClass() != obj.getClass() )
            return false;
        Point other = ( Point ) obj;
        if ( x != other.x )
            return false;
        if ( y != other.y )
            return false;
        return true;
    }
}

class PointComparator implements Comparator < Point >
{
    private final int [][] matrix;

    public PointComparator( int [][] matrix )
    {
        this.matrix = matrix;
    }

    @Override
    public int compare( Point o1, Point o2 )
    {
        if ( matrix[o1.x][o1.y] == matrix[o2.x][o2.y] )
        {
            return -1;
        }
        else if ( matrix[o1.x][o1.y] < matrix[o2.x][o2.y] )
        {
            return 1;
        }
        else
        {
            return 1;
        }
    }
}

初始化在开始时完成一次。初始化完成后,将计算并缓存可能的位置。此信息可用于查找第 k 个最大元素。

但我不确定这种方法的复杂性是什么。

【讨论】:

    【解决方案4】:

    其实有一个O(n)分治算法,可以解决排序矩阵中的选择问题(即找到排序矩阵中的第k个最小元素)。

    Selection In X+Y and Matrices with Sorted Rows and Columns 的作者最初提出了这样一个算法,但它的工作方式并不那么直观。可以在Selection in a sorted matrix 中找到一种更简单的算法,如下所示。

    定义:假设一个已排序的 n x m 矩阵 M,其中 n rank(M,e) = |{M(i,j) | M(i,j) < e}|。

    主定理:该算法依赖于这样一个事实:如果 M 是一个排序矩阵,2*rank(N,e) - 2n &lt;= rank(M,e) &lt;= 2*rank(N,e)。

    证明:以f(i) = min j s.t. M(i,j) &gt;= e,我们可以说明

    rank(M,e) = sum i=1 to n of f(i)
    rank(N,e) = sum i=1 to n of ceil(f(i)/2) <= rank(M,e)/2 + n
    => 2*rank(N,e) - 2n <= rank(M,e)
    rank(N,e) > sum i=1 to n of f(i)/2
    => rank(M,e) <= 2*rank(N,e)
    

    征服:换句话说,如果我们要在 M 中找到一个秩为 k 的元素,我们只需要查看 M 的由元素 a 和 b 包围的子矩阵 P这样rank(N,a) = floor(k/2) 和rank(N,b) = ceil(k/2) + n。这个子矩阵有多少个元素?通过前面的不等式和没有重复的假设,所以最多 O(n)。因此,我们只需要在 P 中选择第 k - rank(N,a) 个元素,这可以通过在 O(m) 中将 P 重新排列为排序数组来完成,然后运行线性时间算法(例如 quickselect)来查找实际元素。 rank(M,a) 可以在 O(m) 中计算,从矩阵中的最小元素开始,遍历列,直到找到大于 a 的元素,然后转到下一行并转到上一列,直到找到第一个大于 a 的元素等。因此,征服部分在 O(m) 中运行。

    除法:唯一剩下要做的就是找到 a 和 b 使得 rank(N,a) = k/2 和 rank(N,b) = k/2 + n。这显然可以在 N 上递归完成(相对于 M,其大小除以 2)。

    运行时分析:总而言之,我们有一个 O(m) 征服算法。将f(n,m) 作为 n x m 矩阵的算法复杂度,其中 n f(m) = c*m + f(m/2)。根据主定理,由于f(1) = 1,我们找到f(n,m) = O(m)。因此整个算法的运行时间为 O(m),即 O(n) 在方阵的情况下(这也是 n.b. 也是 O(k),因为我们可以将搜索限制在包含第一个k 列和行)。

    对于具有重复的矩阵的一般情况,可以用行号和列号标记矩阵的元素。

    【讨论】:

      【解决方案5】:

      这个怎么样?

      假设

      1. 行和列按 wlog 升序排列。
      2. 我们必须从 m*n 个数中找出第 k 个最小的数(这是问题陈述)
      3. m*n >= k,否则返回 null/引发异常

      维护一个大小为 k 的最大堆。

      Push A[0][0] in the heap.
      
      for i = 1 to k
          curr_element = pop max element from heap
          Push the right and bottom neighbor of the popped element from the matrix
              (if they exist and have not been pushed earlier)
      
      return curr_element
      

      时间复杂度 = 循环运行 k 次 (O(k)) * 1 次迭代运行 O(3*log(k)) 次 = O(k*log(k))

      【讨论】:

        【解决方案6】:

        让我们假设一个包含 r 行和 c 列的数组。 索引从 1 开始。

        更新:对不起,我忘了提到,首先您必须转换 k 才能使以下公式起作用:

        k = n - (k-1)。其中n是元素的总数,即r*c。

        可以得到第k个最大元素的行索引:ceil(k/r)

        可以得到k个最大元素的列索引:k%c(%是Mod运算符)

        更新:如果 k%c = 0,则将结果设置为 c。

        运行时间为 O(1)。

        如果您有一个尺寸为 r=4 和 c=4 的数组的 k=14

        k = 16 - (14 - 1)

        k= 3

        ARR[ceil(3/4),3%c] 将返回第 k 个最大的元素。

        【讨论】:

        • 如果允许数组中有重复项,这将不起作用,我相信这里就是这种情况......
        • @gusbro 好吧,这个问题并没有说明应该如何处理重复。如果您有重复,并且应该像单个值一样处理它,这将不起作用。如果重复项是独立的,那么这将起作用。
        • 我有 3x3 数组。 {{1,2,3},{4,5,6},{7,8,9}}。我想找到第二大元素。在这种情况下,第二大元素是 8,如果索引从 (1,1) 开始,则位置是 a(3,2),如果索引从 (0,0) 开始,则位置是 a(2,1)。通过解决方案,第二大元素应该是 a(2/3,2%3),那将是 a(0,2)?这似乎不正确。我认为,应该处理重复。我的朋友没有提到重复值。
        • @Paul Nibin 在公式中 k=8 而不是 2。我已经更新了我的答案。
        • 对不起...我没有看到更新的答案。检查另一个阵列。 {{1,2,9},{4,5,10},{7,8,11}}。我想找到第二大元素。在这种情况下,第二大元素是 10,位置是 a(2,3)。 k=9-(2-1)=9-1=8。通过该解决方案,第二大元素应该是 a(ceil(8/3),8%3),那将是 a(3,2)?但它应该是 a(2,3)。
        【解决方案7】:

        方法:

        1. 复制每个节点并将其插入到原始节点的右侧。
        2. 重复的随机指针。
        3. 左指针重复。
        4. 分离两棵树。

        图形解释请参考http://mytechspaze.com/index.php/2016/09/15/clone-binary-tree/

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2017-03-04
          • 2021-03-03
          • 1970-01-01
          • 1970-01-01
          • 2019-08-28
          • 1970-01-01
          相关资源
          最近更新 更多