文章目录
题目
标题和出处
标题:K 和数对的最大数目
难度
4 级
题目描述
要求
给定一个整数数组 nums \texttt{nums} nums 和一个整数 k \texttt{k} k。
每一步操作,需要从数组中选出和为 k \texttt{k} k 的两个整数,并将它们移除。
返回可以对数组执行的最大操作数。
示例
示例 1:
输入: nums = [1,2,3,4], k = 5 \texttt{nums = [1,2,3,4], k = 5} nums = [1,2,3,4], k = 5
输出: 2 \texttt{2} 2
解释:初始时 nums = [1,2,3,4] \texttt{nums = [1,2,3,4]} nums = [1,2,3,4]:
- 移除 1 \texttt{1} 1 和 4 \texttt{4} 4,之后 nums = [2,3] \texttt{nums = [2,3]} nums = [2,3]。
- 移除 2 \texttt{2} 2 和 3 \texttt{3} 3,之后 nums = [] \texttt{nums = []} nums = []。
不再有和为 5 \texttt{5} 5 的数对,因此最多执行 2 \texttt{2} 2 次操作。
示例 2:
输入: nums = [3,1,3,4,3], k = 6 \texttt{nums = [3,1,3,4,3], k = 6} nums = [3,1,3,4,3], k = 6
输出: 1 \texttt{1} 1
解释:初始时 nums = [3,1,3,4,3] \texttt{nums = [3,1,3,4,3]} nums = [3,1,3,4,3]:
- 移除前两个 3 \texttt{3} 3,之后 nums = [1,4,3] \texttt{nums = [1,4,3]} nums = [1,4,3]。
不再有和为 6 \texttt{6} 6 的数对,因此最多执行 1 \texttt{1} 1 次操作。
数据范围
- 1 ≤ nums.length ≤ 10 5 \texttt{1} \le \texttt{nums.length} \le \texttt{10}^\texttt{5} 1≤nums.length≤105
- 1 ≤ nums[i] ≤ 10 9 \texttt{1} \le \texttt{nums[i]} \le \texttt{10}^\texttt{9} 1≤nums[i]≤109
- 1 ≤ k ≤ 10 9 \texttt{1} \le \texttt{k} \le \texttt{10}^\texttt{9} 1≤k≤109
解法一
思路和算法
对于数组中的元素 num \textit{num} num,如果数组中存在另一个不同下标的 元素 k − num k - \textit{num} k−num,则可以执行一次操作将 num \textit{num} num 和 k − num k - \textit{num} k−num 移除。
可以使用哈希表记录数组中每个值的剩余次数。遍历数组的过程中,对于每个元素 num \textit{num} num,记 num 2 = k − num \textit{num}_2 = k - \textit{num} num2=k−num,执行如下操作。
-
如果 num 2 \textit{num}_2 num2 在哈希表中的剩余次数大于 0 0 0,则有剩余的 num 2 \textit{num}_2 num2,可以执行一次操作将 num \textit{num} num 和 num 2 \textit{num}_2 num2 移除。因此将操作数加 1 1 1,将 num 2 \textit{num}_2 num2 在哈希表中的剩余次数减 1 1 1。
-
如果 num 2 \textit{num}_2 num2 在哈希表中的剩余次数等于 0 0 0,则没有剩余的 num 2 \textit{num}_2 num2,当前不能执行操作将 num \textit{num} num 和 num 2 \textit{num}_2 num2 移除。因此当前的 num \textit{num} num 保留,将 num \textit{num} num 在哈希表中的剩余次数加 1 1 1。
由于每一步操作必须移除数组中的两个整数,不能将同一个整数移除两次(移除的两个整数可以相等但是下标必须不同),因此为了避免在 num = num 2 \textit{num} = \textit{num}_2 num=num2 时产生错误,只有当不能执行删除操作时才将 num \textit{num} num 在哈希表中的剩余次数加 1 1 1。
遍历结束之后,即可得到最大操作数。
代码
java
class Solution {
public int maxOperations(int[] nums, int k) {
int operations = 0;
Map<Integer, Integer> counts = new HashMap<Integer, Integer>();
for (int num : nums) {
int num2 = k - num;
if (counts.containsKey(num2)) {
operations++;
counts.put(num2, counts.get(num2) - 1);
if (counts.get(num2) == 0) {
counts.remove(num2);
}
} else {
counts.put(num, counts.getOrDefault(num, 0) + 1);
}
}
return operations;
}
}
复杂度分析
-
时间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。遍历数组一次,每个元素的计算时间都是常数。
-
空间复杂度: O ( n ) O(n) O(n),其中 n n n 是数组 nums \textit{nums} nums 的长度。空间复杂度主要取决于哈希表,哈希表中的元素个数不超过 n n n。
解法二
思路和算法
将数组排序之后,可以使用双指针计算数组中的元素之和等于 k k k 的元素对数目,即最大操作数。
用 left \textit{left} left 和 right \textit{right} right 表示两个指针,初始时 left \textit{left} left 和 right \textit{right} right 分别指向数组的最小下标和最大下标。每次计算两个指针指向的元素之和 sum \textit{sum} sum,即 sum = nums [ left ] + nums [ right ] \textit{sum} = \textit{nums}[\textit{left}] + \textit{nums}[\textit{right}] sum=nums[left]+nums[right],比较 sum \textit{sum} sum 和 k k k 的大小关系,调整指针指向的下标。
-
如果 sum = k \textit{sum} = k sum=k,则找到元素之和等于 k k k 的两个元素,将操作数加 1 1 1。由于当前的 nums [ left ] \textit{nums}[\textit{left}] nums[left] 和 nums [ right ] \textit{nums}[\textit{right}] nums[right] 都被移除,因此将 left \textit{left} left 向右移动一位,将 right \textit{right} right 向左移动一位。
-
如果 sum < k \textit{sum} < k sum<k,则将 left \textit{left} left 向右移动一位。
-
如果 sum > k \textit{sum} > k sum>k,则将 right \textit{right} right 向左移动一位。
上述操作的条件是 left < right \textit{left} < \textit{right} left<right。当 left = right \textit{left} = \textit{right} left=right 时,两个指针指向同一个元素,因此不存在两个不同下标的 元素之和等于 k k k,结束上述操作,此时即可得到最大操作数。
上述做法不会遗漏掉答案,理由如下。
对于任意满足 nums [ left k ] + nums [ right k ] = k \textit{nums}[\textit{left}_k] + \textit{nums}[\textit{right}_k] = k nums[leftk]+nums[rightk]=k 的指针对 ( left k , right k ) (\textit{left}_k, \textit{right}_k) (leftk,rightk),初始时一定有 left ≤ left k < right k ≤ right \textit{left} \le \textit{left}_k < \textit{right}_k \le \textit{right} left≤leftk<rightk≤right。如果初始时 left = left k \textit{left} = \textit{left}_k left=leftk 且 right = right k \textit{right} = \textit{right}_k right=rightk,则找到两个不同下标的元素之和等于 k k k,这两个元素都被移除,因此将 left \textit{left} left 向右移动一位,将 right \textit{right} right 向左移动一位。否则,一定有 left \textit{left} left 先到达 left k \textit{left}_k leftk 或 right \textit{right} right 先到达 right k \textit{right}_k rightk。
-
如果 left \textit{left} left 先到达 left k \textit{left}_k leftk,则当 left \textit{left} left 到达 left k \textit{left}_k leftk 时, right > right k \textit{right} > \textit{right}_k right>rightk,此时 nums [ left ] + nums [ right ] > k \textit{nums}[\textit{left}] + \textit{nums}[\textit{right}] > k nums[left]+nums[right]>k,因此一定是 right \textit{right} right 向左移动直到 right = right k \textit{right} = \textit{right}_k right=rightk。
-
如果 right \textit{right} right 先到达 right k \textit{right}_k rightk,则当 right \textit{right} right 到达 right k \textit{right}_k rightk 时, left < left k \textit{left} < \textit{left}_k left<leftk,此时 nums [ left ] + nums [ right ] < k \textit{nums}[\textit{left}] + \textit{nums}[\textit{right}] < k nums[left]+nums[right]<k,因此一定是 left \textit{left} left 向右移动直到 left = left k \textit{left} = \textit{left}_k left=leftk。
因此,上述做法一定会遍历到每一对元素和等于 k k k 的元素,不会遗漏掉答案。
代码
java
class Solution {
public int maxOperations(int[] nums, int k) {
int operations = 0;
Arrays.sort(nums);
int left = 0, right = nums.length - 1;
while (left < right) {
int sum = nums[left] + nums[right];
if (sum == k) {
operations++;
left++;
right--;
} else if (sum < k) {
left++;
} else {
right--;
}
}
return operations;
}
}
复杂度分析
-
时间复杂度: O ( n log n ) O(n \log n) O(nlogn),其中 n n n 是数组 nums \textit{nums} nums 的长度。对数组排序需要 O ( n log n ) O(n \log n) O(nlogn) 的时间,使用双指针遍历数组计算最大操作数需要 O ( n ) O(n) O(n) 的时间,因此时间复杂度是 O ( n log n + n ) = O ( n log n ) O(n \log n + n) = O(n \log n) O(nlogn+n)=O(nlogn)。
-
空间复杂度: O ( log n ) O(\log n) O(logn),其中 n n n 是数组 nums \textit{nums} nums 的长度。空间复杂度主要取决于排序的递归调用栈空间。