二维矩阵指每一行递增且每一列递增的整数二维矩阵

240. 搜索二维矩阵 II

编写一个高效的算法来搜索 _m_ x _n_ 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列。
  • 每列的元素从上到下升序排列。


    示例 1:
    二维矩阵问题 - 图1
    输入:matrix = [[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]], target = 5
    输出:true

示例 2:
二维矩阵问题 - 图2
输入:matrix = [[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]], target = 20
输出:false


提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -10 <= matrix[i][j] <= 10
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -10 <= target <= 10


  1. //从右上往左下搜索,每次排除一行或一列
  2. class Solution {
  3. public boolean searchMatrix(int[][] matrix, int target) {
  4. int n = matrix.length;
  5. if (n == 0)
  6. return false;
  7. int m = matrix[0].length;
  8. if (m == 0)
  9. return false;
  10. int i = 0, j = m - 1;
  11. while (i < n && j >= 0) {
  12. if (matrix[i][j] == target)
  13. return true;
  14. else if (matrix[i][j] > target)
  15. j--;
  16. else
  17. i++;
  18. }
  19. return false;
  20. }
  21. }

378. 有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k不同 的元素。

示例 1:
输入:matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 8
输出:13
解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:
输入:matrix = [[-5]], k = 1
输出:-5


提示:

  • n == matrix.length
  • n == matrix[i].length
  • 1 <= n <= 300
  • -10 <= matrix[i][j] <= 10
  • 题目数据 保证 matrix 中的所有行和列都按 非递减顺序 排列
  • 1 <= k <= n

思路:
相较于240,不是查询特定的数是否存在于二维矩阵中,而是问第k小的数是什么。
二分套二分可以解决!

  1. class Solution {
  2. public int kthSmallest(int[][] g, int k) {
  3. int n = g.length, m = g[0].length;
  4. int min = g[0][0], max = g[n - 1][m - 1];
  5. //二分结果
  6. int l = min, r = max;
  7. while (l < r) {
  8. int mid = l + (r - l >> 1);
  9. int c = 0;
  10. //二分查找小于等于当前查找值(mid)的元素个数
  11. for (int[] a : g) {
  12. int ll = 0, rr = m - 1;
  13. while (ll < rr) {
  14. int mm = ll + rr + 1 >> 1;
  15. if (a[mm] > mid)
  16. rr = mm - 1;
  17. else
  18. ll = mm;
  19. }
  20. if (a[ll] > mid)
  21. c += 0;
  22. else
  23. c += ll + 1;
  24. }
  25. if (c < k)
  26. l = mid + 1;
  27. else
  28. r = mid;
  29. }
  30. return l;
  31. }
  32. }

373. 查找和最小的K对数字

给定两个以升序排列的整数数组 nums1 nums2 , 以及一个整数 k
定义一对值 (u,v),其中第一个元素来自 nums1,第二个元素来自 nums2
请找到和最小的 k 个数对 (u,v), (u,v)(u,v)

