哈希表
454. 四数相加 II
题意:给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:
- 0 <= i, j, k, l < n
- nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
示例:
思路:本题我自己的思路就是暴力求解,给出的数组个数有限,那么我们就用四个for循环求出每种排列组合,然后在等于0时++count,由于时间复杂度较高的原因,这样我想是通过不了的,因此也就没有写代码了;还有一种方式是使用unordered_map求解:我们可以将数组分成两大组,例如1和2一组,3和4一组,然后对12组和的所有情况进行出现次数统计,然后再遍历34组和的情况于12组和相加,若相加等于0,则说明该组数据就是元组,count+=12组和出现的次数,这样即排除了组和重复的问题,又大大缩短了时间
C++代码:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
unordered_map<int,int> map;
for(auto m:nums1)//统计出数组1和2出现的次数
{
for(auto n:nums2)
{
map[m+n]++;
}
}
int count=0;
for(auto m:nums3)//这里相当于是让3和4数组的和于1和2数组的和相加
{
for(auto n:nums4)
{
int tmp=-(m+n);
if(map.find(tmp)!=map.end())//若相加等于0,则说明四个数为元组,那么我们就累加1和2相加组合出现的次数
{
count+=map[tmp];
}
}
}
return count;
}
383. 赎金信
题意:给你两个字符串ransomNote和magazine,判断ransomNote能不能由 magazine里面的字符构成。如果可以,返回true;否则返回false。magazine 中的每个字符只能在ransomNote中使用一次。
示例:
思路:本题我的思路是,先创建一个unordered_map作为仓库,用于存储我们可以使用的元素及其次数,也就是遍历magazine元素出现的次数;然后是遍历ransomNote,每构建一个部位就在仓库中减去相应元素的次数;最后我们在遍历整个仓库,看是否由元素次数小于0的情况,若有则ransomNote的构建我们无法满足,返回false;否则输出true
bool canConstruct(string ransomNote, string magazine) {
if(magazine.size()<ransomNote.size())//先简单判断一下,能使用的资源是否小于需要的资源
{
return false;
}
unordered_map<char,int> magaz;
for(auto e:magazine)//统计出我们可以使用的元素和次数,称仓库
{
magaz[e]++;
}
for(auto e:ransomNote)//计数我们使用元素构造完ransomNote后,仓库的情况
{
magaz[e]--;
}
for(auto e:magaz)//遍历仓库,看资源是否使用错误
{
if(e.second<0)
{
return false;
}
}
return true;
}
15. 三数之和
题意:给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请你返回所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组
示例:
思路:本题我最开始的想法是往哈希表上考虑,但是思索了一下,发现哈希表其实并不能很好的解决,因为我们需要将三元组输入arr中,而哈希表只起到存储的作用,并没有标记的作用,因此该题放弃了哈希表的做法。之后我也想到过使用三层for循环暴力求解的方法,然后对三元组排序,在判断arr中是否有相同的三元组,在判断是否需要插入,但很明显,O(n^2)的时间复杂程度无法通过lettcode编译;
其次就是使用双指针的方式,将两次循环遍历变成一次操作,我们先将数组排序,这也是为了之后更好的去重,然后让i作为单独的三元组之一进行遍历数组,然后在i到nums.size()之间定义两个指针,left和right,一个指向开头,一个指向结尾,即一个最小,一个最大,按照滑动窗口的思路:
- nums[i]+nums[left]+nums[right]<0时,表示窗口过小,left++
- nums[i]+nums[left]+nums[right]>0时,表示窗口过大,right--
- nums[i]+nums[left]+nums[right]==0时,输入三元组
这里我们需要注意两点:
- i的去重,当i等于i-1时,跳过本次寻找三元组
- left和right的去重,在我们找到三元组输入后,我们需要再次对left和right进行判断,因为i可能于left和right继续组成三元组,相同的left和right可能导致三元组重复,若left的后一个指针指向的值等于前一个left++,right同理,right--
暴力求解代码:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> arr;
for(int i=0;i<nums.size();i++)
{
for(int j=i+1;j<nums.size();j++)
{
for(int t=j+1;t<nums.size();t++)
{
if(nums[i]+nums[j]+nums[t]==0)
{
vector<int> tmp{nums[i],nums[j],nums[t]};
sort(tmp.begin(),tmp.end());//排序为了更好的去重
if(find(arr.begin(),arr.end(),tmp)==arr.end())//查找有没有相同的三元组
{
arr.push_back(tmp);
}
}
}
}
}
return arr;
}
双指针代码:
vector<vector<int>> threeSum(vector<int>& nums) {
sort(nums.begin(),nums.end());//先排除,这样也是为了更好的去重
vector<vector<int>> arr;
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1])//当i的遍历出现重复,直接跳过
{
continue;
}
int left=i+1,right=nums.size()-1;//双指针,类似滑动窗口
while(left<right)
{
if(nums[i]+nums[left]+nums[right]<0)//三者和小于0,说明和太小,left边界++
{
left++;
}
else if(nums[i]+nums[left]+nums[right]>0)//三者和大于0,说明和太大,right边界--
{
right--;
}
else//三者和相等为三元组
{
vector<int> tmp{nums[i],nums[left],nums[right]};
arr.push_back(tmp);
while(left<right&&nums[left]==nums[left+1])//这里需要二次去重,因为除了i需要去重外,left和right也需要去重
{
left++;
}
while(left<right&&nums[right]==nums[right-1])
{
right--;
}
left++,right--;
}
}
}
return arr;
}
18. 四数之和
题意:给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
示例:
思路:本题思路于上一题非常类似,上一题我们使用双指针的方式,将时间复杂程度降低了一个层级。本题也一样,我们使用双指针将原本的n的四次方变为三次方
双指针代码:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
sort(nums.begin(), nums.end());//排序,为了更好的去重和跳过不必要的过程
vector<vector<int>> arr;
for (int i = 0; i<nums.size(); i++)
{
if (i > 0 && nums[i] == nums[i - 1])//排除i重复的情况
continue;
for (int j = i + 1; j < nums.size(); j++)
{
if(j>i+1&&nums[j]==nums[j-1])//排除j重复的情况
continue;
int left = j + 1, right = nums.size() - 1;
while (left < right)
{
if ((long)nums[i]+nums[j]+nums[left]+nums[right] < target)//这里需要强转,防止越界
left++;
else if ((long)nums[i]+nums[j]+nums[left]+nums[right] > target)
right--;
else
{
arr.push_back(vector<int>{nums[i], nums[j], nums[left], nums[right]});//插入的都是不重复的
while(left<right&&nums[left]==nums[left+1])//排除left和right重复的情况
left++;
while(left<right&&nums[right]==nums[right-1])
right--;
left++, right--;
}
}
}
}
return arr;
}