两数之和、三数之和、四数之和和K数之和是最近听室友提起的几道有意思的基础题,可以说是把双指针运用的淋漓尽致。(K数之和其实是一个动态规划的题,此处因为满足*数之和的的结构,放在一起对比提一下)。

1、两数之和等于Target的下标

LintCode:https://www.lintcode.com/problem/two-sum/

题目描述:给一个整数数组,找到两个数使得他们的和等于一个给定的数 target。假设只有一组答案。

你需要实现的函数twoSum需要返回这两个数的下标, 并且第一个下标小于第二个下标。注意这里下标的范围是 0 到 n-1。

简析:此处需要返回的是数字所在位置的下标,不是判断是否存在。

第一种思路借助hashMap,key为数组中的值,value为其下标。遍历一次就可以找出目标答案,空间复杂度为O(n),时间复杂度为O(n);

第二种思路构造Pair类然后进行排序,排序之后通过头尾双指针找到目标答案,时间复杂度为O(nlogn),空间复杂度为O(n);

代码:

    //方案一:自定义pair类,排序之后使用双指针 时间复杂度o(nlogn),空间复杂度o(1)// 并实现comparable接口-compare方法重写(当然也可以使用匿名内部类,继承comparator类,compare方法重写)public class Solution {class Pair implements Comparable<Pair>{public int key;public int value;Pair(int key, int value){this.key = key;this.value = value;}@Overridepublic int compareTo(Pair o1){if(o1.value > this.value)return -1;else if( o1.value == this.value)return 0;elsereturn 1;}}  //end of Pair Classpublic int[] twoSum(int[] numbers, int target) {// write your code hereint[] res = new int[2];if(numbers == null || numbers.length <= 1)return res;Pair[] pairs = new Pair[numbers.length];for(int i = 0; i < numbers.length; i ++){Pair pair = new Pair(i,numbers[i]);pairs[i] = pair;}Arrays.sort(pairs);int left = 0, right = numbers.length - 1;while(left < right){if(pairs[left].value + pairs[right].value == target){res[0] = Math.min(pairs[left].key, pairs[right].key);res[1] = Math.max(pairs[left].key, pairs[right].key);left ++;right --;}else if(pairs[left].value + pairs[right].value > target){right --;}elseleft ++;}return res;}
     //方法二:使用hashmap,此时不需要排序。时间复杂度o(n),空间复杂度o(n)//hashmap中存储键为数值,值为对应的数组下标public int[] twoSum2(int[] numbers, int target){if(numbers == null || numbers.length <= 1)return null;int[] res = new int[2];HashMap<Integer, Integer> map = new HashMap<>();  //key-numbers[i]  value- ifor(int i = 0; i < numbers.length; i ++){//首先判断当前元素与前面的元素是否可以组成targetif(map.keySet().contains(target - numbers[i])){res[1] = i;res[0] = map.get(target - numbers[i]); //另一个数的下标序号return res; //只有一组结果,此处直接返回}map.put(numbers[i],i); //将当前元素加入到HashMap}return res;}

2、三数之和等于0的组合排列

LintCode
题目描述:给出一个有n个整数的数组S,在S中找到三个整数a, b, c,找到所有使得a + b + c = 0的三元组。在三元组(a, b, c),要求a <= b <= c。结果不能包含重复的三元组。

简析:此题目target是固定等于0,那么要求的a、b、c中一定是正负相加或者全0。我们可以枚举首位a,然后用双指针遍历找出和为target - a的俩数。注意,要使用双指针必须首先保证有序性。

注意此题目要求输出的组合中不能有重复,所以要去重。去重的方法无非就是:枚举首位元素a的时候,保证不重复。当找到某组组合之后,第二位的元素和第三位的元素都需要去重。在有序数组中,该去重操作就是就是移到到下一位不想等的元素上。a元素如果发现已经被枚举过了,则将a元素指针i进行i ++b元素如果之后重复是相等的,则不断后移,直到移到后面第一个不是该元素的位置上。实现起来就是while(numbers[left] == numbers[left + 1]) left ++,完成while循环之后,再加一个left ++

代码:

