热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

你真的会做2Sum吗?

点击上方“五分钟学算法”,选择“星标”公众号重磅干货,第一时间送达2Sum这题是Leetcode的第一题,相信大部分小伙伴都听过的吧。作为

点击上方“五分钟学算法”,选择“星标”公众号

重磅干货,第一时间送达

2 Sum 这题是 Leetcode 的第一题,相信大部分小伙伴都听过的吧。

作为一道标着 Easy 难度的题,它真的这么简单吗?

我在之前的刷题视频里说过,大家刷题一定要吃透一类题,为什么有的人题目做着越来越少,有的人总觉得刷不完的题,就是因为没有分类吃透。

单纯的追求做题数量是没有意义的,Leetcode 的题目只会越来越多,就像高三时的模考试卷一样做不完,但分类总结,学会解决问题的方式方法,才能遇到新题也不手足无措。

2 Sum

这道题题意就是,给一个数组和一个目标值,让你在这个数组里找到两个数,使得它俩之和等于这个目标值的。

比如题目中给的例子,目标值是 9,然后数组里 2 + 7 = 9,于是返回 2 和 7 的下标。

方法一

在我多年前还不知道时空复杂度的时候,我想这还不简单嘛,就每个组合挨个试一遍呗,也就是两层循环。

后来我才知道,这样时间复杂度是很高的,是 O(n^2);但另一方面,这种方法的空间复杂度最低,是 O(1)

所以,面试时一定要先问面试官,是希望优化时间还是优化空间

一般来说我们追求优化时间,但你不能默认面试官也是这么想的,有时候他就是想考你有没有这个意识呢。

如果一个方法能够兼具优化时间和空间那就更好了,比如斐波那契数列这个问题中从递归到 DP 的优化,就是时间和空间的双重优化,不清楚的同学后台回复「递归」快去补课~

我们来看下这个代码:

class Solution {public int[] twoSum(int[] nums, int target) {for (int i &#61; 0; i < nums.length; i&#43;&#43;) {for (int j &#61; i &#43; 1; j < nums.length; j&#43;&#43;) {if (nums[i] &#43; nums[j] &#61;&#61; target) {return new int[]{i, j};}}}return new int[]{-1, -1};}
}

  • 时间复杂度&#xff1a;O(n^2)

  • 空间复杂度&#xff1a;O(1)

喏&#xff0c;这速度不太行诶。

方法二

那在我学了 HashMap 这个数据结构之后呢&#xff0c;我又有了新的想法。

HashMap 或者 HashSet 的最大优势就是能够用 O(1) 的时间获取到目标值&#xff0c;那么是不是可以优化方法一的第二个循环呢&#xff1f;

有了这个思路&#xff0c;假设当前在看 x&#xff0c;那就是需要把 x 之前或者之后的数放在 HashSet 里&#xff0c;然后看下 target - x 在不在这个 hashSet 里&#xff0c;如果在的话&#xff0c;那就匹配成功&#xff5e;

诶这里有个问题&#xff0c;这题要求返回这俩数的下标&#xff0c;可是 HashSet 里的数是无序的...

那就用升级版——HashMap 嘛&#xff5e;&#xff5e;还不了解 HashMap 的原理的同学快去公众号后台回复「HashMap」看文章啦。

HashMap 里记录下数值和它的 index 这样匹配成功之后就可以顺便得到 index 了。

这里我们不需要提前记录所有的值&#xff0c;只需要边过数组边记录就好了&#xff0c;为了防止重复&#xff0c;我们只在这个当前的数出现之前的数组部分里找另一个数。

总结一下&#xff0c;

  • HashMap 里记录的是下标 i 之前的所有出现过的数&#xff1b;

  • 对于每个 nums[i] &#xff0c;我们先检查 target - nums[i] 是否在这个 map 里&#xff1b;

  • 如果在就直接返回了&#xff0c;如果不在就把当前 i 的信息加进 map 里。

class Solution {public int[] twoSum(int[] nums, int target) {int[] res &#61; new int[2];Map map &#61; new HashMap<>();for (int i &#61; 0; i < nums.length; i&#43;&#43;) {if (map.containsKey(target - nums[i])) {res[0] &#61; map.get(target - nums[i]);res[1] &#61; i;return res;}map.put(nums[i], i);}return res;}
}

  • 时间复杂度&#xff1a;O(n)

  • 空间复杂度&#xff1a;O(n)

喏&#xff0c;速度提升至 beat 99.96%

拓展

这是最基本的 2 Sum 问题&#xff0c;这个题可以有太多的变种了&#xff1a;

  • 如果这个数组里有不止一组结果&#xff0c;要求返回所有组合&#xff0c;该怎么做&#xff1f;

  • 如果这个数组里有重复元素&#xff0c;又该怎么做&#xff1f;

