【算法练习题】力扣练习题——数组(2):三数之和

原题说明:

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

注意:答案中不可以包含重复的三元组。

例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4],

满足要求的三元组集合为:
[[-1, 0, 1],[-1, -1, 2]]

原题链接:https://leetcode-cn.com/problems/3sum



解法一:基于HashMap的暴力求解

参考力扣题:https://leetcode-cn.com/problems/two-sum/

可以先固定三数中的一个,然后对剩下的两数进行一次遍历。时间复杂度应该是$O\left(\mathrm{n}^{2}\right)$

但是不同于两数加和题,本题有一个难点,由于要求输出所有组合,因此需要避免重复情况

我的初步想法是

  1. 找到满足条件的三数;
  2. 对三数进行排列;
  3. 将三数组合转化成字符串;
  4. 将其存储到容器中;
  5. 通过容器特性进行筛选;

第一步:对于查找部分,声明了HashMapmaptwo用于查找一个数i固定后的其余两数。

该部分代码(查找代码)如下:

1 for(int j = i+1;j<nums.length;j++) {
2     int target = sum - nums[j];
3     if(maptwo.containsKey(target)) {//used for judging the sum
4         int k = maptwo.get(target);
5             sumlist.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[j], nums[k])));
6     }
7     maptwo.put(nums[j], j);
8 }    

第二步:对于排列部分,直接使用了Array对象自带的排序函数

int[] newnums = {nums[i],nums[k],nums[j]};
Arrays.sort(newnums);

排序的原因是,防止出现$-101$与$-110$这样从字符串的变量类型看不同、实际是相同的情况。

这里可能要注意的是,一开始我曾尝试在获得输入数组时,先排序,后期就不用再次排序。但是后来调试的时候,发现由于HashMap的缘故,得到的三数顺序仍然会变。举个例子,对于$-1,0,1$,在查找时,会先找到$0$,但是由于算法的缘故,此时这个数只是被存入maptwo、不做最后的存储,直到找到$1$。此时nums[j]应该是$1$,而nums[k]却是$0$,因此需要重新排序。

第三步:将满足条件的三数转化成字符串

这里使用的数据类型是StringBuffer,这样可以动态添加数组。

sb.append(String.valueOf(newnums[0]));
sb.append(String.valueOf(newnums[1]));
sb.append(String.valueOf(newnums[2]));

添加的位置。一开始我选择添加的位置是得到每一个元素,比如上个代码段的第2行和第3行添加nums[j],第4行之后添加nums[k]。有个问题要注意:在真正满足条件的第三个数出现时,第二个数和第三个数之间所有的数都会被当做nums[j]添加到StringBuffer中。举个例子,对于给定的数组$-2,-1,-1,0,1,3$,在组合$-1,3$中的所有数都会被添加。因此应该要在确认第三个数之后进行字符串转化和添加操作。

第四步&&第五步:存储到容器中并筛选

声明了HashMapmapall负责存储字符串信息并进行筛选。代码如下:

if(!mapall.containsValue(sb.toString())) {//used for judging whether repeated
	mapall.put(h++, sb.toString());	//sb is the type of StringBuffer
} sb = new StringBuffer();

后来注意到,其实没有必要用HashMap的。

在这里,由于兼具筛选的功能,所有最后的对三数组合的存储应该放在这段代码中包裹起来。另外,StringBuffer也应该在存储到HashMap后被释放。

以下是完整的代码:

 1 public ArrayList<ArrayList<Integer>> threeSum(int[] nums) {
 2     if(nums.length < 3 || nums == null) {
 3         return null;
 4     }
 5
 6     ArrayList<ArrayList<Integer>> sumlist = new ArrayList();
 7
 8     HashMap<Integer, String> mapall = new HashMap<Integer, String>();
 9     int h = 0;
10
11     for(int i = 0; i<nums.length -1; i++) {
12         //used for search nums of the left two elements
13         HashMap<Integer, Integer> maptwo = new HashMap<Integer, Integer>();
14         //used for comparing the repeated combination
15         StringBuffer sb = new StringBuffer();
16
17         int sum  = 0-nums[i];
18         for(int j = i+1;j<nums.length;j++) {
19             int target = sum - nums[j];
20             if(maptwo.containsKey(target)) {//used for judging the sum
21                 int k = maptwo.get(target);
22
23                 int[] newnums = {nums[i],nums[k],nums[j]};
24                 Arrays.sort(newnums);
25                 sb.append(String.valueOf(newnums[0]));
26                 sb.append(String.valueOf(newnums[1]));
27                 sb.append(String.valueOf(newnums[2]));
28
29                 if(!mapall.containsValue(sb.toString())) {//used for judging whether repeated
30                     sumlist.add(new ArrayList<Integer>(Arrays.asList(nums[i], nums[j], nums[k])));
31                     mapall.put(h++, sb.toString());
32                 }
33                 sb = new StringBuffer();
34             }
35             maptwo.put(nums[j], j);
36         }
37     }
38     return sumlist;
39 }

