leetcode #15 三数之和 | 刷题之路第一站——数组类相关问题

题号 15

题目描述

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

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

示例:

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

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

链接:leetcode #15 三数之和

注意:以下三种解题思路,只有第三种AC了,可以直接查看第三种。

解题思路1【暴力法】:

最直观的想法就是采用三层for循环,依次遍历所有的三数之和,将符合条件的三元组存放于结果集合中,注意不要存入重复的三元组。
时间复杂度分析: 时间复杂度为O(n^3)
注意: 该方法超出时间限制,未AC。

代码实现:

class Solution {
public:
	vector<vector<int>> threeSum(vector<int>& nums) {
		sort(nums.begin(), nums.end());    //将给定数组nums进行排序
		vector<vector<int>> ans;    //ans是存放和为0、且不重复的三元组的集合
		vector<int> med;
		for (int i = 0; i < nums.size(); i++)    //三重for循环
			for (int j = i + 1; j < nums.size(); j++)
				for (int k = j + 1; k < nums.size(); k++)
				{
					if (nums[i] + nums[j] + nums[k] == 0)    //如果三者和为0,并且结果集合中不包含三者的任意组合,则将该三元组存入结果结合ans中
					{
						med.push_back(nums[i]);
						med.push_back(nums[j]);
						med.push_back(nums[k]);
						if (!count(ans.begin(), ans.end(), med))    //判断结果集合中是否已经存在该三元组
						{
							ans.push_back(med);
						}
						med.clear();
					}
				}
		return ans;
	}
};

备注:一开始先对给定数组进行排序,是为了保证重复三元组中的三个数是按照相同的顺序重复的,这样才可以通过count()函数判定结果集合中是否已经存在与之重复的三元组。
这样解决太生硬了,而且时间复杂度是n^3级别,肯定是超时的。

解题思路2【哈希表】:

参考 leetcode # 15 两数之和 | 刷题之路第一站——数组类相关问题 中的哈希表方法进行解决。
在【两数之和】问题中,我们是计算target和数组中每一个元素的差值,如果该差值在哈希表中,则该元素与哈希表中的这个差值相加就是target值,这两个元素就是我们所要寻找的元素;如果该差值不再哈希表中,则将数组中的该元素值作为key,该元素在数组中的位置存放在哈希表中。

而对于本题来说,是对于给定数组中每一个元素 i ,从该数组中剩余元素中找出两个元素 m、n,使得这三个元素的和为0,则就相当于
nums[i] + nums[m] + nums[n] =0
nums[m] + nums[n] = -num[i]
则是说:两数之和中的target为 -nums[i]。
找到这样的三个元素后,判断该三元组的任意组合是否存在于结果集合中,如果不存在,则将其存放于结果集合中。

时间复杂度分析:O(n^2)
注意: 该方法超出时间限制,未AC。

代码实现:

class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {    //对【两数之和】算法的修改,返回的结果集合中,元素1和元素2之和为target,元素3和元素4之和为target,依次类推。
		map<int, int> m;
		vector<int> ans;
		for (int i = 0; i < nums.size(); i++)
		{
			if (m.count(target - nums[i]))    
			{
				ans.push_back(target - nums[i]);
				ans.push_back(nums[i]);
			}
			m[nums[i]] = i;    
		}
		return ans;
	}

	vector<vector<int>> threeSum(vector<int>& nums) {
		if (nums.size() < 3)    
		{
			return {};
		}
		vector<vector<int>> ans;
		vector<int> med;
		vector<int> med2;
		vector<int> nums2;

		for (int i = 0; i < nums.size(); i++)
		{
			med.clear();
			nums2.clear();
			for (int j = 0; j < nums.size(); j++)
			{
				if (j != i)
				{
					nums2.push_back(nums[j]);
				}
			}
			med = twoSum(nums2, -nums[i]);
			if (med.size() != 0)
			{
				int a = med.size() , b = 0;
				while (b!=a)
				{
					med2.push_back(med[b]);
					b++;
					med2.push_back(med[b]);
					b++;

					med2.push_back(nums[i]);
					sort(med2.begin(), med2.end());
					if (!count(ans.begin(), ans.end(), med2))
					{
						ans.push_back(med2);
					}
					med2.clear();
				}
			}
		}
		return ans;
	}
};

备注:代码写的比较乱,并且空间复杂度比较大,借助了三个一维vector和一个二维vector 。由于未AC,也就未进行代码的改进和优化。

解题思路3【双指针】:

双指针方法是对三重循环暴力法的改进,排除了大量的无用循环和判断。

1 - 对给定的数组nums进行排序,排序是为了更好地排除无用循环;

2 - 如果nums数组中存放元素的个数小于3,则直接返回空值,问题结束;否则进入步骤3

