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

  • LeetCode 15.三数之和
  • 扩展
    • 三数之和变种题
    • 两数之和变种题

LeetCode 15.三数之和

这道题的题目大家自行查看:链接在这 ,题目和两数之和有些相似的地方,区别是这题是要返回所有三个数加起来等于0的组合,但是又不能是三个都重复的数字,那么我们能不能使用两数之和的思路来解这道题目呢?(ps.对两数之和的解法不熟悉的请看【LeetCode 1.两数之和】)

首先我们知道 a + b + c = 0 a+b+c=0 a+b+c=0,那么就是说 0 − a − b = c 0-a-b=c 0−a−b=c即 − a − b = c -a-b=c −a−b=c,所以我们只要按照上面的思路,有 − a − b = c = = − a − c = b -a-b=c == -a-c=b −a−b=c==−a−c=b,这时候我们将a固定,即看成和两数之和的target一样,这样是不是就和两数之和的思路是一样的了,只不过我们需要循环遍历的改变target即a的值,找出所有target下的所有组合,这样我们就能得出所以的解了,可以看出来这是一个2层的循环,时间复杂度为 O ( n 2 ) O(n^2) O(n2)。

另外题目要求我们答案中不可以包含重复的三元组,那么我们就需要有去重的步骤,如果我们在最终的结果中做去除重复,想想也知道,这肯定又是非常耗时的,所以我们需要另想办法,我们从例题1的解中可以观察出来,如[-1,0,1,2,-1,-4]的解[[-1,0,1],[0,1,-1]]可以看出,这两个解其实都是-1,0,1的不同组合,即a+b+c和b+c+a的区别罢了,所以我们可以想到如果我们把整个数组进行排序后,在循环遍历的时候,跳过一样的元素,那么自然而然的就能达到去除重复的效果了,并且这样做也达到了了剪枝的效果,即减少了一些没必要的循环,节约了时间,这也是这道题目的关键了,即先进行数组的排序,所以代码如下

public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> retList = new ArrayList<>();Arrays.sort(nums);//排序,为了下面去重HashMap<Integer, Integer> hashMap = new HashMap<>();for(int i = 0; i < nums.length -2; i++){if(nums[i] > 0) break;if(i > 0 && nums[i] == nums[i - 1]) continue;//去重hashMap.clear();for(int j = i + 1; j < nums.length;){if(hashMap.containsKey(-nums[i] - nums[j])){retList.add(new ArrayList<Integer>(Arrays.asList(nums[i], -nums[i] - nums[j], nums[j])));//添加结果后去重j++;while(j < nums.length){if(nums[j] == nums[j - 1]){j++;}else{break;}}}else{hashMap.put(nums[j], 1);j++;}}}return retList;
}

如果采用这种解法,比暴力解法 O ( n 3 ) O(n^3) O(n3)的时间复杂度是好多了,时间复杂度变为 O ( n 2 ) O(n^2) O(n2) ,不过还是额外的使用了hashMap的存储空间,那能不能再优化呢?其实这题我们可以不使用hashMap来做,而是使用双指针来做,先看代码

public List<List<Integer>> threeSum(int[] nums) {List<List<Integer>> retList = new ArrayList<>();Arrays.sort(nums);//排序,为了下面去重for(int i = 0; i < nums.length -2; i++){if(nums[i] > 0) break;if(i > 0 && nums[i] == nums[i - 1]) continue;int j = i + 1;int k = nums.length - 1;while(j < k){int sum = nums[i] + nums[j] + nums[k];if(sum > 0){while(j < k && nums[k] == nums[--k]);}else if(sum < 0){while(j < k && nums[j] == nums[++j]);}else{retList.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[j], nums[k])));while(j < k && nums[j] == nums[++j]);while(j < k && nums[k] == nums[--k]);}}}return retList;
}