最后失败在了求解时间上。。。

解法二:双指针

时间复杂度上,该解法和上述解法一致。同样需要先排序(其实解法一可能不需要先排序?)

原理是,固定最左端的数字。剩下的数值作为两个数的遍历空间。两个数分别在区域的两端,设为L和R。

遍历的方式为

$\operatorname{sum}=n u m s[i]+n u m s[L]+n u m s[R]\left\{\begin{array}{l}{=0, \text { done }} \\ {>0, R--} \\ {<0, L++}\end{array}\right.$

图解为

找到满足条件的三值后,采用和解法一相同的方式存入数值

然后防止重复的方法为,

对于,若$\operatorname{nums}[L]==\operatorname{nums}[L++]$,则$L++$。如图,使得左端数遇到重复数值时,选择角标最大的(跳过左边的数),不至于重复。

代码实现为

while(L<R && nums[L]==nums[L+1]) L++;

同理,若$\operatorname{nums}[R--]==\operatorname{nums}[R]$,则$R--$,如图,使得右端数遇到重复数值时,选择角标最小的(跳过右边的数),不至于重复。

代码实现为

while(L<R && nums[R]==nums[R-1]) R--;

这里需要注意的问题有两处:

  • 两行代码都应该在确认三数加和满足要求的情况下,否则,直接跳过会缺失解。
  • 循环判定条件务必不能遗漏L<R(也是第二层循环的循环条件)。反例是$-2,1,1,1,1$,如此一来,$L$会一直增加、直到数组越界。

之后是第一层循环的迭代。这里注意的是防止重复,原理和左端数值时一样的。只是我在写代码的时候,被边界条件困住了。这里先给出整体的代码:

 1 public ArrayList<ArrayList<Integer>> newSolution (int[] nums) {
 2     ArrayList<ArrayList<Integer>> sumlist = new ArrayList();
 3
 4     if(nums.length < 3 || nums == null) {
 5         return sumlist;
 6     }
 7
 8     Arrays.sort(nums);
 9
10     int i = 0, L = i + 1, R = nums.length - 1;
11     while(i<nums.length && nums[i]<=0) {
12         while(L<R) {
13             int sum = nums[i]+nums[L]+nums[R];
14             if(sum==0) {
15                 sumlist.add(new ArrayList<Integer> (Arrays.asList(nums[i],nums[L],nums[R])));
16                 while(L<R && nums[L]==nums[L+1]) L++;//in case the left element is repeat
17                 while(L<R && nums[R]==nums[R-1]) R--;
18                 L++;R--;
19             }
20             else if(sum<0){//means the left element is bigger
21                 L++;
22             }
23             else if(sum>0) {//means the right element is bigger
24                 R--;
25             }
26         }
27         while(i+1<nums.length && nums[i+1]==nums[i]){//in case it is repeat,
28             i++;
29         }
30         i++;//make the iteration run
31         L=i+1;
32         R=nums.length-1;
33     }
34     return sumlist;
35 }

一开始我没有考虑第二个i++使得循环跑不起来。

后来在循环的判定条件上没有加入i+1<nums.length ,这就使得数组越界。

对于L也不是没有担心。后来发现,直接在第一层循环处,加入i的循环判定条件就好了。



总结:

这道题给我折腾坏了。

  • 这些内容都不难,只是实现的过程,代码之间的相关关系让人很头疼。所以以后实现的顺序、也就是逻辑一定分清楚。
  • 另外第一次遇到运算问题的麻烦,解法二的完整代码中,第15行、第16行代码,一开始是将L<R的条件放在后面的,所以就总是数组越界,我当时也没太明白,后来才知道交集运算是从左往右算,所以一看到数组越界,程序执行就报错了。
  • 最后第27行到第30行的代码,我鼓捣了得有半个小时,就是总越界?后来冷静想想,这玩意儿有啥呀,很简单。就是我对每个步骤的意义没有明确。比如说好几次都是删除了第30行代码,可是这个就是防止上面循环跑不动了才存在的。循环部分也是不断鼓捣,有一些人用的判定条件是$n u m s[i]==n u m s[i-1]$,为了达到这个目的,我就用了do-while语句,一通折腾、导致边界条件非常混乱。其实我觉得几种语句都是类似的,无非是执行的逻辑不一样,所以自己撸代码的时候还是应该就自己的情况,明确自己的边界条件。

原文地址:https://www.cnblogs.com/RicardoIsLearning/p/12028069.html

时间: 2024-10-03 10:11:28

【算法练习题】力扣练习题——数组(2):三数之和的相关文章

力扣 ——3Sum python (三数之和)实现

题目描述: 中文: 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 英文: Given an array nums of n integers, are there elements a, b, c in nums such that a + b + c = 0? Find all unique triplets in the array which gives the sum o

LeetCode第十六题-找出数组中三数之和最接近目标值的答案

3Sum Closest 问题简介: 给定n个整数的数组nums和整数目标,在nums中找到三个整数,使得总和最接近目标,返回三个整数的总和,可以假设每个输入都只有一个解决方案 举例: 给定数组:nums=[-1, 2, 1, -4], 目标值:target = 1. 最接近目标值的答案是2 (-1 + 2 + 1 = 2). 解法一: 与上一道题类似,这次要求的是三数之和与目标值的差值最小值,可以定义一个变量来记录这个差值 思路就是想先定义一个最接近的值默认取前三个数的合,然后将数组排序后 小

力扣 —— Two Sum ( 两数之和) python实现

题目描述: 中文: 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标. 你可以假设每种输入只会对应一个答案.但是,你不能重复利用这个数组中同样的元素. 英文: Given an array of integers, return indices of the two numbers such that they add up to a specific target. You may assume that each in

LeetCode-3SUM(数组中三数之和为0)

Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. Note: Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c) The solut