示例 1:
输入: nums1 = [1,7,11], nums2 = [2,4,6], k = 3
输出: [1,2],[1,4],[1,6]
解释: 返回序列中的前 3 对数:
[1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

示例 2:
输入: nums1 = [1,1,2], nums2 = [1,2,3], k = 2
输出: [1,1],[1,1]
解释: 返回序列中的前 2 对数:
[1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

示例 3:
输入: nums1 = [1,2], nums2 = [3], k = 3
输出: [1,3],[2,3]
解释: 也可能序列中所有的数对都被返回:[1,3],[2,3]


提示:

  • 1 <= nums1.length, nums2.length <= 10
  • -10 <= nums1[i], nums2[i] <= 10
  • nums1, nums2 均为升序排列
  • 1 <= k <= 1000

思路:
相较于373:

  • 二维矩阵变成了两个数组的和构成的二维矩阵!
  • 最终结果不是查询第k小的数,而是返回前k小数对!

除了利用之前373的二分套二分以外,还可以使用多用归并!

  1. //二分套二分
  2. class Solution {
  3. public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
  4. int n = nums1.length, m = nums2.length;
  5. int min = nums1[0] + nums2[0], max = nums1[n - 1] + nums2[m - 1];
  6. int l = min, r = max;
  7. while (l < r) {
  8. int mid = l + (r - l >> 1);
  9. long c = 0;
  10. for (int x : nums1) {
  11. if (x + nums2[m - 1] <= mid)
  12. c += m;
  13. else if (x + nums2[0] > mid) {
  14. break;
  15. } else {
  16. int ll = 0, rr = m - 1;
  17. while (ll < rr) {
  18. int mm = ll + (rr - ll + 1 >> 1);
  19. if (x + nums2[mm] > mid)
  20. rr = mm - 1;
  21. else
  22. ll = mm;
  23. }
  24. c += ll + 1;
  25. }
  26. }
  27. if (c < k)
  28. l = mid + 1;
  29. else
  30. r = mid;
  31. }
  32. List<List<Integer>> res = new ArrayList<>(k);
  33. Queue<List<Integer>> tmp = new LinkedList<>();
  34. for (int x : nums1) {
  35. for (int y : nums2) {
  36. if (x + y <= l) {
  37. List<Integer> arr = new ArrayList<>();
  38. arr.add(x);
  39. arr.add(y);
  40. if (x + y == l) {
  41. tmp.offer(arr);
  42. continue;
  43. }
  44. res.add(arr);
  45. k--;
  46. } else
  47. break;
  48. }
  49. }
  50. while (k-- > 0 && !tmp.isEmpty()) {
  51. res.add(tmp.poll());
  52. }
  53. return res;
  54. }
  55. }
  1. //多路归并
  2. class Solution {
  3. public List<List<Integer>> kSmallestPairs(int[] a, int[] b, int k) {
  4. List<List<Integer>> res = new ArrayList<>(k);
  5. int n = a.length, m = b.length;
  6. PriorityQueue<int[]> q = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
  7. for (int i = 0; i < m; i++)
  8. q.offer(new int[]{a[0] + b[i], 0, i});
  9. while (k-- > 0 && !q.isEmpty()) {
  10. int[] cur = q.poll();
  11. List<Integer> t = new ArrayList<>();
  12. t.add(a[cur[1]]);
  13. t.add(b[cur[2]]);
  14. res.add(t);
  15. if (cur[1] + 1 < n)
  16. q.offer(new int[]{a[cur[1] + 1] + b[cur[2]], cur[1] + 1, cur[2]});
  17. }
  18. return res;
  19. }
  20. }

2040. 两个有序数组的第 K 小乘积

给你两个 从小到大排好序 且下标从 0 开始的整数数组 nums1nums2 以及一个整数 k ,请你返回第 k (从 1 开始编号)小的 nums1[i] * nums2[j] 的乘积,其中 0 <= i < nums1.length 0 <= j < nums2.length

示例 1:

  1. 输入:nums1 = [2,5], nums2 = [3,4], k = 2
  2. 输出:8
  3. 解释:第 2 小的乘积计算如下:
  4. - nums1[0] * nums2[0] = 2 * 3 = 6
  5. - nums1[0] * nums2[1] = 2 * 4 = 8
  6. 2 小的乘积为 8

示例 2:

  1. 输入:nums1 = [-4,-2,0,3], nums2 = [2,4], k = 6
  2. 输出:0
  3. 解释:第 6 小的乘积计算如下:
  4. - nums1[0] * nums2[1] = (-4) * 4 = -16
  5. - nums1[0] * nums2[0] = (-4) * 2 = -8
  6. - nums1[1] * nums2[1] = (-2) * 4 = -8
  7. - nums1[1] * nums2[0] = (-2) * 2 = -4
  8. - nums1[2] * nums2[0] = 0 * 2 = 0
  9. - nums1[2] * nums2[1] = 0 * 4 = 0
  10. 6 小的乘积为 0

示例 3:

  1. 输入:nums1 = [-2,-1,0,1,2], nums2 = [-3,-1,2,4,5], k = 3
  2. 输出:-6
  3. 解释:第 3 小的乘积计算如下:
  4. - nums1[0] * nums2[4] = (-2) * 5 = -10
  5. - nums1[0] * nums2[3] = (-2) * 4 = -8
  6. - nums1[4] * nums2[0] = 2 * (-3) = -6
  7. 3 小的乘积为 -6

提示:

  • 1 <= nums1.length, nums2.length <= 5 * 10
  • -10 <= nums1[i], nums2[j] <= 10
  • 1 <= k <= nums1.length * nums2.length
  • nums1nums2 都是从小到大排好序的。

思路:
相较于373,从两个数组各元素和变为两个数组各元素的乘积,特别是存在负数的情况!
由于本题是查找第k小的乘积是多少,而不是找前k小数对,不需要用多路归并(其实是因为会超时!!!)
所以用二分套二分即可!只是要注意正负的问题!
先判断第k小数位于正数还是负数范围内再处理!

  1. class Solution {
  2. public long kthSmallestProduct(int[] nums1, int[] nums2, long k) {
  3. List<Integer> a = new ArrayList<>();
  4. List<Integer> b = new ArrayList<>();
  5. List<Integer> na = new ArrayList<>();
  6. List<Integer> nb = new ArrayList<>();
  7. for (int x : nums1) {
  8. if (x < 0)
  9. na.add(x);
  10. else
  11. a.add(x);
  12. }
  13. for (int x : nums2) {
  14. if (x < 0)
  15. nb.add(x);
  16. else
  17. b.add(x);
  18. }
  19. long c = 1L * a.size() * nb.size() + b.size() * na.size();
  20. long res = 0;
  21. //正数情况
  22. if (k > c) {
  23. k -= c;
  24. Collections.reverse(na);
  25. Collections.reverse(nb);
  26. res = get(k, a, b, na, nb);
  27. } else {
  28. Collections.reverse(a);
  29. Collections.reverse(b);
  30. res = get(k, a, nb, b, na);
  31. }
  32. return res;
  33. }
  34. long get(long k, List<Integer> a, List<Integer> b, List<Integer> na, List<Integer> nb) {
  35. long res = 0;
  36. long l = (long)(-1e10 - 10), r = (long)(1e10 + 10);
  37. while (l < r) {
  38. long mid = l + r >> 1;
  39. long cnt = 0;
  40. cnt += binarySearch(mid, a, b);
  41. cnt += binarySearch(mid, na, nb);
  42. if (cnt < k)
  43. l = mid + 1;
  44. else
  45. r = mid;
  46. }
  47. return l;
  48. }
  49. long binarySearch(long mid, List<Integer> a, List<Integer> b) {
  50. long cnt = 0;
  51. if (a.size() == 0 || b.size() == 0)
  52. return cnt;
  53. for (Integer x : a) {
  54. int ll = 0, rr = b.size() - 1;
  55. while (ll < rr) {
  56. int mm = ll + (rr - ll + 1 >> 1);
  57. long val = 1L * x * b.get(mm);
  58. if (val > mid)
  59. rr = mm - 1;
  60. else
  61. ll = mm;
  62. }
  63. if (1L * x * b.get(ll) <= mid)
  64. cnt += ll + 1;
  65. }
  66. return cnt;
  67. }
  68. }

668. 乘法表中第k小的数

几乎每一个人都用 乘法表。但是你能在乘法表中快速找到第k小的数字吗?
给定高度m 、宽度n 的一张 m * n的乘法表,以及正整数k,你需要返回表中第k 小的数字。
例 1:
输入: m = 3, n = 3, k = 5
输出: 3
解释:
乘法表:
1 2 3
2 4 6
3 6 9

第5小的数字是 3 (1, 2, 2, 3, 3).

例 2:
输入: m = 2, n = 3, k = 6
输出: 6
解释:
乘法表:
1 2 3
2 4 6

第6小的数字是 6 (1, 2, 2, 3, 4, 6).

注意:

  1. mn 的范围在 [1, 30000] 之间。
  2. k 的范围在 [1, m * n] 之间。

思路:
类似于373,加法换成乘法

  1. class Solution {
  2. public int findKthNumber(int m, int n, int k) {
  3. int l = 1 * 1, r = m * n;
  4. while (l < r) {
  5. int mid = l + (r - l >> 1);
  6. int cnt = get(m, n, mid);
  7. if (cnt < k)
  8. l = mid + 1;
  9. else
  10. r = mid;
  11. }
  12. return l;
  13. }
  14. int get(int m, int n, int x) {
  15. int cnt = 0;
  16. for (int i = 1; i <= n; i++) {
  17. cnt += Math.min(x / i, m);
  18. }
  19. return cnt;
  20. }
  21. }