很多事情大家都心知肚明,只是聪明人不说出来
关于两数之和的问题,一般都是给定数组nums和目标值target, 处理这种问题一般需要注意以下两点:
-
数组是否有序;
-
给定目标值target,根据两数之和与target的大小情况展开讨论:
- 等于target
- 小于target
- 大于target -
返回值是下标 or 方案数
如果需要返回值是下标,则在数组无序的情况下,我们是不能通过排序 和双指针方案来解决的。
结合Leetcode的题目,我们对上面所列举的情况逐一来展开讨论。
第一类:等于target
1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
根据上面的总结我们可以找出关键点:
- 数组无序;
- 两数之和等于target的情况;
- 返回数组下标;
所以此题就排除了排序 + 双指针的解决方案。推荐使用哈希表将时间复杂度给降到常数级。
解题思路就是遍历数组,对于每一个元素num,查询是否存在对应的 target - num。
public int[] twoSum(int[] nums, int target) {
int[] result = new int[2];
if(nums == null || nums.length == 0){
return result;
}
// 注意key为nums[i],value为下标
HashMap<Integer,Integer> hashMap = new HashMap<>();
for(int i = 0;i < nums.length; i++){
if(hashMap.containsKey(target-nums[i])){
result[0] = i;
result[1] = hashMap.get(target-nums[i]);
return result;
}
hashMap.put(nums[i],i);
}
return result;
}
PS : 也可以用哈希表存储target-nums[i]实现。原理是一样的。
1.2 常见问题
很多同学对这里的哈希表究竟谁为key,谁为value有所疑问?相信自己,不是只有你一个人有此困惑。
这里郑重声明:
- key ——— 元素num[i] 或者 target-nums[i]
- value ———— 元素下标
很多之所以困惑是 考虑到数组中存在重复元素时,其作为key会被覆盖的原因。
对于这个题目,题目中明确了只会存在一个有效答案。这样的话,就算有重复元素,找到一组解就直接返回啦。
那么问题来了,假设可能存在多组解的情况,这种解法还有效吗?
答曰:然也
举个例子:
输入用例为[1,1,2,5], target = 3
当遍历到第二个1的时候,hashMap会覆盖前面的1,然后继续执行。最终会返回[2,1]这组有效答案。
纸上得来终觉浅,绝知此事要躬行
刷算法题时,很多时候不是我们没有思路,而是没有真正动起手去实现罢了。
167. 两数之和 II - 输入有序数组
给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。
以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。
关键点:
- 数组有序——非递减顺序排列;
- 找出满足相加之和等于目标数 target 的两个数;
- 返回下标且index1 < index2;
- 下标从1开始;
数组有序的情况下,双指针,舍我其谁?
public int[] twoSum(int[] numbers, int target) {
int[] result = new int[2];
if(numbers == null || numbers.length == 0){
return result;
}
int left = 0;
int right = numbers.length - 1;
while(left < right){
int sum = numbers[left]+numbers[right];
if( sum == target){
// 因为下标从1开始,
result[0] = left+1;
result[1] = right+1;
return result;
}
if(sum < target){
left++;
}else{
right--;
}
}
return result;
}
顺手补一刀吧—— 剑指 Offer 57. 和为s的两个数字
第二类:小于target
1099. 小于 K 的最大两数之和
给你一个整数数组 nums 和整数 k ,返回最大和 sum ,满足存在 i < j 使得 nums[i] + nums[j] = sum 且 sum < k 。如果没有满足此等式的 i,j 存在,则返回 -1 。
关键点:
- 数组无序
- (sum = nums[i] + nums[j]) < k;
- 返回 最大和sum尽可能的大;
不用想了,排序 + 双指针。
public int twoSumLessThanK(int[] nums, int k) {
if(nums == null || nums.length == 0){
return -1;
}
Arrays.sort(nums);
int left = 0;
int right = nums.length-1;
int result = -1;
while(left < right){
int sum = nums[left] + nums[right];
if(sum >= k){
right--;
}else{
// 求最大和
result = Math.max(result,sum);
left++;
}
}
return result;
}
LCP 28. 采购方案
小力将 N 个零件的报价存于数组 nums。小力预算为 target,假定小力仅购买两个零件,要求购买零件的花费不超过预算,请问他有多少种采购方案。
注意:答案需要以 1e9 + 7 (1000000007) 为底取模,如:计算初始结果为:1000000008,请返回 1
总结出关键点:
- 数组无序;
- num[i] + num[j] <= target;
- 求符合条件的方案数;
这道题是竞赛题,我们来看一下同学们的常见问题:
对于我竟无言以对,唯有张飞三连送于大家。
解决此题有个核心要素:方案数的锁定(右边界 - 左边界)。
public int purchasePlans(int[] nums, int target) {
if(nums == null || nums.length == 0){
return 0;
}
// 排序
Arrays.sort(nums);
int result = 0;
int left = 0;
int right = nums.length - 1;
while(left <= right){
int sum = nums[left] + nums[right];
if(sum > target){
right --;
}else{
// 这里利用[left,right]的元素都符合sum <= target的特性,直接得出方案数为right - left.
result += (right - left);
result %= (1000000007);
left++;
}
}
return result;
}