首先是排序步骤,一样是为了下面的去重,去重的逻辑与【LeetCode 1.两数之和】是一样的,接下来的遍历以及一些细节的控制都一样,重要的是里层循环我们使用了2个变量来帮助我们找寻符合条件的元素,j位于当前i+1的位置,k位于最右边位置,这样我们就形成了一个区间,j在区间的最左边,k在区间的最右边,然后我们不断缩小这个区间,我们会看j和k有没有重叠作为条件,如果重叠即j<k则说明我们的当次查找结束,那么关键点是我们怎么查找呢?我们可以顺着题目思路 a + b + c = 0 a+b+c=0 a+b+c=0,num[i]是a、nums[j] 是 b、nums[k] 是 c,那么由于我们事先排序了,所以肯定有a<b<c,在遍历中,a是固定的情况下,如果结果>0,那就说明至少b和c中有个大了一点,结果才会>0,结合a<b<c,那肯定c比较大,所以我们就应该把c减少一点再试试结果是多少,相反的如果结果<0,那就说明至少b和c中有个小了点,结果也就小了点,同样结合a<b<c,所以我们就应该增加一点b再来试一下,如果结果等于0,那么就是我们要找的一组结果了,那么我们就换一个a再来继续寻找即可,由于我们排序过了,所以只要顺着数组继续遍历下一个数且这个数不和之前的a相同,即当为a继续即可求得最终结果。

另外至于为什么我们在【LeetCode 1.两数之和】中要使用HashMap的解法,而在这道题目中则使用双指针的解法,那是因为对于【LeetCode 1.两数之和】那道题目来说,他只要求得一个解即可,并且其case里面也会存在 [ 3 , 3 ] [3,3] [3,3]这样的题目来让你求两数之和,所以HashMap的解法在不考虑去重且找一个解的情况下会比较好,而当前这道题目明显就会是双指针的解法有优势了,所以每种解法也都会有其对应的最佳题目,这样你们理解了吗。

扩展

三数之和变种题

LeetCode 259.较小的三数之和

由于会员题,附上题目:

给定一个长度为 n 的整数数组和一个目标值 target ,寻找能够使条件 nums[i] + nums[j] + nums[k] < target 成立的三元组  i, j, k 个数(0 <= i < j < k < n)。示例 1:输入: nums = [-2,0,1,3], target = 2
输出: 2
解释: 因为一共有两个三元组满足累加和小于 2:[-2,0,1][-2,0,3]示例 2:输入: nums = [], target = 0
输出: 0
示例 3:输入: nums = [0], target = 0
输出: 0 > 来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/3sum-smaller

该题目和三数之和的思路是相同的,只不过解题的核心是看是否比目标值来的小而已,所以我们依旧利用双指针来做,并且只需要判断他们的和是否比目标值小,其余情况都是移动最右边的 k k k指针即可,并且如果是判断到其和比目标值小,那么可以说明在 k k k指针之前的所有组合肯定都比目标值小,所以我们只要用 k − j k-j k−j即可得到在j和k之间的组合的个数。

public int threeSumSmaller(int[] nums, int target) {int sum = 0;Arrays.sort(nums);for(int i = 0; i < nums.length - 2; i++){if ((nums[i] + nums[i + 1] + nums[i + 2]) > target) break;int j = i + 1;int k = nums.length - 1;while(j < k){if(nums[i] + nums[j] + nums[k] < target){sum += k - j;j++;}else{k--;}}}return sum;
}

LeetCode 16.最接近的三数之和

此题目使用三数之和的思路,使用双指针可以轻松搞定,唯一要知道的知识点就是需要求最接近目标值的三数之和,这个问题我们可以先求1个数与目标数相差了几个数,然后取相差最小的这个数即可,求1个数与另一个数相差了几个数的话我们可以使用这个数减去另一个数然后取绝对值即可,公式为 m i n = ∣ a − t a r g e t ∣ min = |a-target| min=∣a−target∣,min为要求的差几个数,a为当前数,target为目标数,如我们要求-1与1之间差几个数,带入 ∣ − 1 − 1 ∣ = 2 |-1 - 1| = 2 ∣−1−1∣=2,所以-1差1两个数,这样就能来解决这道题目了。

public int threeSumClosest(int[] nums, int target) {Arrays.sort(nums);int min = Math.abs(nums[0] + nums[1] + nums[2] - target);int result = nums[0] + nums[1] + nums[2];for(int i = 0; i < nums.length; i++){if (i > 0 && nums[i] == nums[i - 1]) continue;int left = i + 1;int right = nums.length - 1;while(left < right){int sum = nums[i] + nums[left] + nums[right];if(sum == target){return sum;}int tempMin = Math.abs(sum - target);if(tempMin < min){min = tempMin;result = sum;}if(sum < target){++left;}else{--right;}}}return result;
}

两数之和变种题

LeetCode 167.两数之和II

这题和两数之和区别在于它的数组已经是一个有序数组,所以我们可以直接利用这个特性来做,直接使用双指针可以很快的解决这道题目。