3 - 对于nums数组中的每一个元素nums[i]:
首先进行设定两个指针:指针L = i + 1,指针R = n - 1
当 L < R 时,进行如下的循环:
(1)首先如果 i >0 并且 i 所指元素与 i 的前一个元素相同,则跳出本次循环,直接将 i 指向 i 的下一个元素,无需进行步骤(2)-()
【因为符合条件的三元组在上一次循环中已经存放在结果集合中,如果再进行循环,则重复。】
(2)如果nums[i] + nums[L] + nums[R] < 0,显然需要增大才能使其等于0,所以我们将L向后移动一个元素;
(3)如果nums[i] + nums[L] + nums[R] > 0,显然需要减小才能使其等于0,所以我们将R向前移动一个元素;
(4)如果nums[i] + nums[L] +nums[R] =0
____1… 先将该三元素存放于结果数组中;
____2… 判断L与L的下一个元素是否相等,若相等,则将L++,直至L>=R;
____3… 判断R与R的前一个元素是否相等,若相等,则将R–,直至L>=R;
【步骤2… 和3… 的目的也是避免结果集合中出现重复的三元组,如果L和R临近的元素与其相等,则表示符合条件的三元组已经存放在结果集合中,无需再存入】
_____4… 将L向后移动一个元素,同时将R向前移动一个元素。
【为什么同时移动?因为如果移动一个,势必会将三者之和从0变小或者变大,一定不符合条件。】

代码实现:

class Solution {
public:
	vector<vector<int>> threeSum(vector<int>& nums) {
		vector<int> med;
		vector<vector<int>> ans;    //ans为存放符合条件的三元组的结果集合
		sort(nums.begin(), nums.end());    //首先对给定数组nums进行排序
		if (nums.size() < 3)    //如果nums中元素个数少于3个,则直接返回空
		{
			return {};
		}
		for (int i = 0; i < nums.size(); i++)    //对于nums中的每一个元素,在其后的元素中寻找符合条件的两个元素,使三者之和为0
		{
			if (nums[i] > 0)    //如果nums[i] > 0, 表明其及其后的元素均大于0,则跳出整个循环,没有循环的必要了。
			{
				break;
			}
			if (i > 0 && nums[i] == nums[i - 1])    //如果i所指元素与其前一个元素相同,则跳出本层循环,进入下层循环
			{
				continue;
			}
			int L = i + 1, R = nums.size() - 1;    //设立两个指针L和R,分别指向i的下一个元素和数组中最后一个元素
			while (L < R)    
			{
				int sum = nums[i] + nums[L] + nums[R];    //根据两个元素之和,进行指针的移动
				if (sum < 0)    //如果和小于0,则需要将三者之和变大,nums[i]是固定的,无法改变,则只好将指针L右移,指向更大的元素
				{
					L++;
				}
				else if (sum > 0)    //同理如果和大于0,则需要将三者之和变小,将指针R左移
				{
					R--;
				}
				else if (sum == 0)    //如果三者之和等于0,则将该三元组存入结果集合中
				{
					med.clear();
					med.push_back(nums[i]);
					med.push_back(nums[L]);
					med.push_back(nums[R]);
					ans.push_back(med);
					while (L < R&&nums[L] == nums[L + 1])    //判断L的下一个元素是否与L所指元素相等,若相等,则将L指向L的下一个元素
					{
						L++;
					}
					while (L < R &&nums[R] == nums[R - 1])    //判断R的前一个元素是否与R所指元素相等,若相等,则将R指向R的前一个元素
					{
						R--;
					}
					L++;    //将L右移
					R--;    //同时将R左移
				}
			}

		}
		return ans;
	}
};

代码运行评判:
leetcode #15 三数之和 | 刷题之路第一站——数组类相关问题_第1张图片
代码改进:

class Solution {
public:
	vector<vector<int>> threeSum(vector<int>& nums) {
		vector<vector<int>> ans;
		sort(nums.begin(), nums.end());
		if (nums.size() < 3)
			return ans;
		for (int i = 0; i < nums.size(); i++)
		{
			if (nums[i] > 0)
				break;
			if (i > 0 && nums[i] == nums[i - 1])
				continue;
			int L = i + 1, R = nums.size() - 1;
			while (L < R)
			{
				int sum = nums[i] + nums[L] + nums[R];
				if (sum < 0)
					++L;
				else if (sum > 0)
					--R;
				else if (sum == 0)
				{
					ans.push_back({ nums[i],nums[L],nums[R] });
					while (L < R&&nums[L] == nums[L + 1])
						++L;
					while (L < R &&nums[R] == nums[R - 1])
						--R;
					++L;
					--R;
				}
			}
		}
		return ans;
	}
};

你可能感兴趣的:(算法设计与分析)