代码随想录:哈希表

1.有效的字母异位词

哈希表的思路非常好。

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

2.两个数组的交集

C++中的set容器,需要了解一下源码。后面再了解吧,刚开始只要知道思路就好了,unordered_set是一个库,包括nums.begin(),nums.end()需要查看源码认真总结。其中insert和set对应的东西就够多了,还要多学习。
后边需要常回来看看。
比较难,使用了orderred的

#include 
#include 
#include 
#include 
#include 
using std::cout;
using std::endl;
using std::vector;
using std::string;
using std::unordered_set;

class Solution {
public:
	vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
		unordered_set<int> resultSet;
		unordered_set<int> nums_set(nums1.begin(), nums1.end());
		for (int num : nums2) {
			if (nums_set.find(num) != nums_set.end()) {
				resultSet.insert(num);
			}
		}
		return vector<int>(resultSet.begin(), resultSet.end());
	}
};
int main() {
		//测试
	vector<int> linshi = { 1,2,3,4,5 };
	cout << *(linshi.begin())<<endl;
	cout << *(linshi.end())<<endl;
	cout << (linshi.size())<<endl;
	return 0;
}

3.快乐数

依然是对于unordered_set的使用,对数字各个位上的求和写法非常漂亮值得借鉴。目前对于unordered_set的find(),end(),insert()的用法更加熟悉了。STL底层库很值得学习。

class Solution {
public:
	int getSum(int n) {
		int sum = 0;
		while (n) {
			sum += (n % 10) * (n % 10);
			n = n / 10;
		}
		return sum;
	}
	bool isHappy(int n) {
		unordered_set<int> set;
		while (1) {
			n = getSum(n);
			if (n == 1) {
				return true;
			}
			else {
				if (set.find(n) != set.end()) {
					return false;
				}
				else
				{
					set.insert(n);
				}
			}
		}
	}
};

4.两数之和

这道题用到map的数据结构,返回空vector的方式第一次见,以后可以学着使用。从代码可以看出,两种vector返回方式都是合理的。第一种更简洁方便。
同时根据题目5,map的key,value索引也可以用简洁的方式,pair应该是map内部的数据结构。

第一种:
class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		unordered_map<int, int> map;//先不用初始化,为什么呢?因为题目要求数组的同一个元素在答案里不能重复出现,如果先初始化,后边用map.find会造成干扰;另外,map的初始化比较复杂吧
		auto length = nums.size();
		for (int i = 0; i < length; i++) {
			auto index = map.find(target - nums[i]);  //map.find()寻找的是key,返回的是(key,value)
			if (index != map.end()) {
				return { index->second,i };//这种返回空vector列表的方式是第一次见到,->second,选择的是value,->first对应的是key.
			}
			else
			{
				map.insert(pair<int, int>(nums[i], i));//pair数据结构是map内部的数据组织结构
				//map[nums[i]]=i;
			}
		}
		return {};//这种返回空vector列表的方式是第一次见到
	}
};
第二种:
class Solution {
public:
	vector<int> twoSum(vector<int>& nums, int target) {
		unordered_map<int, int> map;//先不用初始化,为什么呢?因为题目要求数组的同一个元素在答案里不能重复出现,如果先初始化,后边用map.find会造成干扰;另外,map的初始化比较复杂吧
		auto length = nums.size();
		for (int i = 0; i < length; i++) {
			auto index = map.find(target - nums[i]);  //map.find()寻找的是key,返回的是(key,value)
			if (index != map.end()) {
				return vector<int>{ index->second,i };//这种返回空vector列表的方式是第一次见到,->second,选择的是value,->first对应的是key.
			}
			else
			{
				map.insert(pair<int, int>(nums[i], i));//pair数据结构是map内部的数据组织结构
			}
		}
		return vector<int>{};//这种返回空vector列表的方式是第一次见到
	}
};

5.四数相加

代码随想录的方法确实好。我起初的想法是nums1,nums2先组合成一个map,同理nums3,nums4也组合成一个map.之后遍历一个map,find一个map,但是map好像不能遍历。代码随想录这个答案最节省空间。
同时学到了:map内部元素索引的方法


class Solution {
public:
	int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
		unordered_map<int, int> map;
		for (int num1 : nums1) {
			for (int num2 : nums2) {
				map[num1 + num2]++;  //map的key,value值是可以这样进行赋值的。
			}
		}
		int cout{ 0 };
		for (int num3 : nums3) {
			for (int num4 : nums4){
				if (map.find(0 - num3 - num4) != map.end()) {//find()是在找key,不是value
					cout += map[0 - num3 - num4];//map的value是可以这样索引的
				}
			}
		}
		return cout;
	}
};

6.赎金信

代码随想录里的解法更快,好的方面:1.添加了数组长度判断,可以快速判定false的情况。2.数组元素值小于0直接放在第二个循环里,效率更高,我的放在了第三个循环。
给自己一个提醒,按自己的思路写完一道题之后,缓一缓,改进一下代码效率。

