LeetCode 15 三数之和

点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)

生活中的算法

想象你是一个收银员,顾客给了你一张100元钱,商品只要85元。你要从收银柜里找零15元,但是柜子里只有一堆1元、2元、5元、10元的零钱。你会怎么做?你可能会拿起一张5元,然后找另外两张,加起来正好等于15元。

这就是我们今天要讲的"三数之和"问题的现实版本。不过在算法题中,我们要找的不是指定的和,而是和为0的三个数。

问题描述

LeetCode第15题"三数之和"是这样描述的:给你一个整数数组nums,请你找出所有和为0且不重复的三元组。

例如,给定数组 nums = [-1,0,1,2,-1,-4],满足要求的三元组是:[[-1,-1,2], [-1,0,1]]

这个问题看似简单,但要处理好"不重复"这个要求,还真需要一些巧妙的思路。

最直观的解法:三重循环法

最容易想到的方法就是:用三重循环遍历所有可能的三元组组合。就像收银员可能会一张一张地尝试所有零钱的组合。

具体步骤是这样的:

  1. 用三层循环遍历所有可能的三元组
  2. 检查每个三元组的和是否为0
  3. 如果找到了和为0的三元组,还要检查是否重复

让我们用一个小例子来模拟这个过程:

  1. nums = [-1,0,1]
  2. 尝试所有组合:
  3. (-1,0,1): -1 + 0 + 1 = 0
  4. (-1,1,0): -1 + 1 + 0 = 0 (重复)
  5. (0,-1,1): 0 + -1 + 1 = 0 (重复)
  6. ...
  7. 最终结果:[[-1,0,1]]

这种思路可以用Java代码这样实现:

  1. public List<List<Integer>> threeSum(int[] nums) {
  2. Set<List<Integer>> result = new HashSet<>();
  3. for (int i = 0; i < nums.length; i++) {
  4. for (int j = i + 1; j < nums.length; j++) {
  5. for (int k = j + 1; k < nums.length; k++) {
  6. if (nums[i] + nums[j] + nums[k] == 0) {
  7. // 对三个数排序,以避免重复
  8. List<Integer> triplet = Arrays.asList(nums[i], nums[j], nums[k]);
  9. Collections.sort(triplet);
  10. result.add(triplet);
  11. }
  12. }
  13. }
  14. }
  15. return new ArrayList<>(result);
  16. }

优化解法:排序+双指针法

仔细想想,我们其实可以把问题转化为:固定一个数,然后在剩下的数中找两个数,使它们的和等于第一个数的相反数。这就变成了我们熟悉的"两数之和"问题!

关键是要先对数组排序,这样就可以:

  1. 方便地跳过重复的数字
  2. 使用双指针高效地寻找两个数

排序+双指针法的原理

  1. 先将数组排序
  2. 固定第一个数nums[i],目标变成找两个数之和等于-nums[i]
  3. 使用左右指针在nums[i]后面的区域寻找这两个数
  4. 根据三数之和与0的比较,移动左右指针
  5. 注意跳过重复的数字以避免重复的三元组

算法步骤(伪代码)

  1. 对数组排序
  2. 遍历数组,固定第一个数nums[i]:
    • 如果nums[i]大于0,后面不可能有解,直接结束
    • 如果nums[i]和前一个数相同,跳过以避免重复
    • 使用左右指针在[i+1, end]区间寻找两数之和等于-nums[i]的组合
  3. 记录所有找到的三元组

示例运行

让我们用例子[-1,0,1,2,-1,-4]模拟这个过程:

  1. 排序后:[-4,-1,-1,0,1,2]
  2. 固定-4
  3. 目标找和为4的两个数
  4. left=1,right=5: -1+2=1<4left++
  5. left=2,right=5: -1+2=1<4left++
  6. left=3,right=5: 0+2=2<4left++
  7. left=4,right=5: 1+2=3<4,结束
  8. 固定第一个-1
  9. 目标找和为1的两个数
  10. left=2,right=5: -1+2=1,找到[-1,-1,2]!
  11. right--继续找...
  12. 固定第二个-1:(跳过,避免重复)
  13. 固定0
  14. 目标找和为0的两个数
  15. left=4,right=5: 1+2=3>0right--
  16. left=4,right=4:指针相遇,结束

