代码随想录(三) 哈希表

哈希表:
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

1.有效的字母异位词 用数组

class Solution {
public:
    bool isAnagram(string s, string t) {
		int record[26] = {0};
		for(int i = 0; i < s.size(); i++) {
			record[s[i] - 'a']++;
		} 
		for(int i = 0; i < t.size(); i++) {
			record[t[i] - 'a']--;
		}
		for(int i = 0; i < 26; i++) {
			if(record[i] != 0) {
				return false;
			}
		}
		return true;
    }
};



3.两个数组的交集

用set 写法1:

class Solution {
public:
	vector<int> intersection(vector<int>& nums1, vector<int>& nums2) { //该函数接受两个输入参数 nums1 和 nums2,它们都是整数型的向量(即 vector<int>)。
		unordered_set<int> result_set;
		unordered_set<int> nums_set(nums1.begin(), nums1.end());
		for(int i = 0; i < nums2.size(); i++) {
			if(nums_set.find(nums2[i]) != nums_set.end()) {  //当迭代器 it 等于 nums_set.end() 时,表示遍历已经完成,退出循环。
				result_set.insert(nums2[i]);
			}
		}
		return vector<int>(result_set.begin(), result_set.end()); 
	}
};

用set 写法二:
在这里插入图片描述

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 空的 存放结果,之所以用set是为了给结果集去重
        unordered_set<int> nums_set(nums1.begin(), nums1.end());//使用 nums1 中的元素初始化另一个 unordered_set nums_set。
        for (int num : nums2) {
            // 发现nums2的元素 在nums_set里又出现过
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

用数组来做哈希表:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
       unordered_set<int> result_set;
	   int hash[1004]= {0}; 
	   for (int num : nums1) {
	   		hash[num] = 1;
	   }
	   
	    for (int num : nums2) {
	   		if (hash[num] == 1) {
	   				result_set.insert(num); 
			   }
	   }
	   return vector<int>(result_set.begin(),result_set.end());
	   
    }
};

4.快乐数

class Solution {
public:
	//取数值各个位的单数之和
	int getSum(int n) {
		int sum = 0;
		while(n) {
			sum += (n % 10)*(n % 10);  //首先 取个位的平方,再取十位的平方, 
			n /= 10;
		}
		return sum; 
	} 	

    bool isHappy(int n) {
    	unordered_set<int> set;
    	while(1) {
    		int sum = getSum(n);  //把更新的n作为参数传进去 求出新的sum 
    		if (sum == 1) {
    			return true;
			}
			if(set.find(sum) != set.end()) { //用哈希法,sum出现过,就出现死循环了 
				return false;
			} else { //放到哈希表里 要快速判断一个元素是否出现集合里的时候 
				set.insert(sum);
			}
			n = sum; //更新n的值为新的sum 
		}
    }
};

5.两数之和 时O(n) 空O(n)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) { //vector<int>& nums 的写法表示该函数接受一个对 vector<int> 对象的引用作为参数,并且可以直接修改传入的 nums 向量,而不产生额外的拷贝。这样可以提高代码的效率,并且可以保证函数内对 nums 的修改在函数外部也能得到反映
        std::unordered_map<int, int> map;
        for(int i=0; i<nums.size(); i++) {
            auto iter = map.find(target-nums[i]);//在名为 map 的映射容器中查找键为 target - nums[i] 的元素,并将返回的迭代器赋值给 iter 变量;如果在 map 中找不到对应的键,则 iter 将指向 map.end(),即表示未找到的迭代器。
            if(iter != map.end()) {
                return {iter->second, i};
            }
            //
            //map.insert(pair<int, int>(nums[i], i)); 
            map.insert(pair<int, int>(nums[i], i));
        }
        //return {} 表示在函数中返回一个空的对象或值。它可以用来表示函数的返回类型为某个类的默认构造函数产生的空对象,或者返回一个空的容器、数组等。
      //具体来说,{} 是C++11引入的统一的初始化语法,可以用于直接创建对象并进行初始化。在 return {} 中,大括号 {} 表示创建一个对象,并将其初始化为空值。编译器会根据上下文来确定返回值的类型。
        return {};
        
    }
};



6.四数之和 时 :O(n^2)
空:最坏的情况是num1和nums2的元素各不相同 a+b的个数为n^2个

class Solution {
public:
    int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
        unordered_map<int, int> umap;
        for (int a : nums1) {
            for (int b : nums2) {
                umap[a+b] ++; //umap是无限大吗 任意大?
            }
        }
        int count = 0;
        for (int c : nums3) {
            for (int d : nums4) {
               count += umap[0-(c+d)];
            }
        }
        return count;
    }
};

7.赎金信 时间O(n) 空间O(1)
因为小写字母26位,用数组(简单的哈希表)消耗的空间更小,本题用map消耗的空间更大(因为map要维护红黑树或哈希表,还要做哈希函数),用数组更加简单直接高效。

法一:

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) { //magazine是长的
        int record[26] = {0}; 
        if(ransomNote.length() > magazine.length()) {
            return false;
        }

        //通过record数组记录magazine中每个字母出现的次数
        for (int i = 0; i < magazine.length(); i++) {
            record[magazine[i] - 'a']++;
        }

        //遍历ransomNote
        for(int j = 0; j < ransomNote.length(); j++) {
            record[ransomNote[j] - 'a'] --;
            if(record[ransomNote[j] - 'a']<0) {
                return false;
            }
        }
        return true;
    }
};


