力扣 373:有序数组最小K数对的暴力与优化双解
力扣 373:有序数组最小K数对的暴力与优化双解
- 📜 前言:算法千般道,有序为捷径
- Bilibili 同步视频
- 🧩 一、题意剖释:明解题之本,晓边界之规
- 1.1 原题题意
- 1.2 解题前置心法:为何选用大顶堆?
- 📊 二、暴力枚举解法:直白可行,却逢时限之困
- 2.1 算法思路(Plain Text原理示意图)
- 2.2 完整C++暴力源码
- 2.3 致命缺陷与超时根源
- ⚡ 三、有序特性优化:顺势而为,斩断无效遍历
- 3.1 优化核心原理(Plain Text分步图解)
- 3.2 优化前后性能直观对比
- 3.3 完整版优化C++代码(可直接AC通过)
- 3.4 关键代码逐行注解
- 📖 四、算法求学悟道:万般阻碍,皆为成长土壤
- 📝 全文骈文速记口诀(一键吃透本题)
- 🎯 文末总结
📜 前言:算法千般道,有序为捷径
数组分列,有序成行;数对相依,和值分章。
刷题千万,误区暗藏:枚举全域虽稳,难免超时之殇;善用序列天性,方可破壁图强。
本篇以双有序数组寻找和最小K个数对为核心,骈文行文、对仗释理,逐层拆解暴力大顶堆解法、超时根源、有序特性极致优化方案,附完整可运行C++源码、分步原理图、性能对照分析,由表及里,由愚至巧,吃透堆排序+有序数组双重算法核心✨。
Bilibili 同步视频
力扣 373:有序数组最小K数对的暴力与优化双解
🧩 一、题意剖释:明解题之本,晓边界之规
1.1 原题题意
给定两个升序排列的整数数组 nums1、nums2,从两数组中分别取出一个元素组成数对,要求返回所有数对中和值最小的前K个数对。
约束核心:
元素一一配对,一数取自nums1,一数取自nums2
数组全程升序,元素从左至右单调递增
输出结果无需再次排序,保留堆筛选后的有效数对即可
1.2 解题前置心法:为何选用大顶堆?
求前K小,堆分两类:
小顶堆逐次弹出最小值,冗余遍历,开销居高不下;
大顶堆留存备选集合,堆顶为当前备选最大值,超限则剔除大数,留小去大,适配本题最优场景✅。
核心逻辑:维护容量为K的大顶堆,始终保留当前最小的K组数对,新数对小于堆顶则入堆,大于堆顶直接舍弃。
📊 二、暴力枚举解法:直白可行,却逢时限之困
2.1 算法思路(Plain Text原理示意图)
【暴力枚举流程】 nums1: [1,2,4] nums2: [1,3,5] 全量两两枚举 → 生成全部9组数对 全部入大顶堆 → 堆容量超过K → 弹出堆顶最大值 最终剩余K组最小数对 缺陷:无视数组有序性,无脑全遍历,数据量大直接TLE2.2 完整C++暴力源码
#include<iostream>#include<vector>#include<queue>usingnamespacestd;// 自定义比较器:构建大顶堆,按照数对之和降序排列structcmp{booloperator()(vector<int>&a,vector<int>&b){returna[0]+a[1]<b[0]+b[1];}};vector<vector<int>>kSmallestPairs(vector<int>&nums1,vector<int>&nums2,intk){// 定义大顶堆priority_queue<vector<int>,vector<vector<int>>,cmp>maxHeap;// 双层循环:无脑枚举所有数对for(intx:nums1){for(inty:nums2){maxHeap.push({x,y});// 堆容量超出K,弹出当前最大数对if(maxHeap.size()>k){maxHeap.pop();}}}// 导出结果vector<vector<int>>res;while(!maxHeap.empty()){res.push_back(maxHeap.top());maxHeap.pop();}returnres;}intmain(){vector<int>n1={1,2,4};vector<int>n2={1,3,5};autoans=kSmallestPairs(n1,n2,3);for(auto&item:ans){cout<<item[0]<<" "<<item[1]<<endl;}return0;}2.3 致命缺陷与超时根源
双循环嵌套,全域遍历所有组合,时间复杂度高达O(N*M)。
两数组皆为升序序列,代码完全舍弃有序天性,后续递增数对无需校验依旧强行入堆,无效计算堆砌,大数据场景直接触发TLE超时错误。
痛点总结:算法切忌蛮力遍历,无视题干特性,直白代码终究难抗大数据评测用例。
⚡ 三、有序特性优化:顺势而为,斩断无效遍历
3.1 优化核心原理(Plain Text分步图解)
【有序数组优化逻辑】 已知:nums1、nums2 全局升序 固定 nums1 中元素 x,向后遍历 nums2 nums2 元素y持续变大 → x+y 和值持续单调递增 判定规则: 1. 堆未满k个:直接入堆,无需判断 2. 堆已满k个:当前和 < 堆顶和 → 替换堆顶 3. 当前和 ≥ 堆顶和 → 后续所有和只会更大 → 直接break终止内层循环 核心:依托单调性,提前截断循环,消灭全部无效遍历3.2 优化前后性能直观对比
| 解法类型 | 时间复杂度 | 运行耗时 | 是否超时 |
|---|---|---|---|
| 暴力全枚举 | O(N*M) | 130ms+ | 是 |
| 有序截断优化 | O(N*K) | 12ms左右 | 否 |
3.3 完整版优化C++代码(可直接AC通过)
#include<iostream>#include<vector>#include<queue>usingnamespacestd;structcmp{booloperator()(vector<int>&a,vector<int>&b){returna[0]+a[1]<b[0]+b[1];}};vector<vector<int>>kSmallestPairs(vector<int>&nums1,vector<int>&nums2,intk){priority_queue<vector<int>,vector<vector<int>>,cmp>maxHeap;for(intx:nums1){for(inty:nums2){intcurSum=x+y;// 分支1:堆内元素不足k,直接存入if(maxHeap.size()<k){maxHeap.push({x,y});}else{// 分支2:堆已满,当前数对更小则替换堆顶if(curSum<maxHeap.top()[0]+maxHeap.top()[1]){maxHeap.pop();maxHeap.push({x,y});}else{// 依托升序单调性,后续和值只会更大,直接截断内层循环break;}}}}vector<vector<int>>res;while(!maxHeap.empty()){res.push_back(maxHeap.top());maxHeap.pop();}returnres;}intmain(){vector<int>n1={1,2,4};vector<int>n2={1,3,5};autoans=kSmallestPairs(n1,n2,3);for(auto&item:ans){cout<<item[0]<<"+"<<item[1]<<"="<<item[0]+item[1]<<endl;}return0;}3.4 关键代码逐行注解
堆容量判断前置:优先判断堆空间,未满直接存入,省去多余比较开销
break截断核心:内层循环一旦命中大于等于堆顶,立刻终止本轮nums2遍历,杜绝无效循环
比较器无需深究:数组比较依据为元素之和,底层语法实现属于语言细节,无需纠结底层重载逻辑,聚焦算法思维即可
📖 四、算法求学悟道:万般阻碍,皆为成长土壤
刷题之路,荆棘相伴;代码之途,苦练为岸。
常有学子观他人代码行云流水,自敲代码寸步难行,妄图跳过实操,一步登天,此乃虚妄之念。
天下代码,无捷径可走;一身功力,唯苦练可成。天赋分高下,努力无偏颇,付出几分耕耘,便得几分收获。
遇难题而退缩,困当下之桎梏;迎难题而攻坚,铺来日之坦途。
譬如种子埋于泥土,泥土一时为阻隔,压制破土锋芒;待到嫩芽而出,泥土便为根基,托举枝干生长。
当下算法之难、代码之苦,皆是脚下土壤;今日熬过万般阻碍,来日便可傲视群雄,自成锋芒✨。
📝 全文骈文速记口诀(一键吃透本题)
双序数组寻小数,大顶堆存备选组;
暴力双层全遍历,无视序列超时苦;
升序单调和递增,遇大截断少往复;
算法巧用题干性,少算一步快一步;
刷题不惧当下苦,困境终成脚下土。
🎯 文末总结
本题看似是堆结构基础应用题,实则考察算法优化思维:优秀的代码从不是无脑模拟流程,而是读懂题干隐藏条件,顺势简化计算。
暴力解法保正确率,优化解法保运行效率,二者结合,方能兼顾逻辑与性能。
💬 评论区交流:你刷题时是否也经常无脑遍历忽略数组有序特性?欢迎留言讨论!
#算法 #C++ #大顶堆 #数组算法 #LeetCode刷题 #代码优化