力扣 373. 查找和最小的K对数字

本文介绍了一种使用优先队列解决寻找两个升序数组中和最小的k个数对的方法。通过初始化特定数对进入队列,并不断更新队列来确保找到正确的答案。

题目来源:https://leetcode-cn.com/problems/find-k-pairs-with-smallest-sums/

大致题意:
给两个升序数组,将两个数组的元素两两成对,返回和最小的 k 个数对

思路

设数组为 a, b,m 为数组 a 的长度,n 为数组 b 的长度
首先可以知道 (a[0], b[0]) 为最小的数对,而若 m 大于 k 时,第 k 小的数对一定小于等与 a[k] + b[0]
题目规定 k 最大值为 1000,于是可以考虑使用优先队列将候选的数对放入,每次取出队首最小的数对即可

优先队列

如何保证最小的数对一定在优先队列中?
简单的办法就是把 a[0] ~ a[min(m, k)] 与 b[0] ~ b[min(n, k)] 组成的所有数对放入队列,然后依次取出 k 个
这样做消耗比较大,可以进行优化

已知

  • 若最小的数对为 (a[i], b[j])
  • 那么次小的数对只可能为 (a[i], b[j + 1]) 或 a[i + 1] 与 b[0] ~ b[min(n, k)] 组成的某个数对

于是可以

  1. 初始时将 a[0] ~ a[min(m, k)] 与 b[0] 组成的数对放入队列
  2. 每次出队后,出队数对为 ( a[x], b[y] ),只要 y + 1 小于 n,那么就可以将 ( a[x], b[y + 1] ) 放入队列。这样保证了队首元素一定为次小的数对
  3. 出队 k 次,或者队列为空时停止
public List<List<Integer>> kSmallestPairs(int[] nums1, int[] nums2, int k) {
        // 优先队列
        PriorityQueue<int[]> queue = new PriorityQueue<>(k, (a, b) -> {
            return nums1[a[0]] + nums2[a[1]] - nums1[b[0]] - nums2[b[1]];
        });
        List<List<Integer>> ans = new ArrayList<>();
        int m = nums1.length;
        int n = nums2.length;
        // 初始时放入a[0] ~ a[min(m, k)] 与 b[0] 组成的数对
        for (int i = 0; i < Math.min(m, k); i++) {
            queue.offer(new int[]{i, 0});
        }
        // 当出队 k 次或者队列为空时中止循环
        while (k-- > 0 && !queue.isEmpty()) {
            // 取队首元素
            int[] idx = queue.poll();
            List<Integer> list = new ArrayList<>();
            list.add(nums1[idx[0]]);
            list.add(nums2[idx[1]]);
            ans.add(list);
            // 若 idx[1] + 1 小与 nums2 长度,将之后位置的元素与 idx[0] 组成数对
            if (idx[1] + 1 < n) {
                queue.offer(new int[]{idx[0], idx[1] + 1});
            }
        }

        return ans;
    }
