代码随想录算法训练营Day7 | 454.四数相加II、383.赎金信、15.三数之和、18.四数之和

454.四数相加II

最简单的思路肯定是暴力循环。除了暴力循环外,最开始的思路是能否嵌套map一层层地查询,这样每个数组都只需遍历一遍,时间复杂度是O(n)。但是继续想发现除了最后一层,其他层的查询条件全都未知,只能放弃了这个想法。

之后又想能不能对四个数组进行分组,12一组34一组,分别嵌套循环,这样时间复杂度是n^2 + n^2即O(n^2)。好像可行就写了下去,想的是使用set实现,但写着写着发现由于set去重的特性,如果有多个成功匹配值结果只能+1。

看了看解析是使用map,将出现次数放在val中,妙啊。

思路:将四个数组分为两组,使用map记录nums1和nums2的和(key),以及该和出现的次数(val)。

// 使用map记录nums1+nums2,使用nums3+nums4对map进行查询
int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4) {
	unordered_map record12;
	int ans = 0;

	for (int i1 : nums1) {
		for (int i2 : nums2) {
			auto iter = record12.find(i1 + i2);
			if (iter == record12.end())
                // 两数的和作为key,其出现的次数作为val存入map
				record12.insert({ i1 + i2, 1 });
			else
				++(iter->second);
			iter = record12.find(i1 + i2);
		}
	}
	for (int i3 : nums3) {
		for (int i4 : nums4) {
			auto iter = record12.find(-(i3 + i4));
			if (iter != record12.end() && iter->second > 0) {
                // // 一个解的所有实现方法全部加上
				ans += iter->second;
			}
		}
	}
	return ans;
}

383.赎金信

和242.有效的字母异位词几乎完全一样(冒充新题)。

不同点就是两个字符串不需要组成元素完全相同,只需要magazine能够完全包含ransomNote。

bool canConstruct(string ransomNote, string magazine) {
    vector ans(26, 0);
    for (char c : magazine)
        ++ans[c - 'a'];
    for (char c : ransomNote)
        --ans[c - 'a'];
    // 和242相比就返回条件稍作修改
    for (int i : ans)
        if (i < 0)
            return false;
    return true;
}

15.三数之和

(太难了呀)

重点应该是如何去重

初步想法是和454.四数相加一样先筛选出符合条件的解,对于每一个解再像242异位词那样判断是否与之前的解是重复解。结果查询两分钟去重两小时,写了一个多小时写了坨运行不起来的答辩,哈哈:D。

思路:

1、由于不要求返回下标,所以可以先对数组进行排序:排序不仅利于搜索元素,也大幅节省了去重的工作量(相同的数会聚集在一起)

2、使用双指针法。固定一个元素,在该元素之后与数组尾端各置一个指针,移动两个指针逐步判断与该固定元素的三数之和是否为0。(双指针思路有点类似209.长度最短的子数组,大了就缩右端,小了就缩左端)


去重思路重点:

1、剪枝条件是nums[i] == nums[i+1]还是nums[i] == num[i-1]?

        nums[i] == nums[i-1]表示如果当前值在之前已经使用过了则跳过,逻辑正确

        nums[i] == nums[i+1]会影响到left指针的判断(left的初始值为i+1),逻辑错误

2、双指针如何去重?何时去重?

        先记录结果再去重,先去重可能会导致诸如{0, 0, 0, 0}这样的数组丢失可行解(left和right都移动完了还没有完成去重)。

vector> threeSum(vector& nums) {
	vector> ans;
	int left, right;

	std::sort(nums.begin(), nums.end());
	for (int i = 0; i < nums.size(); ++i) {
        // nums[i]>0则之后不管怎么加都是>0,所以剪枝
		if (nums[i] > 0)
			return ans;
		// 对i进行去重
		if (i > 0 && nums[i] == nums[i - 1])
			continue;
		left = i + 1;
		right = nums.size() - 1;
		while (left < right) {
			if (nums[i] + nums[left] + nums[right] > 0)
				--right;
			else if (nums[i] + nums[left] + nums[right] < 0)
				++left;
			else {
				ans.push_back({ nums[i], nums[left], nums[right] });
				// 对left和right进行去重
				while (left < right && nums[left + 1] == nums[left])
					++left;
				while (left < right && nums[right - 1] == nums[right])
					--right;
				// 找到解时,left和right同时收缩
				++left;
				--right;
			}
		}
	}
	return ans;
}

18.四数之和

思路与三数之和相同,不过变为固定两个数。和454相比难点应该在去重上。

细节有点多,提交了好几次才AC。尤其注意目标值变为了一个可变的target,target可能为负,所以剪枝的条件与三数之和有所不同。

vector> fourSum(vector& nums, int target) {
	vector> ans;
	int left, right;

	std::sort(nums.begin(), nums.end());
	for (int i = 0; i < nums.size(); ++i) {
		// 注意这里nums[i]还应>=0才进行剪枝(target可能为负数),下面同理
		if (nums[i] > target && nums[i] >= 0) {
			break;
		}
		if (i > 0 && nums[i] == nums[i - 1])
			continue;
		for (int j = i + 1; j < nums.size(); ++j) {
			if (nums[i] + nums[j] > target && nums[i] + nums[j] >= 0) {
				// 注意这里应该为break,不能为return
				break;
			}
			if (j > i + 1 && nums[j] == nums[j - 1])
				continue;
			
			left = j + 1;
			right = nums.size() - 1;
			while (left < right) {
				if ((long)nums[i] + nums[j] + nums[left] + nums[right] > target)
					--right;
				else if ((long)nums[i] + nums[j] + nums[left] + nums[right] < target)
					++left;
				else {
					ans.push_back({ nums[i], nums[j], nums[left], nums[right] });
					while (left < right && nums[left + 1] == nums[left])
						++left;
					while (left < right && nums[right - 1] == nums[right])
						--right;
					++left;
					--right;
				}
			}
		}
	}
	return ans;
}

哈希表总结

(哈希表好难啊)

使用重点:

1、何时使用哈希表?

        · 根据某种条件进行查询(如异位词查找字母、四数相加查找和为0)

        · 有些时候不使用哈希表反而更好,判断何时不用哈希表感觉也是个大难点。

2、使用哈希表的话使用数组、set还是map?

        · 查询的大小可确定,且下标不会过于分散(如0, 1, 2, 100000)时,使用数组

        · 查询的大小未知,只查找是否存在时,使用set

        · 查询的大小未知,除了判断是否存在还需要存储其他信息时,使用map

3、哈希表的查询条件(key)是什么?

        · 结合题目判断

你可能感兴趣的:(算法)