public int[] twoSum(int[] numbers, int target) {int low = 0, high = numbers.length - 1;while (low < high) {int sum = numbers[low] + numbers[high];if (sum == target) {return new int[]{low + 1, high + 1};} else if (sum < target) {++low;} else {--high;}}return new int[]{-1, -1};
}

剑指offer II 006. 排序数组中两个数字之和

public int[] twoSum(int[] numbers, int target) {int low = 0, high = numbers.length - 1;while (low < high) {int sum = numbers[low] + numbers[high];if (sum == target) {return new int[]{low, high};} else if (sum < target) {++low;} else {--high;}}return new int[]{-1, -1};
}

LeetCode 1099.小于 K 的两数之和
会员题,附上题目:

给你一个整数数组 nums 和整数 k ,返回最大和 sum ,满足存在 i < j 使得 nums[i] + nums[j] = sum 且 sum < k 。如果没有满足此等式的 i,j 存在,则返回 -1 。示例 1:输入:nums = [34,23,1,24,75,33,54,8], k = 60
输出:58
解释:
34 和 24 相加得到 58,58 小于 60,满足题意。示例 2:输入:nums = [10,20,30], k = 15
输出:-1
解释:
我们无法找到和小于 15 的两个元素。来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-less-than-k
public int twoSumLessThanK(int[] nums, int k) {Arrays.sort(nums);int result = -1;int left = 0;int right = nums.length - 1;while(left < right){int sum = nums[left] + nums[right];if(sum < k){result = Math.max(sum, result);left++;}else{right--;}}return result;
}

LeetCode 170.两数之和III
附上题目:

设计一个接收整数流的数据结构,该数据结构支持检查是否存在两数之和等于特定值。实现 TwoSum 类:TwoSum() 使用空数组初始化 TwoSum 对象
void add(int number) 向数据结构添加一个数 number
boolean find(int value) 寻找数据结构中是否存在一对整数,使得两数之和与给定的值相等。如果存在,返回 true ;否则,返回 false 。示例:输入:
["TwoSum", "add", "add", "add", "find", "find"]
[[], [1], [3], [5], [4], [7]]
输出:
[null, null, null, null, true, false]解释:
TwoSum twoSum = new TwoSum();
twoSum.add(1);   // [] --> [1]
twoSum.add(3);   // [1] --> [1,3]
twoSum.add(5);   // [1,3] --> [1,3,5]
twoSum.find(4);  // 1 + 3 = 4,返回 true
twoSum.find(7);  // 没有两个整数加起来等于 7 ,返回 false来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/two-sum-iii-data-structure-design

这道题目要求我们直接编写一个对象来求两数之和,其思路和两数之和以及三数之和是一样的,之前的两数之和我们使用了无序数组+Hash表映射来做,而三数之和我们采用了有序数组+双指针来做,这两种做法都是可以的,不过有序数组+双指针的方式效率会比较高一点。

无序数组+Hash表代码

class TwoSum {Map<Integer, Integer> nums = new HashMap<Integer, Integer>();public TwoSum() {nums = new HashMap<Integer, Integer>();}public void add(int number) {if(nums.containsKey(number)){nums.replace(number, nums.get(number) + 1);}else{nums.put(number, 1);}}public boolean find(int value) {for(Map.Entry<Integer, Integer> entry: nums.entrySet()){int diff = value - entry.getKey();if(diff != entry.getKey()){if(nums.containsKey(diff)){return true;}}else{if(entry.getValue() > 1){return true;}}}return false;}
}

这边我们使用了一个小技巧,我们之前的两数之和的解法中,我们需要使用一个hashmap来存储差值从而得出结果,这样就会要在每次find的时候都创建一个hashmap来帮助我们,其实,我们可以使用一个hashMap即存储数据又进行数据查找,节省了空间,其核心就是看其差值和其值是否是相同的,不相同则看hashMap中是否存在该值即可,如果相同,利用了hashmap的value来存储对应数字出现的次数,如果大于1,说明不是自己本身,即说明存在2个相同的数加起来正好等于目标值。

