当前位置: 首页 > news >正文

杨氏矩阵找第N大(小)的O(N)线性算法 LeetCode 378. Kth Smallest Element in a Sorted Matrix 373. Find K Pairs 钓鱼问题

杨氏矩阵:一个N*N的矩阵,它的每行每列都单调递增(或者宽松一些,单调不减),即a[i][j]<=a[i+1][j], a[i][j]<=a[i][j+1]。

遇到的两道面试题:
1. 输出杨氏矩阵中最小的N个数。
2. 两个升序数组A和B,长度都是N。从两个数组中分别取出一个数,相加得到一个和。求这N*N个和的前N小。

本质上第2题可以转化成第一题:把A[0]+B[k]的结果填入矩阵第一行,A[1]+B[k]的结果填入第二行……就得到一个杨氏矩阵。所以现在就只考虑第1题咯。

此题常见的一种做法:N路归并,用一个大小为N的堆,可以O(NlgN)得到解。但是利用杨氏矩阵的性质,这题是有O(N)的算法的……

(为了方便,把矩阵记为num)

首先根据杨氏矩阵的性质得到最关键的一点:前N小的数,肯定不大于矩阵中的num[sqrt(N)+1][sqrt(N)+1]。

(为了方便,令M = sqrt(N)+1)

因此要找的前N小的数,肯定在矩阵的前M行和前M列中。

所以,要找的是一个M*N的矩阵和另外一个(N-M)*M的矩阵。

这样的规模相当于M*(2N-M),相当于M*N

这样问题可以转化为:在M个长度为N的有序数组中,查找前N小的数。(*)

除了之前提到的方法,此题还有一个比较容易想到的方法:二分上界并计数。在INT_MIN~INT_MAX中二分第K大数的上界,每次对所有数组二分统计其中不大于上界的数的个数。总体的复杂度是O(R*M*lgN)。其中R是最坏情况下二分的次数。对于32位整数,最多二分32次,R=32。但是对于浮点数,需要的二分次数会增多。

在这个思路的基础上加以改进,把R改进为lgN,便可得到线性算法。

基本思路是,把二分时取数的范围从INT_MIN~INT_MAX缩小到这MN个数中。每次从这些数中选一个,来作为计数的上界。

选的方法:

每一轮计数时,先找出这M个数组的中位数,作为每个数组潜在的切分点,然后选择这些切分点的中位数作为上界。O(M)选出M个切分点,O(MlgM)把这些数排个序再选中间的,所以这一步可以O(MlgM)(注:无序数组选中位数有均摊O(M)的算法)。

但是为了每一轮都能缩小查找范围,所以对于每个数组,还要维护一个“潜在切分点的可能区间”,选择该数组的新切分点时,取这个区间的中位数。实际上就是对每个数组,维护一个二分切分点的过程信息。

这样一轮统计过后,某些偏大(或偏小)的切分点所在区间长度就需要减半。并且,至少有半数区间的长度是要减半的。(对于一个数组,不大于中位数的数的个数至少是一半。“不小于”同理)

由于所有数组一共有MN个数,因此在lg(MN)轮后,所有区间长度都会减到1。

整理一下复杂度。一共要进行lg(MN)次计数;每次计数需要O(MlgM)找切分点的中位数,以及O(MlgN)对一个数组计数。因此整体的复杂度是:
O(lg(MN)*(MlgM+MlgN)) = O(sqrt(N)*(lgN)^2) = o(sqrt(N)*sqrt(N)) = o(N)
ps. 所以这个算法复杂度其实是低于O(N)的.

(*)附代码:用以上算法实现在M个有序数组中,查找第K小的数。

转自:

http://wolf5x.cc/blog/algorithm/young-tableau-smallest-kth#comment-123

