力扣寻找数组中第k小元素遇到的问题

思路是用二分法筛选该元素位序,计算有多少元素小于等于该位序的元素,与期望值k比较,请问问题在哪,怎么改进

img

本题的二分法确实具有迷惑性,题主的二分法将矩阵的下标和矩阵的值搞混了。下图是题解中的矩阵举例,每行呈递增趋势,每列呈递增趋势,并不是后一行一定大于前一行,如matrix[3][0] < matrix[0][3]. 这可能是造成题主理解错题目的根本原因。

img

下面给出了修改后的代码,并附带了每一处修改的原因。代码经过leetcode验证通过率100%。

class Solution {
public:
    int kthSmallest(vector<vector<int>>&matrix,int k){
        int count=0;
        int n = matrix.size()*matrix[0].size();
        // int left=0;
        // int right=n-1; 修改此处【1】,left是矩阵的最小值,right是矩阵的最大值。
        int left = matrix[0][0];
        int right = matrix[matrix.size()-1][matrix.size()-1];
        int mid;
        while(left<right){
            mid=left+(right-left)/2;
            int i=matrix.size()-1,j=0;
            // count = 0; count每统计完一次要清零
            // while(i>=0&&j<=matrix[0].size()-1){
            //     if(matrix[i][j]>matrix[mid/n][mid%n]){ 修改此处【2】, 和【1】的修改理由相同,mid是数值不是下标
            //         j++;
            //     }
            //     else i--;
            //     count++;        // count的统计方式不对
            // }
            while(i>=0&&j<=matrix[0].size()-1){
                if(matrix[i][j]<=mid){
                    count += i+1; 
                    j++;
                }
                else i--;
            }
    
            // if(count>k){ 修改【3】,此处的二分法区间对应错误
            //     left=mid+1;
            // }
            // else if(count<k){
            //     right=mid-1;
            // }
            if(count >= k){
                right=mid;
            }
            else if(count<k){
                left = mid+1;
            }
        }
        // return matrix[mid/n][mid%n]; 这里同样是,left是返回值
        return left;
    }
};

关于第2处修改,题主可能会有疑惑,为什么count += i+1; 这里其实是按列统计的,如下图,扫描到6时,将6之上的一整列统计进去,因为其上的数必然小于6,也就小于mid。

img


关于返回值题主可能会疑惑怎么保证返回的left一定是矩阵中的值,leetcode的评论中有一句很精辟的理解

因为每次循环中都保证了第k小的数在left~right之间,当left==right时,第k小的数即被找出,等于right或left

img

按照它的要求你可以用堆排

#include <iostream>
#include <vector>
#include <queue>
using namespace std;

int kthSmallest(vector<vector<int>>& matrix, int k) {
    int n = matrix.size();
    priority_queue<int> maxHeap;

    for (int i = 0; i < n; i++) {
        for (int j = 0; j < n; j++) {
            maxHeap.push(matrix[i][j]);
            if (maxHeap.size() > k) {
                maxHeap.pop();
            }
        }
    }

    return maxHeap.top();
}

int main() {
    vector<vector<int>> matrix = {{1, 5, 9}, {10, 11, 13}, {12, 13, 15}};
    int k = 8;
    int result = kthSmallest(matrix, k);
    cout << "The " << k << "th smallest element is: " << result << endl;
    return 0;
}


引用 皆我百晓生 小程序回复内容作答:
在你的代码中,有几个问题需要改进:

  1. 在二分查找中,你没有更新 left 和 right 的值,导致死循环。你需要在 if(count > k) 和 if(count < k) 的条件语句中更新 left 和 right。

  2. 在计算 count 的时候,你没有考虑相等的情况。如果 matrix[i][j] == matrix[mid/n][mid%n],count 也需要加1。

  3. 在执行 while 循环之前,你应该先检查左上角元素和右下角元素,以确保它们不为空,

以下是修正后的代码:

class Solution {
public:
    int kthSmallest(vector<vector<int>>& matrix, int k) {
        int left = matrix[0][0];
        int right = matrix.back().back();

        while (left < right) {
            int mid = left + (right - left) / 2;
            int count = 0;
            int j = matrix[0].size() - 1;

            for (int i = 0; i < matrix.size(); i++) {
                while (j >= 0 && matrix[i][j] > mid) {
                    j--;
                }
                count += (j + 1);
            }

            if (count < k) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }

        return left;
    }
};

这样修改之后,你的代码应该可以通过测试了。

二分法是一种非常有效的寻找数组中第 k 小元素的方法。这种方法的基本思想是将数组视为一个有序序列,通过不断将序列划分为两部分并比较中间元素与目标 k 的大小关系,最终找到第 k 小的元素。

下面是使用二分法寻找数组中第 k 小元素的步骤:

  1. 初始化两个指针 leftright,分别指向数组的首尾元素。
  2. 进入循环,当 left <= right 时进行迭代:
    a. 计算中间元素的索引 mid,取整数部分,即 mid = left + (right - left) / 2
    b. 计算中间元素的值 nums[mid]
    c. 统计数组中小于等于 nums[mid] 的元素个数 count
    d. 如果 count < k,说明第 k 小的元素在右半部分,更新 left = mid + 1
    e. 如果 count >= k,说明第 k 小的元素在左半部分或者就是中间元素,更新 right = mid - 1
  3. 当循环结束时,返回 nums[left],即为数组中的第 k 小元素。

下面是使用 Python 实现的代码示例:

def kthSmallest(nums, k):
    left, right = 0, len(nums) - 1
    while left <= right:
        mid = left + (right - left) // 2
        count = sum(num <= nums[mid] for num in nums)
        if count < k:
            left = mid + 1
        else:
            right = mid - 1
    return nums[left]

请注意,在使用二分法时,需要确保数组中的元素没有重复值,否则可能会导致结果错误。如果数组中可能存在重复值,可以考虑使用其他方法,如快速选择算法。

【相关推荐】




如果你已经解决了该问题, 非常希望你能够分享一下解决方案, 写成博客, 将相关链接放在评论区, 以帮助更多的人 ^-^

结合GPT给出回答如下请题主参考
问题在于二分法只能帮我们找到一个可能的答案,但并不能保证这个答案就是第k小的元素。因为可能存在多个元素具有相同的值,而这些元素在排序后的位置可能不止一个,导致我们无法确定哪一个才是第k小的元素。

改进的方法可以是我们在进行二分查找时,不仅要计算有多少元素小于等于该位序的元素,还需要记录小于该位序的元素的集合,然后对这个集合进行一次排序。这样就能够确定该位序对应的元素在整个集合中的排序位置,然后再和期望值k进行比较即可。如果期望值k比该元素在集合中的排序位置小,则在小于该位序的集合中继续进行二分查找;反之,在大于等于该位序的集合中继续进行二分查找。这样就可以保证找到的元素就是第k小的元素了。