【问题标题】:Kth smallest element in sorted matrix排序矩阵中的第 K 个最小元素
【发布时间】:2013-02-17 05:47:21
【问题描述】:

这是一道面试题。

在具有已排序行和列的矩阵中找到第 Kth 个最小元素。
第 Kth 最小元素是 a[i, j] 之一是否正确,例如 i + j = K

【问题讨论】:

  • 矩阵是如何排序的?只有每一行或每一列的数字在增加?
  • 是的,每一行每一列的数字都是按升序排列的。
  • 很容易想出一个反例来证明这个陈述是错误的。
  • 这个解决方案显然是不正确的。例如。第一个元素可以在角落找到,但第二个数字可以是两个邻居之一。第三个可能是 5 个可能的索引之一。您必须对二分搜索进行一些修改。

标签: arrays algorithm data-structures matrix multidimensional-array


【解决方案1】:

错误。

考虑一个像这样的简单矩阵:

1 3 5
2 4 6
7 8 9

9 是最大的(第 9 个最小的)元素。但是 9 在 A[3, 3] 和 3+3 != 9 处。(无论您使用什么索引约定,它都不正确)。


您可以在 O(k log n) 时间内通过增量合并行来解决此问题,并通过堆增加以有效地找到最小元素。

基本上,您将第一列的元素放入堆中并跟踪它们来自的行。在每一步中,您从堆中删除最小元素并从它所在的行中推送下一个元素(如果您到达行的末尾,那么您不会推送任何东西)。删除最小值和添加新元素都需要 O(log n)。在第 j 步,您删除了 jth 最小的元素,因此在 k 步骤之后,您完成了 O(k log n) 操作的总成本(其中 n 是矩阵中的行数)。

对于上面的矩阵,您最初在堆中以1,2,7 开始。删除1 并添加3(因为第一行是1 3 5)得到2,3,7。您删除2 并添加4 以获得3,4,7。删除3 并添加5 得到4,5,7。删除4 并添加6 得到5,6,7。请注意,我们正在按全局排序顺序删除元素。您可以看到,继续这个过程将在 k 次迭代后产生kth 最小的元素。

(如果矩阵的行多于列,则改为对列进行操作以减少运行时间。)

【讨论】:

  • 这很好..举一个矩阵是集合的例子。没有重复元素
  • @GrijeshChauhan:好吧,假设它是正确的。但这种假设过于局限。
  • @GrijeshChauhan:看看我的新矩阵。这是按行和列排序的,但您的解决方案不适用于它。
  • 如果只对行或列进行排序(本质上是外部排序中的 n 路合并),此解决方案效果最佳。 @user1987143 更好,因为它利用了行和列都已排序的事实。
  • 您已将行数定义为 n 那么如果您使用第一列初始化最小堆,那么运行时不是 n + k log (n) 吗? (您似乎没有在运行时计算中考虑该初始化步骤)。
【解决方案2】:

O(k log(k)) 解决方案。

  • 构建一个 minheap。

  • (0,0) 添加到堆中。虽然,我们还没有找到kth 最小的元素,从堆中删除顶部元素(x,y) 并添加接下来的两个元素[(x+1,y)(x,y+1)],如果它们之前没有被访问过。

我们正在对大小为O(k) 的堆进行O(k) 操作,因此复杂度也很高。

【讨论】:

  • 你能给这个格式吗?有点难以按原样阅读
  • 你确定这是正确的吗?我的意思是即使我也这么认为,只是惊讶于你的答​​案与另一个答案相比获得的票数没有,即使你的解决方案的复杂性比另一个更好。
  • 我认为这是正确的,请专家确认一下吗?
  • 我认为这是正确的,运行时比公认的答案更好。
  • 同意复杂度为 O(k log (k))。粗略解释:堆弹出复杂度为 O(log(heapsize))。这里堆大小从 1 开始,并在 k 次迭代中一一增长到 k。 Heapsize 在每次迭代中增长一个单元(对于大多数迭代),因为在每个阶段都会删除一个元素并添加两个,即右单元格和下单元格。 (除了在矩阵的边缘)所以,时间复杂度 ~= O(log(1)) + O(log(2)) + .... O(log(k)) ~= k log(k)
【解决方案3】:

这个问题可以使用二分查找和排序矩阵中的优化计数来解决。二进制搜索需要 O(log(n)) 时间,对于每个搜索值,平均需要 n 次迭代才能找到小于搜索数字的数字。二分搜索的搜索空间被限制为矩阵中的最小值mat[0][0]和最大值mat[n-1][n-1]

对于从二进制搜索中选择的每个数字,我们需要计算小于或等于该特定数字的数字。因此可以找到最小的。

为了更好地理解你可以参考这个视频:

https://www.youtube.com/watch?v=G5wLN4UweAM&t=145s