#include <iostream> #include <vector> #include <algorithm> using namespace std; // i, partition_point_lower_index_i, partition_point_upper_index_i typedef pair<int, pair<int,int> > PartRange; typedef vector<vector<int> > VVI; class PartComparator { const VVI &ary; public: PartComparator(const VVI &a): ary(a){} bool operator()(const PartRange &x, const PartRange &y) const { return ary[x.first][(x.second.first+x.second.second)/2] < ary[y.first][(y.second.first+y.second.second)/2]; } }; // Get the count of numbers less than or equal to upper int getCount(VVI &num, int upper) { int ret = 0; for (int i = 0; i < num.size(); i++) { ret += upper_bound(num[i].begin(), num[i].end(), upper) - num[i].begin(); } return ret; } int chooseKthSmallest(VVI num, int k) { int n = num.size(); vector<PartRange> part(n); for (int i = 0; i < n; i++) { part[i] = make_pair(i, make_pair(0, num[i].size()-1)); } int ans = 1<<30; // INT_MAX; while(part.size() > 0) { // sort all the medians sort(part.begin(), part.end(), PartComparator(num)); // choose the median of medians int mid = part.size()/2; int upper = num[part[mid].first][(part[mid].second.first+part[mid].second.second)/2]; int count = getCount(num, upper); if (count >= k) { // update answer ans = min(ans, upper); // halve the median intervals of which the median is too large for(int i = 0; i < part.size(); i++) { int mid = (part[i].second.first+part[i].second.second)/2; if (num[part[i].first][mid] >= upper) { part[i].second.second = mid-1; } } } else { // halve the median intervals of which the median is too small for (int i = 0; i < part.size(); i++) { int mid = (part[i].second.first+part[i].second.second)/2; if (num[part[i].first][mid] <= upper) { part[i].second.first = mid+1; } } } // remove the empty median intervals for (int i = part.size()-1; i >= 0; i--) { if (part[i].second.first > part[i].second.second) { swap(part[i], part[part.size()-1]); part.erase(part.end()-1); } } } return ans; } int main() { int v[][3] = {{1,2,3},{2,3,4},{3,4,5}}; vector<int> vec0(v[0],v[0]+3); vector<int> vec1(v[1],v[1]+3); vector<int> vec2(v[2],v[2]+3); int arr[] = {1,2,2,3,4,4,4,4,5,6,7,8,9,9,10}; vector<int> vec3(arr, arr+sizeof(arr)/sizeof(int)); VVI num; num.push_back(vec0); num.push_back(vec1); num.push_back(vec2); int up = distance(vec3.begin(), upper_bound(vec3.begin(), vec3.end(), 11)); int low = distance(vec3.begin(), lower_bound(vec3.begin(), vec3.end(), 11)); int res = chooseKthSmallest(num, 3); return 0; }

--------------------------------------------------------------------------------------------------------

很久之后发现一种更好的解法,仍然二分,假设数据二分的范围是整个整数,那么log(2^32)次最多是32次,实际中范围可以由左上角和右下角来确定,anyway,二分的次数可以看做是一个常数。。。

剩下的问题就是给定一个target,求这个matrix中<=target的元素个数,这个是可以O(n)实现的,也就是从右上角开始往下找。。所以整体复杂度是O(n)。LeetCode上恰好有这么一道题:

Given anxnmatrix where each of the rows and columns are sorted in ascending order, find the kth smallest element in the matrix.

Note that it is the kth smallest element in the sorted order, not the kth distinct element.

Example:

matrix = [ [ 1, 5, 9], [10, 11, 13], [12, 13, 15] ], k = 8, return 13.

Note:
You may assume k is always valid, 1 ≤ k ≤ n2.

-----------------------------------------------------------

class Solution: def count_lower(self, nums, target, n): j, res = n - 1, 0 for i in range(n): while (j >= 0 and nums[i][j] > target): j -= 1 res += (j + 1) return res def kthSmallest(self, matrix, k: int) -> int: n = len(matrix) left, right = matrix[0][0], matrix[n - 1][n - 1] while (left <= right): target = ((right - left) >> 1) + left lower = self.count_lower(matrix, target, n) if (lower < k): left = target + 1 else: right = target - 1 return left

