LeetCode题解:三数之和

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode题解:三数之和相关的知识,希望对你有一定的参考价值。

参考技术A

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

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]

我们其实可以将这道题转化为LeetCode两数之和那道题,具体做法如下:
前提条件,我们需要将数组排序。
首先,外层遍历,作为第一个数first,并且将目标数target设置为-nums[first]。
接下来,我们只需要两个双指针second与third,分别指向first+1与最后一个数,两个指针随着遍历向中靠拢。如果nums[second]+nums[third]>target,那么third就左移,否则,second就左移。而second是随着内层遍历而增加的。
因为我们事先将数组进行了排序,所以当内层循环达到second=third时依然找不到答案,那么就跳过内层循环,直接遍历下一个first。

复杂度分析

leetcode 18. 四数之和

在这里插入图片描述


四数之和题解集合


排序+双指针

建议大家先从三数之和的题解集合看起来,本方法题解基本是复制三数之和的双指针方法

思路:

  • 两数之和呢,我们就先固定第一个数,然后移动指针去找第二个符合的,三数之和,固定一个数,双指针去找符合情况的其他两位数,那么我们四数之和,也可以先固定两个数,然后利用双指针去找另外两位数。所以我们来搞定他吧。
  • 三数之和是,我们首先确定一个数,然后利用双指针去找另外的两个数,我们在这个题目里面的解题思路是需要首先确定两个数然后利用双指针去找另外两个数,和三数之和思路基本一致很容易理解。我们具体思路可以参考下图。
  • 这里需要注意的是,我们的 target 不再和三数之和一样为 0 ,target 是不固定的,所以解题思路不可以完全照搬上面的题目。另外这里也需要考虑去重的情况,思路和上题一致。
    在这里插入图片描述
  • 上图则为我们查找到一个符合条件的四元组的情况,查找成功之后,下一步移动蓝色指针,重新定义绿蓝指针,继续执行上面步骤。
    在这里插入图片描述

代码:

class Solution {
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) 
	{
		//获取当前数组的长度
		int size = nums.size();
		//构造存储所有结果的数组
		vector<vector<int>> ret;
		//如果当前数组长度小于4,直接返回空容器
		if (size < 4) return ret;
		//给数组排序
		sort(nums.begin(),nums.end());
		for (int i = 0; i < size-3; i++)
		{
			if (i > 0 && nums[i] == nums[i - 1]) continue;//第一个固定指针去重
			for (int j = i + 1; j < size-2; j++)
			{
				if (j > i+1 && nums[j] == nums[j - 1]) continue;//第二个指针固定去重
				int p = j + 1, q = size - 1;//前后两个指针
				while (p < q)
				{
					int sum = nums[i] + nums[j] + nums[p] + nums[q];
					if (sum < target)//和小了,为了增大和,前指针往后移动
						while (p < q && nums[p] == nums[++p]);//如果前指针后移出现重复数字就一直后移,直到没有出现重复元素为止,否则只移动一次
					else if (sum > target)//和大了,后指针前移
						while (p < q && nums[q] == nums[--q]);//同上
					else
					{
						ret.push_back({ nums[i],nums[j],nums[p],nums[q] });
						//前指针后移,后指针前移继续寻找当前固定k值下的可能解
				      //同上,没有出现重复解,移动一次
						while (p < q && nums[p] == nums[++p]);
						while (p < q && nums[q] == nums[--q]);
					}
				}
			}
		}
		return ret;
	}
};

在这里插入图片描述


回溯法

  • 首先将数组 nums升序排序,并把答案四元组中没确定的个数设为 n

  • 我把剪枝分为了 4 类,括号内的是用什么完成剪枝

    1.如果剩余可选的数字数量少于 n,则剪掉(递归返回);

    2.每层递归的 for 循环中,从第二轮循环开始,如果数组中当前数字和前一个相同,则剪掉(进行下一次循环,这条的任务就是去重);

    3.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组中当前数字的下一个数字 > target则说明后面的数无论怎么选,加起来都一定大于 target,所以剪掉(递归返回)

    4.如果 当前数字 + 已确定数字的和 + (n - 1) * 排序后数组最后一个数字 < target则说明后面的数无论怎么选,加起来都一定小于 target,所以剪掉(进行下一次循环)

这里解释一下第 第3和第4 条

