1. 整数对最小和问题解析
最近在技术社区看到一个挺有意思的算法题——"整数对最小和",题目要求用Java、JS、Python和C四种语言分别实现。这个题目看似简单,但实际涉及不少算法优化的思考点,特别适合用来检验编程基本功和算法思维。我自己在实际编码过程中踩过几个坑,也总结出一些性能优化的技巧,今天就来详细拆解这个问题。
2. 问题定义与基础解法
2.1 问题描述
给定两个整数数组arr1和arr2,以及一个整数k。我们需要从arr1和arr2中各选一个数组成数对,返回所有可能数对中前k个和最小的组合。
例如: arr1 = [1,7,11], arr2 = [2,4,6], k = 3 输出应该是:[[1,2],[1,4],[1,6]]
2.2 暴力解法分析
最直观的解法是生成所有可能的数对,计算它们的和,然后排序取前k个:
def kSmallestPairs(nums1, nums2, k): pairs = [] for num1 in nums1: for num2 in nums2: pairs.append([num1, num2]) pairs.sort(key=lambda x: x[0]+x[1]) return pairs[:k]这种解法的时间复杂度是O(mn log mn),其中m和n分别是两个数组的长度。当数组较大时,这种解法效率会很低。
3. 优化解法与实现
3.1 优先队列解法
更高效的解法是使用最小堆(优先队列)来维护当前最小的数对:
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { PriorityQueue<int[]> heap = new PriorityQueue<>((a,b)->(a[0]+a[1])-(b[0]+b[1])); List<List<Integer>> result = new ArrayList<>(); for(int i=0; i<Math.min(nums1.length, k); i++){ for(int j=0; j<Math.min(nums2.length, k); j++){ heap.offer(new int[]{nums1[i], nums2[j]}); } } while(k-- > 0 && !heap.isEmpty()){ int[] pair = heap.poll(); result.add(Arrays.asList(pair[0], pair[1])); } return result; }这个解法的时间复杂度优化到了O(k log k),因为堆的大小最多为k。
3.2 多语言实现对比
JavaScript实现:
function kSmallestPairs(nums1, nums2, k) { const heap = new MinPriorityQueue({ priority: ([a, b]) => a + b }); for(let i=0; i<Math.min(nums1.length, k); i++){ for(let j=0; j<Math.min(nums2.length, k); j++){ heap.enqueue([nums1[i], nums2[j]]); } } const result = []; while(k-- > 0 && !heap.isEmpty()){ result.push(heap.dequeue().element); } return result; }C语言实现:
#include <stdio.h> #include <stdlib.h> typedef struct { int a; int b; int sum; } Pair; int compare(const void* a, const void* b) { return ((Pair*)a)->sum - ((Pair*)b)->sum; } Pair* kSmallestPairs(int* nums1, int nums1Size, int* nums2, int nums2Size, int k, int* returnSize) { int size = nums1Size * nums2Size; Pair* pairs = (Pair*)malloc(size * sizeof(Pair)); int index = 0; for(int i=0; i<nums1Size; i++){ for(int j=0; j<nums2Size; j++){ pairs[index].a = nums1[i]; pairs[index].b = nums2[j]; pairs[index].sum = nums1[i] + nums2[j]; index++; } } qsort(pairs, size, sizeof(Pair), compare); *returnSize = size < k ? size : k; Pair* result = (Pair*)malloc(*returnSize * sizeof(Pair)); for(int i=0; i<*returnSize; i++){ result[i] = pairs[i]; } free(pairs); return result; }4. 性能优化技巧
4.1 剪枝优化
在实际测试中发现,当k远小于m×n时,可以提前终止内层循环:
def kSmallestPairs(nums1, nums2, k): heap = [] for i in range(min(len(nums1), k)): for j in range(min(len(nums2), k)): if len(heap) < k: heapq.heappush(heap, (-(nums1[i]+nums2[j]), nums1[i], nums2[j])) else: current_sum = nums1[i] + nums2[j] if current_sum < -heap[0][0]: heapq.heappop(heap) heapq.heappush(heap, (-current_sum, nums1[i], nums2[j])) else: break result = [] while heap: sum_val, num1, num2 = heapq.heappop(heap) result.append([num1, num2]) return result[::-1]4.2 多指针法
对于已排序的数组,可以使用多指针法进一步优化:
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) { List<List<Integer>> result = new ArrayList<>(); if(nums1.length==0 || nums2.length==0 || k==0) return result; PriorityQueue<int[]> heap = new PriorityQueue<>((a,b)->(nums1[a[0]]+nums2[a[1]])-(nums1[b[0]]+nums2[b[1]])); for(int i=0; i<Math.min(nums1.length, k); i++){ heap.offer(new int[]{i, 0}); } while(k-- > 0 && !heap.isEmpty()){ int[] curr = heap.poll(); result.add(Arrays.asList(nums1[curr[0]], nums2[curr[1]])); if(curr[1] < nums2.length-1){ heap.offer(new int[]{curr[0], curr[1]+1}); } } return result; }5. 测试用例与边界条件
5.1 常见测试用例
# 正常情况 assert kSmallestPairs([1,7,11], [2,4,6], 3) == [[1,2],[1,4],[1,6]] # k大于所有可能组合数 assert kSmallestPairs([1,2], [3], 4) == [[1,3],[2,3]] # 空数组情况 assert kSmallestPairs([], [1,2,3], 2) == [] assert kSmallestPairs([1,2,3], [], 2) == [] # 有重复元素 assert kSmallestPairs([1,1,2], [1,2,3], 4) == [[1,1],[1,1],[1,2],[1,2]]5.2 性能测试
对于大规模数据测试(如两个1000长度的数组,k=10000),优化后的解法比暴力解法快100倍以上。
6. 常见问题与解决方案
6.1 内存溢出问题
当数组很大时,生成所有组合会消耗大量内存。解决方案是使用堆并限制其大小。
6.2 处理重复元素
如果数组中存在重复元素,结果中也会包含重复的数对。如果需要去重,可以在最后一步添加去重逻辑:
function kSmallestPairs(nums1, nums2, k) { // ...原有代码... // 去重 const unique = new Set(result.map(JSON.stringify)); return Array.from(unique).map(JSON.parse).slice(0, k); }6.3 不同语言的优先队列实现
JavaScript没有内置的优先队列,可以使用第三方库如priority-queue或自己实现:
class PriorityQueue { constructor(comparator = (a, b) => a - b) { this._heap = []; this._comparator = comparator; } enqueue(value) { this._heap.push(value); this._siftUp(); } dequeue() { const value = this._heap[0]; const last = this._heap.pop(); if(this._heap.length > 0) { this._heap[0] = last; this._siftDown(); } return value; } // 其他辅助方法... }7. 实际应用场景
这个问题虽然看起来是纯算法题,但在实际开发中有多种应用:
- 推荐系统:从用户偏好和商品特征中各选一个最优组合
- 资源分配:在有限资源下找到最优的任务-资源配对
- 路径规划:在多个起点和终点间找到最优路径组合
我在实际项目中就遇到过类似场景:需要从多个数据源中各选一个数据点,组合后按某种指标排序取前k个。当时直接用了暴力解法,结果性能很差,后来优化为优先队列方案后性能提升了数十倍。