public class Solution {/*** @param numbers: Give an array numbers of n integer* @return: Find all unique triplets in the array which gives the sum of zero.*/public List<List<Integer>> threeSum(int[] numbers) {// write your code here//边界判断if(numbers == null || numbers.length == 0)return null;//返回的二维listList<List<Integer>> res = new ArrayList<>();//数组排序Arrays.sort(numbers);//枚举首位元素a + 双指针遍历for(int i = 0; i < numbers.length - 2 && numbers[i] <= 0; i ++){//首位元素去重while(i >= 1 && i < numbers.length - 2 && numbers[i] == numbers[i - 1])i ++;int left = i + 1;int right = numbers.length - 1;//left < right 同时约束了指针的范围,避免了 i++ 越界while(left < right){ if(numbers[i] + numbers[left] + numbers[right] == 0){List<Integer> tmp =  new ArrayList<>();tmp.add(numbers[i]);tmp.add(numbers[left]);tmp.add(numbers[right]);res.add(tmp);//第二位元素去重while(left + 1 < right && numbers[left] == numbers[left + 1])left ++;//第三位元素去重while(left < right - 1 && numbers[right] == numbers[right - 1])right --;//注意!!去重结束之后仍要同时移动left和right指针//保证指针移动到了第一个不是重复元素的位置上left ++; right --;    }else if(numbers[i] + numbers[left] + numbers[right] > 0)right --;elseleft ++;    }}return res;}
}

3、三数之和小于Target的组合总数目

LintCode: https://www.lintcode.com/problem/3sum-smaller/description

题目描述:给定一个n个整数的数组和一个目标整数target,找到下标为i、j、k的数组元素0 <= i < j < k < n,满足条件nums[i] + nums[j] + nums[k] < target。输出满足上述条件的组合的总数。

简析:需要找和为target的三个数,可以首先确定一个数A,然后找和为target-A的两个数。

(错误思路:“从数学角度来看,三个数和为target,一定至少有1个小于等于target的数。所以可以A数的范围定在<= target的范围进行枚举,剩余两数则在大于A的范围内进行双指针遍历”。 这种想法非常非常错误的,比如target= (-4) = (-2) + (-2) + 0,三个数都比target要大。当target为0和正数的时候,刚才的说法才是正确的。)

通过剖析上面的错误思路,对于首位数字的枚举,不能仅仅只枚举小于等于target的数,应该遍历到数组中所有的数,也就是从[0, length -3],最后两位要留给剩余两个数字。

代码

public class Solution {/*** @param nums:  an array of n integers* @param target: a target* @return: the number of index triplets satisfy the condition nums[i] + nums[j] + nums[k] < target*/public int threeSumSmaller(int[] nums, int target) {// Write your code hereint cnt = 0;if(nums == null || nums.length == 0)return cnt;//数组排序后方可使用双指针Arrays.sort(nums);//枚举首位数字for(int i = 0; i <= nums.length - 3; i ++){int left = i + 1;int right = nums.length - 1;//确定首位数字后,双指针遍历while(left < right){//当和小于target时:保持当前left不变,right指针从此处前移到left+1,所有的和都将小于target//所以此时满足条件的情况的数目等于 right - (left + 1) + 1 = right - leftif(nums[left] + nums[right] < target - nums[i]){ cnt += right - left;  left ++;}elseright --;}}return cnt;}
}

4、四叔之和等于Target的组合

LintCode:https://www.lintcode.com/problem/4sum/description

题目描述:给一个包含n个数的整数数组S,在S中找到所有使得和为给定整数target的四元组(a, b, c, d)。四元组(a, b, c, d)中,需要满足a <= b <= c <= d。答案中不可以包含重复的四元组。

简析:四数之和在三数之和的基础上又多了一个数字,仍旧使用双指针,此时需要枚举前两个元素ab,双指针寻找cd,是的c + d = target - a - b。在有序数组中的去重仍旧采用移动指针的方法,直到移动到第一个不相等的位置。

具体来说,在处理的时候要注意两个问题:1.数组越界 2.去重遍历的范围。

例如对第一个元素来说,i本身的范围是[0, numbers.length - 4],但是当他处理0位置时候,不需要和前面的元素判重。判重的范围是[1, numbers.length - 4],例如第1位置的数值与第0位置的数值相等,则需要跳过第1位置。

针对第二个元素,j本身的范围是[i + 1, numbers.length -3]。和第一个元素一样,他判重的范围是包含i + 1的。从i + 2位置开始,如果和前面元素相等,则需要跳过。

针对第三个和第四个元素而言,当出现满足要求的组合时候,就要进行去重,分别把left和right指针移动到不等于当前指针的第一个位置。移动指针过程中非常容易出现数组越界,在while(left < right)的循环中,要保证left指针left ++后都要小于rightright指针保证right -- 要大于left

代码:

public class Solution {/*** @param numbers: Give an array* @param target: An integer* @return: Find all unique quadruplets in the array which gives the sum of zero*/public List<List<Integer>> fourSum(int[] numbers, int target) {// write your code hereList<List<Integer>> res = new ArrayList<>();if(numbers == null || numbers.length == 0)return res;Arrays.sort(numbers);//a元素遍历for(int i = 0; i < numbers.length - 3; i ++){//a元素去重while(i > 0 && i <  numbers.length - 3 && numbers[i] == numbers[i - 1])i ++;//b元素遍历for(int j = i + 1; j < numbers.length - 2; j ++){//b元素去重while(j > i + 1 &&  j < numbers.length - 2 && numbers[j] == numbers[j - 1])j ++;//双指针int left = j + 1;int right  = numbers.length - 1;while(left < right){if(numbers[left] + numbers[right] + numbers[i] + numbers[j] == target){List<Integer> tmp = new ArrayList<>();tmp.add(numbers[i]); tmp.add(numbers[j]); tmp.add(numbers[left]); tmp.add(numbers[right]);res.add(tmp);//第三个元素去重while(left + 1 < right && numbers[left + 1] == numbers[left])left ++;//第四个元素去重   while(left < right - 1 && numbers[right] == numbers[right - 1])right --;left ++; right --;    }else if(numbers[left] + numbers[right] + numbers[i] + numbers[j] < target)left ++;elseright --;}}}        return res;}
}

5、K数之和

此题目与上面的题目都是同一类型的,前面的题都是双指针的。此题目虽然是k数之和,看起来像三数之和、四数之和的拓展。但其实是动态规划的问题。

LintCode: https://www.lintcode.com/problem/k-sum/

题目描述:给定 n 个不同的正整数,整数 k(k <= n)以及一个目标数字 target。 
在这 n 个数里面找出 k 个数,使得这 k 个数的和等于目标数字,求问有多少种方案?
输入:
List = [1,2,3,4] ; k = 2 ; target = 5
输出: 2
说明: 1 + 4 = 2 + 3 = 5

简析:设状态为f[i][j][p],表示前i个数中找出j个数且和等于p的方案数目。

那么状态转移方程: f[i][j][p] = f[i - 1][j][p] + f[i - 1][j - 1][p -num[i]]

最终返回f[n][k][target]即可

代码

public class Solution {/*** @param A: An integer array* @param k: A positive integer (k <= length(A))* @param target: An integer* @return: An integer*/public int kSum(int[] A, int k, int target) {// write your code hereif(A == null || A.length ==0 || k <= 0)return 0;//正整数的总数从0 - A.length; 可选的个数从 0 - k; target也是从 0 - targetint[][][] f = new int[A.length + 1][ k + 1][target + 1];//初始值for(int i = 0; i <= A.length; i ++){f[i][0][0] = 1;}//f[i][j][t] = f[i - 1][j][t] + f[i - 1][j - 1][t -A[i]]for(int i = 1; i <= A.length; i ++)for(int j = 1; j <= k; j ++)for(int t = 1; t <= target; t ++){f[i][j][t] = f[i - 1][j][t];f[i][j][t] += t - A[i - 1] >=0 ? f[i - 1][j - 1][t -A[i - 1]] : 0;}return f[A.length][k][target];}
}

两数之和、三数之和、四数之和、K数之和相关推荐