Java代码实现

  1. public List<List<Integer>> threeSum(int[] nums) {
  2. List<List<Integer>> result = new ArrayList<>();
  3. // 先排序,这对去重和使用双指针非常关键
  4. Arrays.sort(nums);
  5. for (int i = 0; i < nums.length - 2; i++) {
  6. // 如果第一个数就大于0,后面肯定没有解
  7. if (nums[i] > 0) break;
  8. // 跳过重复的第一个数
  9. if (i > 0 && nums[i] == nums[i-1]) continue;
  10. // 使用双指针寻找另外两个数
  11. int left = i + 1;
  12. int right = nums.length - 1;
  13. while (left < right) {
  14. int sum = nums[i] + nums[left] + nums[right];
  15. if (sum == 0) {
  16. result.add(Arrays.asList(nums[i], nums[left], nums[right]));
  17. // 跳过重复的数
  18. while (left < right && nums[left] == nums[left+1]) left++;
  19. while (left < right && nums[right] == nums[right-1]) right--;
  20. // 继续寻找其他解
  21. left++;
  22. right--;
  23. } else if (sum < 0) {
  24. left++;
  25. } else {
  26. right--;
  27. }
  28. }
  29. }
  30. return result;
  31. }

三重循环vs排序+双指针

让我们比较这两种解法:

三重循环法的时间复杂度是O(n³),空间复杂度是O(1)(不考虑存储结果的空间)。它的优点是直观易懂,缺点是效率太低。

排序+双指针法的时间复杂度是O(n²),其中排序占用O(nlogn)。空间复杂度是O(logn)到O(n),取决于排序算法的实现。它通过巧妙地利用排序数组的特性,将时间复杂度降低了一个维度。

题目模式总结

这道题体现了几个重要的算法思想:

  1. 问题转换:将三数之和转换为一个数加两数之和
  2. 排序预处理:通过排序简化后续的处理
  3. 双指针技巧:在排序数组中高效查找
  4. 去重处理:利用排序后的性质跳过重复元素

这种模式可以扩展到解决其他类似问题:

  • 四数之和
  • K数之和
  • 最接近的三数之和

解决这类问题的通用思路是:

  1. 考虑是否可以通过排序获得额外的性质
  2. 能否将K数之和转换为K-1数之和
  3. 如何高效地避免重复解

小结

通过这道题,我们不仅学会了如何高效地找出三数之和为0的组合,更重要的是理解了如何将一个复杂问题分解成更容易解决的子问题。这种思维方式在算法设计中非常重要。

记住,遇到复杂问题时,不要急于求解,先思考能否通过预处理(如排序)或问题转换来简化问题。有时候,看似复杂的问题,换个角度就豁然开朗!


作者:忍者算法
公众号:忍者算法