<think>我们正在查找关于力扣LeetCode)上C语言数组章节的算法练习与解析。根据提供的引用,我们可以找到一些相关的数组题目代码示例。下面将结合引用内容,整理一些数组相关的经典题目及C语言解析。 ### 1. 数组中数字出现的次数(引用[4]) 题目:一个整型数组 `nums` 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。 **解析**: - 使用异或运算的性质:任何数0异或等于其本身,相同数异或等于0。 - 先遍历数组,将所有数字异或,得到的结果就是两个只出现一次的数字的异或值(因为其他数字都出现两次,异或后为0)。 - 找到这个异或结果中任意一个为1的位(表示两个数在该位不同),根据这个位将数组分成两组,每组各包含一个只出现一次的数字,然后分别对两组进行异或即可得到两个数。 **C语言代码**: ```c int* singleNumbers(int* nums, int numsSize, int* returnSize) { int *arr = (int*)calloc(2, sizeof(int)); // 分配两个整型空间 int sum = 0; for (int i = 0; i < numsSize; i++) { sum ^= nums[i]; // 得到两个目标数的异或值 } int count = 0; // 找到sum中第一个为1的位(从右往左) for (int i = 0; i < 32; i++) { if (sum & (1 << i)) { count = i; break; } } // 根据第count位是0还是1分组 for (int i = 0; i < numsSize; i++) { if (nums[i] & (1 << count)) { arr[0] ^= nums[i]; // 第一组 } else { arr[1] ^= nums[i]; // 第二组 } } *returnSize = 2; return arr; } ``` ### 2. 轮转数组(引用[4]) 题目:给定一个数组,将数组中的元素向右轮转 `k` 个位置,其中 `k` 是非负数。 **解析**: - 方法一:使用额外数组,将原数组的元素放到新数组的对应位置,然后复制回原数组。空间复杂度O(n)。 - 方法二:反转数组三次。先整体反转,然后反转前k个元素,再反转后n-k个元素。空间复杂度O(1)。 **C语言代码(方法二)**: ```c void reverse(int* nums, int start, int end) { while (start < end) { int temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } } void rotate(int* nums, int numsSize, int k) { k %= numsSize; // 防止k大于数组长度 // 整体反转 reverse(nums, 0, numsSize - 1); // 反转前k个 reverse(nums, 0, k - 1); // 反转后numsSize-k个 reverse(nums, k, numsSize - 1); } ``` ### 3. 最大子数组(引用[2]) 题目:给定一个整数数组 `nums` ,找到一个具有最大的连续子数组(子数组最少包含一个元素),返回其最大。 **解析**: - 使用动态规划(Kadane算法)。遍历数组,用 `f(i)` 表示以第 `i` 个数结尾的连续子数组的最大,则状态转移方程为:$f(i) = \max \{ f(i-1) + nums[i], nums[i] \}$。 - 同时用一个变量记录遍历过程中的最大值。 **C语言代码**: ```c int maxSubArray(int* nums, int numsSize) { int maxSum = nums[0]; int currentSum = nums[0]; for (int i = 1; i < numsSize; i++) { // 如果当前元素加上前面的子数组比当前元素小,则从当前元素重新开始 currentSum = (currentSum + nums[i] > nums[i]) ? currentSum + nums[i] : nums[i]; if (currentSum > maxSum) { maxSum = currentSum; } } return maxSum; } ``` ### 4. 旋转数组的最小数字(引用[5]) 题目:把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个递增排序的数组的一个旋转,输出旋转数组的最小元素。 **解析**: - 使用二分查找。旋转数组可以分为两个有序的子数组,且前一个子数组的元素都大于等于后一个子数组的元素。最小元素就是两个子数组的分界点。 - 设置两个指针,分别指向数组的第一个最后一个元素。如果中间元素大于等于第一个指针指向的元素,则中间元素位于前一个子数组,最小元素在中间元素后面;否则,中间元素位于后一个子数组,最小元素在中间元素前面(或就是中间元素)。 **C语言代码**: ```c int minArray(int* numbers, int numbersSize) { int left = 0, right = numbersSize - 1; while (left < right) { int mid = left + (right - left) / 2; if (numbers[mid] > numbers[right]) { left = mid + 1; } else if (numbers[mid] < numbers[right]) { right = mid; } else { right--; // 当相等时,无法判断,只能缩小范围 } } return numbers[left]; } ``` ### 5. 错误的集合(引用[5]) 题目:集合 `s` 包含从 `1` 到 `n` 的整数。其中一个数重复,另一个数丢失。找出重复丢失的数。 **解析**: - 使用数组下标标记法。遍历数组,将每个元素对应的下标位置的值取负,如果遇到某个元素对应的下标位置已经是负数,则说明该元素重复。丢失的数可以通过数组总重复数来推算。 **C语言代码**: ```c int* findErrorNums(int* nums, int numsSize, int* returnSize) { int* res = (int*)calloc(2, sizeof(int)); int sum = 0; for (int i = 0; i < numsSize; i++) { int index = abs(nums[i]) - 1; sum += abs(nums[i]); if (nums[index] < 0) { res[0] = abs(nums[i]); // 重复的数 } else { nums[index] = -nums[index]; } } // 丢失的数 = (1+numsSize)*numsSize/2 - (sum - res[0]) res[1] = (1 + numsSize) * numsSize / 2 - (sum - res[0]); *returnSize = 2; return res; } ``` 以上是力扣上数组章节的一些经典题目及C语言解析。这些题目涵盖了数组的常见操作,如异或、反转、动态规划、二分查找等技巧。 §§相关问题§§ 1. 如何用C语言实现数组的快速排序算法? 2. 力扣数组章节中还有哪些经典的双指针问题? 3. 如何用C语言解决数组中的多数元素问题? 4. 数组章节中关于二维数组的典型例题有哪些?</think>### 力扣 C语言数组章节算法练习与解析 #### 1. 轮转数组(189题) **题目**:将数组向右轮转 `k` 个位置 **核心思路**:三次翻转法(空间复杂度 $O(1)$) 1. 整体翻转数组 2. 翻转前 `k` 个元素 3. 翻转剩余元素 **C语言实现**: ```c void reverse(int* nums, int start, int end) { while (start < end) { int temp = nums[start]; nums[start] = nums[end]; nums[end] = temp; start++; end--; } } void rotate(int* nums, int numsSize, int k) { k %= numsSize; // 处理k>数组长度的情况 reverse(nums, 0, numsSize - 1); // 整体翻转 reverse(nums, 0, k - 1); // 前k个翻转 reverse(nums, k, numsSize - 1); // 剩余部分翻转 } ``` **复杂度**:时间复杂度 $O(n)$,空间复杂度 $O(1)$[^4] --- #### 2. 只出现一次的数字(136题变型) **题目**:除两个数字外,其他数字都出现两次,找出这两个数字 **核心技巧**:位运算(异或性质) 1. 所有元素异或得到 $sum = a \oplus b$ 2. 找到 $sum$ 中任意为1的位(区分 $a$ $b$) 3. 根据该位分组异或 **C语言实现**: ```c int* singleNumbers(int* nums, int numsSize, int* returnSize) { int *arr = (int*)calloc(2, sizeof(int)); int sum = 0; for (int i = 0; i < numsSize; i++) sum ^= nums[i]; int bit = 0; while (!(sum & (1 << bit))) bit++; // 找到差异位 for (int i = 0; i < numsSize; i++) { if (nums[i] & (1 << bit)) arr[0] ^= nums[i]; else arr[1] ^= nums[i]; } *returnSize = 2; return arr; } ``` **关键点**:利用 $x \oplus x = 0$ $x \oplus 0 = x$ 的性质[^4] --- #### 3. 最大子数组(53题) **题目**:找出最大的连续子数组 **最优解**:动态规划($O(n)$) - 状态转移:$dp[i] = \max(nums[i],\ dp[i-1] + nums[i])$ - 空间优化:滚动变量代替数组 **C语言实现**: ```c int maxSubArray(int* nums, int numsSize) { int maxSum = nums[0], curSum = nums[0]; for (int i = 1; i < numsSize; i++) { curSum = (curSum > 0) ? curSum + nums[i] : nums[i]; if (curSum > maxSum) maxSum = curSum; } return maxSum; } ``` **分析**:当 $curSum < 0$ 时重置子序列起点[^2] --- #### 4. 旋转数组的最小数字(剑指 Offer 11题) **题目**:在旋转排序数组中找最小值 **解法**:二分查找变形 ```c int minArray(int* numbers, int numbersSize) { int left = 0, right = numbersSize - 1; while (left < right) { int mid = left + (right - left) / 2; if (numbers[mid] > numbers[right]) left = mid + 1; else if (numbers[mid] < numbers[right]) right = mid; else right--; // 处理重复值 } return numbers[left]; } ``` **注意点**:当 $numbers[mid] = numbers[right]$ 时需特殊处理[^5] --- ### 数组操作核心技巧总结 | **技巧** | **应用场景** | **例题** | |-------------------|-------------------------------|----------------| | 双指针法 | 有序数组去重/合并 | 26, 88题 | | 快慢指针 | 原地修改数组 | 27, 283题 | | 前缀 | 区间查询 | 303, 304题 | | 二分查找变形 | 旋转数组搜索 | 33, 81题 | | 位运算 | 数字出现次数问题 | 136, 137题 | > 提示:练习时注意边界条件(如空数组、单元素数组)特殊值(如 $INT\_MAX$)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值