  1. c语言黑洞数程序三位,C语言5位黑洞数

    任意一个5位数,比如:34256,把它的各位数字打乱,重新排列,可以得到一个最大的数:65432,一个最小的数23456.求这两个数字的差,得:41976,把这个数字再次重复上述过程(如果不足5位,则 ...

  2. 一圈,两圈,三圈,四圈……

    某男晚上经常到学校操场练习跑步.一天,他发现有个女孩也是如此,而且他还发现一个规律,就是这个女孩第一个礼拜每天都跑一圈,第二个礼拜每天都跑两圈,第三个礼拜每天都跑三圈-- 就在第三个礼拜的某天晚上,他 ...

  3. revit二次开发 创建管道三通,管道四通。两根管、三根管、四根管

    创建单跟水管 /// <summary>/// 创建水管Pipe/// </summary>/// <returns></returns>public ...

  4. 我理解的算法 - 三数之和及两数、三数之和扩展题

    我理解的算法 - 三数之和及两数.三数之和扩展题 LeetCode 15.三数之和 扩展 三数之和变种题 两数之和变种题 LeetCode 15.三数之和 这道题的题目大家自行查看:链接在这 ,题目和 ...

  5. 输入一个三位数,求个个数位数字的三次方之和,并判断和是否与该数相同

    解题思路: 先把三位数(n)的3个数位值算出来 然后求数位数字的三次方之和 最后判断数位数字的三次方之和(sum)是否与该数(n)相同 代码: # 输入一个三位数,求个个数位数字的三次方之和,并判断和 ...

  6. 程序员编程艺术第三十四~三十五章:格子取数问题,完美洗牌算法

    第三十四~三十五章:格子取数,完美洗牌算法 作者:July.caopengcs.绿色夹克衫.致谢:西芹_new,陈利人, Peiyush Jain,白石,zinking. 时间:二零一三年八月二十三日 ...

  7. 编写一个判断完数的函数。完数是指一个数恰好等于它的因子之和,如6=1+2+3,6就是完数。

    编写一个判断完数的函数.完数是指一个数恰好等于它的因子之和,如6=1+2+3,6就是完数. 程序之美 编写一个判断完数的函数.完数是指一个数恰好等于它的因子之和,如6=1+2+3,6就是完数. #in ...

  8. 2021-04-09编程求1000以内的所有“完数”。所谓“完数”是指一个数恰好等于它的因子之和。例如,6是完数,因为6=1+2+3

    编程求1000以内的所有"完数".所谓"完数"是指一个数恰好等于它的因子之和.例如,6是完数,因为6=1+2+3 #include"stdio.h&q ...

  9. 因数之和等于数字本身的数称为完全数,比数字本身大的数称为丰沛数, 比数字本身小的数称为不足数

    1.因数之和等于数字本身的数称为完全数,比数字本身大的数称为丰沛数, 比数字本身小的数称为不足数. 编写程序,输出所有[100,10000)区间的所有完全数以及丰沛数和不足数的个数. 请在[pytho ...

最新文章

  1. 腾讯微博API时间线相关接口返回的微博信息中head值使用问题
  2. Ranger中对hive添加policy字后,hive登录用户可用,hive密码不管用的问题解决,HiveServer2 Authentication Custom的编写
  3. 程序员过关斩将--更加优雅的Token认证方式JWT
  4. php 5.4 安装xcache,安装xcache为你的PHP加速
  5. CPU高速缓存与极性代码设计
  6. STM32的JTAG下载模式
  7. Anndroid 使用相机或相册打开图片
  8. OSChina 周一乱弹 ——我后悔让爸妈用微信了!
  9. 单反相机的一般入门设置建议
  10. 1 常用邮箱SMTP/POP3地址及端口
  11. 使用spring validation完成数据后端校验-自定义校验的注解-判断是否为空
  12. 计算机无法进入测试页面,为什么打印机无法打印测试页?介绍具体的多种原因及解决方法...
  13. spring5.1.3使用篇-数据访问
  14. Android App设置状态栏颜色
  15. 如何实现罗克韦尔PLC AB1756的远程监控数据采集?
  16. 基因家族的鉴定-基于Windows系统上的HMMER
  17. 【机器学习】预测偏差与其原因
  18. not found error :\tensorflow\contrib\coder\python\ops\_coder_ops.so——_gru_ops.so——_lstm_ops.so···
  19. TYVJ 1089 smrtfun
  20. 学习PCL库需要知道哪些知识?

热门文章

  1. 英语学习APP—百词斩
  2. (十五:2020.08.28)CVPR 2013 追踪之论文纲要(译)
  3. ML之PDP:基于titanic泰坦尼克是否获救二分类预测数据集利用PDP部分依赖图对RF随机森林实现模型可解释性案例
  4. Pandas中to_excel实现数据追加或者覆盖到Excel工作表
  5. python知网查重_学长学姐使用知网查重的经验之谈
  6. 点云处理--点云平移和旋转
  7. linux /sys目录下的各个子目录说明
  8. Linux下的文件管理(初学者必看)
  9. Linux基础之ls命令
  10. leaflet 矢量 经纬网格