  • 如果这个数组是一个排好序了的数组&#xff0c;那如何利用这个条件呢&#xff1f;- Leetcode 167

  • 如果不是数组而是给一个 BST &#xff0c;该怎么在一棵树上找这俩数呢&#xff1f;- Leetcode 653

...

这里讲一下排序数组这道题&#xff0c;之后会在 BST 的文章里会讲 653 这题。

排序数组

我们知道排序算法中最快的也需要 O(nlogn)&#xff0c;所以如果是一个 2 Sum 问题&#xff0c;那没必要专门排序&#xff0c;因为排序会成为运算的瓶颈。

但如果题目给的就是个排好序了的数组&#xff0c;那肯定要好好收着了呀&#xff01;

因为当数组是排好序的时候&#xff0c;我们可以进一步优化空间&#xff0c;达到 O(n) 的时间和 O(1) 的空间。

该怎么利用排好序这个性质呢&#xff1f;

那就是说&#xff0c;在 x 右边的数&#xff0c;都比 x 要大&#xff1b;在 x 左边的数&#xff0c;都比 x 要小。

  • 如果 x &#43; y > target&#xff0c;那么就要 y 往左走&#xff0c;往小的方向走&#xff1b;

  • 如果 x &#43; y &#xff0c;那么就要 x 往右走&#xff0c;往大的方向走。

这也就是典型的 Two pointer 算法&#xff0c;两个指针相向而行的情况&#xff0c;我之后也会出文章详细来讲哒。

class Solution {public int[] twoSum(int[] numbers, int target) {int left &#61; 0;int right &#61; numbers.length - 1;while (left < right) {int sum &#61; numbers[left] &#43; numbers[right];if (sum &#61;&#61; target) {return new int[]{left &#43; 1, right &#43; 1}; //Your returned answers are not zero-based.} else if (sum < target) {left &#43;&#43;;} else {right --;}}return new int[]{-1, -1};}
}

3 Sum

3 Sum 的问题其实就是一个 2 Sum 的升级版&#xff0c;因为 1 &#43; 2 &#61; 3 嘛。。

那就是外面一层循环&#xff0c;固定一个值&#xff0c;在剩下的数组里做 2 Sum 问题。

反正 3 Sum 怎么着都得 O(n^2) &#xff0c;就可以先排序&#xff0c;反正不在乎排序的这点时间了&#xff0c;这样就可以用 Two pointer 来做了。

还需要注意的是&#xff0c;这道题返回的是数值&#xff0c;而非 index&#xff0c;所以它不需要重复的数值——The solution set must not contain duplicate triplets.

class Solution {public List> threeSum(int[] nums) {List> res &#61; new ArrayList<>();Arrays.sort(nums);for (int i &#61; 0; i &#43; 2 < nums.length; i&#43;&#43;) {if (i > 0 && nums[i] &#61;&#61; nums[i - 1]) {// skip same resultcontinue;}int j &#61; i &#43; 1;int k &#61; nums.length - 1;  int target &#61; -nums[i];while (j < k) {if (nums[j] &#43; nums[k] &#61;&#61; target) {res.add(Arrays.asList(nums[i], nums[j], nums[k]));j&#43;&#43;;k--;while (j < k && nums[j] &#61;&#61; nums[j - 1]) {j&#43;&#43;;  // skip same result}while (j < k && nums[k] &#61;&#61; nums[k &#43; 1]) {k--;  // skip same result}} else if (nums[j] &#43; nums[k] > target) {k--;} else {j&#43;&#43;;}}}return res;}
}

4 Sum

最后就是 4 Sum 问题啦。

这一题如果只是 O(n^3) 的解法没什么难的&#xff0c;因为就是在 3 Sum 的基础上再加一层循环嘛。

但是如果在面试中只做出 O(n^3) 恐怕就过不了了哦????

这 4 个数&#xff0c;可以想成两两的 2 Sum&#xff0c;先把第一个 2 Sum 的结果存下来&#xff0c;然后在后续的数组中做第二个 2 Sum&#xff0c;这样就可以把时间降低到 O(n^2) 了。

这里要注意的是&#xff0c;为了避免重复&#xff0c;也就是下图的 nums[x] &#43; nums[y] &#43; nums[z] &#43; nums[k] &#xff0c;其实和 nums[z] &#43; nums[k] &#43; nums[x] &#43; nums[y] 并没有区别&#xff0c;所以我们要限制第二组的两个数要在第一组的两个数之后哦。

看下代码吧&#xff1a;

class Solution {public List> fourSum(int[] nums, int target) {Set> set &#61; new HashSet<>();Map>> map &#61; new HashMap<>();Arrays.sort(nums);// 先处理第一对&#xff0c;把它们的sum存下来for(int i &#61; 0; i < nums.length - 3; i&#43;&#43;) {for(int j &#61; i &#43; 1; j < nums.length - 2; j&#43;&#43;) {int currSum &#61; nums[i] &#43; nums[j];List> pairs &#61; map.getOrDefault(currSum, new ArrayList<>());pairs.add(Arrays.asList(i, j));map.put(currSum, pairs);}}// 在其后做two sumfor(int i &#61; 2; i < nums.length - 1; i&#43;&#43;) {for(int j &#61; i &#43; 1; j < nums.length; j&#43;&#43;) {int currSum &#61; nums[i] &#43; nums[j];List> prevPairs &#61; map.get(target - currSum);if(prevPairs &#61;&#61; null) {continue;}for(List pair : prevPairs) {if(pair.get(1) < i) {set.add(Arrays.asList(nums[pair.get(0)], nums[pair.get(1)], nums[i], nums[j]));}}}}return new ArrayList<>(set);}
}

好啦&#xff0c;以上就是 2 Sum 相关的所有问题啦&#xff0c;如果有收获的话&#xff0c;记得给我点个赞哦&#xff5e;

---

爱分享&#xff0c;爱开源&#xff0c;GitHubPorn 现已正式上线&#xff01;专注于为大家分享优质的计算机学习资源与开发者工具。

如果今天的推荐符合你的口味&#xff0c;请在文章点赞&#xff0c;以表示对我的支持&#xff0c;你们的点赞和转发关注&#xff0c;是我持续更新的动力^_^




推荐阅读
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 如何自行分析定位SAP BSP错误
    The“BSPtag”Imentionedintheblogtitlemeansforexamplethetagchtmlb:configCelleratorbelowwhichi ... [详细]
  • SpringBoot uri统一权限管理的实现方法及步骤详解
    本文详细介绍了SpringBoot中实现uri统一权限管理的方法,包括表结构定义、自动统计URI并自动删除脏数据、程序启动加载等步骤。通过该方法可以提高系统的安全性,实现对系统任意接口的权限拦截验证。 ... [详细]
  • 本文介绍了Redis的基础数据结构string的应用场景,并以面试的形式进行问答讲解,帮助读者更好地理解和应用Redis。同时,描述了一位面试者的心理状态和面试官的行为。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • eclipse学习(第三章:ssh中的Hibernate)——11.Hibernate的缓存(2级缓存,get和load)
    本文介绍了eclipse学习中的第三章内容,主要讲解了ssh中的Hibernate的缓存,包括2级缓存和get方法、load方法的区别。文章还涉及了项目实践和相关知识点的讲解。 ... [详细]
  • 本文讨论了一个关于cuowu类的问题,作者在使用cuowu类时遇到了错误提示和使用AdjustmentListener的问题。文章提供了16个解决方案,并给出了两个可能导致错误的原因。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 判断数组是否全为0_连续子数组的最大和的解题思路及代码方法一_动态规划
    本文介绍了判断数组是否全为0以及求解连续子数组的最大和的解题思路及代码方法一,即动态规划。通过动态规划的方法,可以找出连续子数组的最大和,具体思路是尽量选择正数的部分,遇到负数则不选择进去,遇到正数则保留并继续考察。本文给出了状态定义和状态转移方程,并提供了具体的代码实现。 ... [详细]
  • Android JSON基础,音视频开发进阶指南目录
    Array里面的对象数据是有序的,json字符串最外层是方括号的,方括号:[]解析jsonArray代码try{json字符串最外层是 ... [详细]
  • 本文详细介绍了Spring的JdbcTemplate的使用方法,包括执行存储过程、存储函数的call()方法,执行任何SQL语句的execute()方法,单个更新和批量更新的update()和batchUpdate()方法,以及单查和列表查询的query()和queryForXXX()方法。提供了经过测试的API供使用。 ... [详细]
  • 个人学习使用:谨慎参考1Client类importcom.thoughtworks.gauge.Step;importcom.thoughtworks.gauge.T ... [详细]
  • 高质量SQL书写的30条建议
    本文提供了30条关于优化SQL的建议,包括避免使用select *,使用具体字段,以及使用limit 1等。这些建议是基于实际开发经验总结出来的,旨在帮助读者优化SQL查询。 ... [详细]
  • [大整数乘法] java代码实现
    本文介绍了使用java代码实现大整数乘法的过程,同时也涉及到大整数加法和大整数减法的计算方法。通过分治算法来提高计算效率,并对算法的时间复杂度进行了研究。详细代码实现请参考文章链接。 ... [详细]
  • ALTERTABLE通过更改、添加、除去列和约束,或者通过启用或禁用约束和触发器来更改表的定义。语法ALTERTABLEtable{[ALTERCOLUMNcolu ... [详细]
author-avatar
明年夏天1314520
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有