由于我们已经对数组进行了升序排序,所以右边数字大于等于左边数字;另外,我们找数是从数组中的某一点开始往右找,不会往左找,起点的位置也是依次往右走的。以上两条是大前提。

  • 对于第 3 条,假设要找的四个数都还没确定,现在想要把脚标为i的数字(记为 nums[i])加入答案中。为了避免无用功,在加入之前先瞅一眼,如果 nums[i] 加上它右边数字的三倍之后大于目标值,说明就算后面所有数字都相等,也不可能在 nums[i] 的右边找到另外三个数加上 nums[i]的和等于目标值而且如果进行下一轮循环让 i往右移动,由于数组递增,就更不可能找到四个数加起来等于目标值了,所以直接递归返回,而不是进行下一轮循环

  • 对于第 4 条,依然假设要找的四个数都还没确定,现在想要把脚标为 i 的数字(记为 nums[i])加入答案中。加入之前也要先瞅一眼,如果 nums[i] 加上数组最后一个数字(也就是数组中最大的那个)的三倍之后仍小于目标值,说明就算后面所有数字都相等,都是最大值,也不可能在 nums[i]的右边找到另外三个数加上 nums[i]的和等于目标值但是与上面不同的是,由于数组递增,进行下一轮循环后 nums[i]会变大,整体的和也会变大,这样就有可能找到四个数加起来等于目标值了,所以是进行下一轮循环而不是递归返回

  • 上面说的都是基于没有已确定数字,如果有已确定数字那么可以依此类推,将倍数改成两倍、一倍就行了。

代码:

class Solution {
	vector<vector<int>> ret;
	vector<int> mynums,subans;
	int tar, numSize;//tar:目标值  numSize:数组大小
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) 
	{
        sort(nums.begin(), nums.end());
		//获取当前数组的长度
		numSize = nums.size();
		mynums = nums;
		tar = target;
		if (numSize < 4) return ret;
		DFS(0,0);
		return ret;
	}
	void DFS(int low, int sum)//low: 起始遍历的下标  sum:当前需要凑出的目标值
	{
		//找到一个解
		if (sum == tar && subans.size() == 4)
		{
			ret.push_back(subans);
			return;
		}
		//寻找可能解
		for (int i = low; i < numSize; i++)
		{
			//剪枝:当前需要找的剩余元素个数比剩余元素多,那么说明无解,返回
			if (numSize - i <4 - subans.size()) return;
			if (i > low && mynums[i] == mynums[i - 1]) continue;//去重
			//第三种情况的剪枝
			if (i<numSize - 1 && sum + mynums[i] + mynums[i + 1] * (int)(3- subans.size())>tar) return;
			//第四种情况的剪枝
			if (i < numSize - 1 && sum + mynums[i] + mynums[numSize-1] * (int)(3 - subans.size()) < tar) continue;
			//确定一个位置
			subans.push_back(mynums[i]);
			//去确定下一个位置
			DFS(i + 1, sum + mynums[i]);
			//回溯,寻找其他解
			subans.pop_back();
		}
	}
};

在这里插入图片描述


哈希法

建议先去看三数之和哈希法,稍有不同,这里先确定三个数,再确定最后一个数,即增加一个循环

代码:

class Solution {
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) 
	{
		int size = nums.size();
		vector<vector<int>> ret;
		if (size < 4) return ret;
		sort(nums.begin(), nums.end());
		map<int, int> m;
		for (int i = 0; i < size; i++)
			m[nums[i]] = i;
		for (int i = 0; i < size - 3; i++)
		{
			if (i > 0 && nums[i] == nums[i-1]) continue;
			for (int j = i + 1; j < size - 2; j++)
			{
				if (j > i+1 && nums[j] == nums[j-1]) continue;
				for (int k = j + 1; k < size - 1; k++)
				{
					if (k > j+1 && nums[k] == nums[k-1]) continue;
					int c = target - nums[i] - nums[j] - nums[k];
					//当前数组中存在元素d,并且元素d下标大于元素c的下标
					if (m.find(c) != m.end())
					{
						if (m[c] > k)
							ret.push_back({ nums[i],nums[j],nums[k],c });
						else//首先当前已经不存在满足条件的元素d了,在往数组后面寻找元素c,c越大,需要的d越小,更不可能满足条件
							break;
					}

				}
			}
		}
        return ret;
	}
};

在这里插入图片描述


总结

之前的三数之和题解中没有补充DFS解法,这里建议大家看完之后,自己尝试去补充三数之和的DFS解法

以上是关于LeetCode题解:三数之和的主要内容,如果未能解决你的问题,请参考以下文章

leetcode 18. 四数之和

精选力扣500题 第9题 LeetCode 15. 三数之和 c++详细题解

leetcode刷题21.三数之和——Java版

LeetCode15_三数之和

leetcode 15. 三数之和

算法leetcode|15. 三数之和(rust重拳出击)