day07:哈希表- 454.四数相加II、383. 赎金信、15. 三数之和、18. 四数之和
LeetCode 454.四数相加II
题目链接:
文章讲解:
https://programmercarl.com/0454.%E5%9B%9B%E6%95%B0%E7%9B%B8%E5%8A%A0II.html
视频讲解:
https://www.bilibili.com/video/BV1Md4y1Q7Yh/
思路和解法:
哈希表:
如果采用暴力解法,时间复杂度为 O ( n 4 ) O(n^4) O(n4) ,效率很低,所以考虑其他方法。
题目说的是四个独立数组,所以只要找到nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0就可以,不用考虑有重复的四个元素相加等于0的情况。
1、首先定义一个Dictionary,key存放nums1、num2两个数组中所有和的情况,对应的value是和出现的次数。
2、开始遍历nums1和nums2,统计两个数组元素之和以及和出现的次数,放到Dictionary中。
3、定义一个int变量res存储nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0出现的次数。
4、再遍历nums3和nums4,如果在Dictionary中找到了0-(nums3[k] + nums4[l]),就用res把Dictionary中key对应的value加上。
之所以要在Dictionary中找0-(nums3[k] + nums4[l]),是因为题目中要求找nums1[i] + nums2[j] + nums3[k] + nums4[l] = 0的情况,Dictionary中已经存放了nums1[i] + nums2[j],nums1[i] + nums2[j] = 0 - (nums3[k] + nums4[l]),所以只有在Dictionary中找到符合**0-(nums3[k] + nums4[l])**的数,才能符合条件。
5、最后返回res
public class Solution {
public int FourSumCount(int[] nums1, int[] nums2, int[] nums3, int[] nums4) {
Dictionary<int, int> dict = new Dictionary<int, int>();
foreach(int i in nums1)
{
foreach(int j in nums2)
{
int sum = i + j;
if(dict.ContainsKey(sum))
{
dict[sum]++;
}
else
{
dict.Add(sum,1);
}
}
}
int res = 0;
foreach(int k in nums3)
{
foreach(int l in nums4)
{
int sum = k + l;
if(dict.TryGetValue(-sum, out int result))
{
res += result;
}
}
}
return res;
}
}
LeetCode 383. 赎金信
题目链接:
文章讲解:
https://programmercarl.com/0383.%E8%B5%8E%E9%87%91%E4%BF%A1.html
思路和解法:
哈希表
这题是242.有效的字母异位词 的变种,可以采用相同思路,同样使用哈希表来做。
1、题目中明确了ransomNote
(以下采用A) 和 magazine
(以下采用B) 由小写英文字母组成,且当A能用B里面的字符组成时才返回true,典型的在B中查找所需元素的问题,而且又因为明确小写字母构成,数据数量固定,所以采用数组,数组存放的是下标对应的字母出现的次数,比如a对应下标0,b对应1…z对应25等。数组初始化为0。
2、先遍历B中的字母,每个字母出现一次,字母对应下标的对应数据+1。
3、再遍历A中的字母,每个字母出现一次,字母对应下标的对应数据-1。
4、遍历结束后,因为是要判断A能不能由B里面的数字组成,所以只要判断数组中有没有数据小于0就可以,如果都大于或等于0,说明B中的字母可以组成A,返回true,反之返回false。
public class Solution {
public bool CanConstruct(string ransomNote, string magazine) {
int rl = ransomNote.Length;
int ml = magazine.Length;
if(rl > ml)
return false;
int[] a = new int[26];
for(int i = 0; i < ml; i++)
{
a[magazine[i] - 'a']++;
}
for(int j = 0; j < rl; j++)
{
a[ransomNote[j] - 'a']--;
}
foreach(var i in a)
{
if(i < 0)
return false;
}
return true;
}
}
LeetCode 15. 三数之和
题目链接:
文章讲解:
https://programmercarl.com/0015.%E4%B8%89%E6%95%B0%E4%B9%8B%E5%92%8C.html
视频讲解:
https://www.bilibili.com/video/BV1GW4y127qo/
思路和解法:
哈希表:
两层for循环确定a和b的数值,然后用哈希法来确定0-(a+b) 是否在数组里出现过,但问题就在题目要求不能有重复值,就是要去重,有很多细节要注意,很麻烦。所以考虑其他方法。
双指针:
1、以这个nums数组来举例,首先将数组排序,然后有一层for循环,i从下标0的地方开始,同时定一个下标left 定义在i+1的位置上,定义下标right 在数组结尾的位置上。
2、在数组中找到 abc 使得a + b +c =0,这里相当于 a = nums[i],b = nums[left],c = nums[right]。
3、移动left和right:
如果nums[i] + nums[left] + nums[right] > 0 就说明 此时三数之和大了,因为数组是排序后了,所以right下标就应该向左移动,这样才能让三数之和小一些。
如果 nums[i] + nums[left] + nums[right] < 0 说明 此时 三数之和小了,left 就向右移动,才能让三数之和大一些,直到left与right相遇为止。
4、去重是重点,因为比如{-1, -1 ,2} 这组数据,当遍历到第一个-1 的时候,判断 下一个也是-1,那这组数据就pass了。所以我们要做的是 不能有重复的三元组,但三元组内的元素是可以重复的!
public class Solution {
public IList<IList<int>> ThreeSum(int[] nums) {
var result = new List<IList<int>>();
Array.Sort(nums);
for(int i = 0; i < nums.Length - 2; i++)
{
int a = nums[i];
//因为已经对数组进行了排序,如果第一个数就大于0,那么后续的数都大于0,绝对没有符合条件的数
if(a > 0)
break;
//去重 a
if(i > 0 && a == nums[i - 1])
continue;
int left = i + 1;
int right = nums.Length - 1;
while(left < right)
{
int b = nums[left];
int c = nums[right];
int sum = a + b + c;
if(sum > 0)
{
right--;
}
else if(sum < 0)
{
left++;
}
else
{
result.Add(new List<int> {a, b, c});
//找到答案后,对left和right进行收缩,且收缩的时候要对bc进行去重
while(left < right && nums[left] == b)
{
left++;
}
while(left < right && nums[right] == c)
{
right--;
}
}
}
}
return result;
}
}
LeetCode 18. 四数之和
题目链接:
文章讲解:
https://programmercarl.com/0018.%E5%9B%9B%E6%95%B0%E4%B9%8B%E5%92%8C.html
视频讲解:
https://www.bilibili.com/video/BV1DS4y147US/
思路和解法:
双指针:
和三数之和类似,也采用双指针的方法。
四数之和的双指针解法是两层for循环nums[k] + nums[i]为确定值,依然是循环内有left和right下标作为双指针,找出nums[k] + nums[i] + nums[left] + nums[right] == target的情况,三数之和的时间复杂度是 O ( n 2 ) O(n^2) O(n2),四数之和的时间复杂度是 O ( n 3 ) O(n^3) O(n3) 。
public class Solution {
public IList<IList<int>> FourSum(int[] nums, int target) {
var result = new List<IList<int>>();
Array.Sort(nums);
for(int i = 0; i < nums.Length - 3; i++)
{
int a = nums[i];
if(a > 0 && a > target)
break;
//去重a
if(i > 0 && a == nums[i - 1])
continue;
for(int j = i + 1; j < nums.Length - 2; j++)
{
int b = nums[j];
//去重b
if(j > i + 1 && b == nums[j - 1])
continue;
int left = j + 1;
int right = nums.Length - 1;
while(left < right)
{
int c = nums[left];
int d = nums[right];
int sum = a + b + c + d;
if(sum > target)
{
right--;
}
else if(sum < target)
{
left++;
}
else
{
result.Add(new List<int>{a, b, c, d});
while(left < right && nums[left] == c)
{
left++;
}
while(left < right && nums[right] == d)
{
right--;
}
}
}
}
}
return result;
}
}