【讨论】:

    【解决方案4】:

    从左上角 (0,0) 开始遍历矩阵,并使用二进制堆来存储“边界”——矩阵的已访问部分与其其余部分之间的边界。

    Java 实现:

    private static class Cell implements Comparable<Cell> {
    
        private final int x;
        private final int y;
        private final int value;
    
        public Cell(int x, int y, int value) {
            this.x = x;
            this.y = y;
            this.value = value;
        }
    
        @Override
        public int compareTo(Cell that) {
            return this.value - that.value;
        }
    
    }
    
    private static int findMin(int[][] matrix, int k) {
    
        int min = matrix[0][0];
    
        PriorityQueue<Cell> frontier = new PriorityQueue<>();
        frontier.add(new Cell(0, 0, min));
    
        while (k > 1) {
    
            Cell poll = frontier.remove();
    
            if (poll.y + 1 < matrix[poll.x].length) frontier.add(new Cell(poll.x, poll.y + 1, matrix[poll.x][poll.y + 1]));
            if (poll.x + 1 < matrix.length) frontier.add(new Cell(poll.x + 1, poll.y, matrix[poll.x + 1][poll.y]));
    
            if (poll.value > min) {
                min = poll.value;
                k--;
            }
    
        }
    
        return min;
    
    }
    

    【讨论】:

      【解决方案5】:

      上述解决方案无法处理对角线条件,不能应用于以下矩阵

      int arr2[][] = { { 1, 4, 7, 11, 15 }, 
                       { 2, 5, 8, 12, 19 }, 
                       { 3, 6, 9, 16, 22 }, 
                       { 10, 13, 14, 17, 24 },
                       { 18, 21, 23, 26, 30 } }
      

      而 k=5

      返回 7 而答案是 5

      【讨论】:

        【解决方案6】:

        似乎这只是使用该功能:每一行都已排序,但不使用其按列排序的功能。

        【讨论】:

          【解决方案7】:

          正如人们之前提到的,最简单的方法是构建一个min heap。这是一个使用 PriorityQueue 的 Java 实现:

          private int kthSmallestUsingHeap(int[][] matrix, int k) {
          
              int n = matrix.length;
          
              // This is not necessary since this is the default Int comparator behavior
              Comparator<Integer> comparator = new Comparator<Integer>() {
                  @Override
                  public int compare(Integer o1, Integer o2) {
                      return o1 - o2;
                  }
              };
          
              // building a minHeap
              PriorityQueue<Integer> pq = new PriorityQueue<>(n*n, comparator);
              for (int i = 0; i < n; i++) {
                  for (int j = 0; j < n; j++) {
                      pq.add(matrix[i][j]);
                  }
              }
          
              int ans = -1;
              // remove the min element k times
              for (int i = 0; i < k; i++) {
                  ans = pq.poll();
              }
          
              return ans;
          }
          

          【讨论】:

            【解决方案8】:

            矩阵中第K个最小的元素:

            问题可以缩小如下。

            如果 k 为 20,则取 k*k 矩阵(答案肯定在哪里。)

            现在您可以重复成对合并行以构建排序数组,然后找到第 k 个最小的数字。

            【讨论】:

              【解决方案9】:
              //int arr[][] = {{1, 5, 10, 14},
              //        {2, 7, 12, 16},
              //        {4, 10, 15, 20},
              //        {6, 13, 19, 22}
              //};
              // O(k) Solution
              public static int myKthElement(int arr[][], int k) {
                  int lRow = 1;
                  int lCol = 0;
                  int rRow = 0;
                  int rCol = 1;
                  int count = 1;
              
                  int row = 0;
                  int col = 0;
              
                  if (k == 1) {
                      return arr[row][col];
                  }
              
                  int n = arr.length;
                  if (k > n * n) {
                      return -1;
                  }
              
                  while (count < k) {
                      count++;
              
                      if (arr[lRow][lCol] < arr[rRow][rCol]) {
                          row = lRow;
                          col = lCol;
              
                          if (lRow < n - 1) {
                              lRow++;
                          } else {
                              if (lCol < n - 1) {
                                  lCol++;
                              }
              
                              if (rRow < n - 1) {
                                  lRow = rRow + 1;
                              }
                          }
                      } else {
                          row = rRow;
                          col = rCol;
              
                          if (rCol < n - 1) {
                              rCol++;
                          } else {
                              if (rRow < n - 1) {
                                  rRow++;
                              }
                              if (lCol < n - 1) {
                                  rCol = lCol + 1;
                              }
                          }
                      }
                  }
              
                  return arr[row][col];
              }
              

              【讨论】:

              • 除了代码之外,请在您的答案中添加一些内容以详细说明您的方法或解决方案,以便对正在阅读答案的任何人更有意义。
              猜你喜欢
              • 2021-09-07
              • 2019-08-13
              • 1970-01-01
              • 1970-01-01
              • 2017-03-04
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多