首页 > 试题广场 >

二维表第k大数

[编程题]二维表第k大数
  • 热度指数:3342 时间限制:C/C++ 1秒,其他语言2秒 空间限制:C/C++ 32M,其他语言64M
  • 算法知识视频讲解
在一块长为n,宽为m的场地上,有n✖️m个1✖️1的单元格。每个单元格上的数字就是按照从1到n和1到m中的数的乘积。具体如下

n = 3, m = 3
1   2   3
2   4   6
3   6   9

给出一个查询的值k,求出按照这个方式列举的的数中第k大的值v。
例如上面的例子里,
从大到小为(9, 6, 6, 4, 3, 3, 2, 2, 1)
k = 1, v = 9
k = 2, v = 6
k = 3, v = 6
...
k = 8, v = 2
k = 9, v = 1

输入描述:
只有一行是3个数n, m, k 表示场地的宽高和需要查询的k。使用空格隔开。


输出描述:
给出第k大的数的值。
示例1

输入

3 3 4

输出

4

备注:
【数据范围】
100%的数据
1 <= n, m <= 40000
1 <= k <= n * m
30%的数据
1 <= n, m <= 1000
import java.util.Scanner;

public class Main {
	public static void main(String[] args) {
		Scanner sc = new Scanner(System.in);
		int n = sc.nextInt();
		int m = sc.nextInt();
		int k = sc.nextInt();

		int start = 1;
		int end = n * m;
		while (start < end) {
			// 中位数, 这里不写成 mid = (start + end) / 2, 是为了防止整型溢出
			int mid = start + (end - start) / 2;
			// 记录数组中比mid大的数值个数
			int cnt = 0;
			// 记录数组中刚好大于mid的整数值
			// 例如mid = 50时, 若数组中大于50的数中最小值为51, 则res = 51. 
			int res = n * m;
			// 大于mid的数, 其x必大于mid/m, 其y必大于mid/n
			for (int i = mid / m + 1; i <= n; i++)
				for (int j = mid / n + 1; j <= m ; j++)
					// 找到第一个满足条件的数后, 直接将该列后续所有数字加进来, 减少复杂度, 非常重要!!!!
					if (i * j > mid) {
						cnt += (m - j + 1);
						if (res > i * j)
							res = i * j;
						// 这里的break只能跳出最内层的for循环
						break;
					}

			if (cnt > k)
				start = mid + 1;
			else if (cnt == k) {
				// 当cnt == k时, 直接取res即可
				start = res;
				break;
			} else if (cnt < k)
				// 这里不能令end = mid -1,否则有可能将最优解滤除
				// 假设大于50的数有19个, 等于50的数有3个, 此时cnt = 19, 若k = 20,
				// 最优解应为50, 故不能令end = mid -1
				end = mid;
		}
		System.out.println(start);
	}
}


编辑于 2020-10-21 22:17:41 回复(0)
这道题我提交后显示“请检查是否存在数组越界等非法访问情况,case通过率为30.00%”,想问下大家我代码是哪里出了错误。我的思路是将这个n*m数组转换为一维数组,在进行排序,最后要输出的第k大的值就是一维数组下表为n*m-k的值,测试用例我没试出问题,大家能帮我找到哪里错了么
import java.util.*;

public class Main {
    public static void main(String[] args){
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        int k = sc.nextInt();
        int t = 0;
        int[][] arr = new int[n][m];
        int[] a = new int[n*m];
        for(int i=1;i<=n;i++){
            for(int j=1;j<=m;j++){
                arr[i-1][j-1] = i * j;
            }
        }
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                a[t] = arr[i][j];
                t++;
            }
        }
        Arrays.sort(a);

        System.out.println(a[m*n-k]);
    }
}


发表于 2020-08-01 19:20:14 回复(10)
import java.util.Scanner;

public class Main{
    public static void main(String[] args) {
        Scanner in = new Scanner(System.in);
        int N = in.nextInt();
        int M = in.nextInt();
        int K = in.nextInt();
        int l = 0;
        int h = M * N;
        //反向序号
        K = M * N - K + 1;
        while (l <= h) {
            int mid = l + (h - l) / 2;
            // 假定以mid作为最大数的所在行curRow; 由矩阵的特点可知:
            // curRow的上一行所有的数都将小于mid,缩小查找范围
            int curRow = mid / M;
            // 同理,获取所在列curCol;
            int curCol = mid / N;
            int cnt = curRow * M + curCol * (N - curRow);
            // 剩下右下角一个小矩形
            for (int i = curRow + 1; i <= N; i++) {
                for (int j = curCol + 1; j <= M && (i * j <= mid); j++) {
                    cnt++;
                }
            }
            if (cnt >= K) {
                h = mid - 1;
            } else {
                l = mid + 1;
            }
        }
        System.out.println(l);
    }
}


发表于 2020-07-30 10:04:02 回复(5)