学习概要:
代码随想录:哈希表
6.四数相加Ⅱ:leetcode454

本题解题步骤:
- 首先定义 一个unordered_map,key放a和b两数之和,value 放a和b两数之和出现的次数。
- 遍历大A和大B数组,统计两个数组元素之和,和出现的次数,放到map中。
- 定义int变量count,用来统计 a+b+c+d = 0 出现的次数。
- 再遍历大C和大D数组,找到如果 0-(c+d) 在map中出现过的话,就用count把map中key对应的value也就是出现次数统计出来。
- 最后返回统计值 count 就可以了
方法一:
这是我自己想的使用两个unordered_map来解决问题,但是呢逐个访问unordered_map的语法我不会,是网上查的
时间复杂度o(n*n)
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int n=nums1.size();
int sum=0;
unordered_map<int,int> sum12;
unordered_map<int,int> sum34;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(sum12.find(nums1[i]+nums2[j])!=sum12.end()){
sum12[nums1[i]+nums2[j]]++;
}else{
sum12[nums1[i]+nums2[j]]=1;
}
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(sum34.find(nums3[i]+nums4[j])!=sum34.end()){
sum34[nums3[i]+nums4[j]]++;
}else{
sum34[nums3[i]+nums4[j]]=1;
}
}
}
for(auto it = sum12.begin();it!=sum12.end();it++)
{
if(sum34.find(-(it->first))!=sum34.end()){
sum+=sum12[it->first]*sum34[-(it->first)];
}
}
return sum;
}
};
方法二:
优化了空间复杂度的常数因子,并且避开了迭代器逐个访问unordered_map里面所有键值对的步骤
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int n=nums1.size();
int sum=0;
unordered_map<int,int> sum12;
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
sum12[nums1[i]+nums2[j]]++;
}
}
for(int i=0;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(sum12.find(-(nums3[i]+nums4[j]))!=sum12.end()){
sum+=sum12[-(nums3[i]+nums4[j])];
}
}
}
return sum;
}
};
7.赎金信leetcode383
方法一:自己想出来的耶耶耶,依旧是使用unordered_map,不过使用的键值对类型是<char,int>,分别统计每个字符出现的个数,最后保证ran中需要的字符小于等于mag中的字符(好像可以优化,我自己再优化看看,见方法二)
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char,int> mag;
unordered_map<char,int> ran;
for(int i=0;i<ransomNote.length();i++)
{
ran[ransomNote[i]]++;
}
for(int i=0;i<magazine.length();i++)
{
mag[magazine[i]]++;
}
for(auto it=ran.begin();it!=ran.end();it++)
{
if(it->second > mag[it->first]){
return false;
}
}
return true;
}
};
方法二:
优化思路同上提一样,减少了一个unordered_map的使用,并且避免了使用迭代器去访问unordered_map的每一个元素。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
unordered_map<char,int> mag;
for(int i=0;i<magazine.length();i++)
{
mag[magazine[i]]++;
}
for(int i = 0;i<ransomNote.length();i++)
{
mag[ransomNote[i]]--;
if(mag[ransomNote[i]]<0){
return false;
}
}
return true;
}
};
方法三:
因为题目说只有小写字母,那可以采用空间换取时间的哈希策略,用一个长度为26的数组来记录magazine里字母出现的次数。
然后再用ransomNote去验证这个数组是否包含了ransomNote所需要的所有字母。
依然是数组在哈希法中的应用。
一些同学可能想,用数组干啥,都用map完事了,其实在本题的情况下,使用map的空间消耗要比数组大一些的,因为map要维护红黑树或者哈希表,而且还要做哈希函数,是费时的!数据量大的话就能体现出来差别了。 所以数组更加简单直接有效!
就是使用数组映射
a->0
b->1
以此类推。
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int hash_map[26]={0};
for(int i=0;i<magazine.length();i++)
{
hash_map[magazine[i]-'a']++;
}
for(int i=0;i<ransomNote.length();i++)
{
hash_map[ransomNote[i]-'a']--;
if(hash_map[ransomNote[i]-'a']<0){
return false;
}
}
return true;
}
};
- 时间复杂度: O(m+n),其中m表示ransomNote的长度,n表示magazine的长度
- 空间复杂度: O(1)
8.三数之和leetcode15
方法一:
是可以用哈希法解决的,具体见代码随想录网站题解和力扣上题解。——我暂时放过这种方法了,我去寻找其他方法吧,但是到时候重新做的时候要把这种方法搞懂。you know?
方法二:
使用set<vector<int>>容器,将每一个三元组排序后再插入,这样就可以利用set的自动去重功能,达到去重的效果。方法是自己想的,但是过程中一些语法是问AI的。但是好像时间复杂度有点过于高了,过了311/314个测试点,那个最后执行的输入有点太复杂了,时间超限了
好好看好好学,里面有很多语法细节
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
set<vector<int>> result;
unordered_map<int,int> hash;
for(int i=0;i<nums.size();i++)
{
hash[nums[i]]=i;
}
for(int i=0;i<nums.size();i++)
{
for(int j=0;j<nums.size();j++)
{
if(hash.find(-(nums[i]+nums[j]))!=hash.end()){
int target=-nums[i]-nums[j];
if(hash[target]!=i&&i!=j&&j!=hash[target]){
vector<int> tmp={nums[i],nums[j],-nums[i]-nums[j]};
sort(tmp.begin(),tmp.end());
result.insert(tmp);
}
}
}
}
return vector<vector<int>> (result.begin(),result.end());
}
};
把代码给AI后确实发现很多问题,如下:
代码分析
时间复杂度
- 双重循环:外层循环遍历数组 (O(n)),内层循环遍历数组 (O(n)),组合为 O(n²)
- 哈希查找:每次查找操作平均 O(1),最坏 O(n)
- 排序三元组:每次对固定大小的三元组排序 (O(1)),但执行 O(n²) 次
- set插入:红黑树插入时间复杂度为 O(log k),k 为当前元素数量
- 总体时间复杂度:O(n² log n)(log n 来自 set 插入)//时间复杂度确实有点过于高了
空间复杂度
- 哈希表:存储 n 个元素 → O(n)
- 结果集:最坏情况需存储 O(n²) 个三元组 → O(n²)
缺陷
- 索引覆盖:哈希表只存储最后出现的索引,当存在重复元素时可能漏解//这个是必须要解决的并且还没想到什么好方法解决
示例:[0,0,0],哈希表只记录最后一个 0 的索引 - 重复解:依赖排序三元组 + set 去重,代价高
- 冗余计算:双重循环产生大量重复检查
- 边界无效:当元素不足 3 个时仍执行计算
方法三:
排序+双指针+剪枝
剪枝的操作确实蛮繁琐的也很难想完全,还是要自己多画图多想,多实践,还是要自己分析具体输入的时候为什么输出会出错,而不是一直依赖AI;
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
if(nums[i]>0){return result;}
if(i>0&&nums[i]==nums[i-1]){continue;}
int left=i+1;
int 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{
result.push_back({nums[i],nums[left],nums[right]});
while(left<right&&nums[right]==nums[right-1]){right--;}
while(left<right&&nums[left]==nums[left+1]){left++;}
right--;
left++;
}
}
}
return result;
}
};
方法四:排序+半剪枝
在方法三的基础上巧妙的使用set<vector<int>>来实现不遗漏的去重,这样好像会增加时间复杂度,不过也可以通过所有测试点就是了
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
set<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
if(nums[i]>0){return vector<vector<int>> (result.begin(),result.end());}
if(i>0&&nums[i]==nums[i-1]){continue;}
int left=i+1;
int 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{
result.insert({nums[i],nums[left],nums[right]});
//while(left<right&&nums[right]==nums[right-1]){right--;}
//while(left<right&&nums[left]==nums[left+1]){left++;}
right--;
left++;
}
}
}
return vector<vector<int>> (result.begin(),result.end());
}
};
方法五:双指针+排序+完全不剪枝也是可以过的
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
set<vector<int>> result;
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
//if(nums[i]>0){return vector<vector<int>> (result.begin(),result.end());}
//if(i>0&&nums[i]==nums[i-1]){continue;}
int left=i+1;
int 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{
result.insert({nums[i],nums[left],nums[right]});
//while(left<right&&nums[right]==nums[right-1]){right--;}
//while(left<right&&nums[left]==nums[left+1]){left++;}
right--;
left++;
}
}
}
return vector<vector<int>> (result.begin(),result.end());
}
};
8.四数之和leetcode18
方法1;排序+双指针+剪枝
我觉得比三数之和麻烦太多了,特别是剪纸操作,还有一些代码的逻辑其实我还不是很清楚,明天复习三数之和和四数之和,要都重新做一遍,特别是四数之和的剪纸操作!!
在AI和代码随想录的帮助下也勉强AC(我自己写的代码,去交给AI改错,以及和代码随想录比对,但是一些不一样的我不觉得我有错,但是修改了那个地方就是有一些测试点过不了,所以明天我要搞懂这些不一样的地方到底有什么问题)
AC代码如下
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> result;
int n=nums.size();
if(n<4){return {} ;}
sort(nums.begin(),nums.end());
for(int i=0;i<nums.size();i++)
{
if(i>0&&nums[i]==nums[i-1]){continue;}
if(target>=0&&nums[i]>target){return result;}
for(int j=i+1;j<nums.size();j++)
{
if(j>i+1&&nums[j]==nums[j-1]){continue;}
if(target>=0&&nums[i]+nums[j]>target){break;}
int left=j+1;
int 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{
result.push_back({nums[i],nums[j],nums[left],nums[right]});
while(left<right&&nums[left]==nums[left+1]){left++;}
while(left<right&&nums[right]==nums[right-1]){right--;}
right--;
left++;
}
}
}
}
return result;
}
};

465

被折叠的 条评论
为什么被折叠?