从找零钱问题到三数之和:一道经典面试算法题的全面剖析|LeetCode 15 三数之和的更多相关文章

  1. python经典面试算法题4.1:如何找出数组中唯一的重复元素

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. [百度面试题] 难度系数:⭐⭐⭐ 考察频率:⭐⭐⭐⭐ 题目描述 ...

  2. python经典面试算法题1.3:如何计算两个单链表所代表的数之和

    本题目摘自<Python程序员面试算法宝典>,我会每天做一道这本书上的题目,并分享出来,统一放在我博客内,收集在一个分类中. 1.2 如何实现链表的逆序 [华为笔试题] 难度系数:⭐⭐⭐ ...

  3. leetcode:Path Sum (路径之和) 【面试算法题】

    题目: Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up ...

  4. Java实现 LeetCode 15 三数之和

    15. 三数之和 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以 ...

  5. LeetCode 15. 三数之和(3Sum)

    15. 三数之和 15. 3Sum 题目描述 Given an array nums of n integers, are there elements a, b, c in nums such th ...

  6. [Leetcode 15]三数之和 3 Sum

    [题目] Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? ...

  7. Java面试题精选(三) JSP/Servlet Java面试逻辑题

    --   JSP/Servlet  Java面试逻辑题   --     很显然,Servlet/JSP的WEB前端动态制作的重要性比HTML/CSS/JS的价值高很多,但我们都知道他们都是建立在HT ...

  8. LeetCode算法题-Move Zeroes(Java实现-三种解法)

    这是悦乐书的第201次更新,第211篇原创 01 看题和准备 今天介绍的是LeetCode算法题中Easy级别的第67题(顺位题号是283).给定一个数组nums,写一个函数将所有0移动到它的末尾,同 ...

  9. LeetCode 15. 三数之和(3Sum)

    题目描述 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复 ...

  10. LeetCode——15. 三数之和

    给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. ...

随机推荐

  1. Abp vNext 入门到放弃系列

    文章目录 1.模块介绍 2.模块加载机制 3.依赖注入 4.AutoMapper-- 待定 5.本地化--待定 6.模型验证--待定 7.异常处理--待定 8.缓存 9.动态代理和拦截 10.分布式锁 ...

  2. NLP语言学基础

    不同的自然语言有不同的语法结构,因此需要对语言数据进行语法解析,才能让机器更准确地学到相应的模式.而语言不同于图像,数据标注工作需要有一定的语言学知识,因此数据的整理也相对更困难.下面以英语为例(别的 ...

  3. VUE 前端读取excel表格内容

    <el-upload class="upload-demo" :action="''" :show-file-list="false" ...

  4. VulnHub-Sick0s1.1解法二shellshock漏洞

    免责声明 本博客提供的所有信息仅供学习和研究目的,旨在提高读者的网络安全意识和技术能力.请在合法合规的前提下使用本文中提供的任何技术.方法或工具.如果您选择使用本博客中的任何信息进行非法活动,您将独自 ...

  5. SpringMVC源码剖析(三)- DispatcherServlet的初始化流

    在我们第一次学Servlet编程,学java web的时候,还没有那么多框架.我们开发一个简单的功能要做的事情很简单,就是继承HttpServlet,根据需要重写一下doGet,doPost方法,跳转 ...

  6. 如何使用建造者模式(Builder Pattern)创建不可变类

    本文由 ImportNew - 唐小娟 翻译自 Journaldev.如需转载本文,请先参见文章末尾处的转载要求. ImportNew注:如果你也对Java技术翻译分享感兴趣,欢迎加入我们的 Java ...

  7. Python之argparse

    argparse模块可以轻松编写用户友好的命令行界面.该程序定义了它需要的参数,argparse并将找出如何解析这些参数sys.argv.argparse模块还会自动生成帮助和用法消息,并在用户给出程 ...

  8. HZNU Winter Trainning 7 补题 - Zeoy

    CodeForces - 1660C 题目传送门:https://vjudge.net/contest/535955#problem/C 题意:询问一个字符串最少删去几个字符,能够把这个字符串变成aa ...

  9. C# 获取两经纬度之间的距离

    C# 获取两经纬度之间的距离 迷恋自留地 //地球半径,单位米 private const double EARTH_RADIUS = 6378137; /// <summary> /// ...

  10. 以下哪一项是对CSMA/CA和CSMA/CD LAN控制通用的CSMA方法的适当描述?

    A.   检测载波信号并控制数据传输. B.   获得具有传输权的消息(令牌)的终端传输数据. C.   如果在数据传输过程中发生冲突,立即重新发送. D.   即使在使用传输线时也可以传输数据. = ...