我的思路:借鉴第一道题,时间复杂度O(n).
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int data[26]={0};
        for (int i = 0; i < magazine.length(); i++) {
            data[magazine[i]-'a']++;
        }
        for (int i = 0; i < ransomNote.length(); i++) {
            data[ransomNote[i]-'a']--;
        }
        for (int i = 0; i < 26; i++) {
            if(data[i] < 0) {
                return false;
            }
        }
        return true;
    }
};
代码随想录里的解法:
class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        int record[26] = {0};
        //add
        if (ransomNote.size() > magazine.size()) {
            return false;
        }
        for (int i = 0; i < magazine.length(); i++) {
            // 通过record数据记录 magazine里各个字符出现次数
            record[magazine[i]-'a'] ++;
        }
        for (int j = 0; j < ransomNote.length(); j++) {
            // 遍历ransomNote,在record里对应的字符个数做--操作
            record[ransomNote[j]-'a']--;
            // 如果小于零说明ransomNote里出现的字符,magazine没有
            if(record[ransomNote[j]-'a'] < 0) {
                return false;
            }
        }
        return true;
    }
};

7.三数之和(理解难度比较高,需要多加注意)

代码随想录的逻辑非常漂亮,值得反复钻研,这是一道重点题,很重要很重要,这是目前遇到最难的一道。
这是双指针法的应用,思路非常漂亮,值得反复钻研。
做题提醒:
按照给出的示例提示去写代码,同时还要关注特例的情况,不注重特例情况就会出现问题。

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++) {   //nums.size()不用跟-1
 			if (nums[i] > 0) {//这种情况不可能在升序数值中找出和为0的数
				return result;//返回至今保留的列表(内部的逻辑也是升序排列)
			}
			if (i > 0 && nums[i] == nums[i - 1]) {//对第一个数进行去重,i>0是因为i-1无效
				continue;
			}
			int left = i + 1;
			int right = nums.size() - 1;
			while (left < right) {  //不能是left!=right,因为如果left和right紧挨着,left++,right--之后,left>right(这就有问题了)
			//left!=right不能使用,还有一层原因就是当输入数组为[0,0,0]时,对第二个数,第三个数去重时会突然出现left>right的情况,这道题还是比较复杂的。还是用left
				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 (right > left && nums[right] == nums[right - 1]) { //这里的right > left不能少,特例依然是[0,0,0]
						right--;
					}//对第三个数进行去重
					while (right > left && nums[left] == nums[left + 1]) {//这里的right > left不能少,特例依然是[0,0,0]
						left++;
					}//对第二个数进行去重
					left++;
					right--;
				}
			}
		}
		return result;
	}
};

8.四数之和

三数之和的扩展,不过其中的特殊情况比较诡异,明天早上解决就ok.
这道题的解法是给三数之和套上一个循环。此时有几个特殊情况:
1.target可以大于等于0,也可以小于0.(当target>=0,排序后只要第一个数>target,就可以跳出循环了;当target<0,第一个数>target不可以跳出循环,这一点非常重要)所以情况就是(nums[i]>=0,且nums>target,才满足跳出条件),这一个思路很重要。

2.代码中使用break的原因是,代码框架为将四数之和,变为一个数和三个数之和。第一个数大于0并且大于target,又因为升序排列,所以必然可以直接result.但是第二层的三个数之和的循环,只是i确定之下的局部问题,遇到第一个数大于0并且大于target的情况,只需要进行局部跳出循环即可,所以用break;

class Solution {
public:
	vector<vector<int>> fourSum(vector<int>& nums, int target) {
		vector<vector<int>> result;
		sort(nums.begin(), nums.end());//默认升序排列
		for (int i = 0; i < nums.size(); i++) {
			//还要考虑特殊情况,此处的特殊情况比较复杂了,
			if (nums[i] > target && nums[i] >= 0){//将target>=0,<0都包括进去了。
				return result; 
			}
			if (i > 0 && nums[i] == nums[i - 1]) {
				continue;
			}
			//这里还要防止这两个数会不会重复,稍后再想
			for (int j = i + 1; j < nums.size(); j++) {
				if (nums[j] > target - nums[i] && nums[j] >= 0) {     //二重循环里处理的是三数之和,此时的target = target - nums[i]
					break;
				}
				if (j > i + 1 && nums[j] == nums[j - 1])
				{
					continue;
				}
				int left = j + 1;
				int right = nums.size() - 1;
				while (left < right) {
					if (static_cast<long>(nums[i]) + nums[j] + nums[left] + nums[right] < target) {
						left++;
					}
					else if (static_cast<long>(nums[i]) + nums[j] + nums[left] + nums[right] > target){
						right--;
					}
					else {
						result.push_back({ nums[i],nums[j],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;
	}
};

你可能感兴趣的:(散列表,哈希算法,数据结构)