二分答案之第 K 小/大
参考资料来源灵神在力扣所发的题单,仅供分享学习笔记和记录,无商业用途。
核心思路:
设置check函数和采用哪种模型
- 第 k 小等价于:求最小的 x使用01模型,满足条件并且 ≤答案 的数至少有 k 个。
- 第 k 大等价于:求最大的 x使用10模型,满足条件并且≥答案 的数至少有 k 个。
证明:
第k小为什么满足条件并且 ≤答案 的数至少有 k 个?
表明了当前答案前面还有至少k个数,而答案越小答案前面的数就越小具有单调性。采用二分01模型当区间内的数满足条件并且 ≤答案 的数恰好有 k 个,会默认收敛至第一个。
力扣题单练习(灵神题单中摘取题目)
668. 乘法表中第k小的数
题意:
给定m x n 的一个整数矩阵,其中 mat[i][j] == i * j(下标从 1 开始)。找到并返回第k小的数字
思路:
合理右边界:选取最后一个元素
检查函数:枚举每一行,判断当前行有多少小于等于二分答案的数
因为生成的数等于行*列,所以答案/行=列,而列如果有小数说明当前行没有答案数,只有小于它的数(列)
如果答案/行>n,说明当前行里所有元素都小于二分答案。
统计有多少个小于等于答案的数,大于等于k:缩小答案,反之亦然。
为什么二分结束后,答案 ans 一定在乘法表中?
采用01模型可以确定在当二分区间内的所有数都能使cnt>=k,但是除了这个区间的第一位,其他都不是乘法表中的数。
有小数在计算过程中忽略了转换成个数,只有第一位满足条件。
class Solution {
public://检查函数:枚举每一行,判断当前行有多少小于等于二分答案的数//因为生成的数等于行*列,所以答案/行=列,而列如果有小数说明当前行没有答案数,只有小于它的数(列)//如果答案/行>n,说明当前行里所有元素都小于二分答案。//统计有多少个小于等于答案的数,大于等于k:缩小答案,反之亦然。//为什么二分结束后,答案 ans 一定在乘法表中?//采用01模型可以确定在当二分区间内的所有数都能使cnt>=k,但是除了这个区间的第一位,其他都不是乘法表中的数。//有小数在计算过程中忽略了转换成个数,只有第一位满足条件。bool check(int m, int n, int k, int buff){int cnt=0;for(int i=1;i<=m;i++) cnt+=min(buff/i,n);return cnt>=k;}int findKthNumber(int m, int n, int k) {//题意:给定m x n 的一个整数矩阵,其中 mat[i][j] == i * j(下标从 1 开始)。找到并返回第k小的数字//合理右边界:选取最后一个元素int l=1,r=m*n,mid;while(l<r){mid=l+((r-l)>>1);if(check(m,n,k,mid)) r=mid;else l=mid+1;}return l;}
};
378. 有序矩阵中第 K 小的元素
题意:有序矩阵(每行每列升序)中找第k小元素
思路:
二分范围:矩阵最小值(左上角)到最大值(右下角)
检查函数:统计矩阵中≤x的元素数量(利用矩阵有序性优化)
返回值:如果数量≥k,说明第k小元素≤x,需继续向左搜索
class Solution {
public:// 检查函数:统计矩阵中≤x的元素数量(利用矩阵有序性优化)// 返回值:如果数量≥k,说明第k小元素≤x,需继续向左搜索bool check(vector<vector<int>>& matrix, int k, int x) {int cnt = 0;int n = matrix.size();int i = n - 1, j = 0; // 从左下角开始遍历while (i >= 0 && j < n) {if (matrix[i][j] <= x) {cnt += i + 1; // 当前列上方所有元素均≤xj++; // 右移} else {i--; // 上移}}return cnt >= k;}int kthSmallest(vector<vector<int>>& matrix, int k) {// 题意:有序矩阵(每行每列升序)中找第k小元素// 二分范围:矩阵最小值(左上角)到最大值(右下角)int n = matrix.size();int l = matrix[0][0], r = matrix[n-1][n-1];while (l < r) {int mid = l + (r - l) / 2;if (check(matrix, k, mid)) {r = mid; } else {l = mid + 1; }}return l; }
};
719. 找出第 K 小的数对距离
题意:给定一个整数数组和整数,要求返回所有所有数对距离中第k小的数对距离。数对距离定义为a和b的绝对差值。
思路:
核心:二分查找可能的距离 + 滑动窗口统计数量
合理右边界:采用整数数组中绝对差值最大的数对距离
检查函数:统计数组中“距离≤x”的数对数量(利用排序+滑动窗口优化)
原理:数组已排序,固定右边界j,寻找最小左边界i使得nums[j]-nums[i]≤x
因为想要绝对差值越小就需要下标越来越靠近。
此时[j-i]个对数((i,j),(i+1,j),...,j-1,j)均满足距离≤x
返回值:若数量≥k,说明第k小的距离≤x,需向左收缩二分范围
class Solution {
public:// 检查函数:统计数组中“距离≤x”的数对数量(利用排序+滑动窗口优化)// 原理:数组已排序,固定右边界j,寻找最小左边界i使得nums[j]-nums[i]≤x//因为想要绝对差值越小就需要下标越来越靠近。// 此时[j-i]个对数((i,j),(i+1,j),...,j-1,j)均满足距离≤x// 返回值:若数量≥k,说明第k小的距离≤x,需向左收缩二分范围bool check(vector<int>& nums, int k, int x){int cnt = 0, i = 0;for (int j = 0; j < nums.size(); j++) {while (nums[j] - nums[i] > x) i++; cnt += j - i; }return cnt >= k;}int smallestDistancePair(vector<int>& nums, int k) {//题意:给定一个整数数组和整数,要求返回所有所有数对距离中第k小的数对距离。数对距离定义为a和b的绝对差值。//核心:二分查找可能的距离 + 滑动窗口统计数量//合理右边界:采用整数数组中绝对差值最大的数对距离sort(nums.begin(),nums.end());int l=0,r=nums.back()-nums[0],mid;while(l<r){mid=l+((r-l)>>1);if(check(nums,k,mid)) r=mid;else l=mid+1;}return l;}
};
878. 第 N 个神奇数字
思路:
合理右边界:a和b取最小乘n,这样能保证至少有n个神奇的数字
检查函数:使用数学公式计算[1..x]中能被a或b整除的数的个数
最大公约数:分解两个数为质因数乘积,找出最大值 “公共质因数”(两个数都包含的质因数);
最小公倍数:能同时被这些整数整除的最小正整数。等于a*b/最大公约数
容斥原理:|A|:1~x 中 “能被 a 整除” 的数(如 a=2 时,A={2,4,6,...};
|B|:1~x 中 “能被 b 整除” 的数(如 b=3 时,B={3,6,9,...};
计算|A ∪ B|(能被 a 或 b 整除的数的总个数),计算|A ∩ B|(既能被 a 整除、又能被 b 整除的数的个数)=x/a和b的最小公倍数
|A ∪ B|=|A|+|B|-|A ∩ B|(A和B重合部分,减去冗余)
class Solution {
public:// 计算最大公约数:分解两个数为质因数乘积,找出最大值 “公共质因数”(两个数都包含的质因数);long long gcd(long long a, long long b) {return b == 0 ? a : gcd(b, a % b);}// 计算最小公倍数:能同时被这些整数整除的最小正整数。等于a*b/最大公约数long long lcm(long long a, long long b) {return a * b / gcd(a, b);}//检查函数:使用数学公式计算[1..x]中能被a或b整除的数的个数//容斥原理:|A|:1~x 中 “能被 a 整除” 的数(如 a=2 时,A={2,4,6,...});//|B|:1~x 中 “能被 b 整除” 的数(如 b=3 时,B={3,6,9,...});//计算|A ∪ B|(能被 a 或 b 整除的数的总个数),计算|A ∩ B|(既能被 a 整除、又能被 b 整除的数的个数)=x/a和b的最小公倍数//|A ∪ B|=|A|+|B|-|A ∩ B|(A和B重合部分,减去冗余)bool check(int n, int a, int b, long long x) {long long ab = lcm(a, b);long long cnt = x/a + x/b - x/ab;return cnt >= n;}int nthMagicalNumber(int n, int a, int b) {//合理右边界:a和b取最小乘n,这样能保证至少有n个神奇的数字const long long MOD = 1e9 + 7;long long l = min(a, b);long long r = (long long)min(a, b) * n; // 避免整数溢出long long mid;while (l < r) {mid=l+((r-l)>>1);if(check(n,a,b,mid)) r=mid;else l=mid+1;}return l % MOD; }
};
1201. 丑数 III
思路:
合理右边界:在a,b,c中取最小乘n,这样能保证至少有n个丑数
容斥原理: “先包容所有元素,再排斥重复计算的部分,最后补回多排斥的部分”
|A ∪ B ∪ C|(满足条件的数量)= |A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|
class Solution {
public:long long gcd(long long a, long long b){return b==0?a:gcd(b,a%b);}long long lcm(long long a, long long b){return a*b/gcd(a,b);}//容斥原理: “先包容所有元素,再排斥重复计算的部分,最后补回多排斥的部分”//|A ∪ B ∪ C|(满足条件的数量)= |A| + |B| + |C| - |A∩B| - |A∩C| - |B∩C| + |A∩B∩C|bool check(int n, int a, int b, int c, long long x){long long a_b=lcm(a,b);long long a_c=lcm(a,c);long long b_c=lcm(b,c);long long cnt=x/a + x/b + x/c - x/a_b - x/a_c - x/b_c + x/lcm(lcm(a,b),c);return cnt>=n;}int nthUglyNumber(int n, int a, int b, int c) {//合理右边界:在a,b,c中取最小乘n,这样能保证至少有n个丑数long long l=min(min(a,b),c),r=min(min(a,b),c)*n,mid;while(l<r){mid=l+((r-l)>>1);if(check(n,a,b,c,mid)) r=mid;else l=mid+1;}return l;}
};
力扣灵神题单2000分以下二分答案之第 K 小/大已完成,后续会更新2000+的专题。