------------------------------------------------

另外一道题,要求输出也排序,就只能用堆来玩了:

You are given two integer arraysnums1andnums2sorted inascending orderand an integerk.

Define a pair(u, v)which consists of one element from the first array and one element from the second array.

Returnthekpairs(u1, v1), (u2, v2), ..., (uk, vk)with the smallest sums.

Example 1:

Input:nums1 = [1,7,11], nums2 = [2,4,6], k = 3Output:[[1,2],[1,4],[1,6]]Explanation:The first 3 pairs are returned from the sequence: [1,2],[1,4],[1,6],[7,2],[7,4],[11,2],[7,6],[11,4],[11,6]

Example 2:

Input:nums1 = [1,1,2], nums2 = [1,2,3], k = 2Output:[[1,1],[1,1]]Explanation:The first 2 pairs are returned from the sequence: [1,1],[1,1],[1,2],[2,1],[1,2],[2,2],[1,3],[1,3],[2,3]

Example 3:

Input:nums1 = [1,2], nums2 = [3], k = 3Output:[[1,3],[2,3]]Explanation:All possible pairs are returned from the sequence: [1,3],[2,3]

Constraints:

  • 1 <= nums1.length, nums2.length <= 105
  • -109 <= nums1[i], nums2[i] <= 109
  • nums1andnums2both are sorted inascending order.
  • 1 <= k <= 1000
from typing import List import heapq class Solution: def kSmallestPairs(self, nums1: List[int], nums2: List[int], k: int) -> List[List[int]]: l1, l2 = len(nums1), len(nums2) hq,res = [(nums1[0]+nums2[0],0,0)],[] while (hq and len(res) < k): csum,i,j = heapq.heappop(hq) res.append([nums1[i],nums2[j]]) if (j+1<l2): heapq.heappush(hq,(nums1[i]+nums2[j+1],i,j+1)) #列维度扩张由j说了算,只有列是0的时候考虑一下行维度的扩张,也控制了优先级 if j == 0 and i+1<l1: heapq.heappush(hq,(nums1[i+1]+nums2[j],i+1,j)) return res s = Solution() print(s.kSmallestPairs(nums1 = [1,2], nums2 = [3], k = 3)) print(s.kSmallestPairs(nums1 = [1,1,2], nums2 = [1,2,3], k = 2)) print(s.kSmallestPairs(nums1 = [1,7,11], nums2 = [2,4,6], k = 3)) #expected: [[0, -3], [0, -3], [0, -3], [0, -3], [0, -3], [0, 22], [0, 22], [0, 22], [0, 22], [0, 22], [0, 35], [0, 35], [0, 35], [0, 35], [0, 35], [0, 56], [0, 56], [0, 56], [0, 56], [0, 56], [0, 76], [0, 76]] # [[0, -3], [0, 22], [0, 35], [0, 56], [0, 76], [0, -3], [0, 22], [0, 35], [0, 56], [0, 76], [0, -3], [0, 22], [0, 35], [0, 56], [0, 76], [0, -3], [0, 22], [0, 35], [0, 56], [0, 76], [0, -3], [0, 22]] # [[0, -3], [0, -3], [0, -3], [0, -3], [0, -3], [0, 22], [0, 22], [0, 22], [0, 22], [0, 22], [0, 35], [0, 35], [0, 35], [0, 35], [0, 35], [0, 56], [0, 56], [0, 56], [0, 56], [0, 56], [0, 76], [0, 76]] print(s.kSmallestPairs([0,0,0,0,0],[-3,22,35,56,76],22))

------------------------------------------------

又发现一道钓鱼问题,有n个湖在一条路上,每个湖产量不一样,A[n],均为正整数。​
每个湖被钓1个小时之后,产量会减1(再被钓1小时后,产量再减1),没被钓不降低产量。​
有k个小时,求最大钓鱼量:

