240. 搜索二维矩阵 II
编写一个高效的算法来搜索 _m_ x _n_
矩阵 matrix
中的一个目标值 target
。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
每列的元素从上到下升序排列。
示例 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:
输入: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
//从右上往左下搜索,每次排除一行或一列
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int n = matrix.length;
if (n == 0)
return false;
int m = matrix[0].length;
if (m == 0)
return false;
int i = 0, j = m - 1;
while (i < n && j >= 0) {
if (matrix[i][j] == target)
return true;
else if (matrix[i][j] > target)
j--;
else
i++;
}
return false;
}
}
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小的数是什么。
二分套二分可以解决!
class Solution {
public int kthSmallest(int[][] g, int k) {
int n = g.length, m = g[0].length;
int min = g[0][0], max = g[n - 1][m - 1];
//二分结果
int l = min, r = max;
while (l < r) {
int mid = l + (r - l >> 1);
int c = 0;
//二分查找小于等于当前查找值(mid)的元素个数
for (int[] a : g) {
int ll = 0, rr = m - 1;
while (ll < rr) {
int mm = ll + rr + 1 >> 1;
if (a[mm] > mid)
rr = mm - 1;
else
ll = mm;
}
if (a[ll] > mid)
c += 0;
else
c += ll + 1;
}
if (c < k)
l = mid + 1;
else
r = mid;
}
return l;
}
}
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的二分套二分以外,还可以使用多用归并!
//二分套二分
class Solution {
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
int n = nums1.length, m = nums2.length;
int min = nums1[0] + nums2[0], max = nums1[n - 1] + nums2[m - 1];
int l = min, r = max;
while (l < r) {
int mid = l + (r - l >> 1);
long c = 0;
for (int x : nums1) {
if (x + nums2[m - 1] <= mid)
c += m;
else if (x + nums2[0] > mid) {
break;
} else {
int ll = 0, rr = m - 1;
while (ll < rr) {
int mm = ll + (rr - ll + 1 >> 1);
if (x + nums2[mm] > mid)
rr = mm - 1;
else
ll = mm;
}
c += ll + 1;
}
}
if (c < k)
l = mid + 1;
else
r = mid;
}
List<List<Integer>> res = new ArrayList<>(k);
Queue<List<Integer>> tmp = new LinkedList<>();
for (int x : nums1) {
for (int y : nums2) {
if (x + y <= l) {
List<Integer> arr = new ArrayList<>();
arr.add(x);
arr.add(y);
if (x + y == l) {
tmp.offer(arr);
continue;
}
res.add(arr);
k--;
} else
break;
}
}
while (k-- > 0 && !tmp.isEmpty()) {
res.add(tmp.poll());
}
return res;
}
}
//多路归并
class Solution {
public List<List<Integer>> kSmallestPairs(int[] a, int[] b, int k) {
List<List<Integer>> res = new ArrayList<>(k);
int n = a.length, m = b.length;
PriorityQueue<int[]> q = new PriorityQueue<>((o1, o2) -> o1[0] - o2[0]);
for (int i = 0; i < m; i++)
q.offer(new int[]{a[0] + b[i], 0, i});
while (k-- > 0 && !q.isEmpty()) {
int[] cur = q.poll();
List<Integer> t = new ArrayList<>();
t.add(a[cur[1]]);
t.add(b[cur[2]]);
res.add(t);
if (cur[1] + 1 < n)
q.offer(new int[]{a[cur[1] + 1] + b[cur[2]], cur[1] + 1, cur[2]});
}
return res;
}
}
2040. 两个有序数组的第 K 小乘积
给你两个 从小到大排好序 且下标从 0 开始的整数数组 nums1
和 nums2
以及一个整数 k
,请你返回第 k
(从 1 开始编号)小的 nums1[i] * nums2[j]
的乘积,其中 0 <= i < nums1.length
且 0 <= j < nums2.length
。
示例 1:
输入:nums1 = [2,5], nums2 = [3,4], k = 2
输出:8
解释:第 2 小的乘积计算如下:
- nums1[0] * nums2[0] = 2 * 3 = 6
- nums1[0] * nums2[1] = 2 * 4 = 8
第 2 小的乘积为 8 。
示例 2:
输入:nums1 = [-4,-2,0,3], nums2 = [2,4], k = 6
输出:0
解释:第 6 小的乘积计算如下:
- nums1[0] * nums2[1] = (-4) * 4 = -16
- nums1[0] * nums2[0] = (-4) * 2 = -8
- nums1[1] * nums2[1] = (-2) * 4 = -8
- nums1[1] * nums2[0] = (-2) * 2 = -4
- nums1[2] * nums2[0] = 0 * 2 = 0
- nums1[2] * nums2[1] = 0 * 4 = 0
第 6 小的乘积为 0 。
示例 3:
输入:nums1 = [-2,-1,0,1,2], nums2 = [-3,-1,2,4,5], k = 3
输出:-6
解释:第 3 小的乘积计算如下:
- nums1[0] * nums2[4] = (-2) * 5 = -10
- nums1[0] * nums2[3] = (-2) * 4 = -8
- nums1[4] * nums2[0] = 2 * (-3) = -6
第 3 小的乘积为 -6 。
提示:
1 <= nums1.length, nums2.length <= 5 * 10
-10 <= nums1[i], nums2[j] <= 10
1 <= k <= nums1.length * nums2.length
nums1
和nums2
都是从小到大排好序的。
思路:
相较于373,从两个数组各元素和变为两个数组各元素的乘积,特别是存在负数的情况!
由于本题是查找第k小的乘积是多少,而不是找前k小数对,不需要用多路归并(其实是因为会超时!!!)
所以用二分套二分即可!只是要注意正负的问题!
先判断第k小数位于正数还是负数范围内再处理!
class Solution {
public long kthSmallestProduct(int[] nums1, int[] nums2, long k) {
List<Integer> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
List<Integer> na = new ArrayList<>();
List<Integer> nb = new ArrayList<>();
for (int x : nums1) {
if (x < 0)
na.add(x);
else
a.add(x);
}
for (int x : nums2) {
if (x < 0)
nb.add(x);
else
b.add(x);
}
long c = 1L * a.size() * nb.size() + b.size() * na.size();
long res = 0;
//正数情况
if (k > c) {
k -= c;
Collections.reverse(na);
Collections.reverse(nb);
res = get(k, a, b, na, nb);
} else {
Collections.reverse(a);
Collections.reverse(b);
res = get(k, a, nb, b, na);
}
return res;
}
long get(long k, List<Integer> a, List<Integer> b, List<Integer> na, List<Integer> nb) {
long res = 0;
long l = (long)(-1e10 - 10), r = (long)(1e10 + 10);
while (l < r) {
long mid = l + r >> 1;
long cnt = 0;
cnt += binarySearch(mid, a, b);
cnt += binarySearch(mid, na, nb);
if (cnt < k)
l = mid + 1;
else
r = mid;
}
return l;
}
long binarySearch(long mid, List<Integer> a, List<Integer> b) {
long cnt = 0;
if (a.size() == 0 || b.size() == 0)
return cnt;
for (Integer x : a) {
int ll = 0, rr = b.size() - 1;
while (ll < rr) {
int mm = ll + (rr - ll + 1 >> 1);
long val = 1L * x * b.get(mm);
if (val > mid)
rr = mm - 1;
else
ll = mm;
}
if (1L * x * b.get(ll) <= mid)
cnt += ll + 1;
}
return cnt;
}
}
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).
注意:
m
和n
的范围在 [1, 30000] 之间。k
的范围在 [1, m * n] 之间。
思路:
类似于373,加法换成乘法
class Solution {
public int findKthNumber(int m, int n, int k) {
int l = 1 * 1, r = m * n;
while (l < r) {
int mid = l + (r - l >> 1);
int cnt = get(m, n, mid);
if (cnt < k)
l = mid + 1;
else
r = mid;
}
return l;
}
int get(int m, int n, int x) {
int cnt = 0;
for (int i = 1; i <= n; i++) {
cnt += Math.min(x / i, m);
}
return cnt;
}
}