法2:暴力法

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) { //magazine是长的
        for(int i = 0; i < magazine.length() ; i++){  //固定magazine(长的)的一个字符,每次遍历ransomNote
            for(int j = 0; j < ransomNote.length() ; j++) {  
                if (magazine[i] == ransomNote[j]) {
                    ransomNote.erase(ransomNote.begin() + j);  //删除第j+1个元素
                    break;//删除后要退出本轮循环   magazine的一个元素不能删两个ransomNote的
                }
            }
        }

        if (ransomNote.length() == 0){
            return true;
        }

        return false;

        }
};


8.三数之和
法一:用双指针法:(之前的两数之和不能用双指针法,因为两数之和要返回数组下标,使用双指针法需要排序,数组的索引下标会被改变)

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {//一个二维向量(Vector)的声明,其中每个元素都是一个整数向量(Vector)
       vector<vector<int>> result;    
       sort(nums.begin(), nums.end());//nums 进行排序的操作使用了 sort() 函数,该函数的时间复杂度为 O(nlogn)
	   for(int i = 0; i < nums.size(); i++) {
	   		if(nums[i] > 0) {
	   			return result;
			}
			
			//对nums[i]进行去重(第一个元素)    举个极端的例子 -1 -1 -1 -1 -1 -1 -1 2(能够正确去重) 
			if (i>0 && nums[i] == nums[i-1]) {continue;}
			//如果是下面这种去重方式 会发生错误 连唯一的一组都保存不了
			//if(nums[i] == nums[i+1]) {continue;	} 
			 
			int left = i+1;
			int right = nums.size() - 1;
			while(left < right) {
				//如果去重放在这里 没法正确去重 如:0 0 0 0 0 0 0 0 0  
//				while (right > left && nums[right] == nums[right-1]) {right--;}
//				while (right > left && nums[left] == nums[left+1]) {left++;}
				if(nums[i] + nums[left] +nums[right] > 0) {right--;}
				else if(nums[i] + nums[left] +nums[right] < 0) {left++;}
				else{
					result.push_back(vector<int>{nums[i], nums[left], nums[right]});  //我的理解是得存取那唯一的一组数据后才能开始去重 
//剪枝
					while (right > left && nums[right] == nums[right-1]) {right--;}
					while (right > left && nums[left] == nums[left+1]) {left++;}
					right--;
					left++;	
				}
			}	
	   }  
	   return result; 
    }
};




时间:O(n^2)
总体的时间复杂度为 O(nlogn + n^2)

空间:创建了一个二维向量 result 来存储结果,在最坏情况下,结果的数量为 O(n^2)
(即每个元素都与其他所有元素组成三元组),所以空间复杂度为 O(n^2)。

法二:哈希法(看不太懂) 去重麻烦 推荐用双指针
时间复杂度: O(n^2)
空间复杂度: O(n),额外的 set 开销

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> result;
        sort(nums.begin(), nums.end());
        // 找出a + b + c = 0
        // a = nums[i], b = nums[j], c = -(a + b)
        for (int i = 0; i < nums.size(); i++) {
            // 排序之后如果第一个元素已经大于零,那么不可能凑成三元组
            if (nums[i] > 0) {
                break;
            }
            if (i > 0 && nums[i] == nums[i - 1]) { //三元组元素a去重
                continue;
            }
            unordered_set<int> set;
            for (int j = i + 1; j < nums.size(); j++) {
                if (j > i + 2
                        && nums[j] == nums[j-1]
                        && nums[j-1] == nums[j-2]) { // 三元组元素b去重
                    continue;
                }
                int c = 0 - (nums[i] + nums[j]);
                if (set.find(c) != set.end()) {
                    result.push_back({nums[i], nums[j], c});
                    set.erase(c);// 三元组元素c去重
                } else {
                    set.insert(nums[j]);
                }
            }
        }
        return result;
    }
};

在这里插入图片描述
在这里插入图片描述

10.四数之和

class Solution {  // k i left  right
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) {
		vector<vector<int>> result;
		sort(nums.begin(), nums.end());
		for (int k = 0; k < nums.size(); k++) {
			//剪枝 
			if (nums[k] > target && nums[k] >= 0) {
				break;
			}
			//对nums[k]去重 
			if (k > 0 && nums[k] == nums[k-1])  {
				continue;
			}
			for(int i = k + 1; i < nums.size(); i++) {
				// 2级剪枝 
				if (nums[k] + nums[i] > target && nums[k] + nums[i] >= 0) {
					break;
				}
				
				// 对nums[i]去重 
				if (i > k + 1 && nums[i] == nums[i-1]) {
					continue;
				} 
				int left = i + 1;
				int right = nums.size() - 1;
				while (right > left) { 
					if ((long)nums[k] + nums[i] + nums[left] + nums[right] > target) {
						right--;
					} else if ((long) nums[k] + nums[i] + nums[left] + nums[right]  < target) {
                        left++;
                    } 
					else  {
						result.push_back(vector<int>{nums[k], nums[i], nums[left], nums[right]});
						while (right > left && nums[right] == nums[right-1]) right--;
						while (right > left && nums[left] == nums[left+1]) left++;
						right--;
						left++;
					}
				}
			}
	}
		return result;
	}
}; 
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值