解答:这个题也很容易想到用堆的思路,但是比较好的思路是假设产量超过x的湖才会钓鱼,然后二分就可以得到O(n)复杂度解法

------------------------------------------------

一组排序质数,任选两个组成真分数,求所有可能中第k小。请给出复杂度最小的做法

class Solution: def count_lower(self, arr, target, n): i, res, p, q = 0, 0, arr[0], arr[n - 1] for j in range(1, n): while (i < j and arr[i] <= target * arr[j]): i += 1 res += i if (i > 0 and arr[i - 1] * q > p * arr[j]): p, q = arr[i - 1], arr[j] return res, p, q def kthSmallest(self, arr, k): n = len(arr) total = n * (n - 1) // 2 if (k < 1 or k > total): raise ValueError(f"k={k} out of range [1, {total}]") left, right, ans = 0.0, 1.0, [arr[0], arr[n - 1]] while (right - left > 1e-9): target = (left + right) / 2 lower, p, q = self.count_lower(arr, target, n) if (lower < k): left = target else: right = target ans = [p, q] return ans
http://www.jsqmd.com/news/604463/

相关文章:

  • 2026年3月泡棉制造商推荐:行业口碑评价深度分析,有实力的泡棉赋能企业生产效率提升与成本优化 - 品牌推荐师
  • mmap 文件映射 [系统加餐]
  • 工业质检实战:用Dinomaly+Anomalib搞定多品类缺陷检测(附完整配置流程)
  • 【技术底稿 09】MySQL 主从搭建完不算完!37 岁老码农生产级三件套:巡检 + 双节点监控 + 异地备份
  • Web-Maker布局系统完全指南:如何选择最适合你项目的界面布局
  • 微调BERT进行命名实体识别
  • 技术决策的民主与集中:软件测试团队如何寻求平衡?
  • 前端复古风选型必看!像素UI 、精简复古风UI
  • 基于Transformer-BiGRU 5模型多变量时序预测一键对比 (多输入单输出)附Matlab代码
  • NeRF在游戏开发中的5个神级应用:从场景重建到角色动画
  • Java NIO Files 类
  • 2026实测|6款主流PPT生成软件横评,打工人再也不用熬到深夜做PPT - 品牌测评鉴赏家
  • WithClock 桌面时钟,极致轻量化,鼠标穿透无打扰,自定义皮肤,双模式时钟,打造沉浸式桌面时间体验
  • Swagger中常用注解
  • 基于FPGA XDMA中断与双缓存架构的PCIE 3.0性能实测与优化
  • python sendgrid
  • 2026年AI PPT工具大揭秘,轻松解锁高效创作 - 品牌测评鉴赏家
  • 视频批量裁剪助手 - 支持 AVI、MKV 等多格式批量处理,精准设置裁剪时间
  • 【企业级MCP微服务基座】:基于FastAPI+Pydantic+Structured Logging的Python模板,已通过金融级压测(QPS 12,800+)
  • 滑模控制、反步控制、传统PID四旋翼无人机轨迹跟踪控制仿真
  • Taskwarrior钩子脚本开发终极指南:如何扩展你的任务管理功能
  • 如何用抖音下载器实现内容创作效率提升300%?一个开源工具的全方位指南
  • 硬字幕去除难题终结者:AI驱动的Video-subtitle-remover如何重新定义视频修复
  • D3作业1-K8s 存储与服务实验手册(实验1-4)
  • 智能保险箱WiFi配网总失败?保姆级排查指南(附双频路由器设置)
  • 博主实测|5款PPT生成网站,告别熬夜抠图,新手也能一键出片 - 品牌测评鉴赏家
  • 分布式一致性动态事件触发+线性多智能体系统仿真(复现参考文献)Matlab实现
  • 告别混乱依赖!用Melos管理Flutter多包项目的5个关键技巧
  • WebRTC+FFmpeg实战:如何用C++开发一个低延迟视频会议Demo?
  • 揭秘蒸发冷省电空调,成车间降温设备优选