我理解的算法 - 三数之和及两数、三数之和扩展题相关推荐

  1. java两个很大的数相加_两个超大数的相加

    两个超大数的相加,主要是判断进一的情况,另外int型有边界限制,所以转换成字符串型进行处理. /** * @description 两个超大的数字相加 * @param $numA string 第一 ...

  2. php比较两个变量的值_总结PHP不用第三个变量交换两个变量的值的几种方法

    "PHP不用第三个变量交换两个变量的值"这个题看到过好多次了,看来面试确实喜欢考这道题.今天,对于这个题目,我自己总结了几种方法,可能不全,大家来互相补充. 有些仅适用于字符串,方 ...

  3. [算法]力扣刷题-初级算法 - 数组(三)(数组篇完结) [两数之和] [有效的数独] [旋转图像]

    初级算法 - 数组篇完结: 初级算法 - 数组(一): https://blog.csdn.net/weixin_43854928/article/details/121315702 初级算法 - 数 ...

  4. 代码随想录算法训练营15期 Day 7 | 454.四数相加II 、 383. 赎金信 、15. 三数之和 、18. 四数之和

    昨天看了一下别的东西,导致昨天没有练习打卡,今天补上昨天的学习知识. 454.四数相加II 建议:本题是 使用map 巧妙解决的问题,好好体会一下 哈希法 如何提高程序执行效率,降低时间复杂度,当然使 ...

  5. 思维导图整理大厂面试高频数组补充1: 最接近的三数之和 和 三数之和 的两个不同之处, 力扣16

    此专栏文章是对力扣上算法题目各种方法的总结和归纳, 整理出最重要的思路和知识重点并以思维导图形式呈现, 当然也会加上我对导图的详解. 目的是为了更方便快捷的记忆和回忆算法重点(不用每次都重复看题解), ...

  6. 理解GBDT算法(三)——基于梯度的版本

     理解GBDT算法(三)--基于梯度的版本 标签: GBDT梯度残差代价函数回归树 2015-03-31 16:13 1395人阅读 评论(3) 收藏 举报 本文章已收录于: 分类: Machin ...

  7. 双指针解决力扣两/三数之和问题

    双指针解决力扣两/三数之和问题 文章目录 双指针解决力扣两/三数之和问题 一.问题描述 二.分析 1.暴力 2.排序+双指针法 3.hash法 三.问题描述 四.分析 方法一:排序 + 双指针 五.代 ...

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

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

  9. c语言用编译器求两个整数之和的代码,使用OC语言编撰两个超大数相乘或相加的算法的思路和超大正整数相乘的代码...

    使用OC语言编写两个超大数相乘或相加的算法的思路和超大正整数相乘的代码 正文: 在编程中,无论是OC还是C亦或是C++语言,所声明的整数变量都会在内存中占有固定的存储空间,而这些存储空间都是固定的. ...

最新文章

  1. you need to build uWSGI with SSL support to use the websocket handshake api function !!!
  2. 爱心志愿者义工俱乐部公告
  3. MOSS2010 标准版与企业版的区别
  4. Java并发——结合CountDownLatch源码、Semaphore源码及ReentrantLock源码来看AQS原理
  5. mysql 互为主从复制常见问题
  6. 从Mysql slave system lock延迟说开去
  7. 智能制造的灾备问题如何解决?
  8. 啥?分布式啥?啥事务?
  9. ie浏览器在线使用_微软加速反IE战略,超过1000个网站将拒绝渲染
  10. 将PHP文件生成静态文件源码
  11. SpringBoot多模块项目整合Dubbo
  12. 英语“就近原则”和“就远原则”
  13. 如何将多张图片合并成一个PDF文件
  14. java前端开发简历_web前端工程师简历
  15. 基于Python的豆果网食谱数据爬取及可视化分析系统
  16. CSS中设置单机按钮,实现按下效果
  17. 为 什 么 说 Synchronized 是 非 公 平 锁
  18. 趣图 | 程序员的白天 vs 夜晚?
  19. HTML5基础:布局和标签
  20. 张一鸣的“成事哲学”:取势、明道、优术、践行、合众

热门文章

  1. 在OpenCV里用drawMarker画标记符号
  2. 爬虫第二式:猫眼电影前100排行榜
  3. VC 获取打印机与打印作业的状态
  4. 苹果地图 谷歌地图 iPhone XS Max
  5. 米云平台Python调用
  6. [CentOS]添加删除用户
  7. Centos7 查看、增加、删除用户组与用户
  8. 华为云桌面服务安全高效,助力企业业务特殊时期不中断!
  9. (中谷教育视频学习)python笔记
  10. 动态导航多级下拉菜单 html,css3实现的多级渐变下拉菜单导航效果代码