力扣(LeetCode)平方数之和 个人题解

给定一个非负整数 c ,你要判断是否存在两个整数 a 和 b,使得 a2 + b2 = c. 示例1: 输入: 5 输出: True 解释: 1 * 1 + 2 * 2 = 5 示例2: 输入: 3 输出: False 在这题里面,可以使用二分查找来缩小搜索的范围 由数学定理(我忘了具体的哪个定义)可知,a和b的具体取值范围落在0到根号c之间.然后简单运用二分法就能十分便捷找到答案了. 代码如下: class Solution { public: bool judgeSquareSum(int

LeeCode数组第15题三数之和

题目:三数之和 内容: 给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为: [ [-1, 0, 1], [-1, -1, 2] ] 思路:题目实现可分为两个步骤,分别是(1)寻找三个满足条件的元素(2)去重复对于第一个小问题,首先考虑三个for循

LeetCode15. 三数之和

给定一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?找出所有满足条件且不重复的三元组. 注意:答案中不可以包含重复的三元组. 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4], 满足要求的三元组集合为:[ [-1, 0, 1], [-1, -1, 2]] 思路说明: 第一步,将数组排序,C++可以使用 Sort()函数,但是注意不能直接传入vector,由于Vector是容器,需要传入begin

lintcode 中等题: 3 Sum II 三数之和II

题目 三数之和 II 给一个包含n个整数的数组S, 找到和与给定整数target最接近的三元组,返回这三个数的和. 样例 例如S = [-1, 2, 1, -4] and target = 1.  和最接近1的三元组是 -1 + 2 + 1 = 2. 注意 只需要返回三元组之和,无需返回三元组本身 解题 和上一题差不多,程序也只是稍微修改了 public class Solution { /** * @param numbers: Give an array numbers of n integ

[LeetCode] 3Sum Closest 最近三数之和

Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You may assume that each input would have exactly one solution. For example, given array S = {-1 2

找出数组中两数之和为指定值的所有整数对

一,问题描述 给定一个整型数组(数组中的元素可重复),以及一个指定的值.打印出数组中两数之和为指定值的 所有整数对 二,算法分析 一共有两种方法来求解.方法一借助排序,方法二采用HashSet 方法一: 先将整型数组排序,排序之后定义两个指针left和right.left指向已排序数组中的第一个元素,right指向已排序数组中的最后一个元素 将 arr[left]+arr[right]与 给定的元素比较,若前者大,right--:若前者小,left++:若相等,则找到了一对整数之和为指定值的元素