有效的字母异位词
例题242(简单)有效的字母异位词
注意要点:
- 直接通过一个数组来映射字母(全小写,则一共26个映射关系,使用数组更方便操作);
- s中出现就增加,t中出现就减少;如果最后哈希数组不是全0则false。
下面贴出代码:
CPP版本
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]) {return 0;}
}
return 1;
}
};
C版本
bool isAnagram(char * s, char * t){
int len1 = strlen(s), len2 = strlen(t);
if (len1 != len2)
{return false;}
int hashtable[26] = {0};
// memset(hashtable, 0, sizeof(hashtable)); // 从table这个首地址开始,之后的整个数组值都设为0
for (int i = 0; i < len1; i++)
{
hashtable[s[i] - 'a']++;
}
for (int j = 0; j < len2; j++)
{
hashtable[t[j] - 'a']--;
if (hashtable[t[j] - 'a'] < 0)
{return false;}
}
return true;
}
拓展题383(简单)赎金信
注意要点:
- 直接通过数组作为哈希表计算;
- 注意覆盖关系。
下面贴出代码:
CPP版本
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
int record[26] = {0};
if (ransomNote.size() > magazine.size()) {return 0;}
for (int i = 0; i < magazine.size(); i++) {record[magazine[i] - 'a']++;}
for (int i = 0; i < ransomNote.size(); i++)
{
record[ransomNote[i] - 'a']--;
if (record[ransomNote[i] - 'a'] < 0) {return 0;}
}
return 1;
}
};
C版本
bool canConstruct(char * ransomNote, char * magazine){
int len1 = strlen(ransomNote), len2 = strlen(magazine);
if (len1 > len2) {return false;}
else
{
int ans[26] = {0};
for (int i = 0; i < len2; i++)
{
ans[magazine[i] - 'a']++;
}
for (int j = 0; j < len1; j++)
{
ans[ransomNote[j] - 'a']--;
if (ans[ransomNote[j] - 'a'] < 0)
{
return false;
}
}
return true;
}
}
拓展题29(中等)字母异位词分组
注意要点:
- C++版本还在研究怎么使用……
- C语言采取C的自带哈希结构体,UT_hash_handle hh,自行定义key值然后通过HASH_FIND_STR以及HASH_ADD_STR进行哈希的计算,具体的操作直接看代码部分。
下面贴出代码:
C版本
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
typedef struct listnode
{
char key[102]; //key->排序好的单词
int index[100]; //一次记录出现该key的单词在字符串数组中的下标
int cnt; //key出现的次数(index结合,道理想一下就明白了)
int rsz; //记录返回数组中应该出现的下标(和函数中的*returnSize结合)
UT_hash_handle hh;
}Node;
int cmp(const void* a, const void* b)
{
return *(char*)a - *(char*)b;
}
char*** groupAnagrams(char** strs, int strsSize, int* returnSize, int** returnColumnSizes) {
//特殊情况判断
if(strsSize == 0) {
*returnSize = 0;
return NULL;
}
Node* USER = NULL;
//1、遍历字符串数组,将其中的每个单词按字母大写排序,并将排序完毕的单词加入hash
*returnSize = 0;
for (int i = 0; i < strsSize; i++)
{
char* temstrs = (char *)malloc(sizeof(char)*(strlen(strs[i])+1));//创建一个新的字符指针避免更改原数组
strcpy(temstrs, strs[i]); //复制
qsort(temstrs, strlen(temstrs), sizeof(char), cmp); //单词排序
Node* tm = NULL;
HASH_FIND_STR(USER, temstrs, tm); //查找key
if (tm == NULL) //创建新节点
{
tm = (Node*)malloc(sizeof(Node));
strcpy(tm->key, temstrs);
tm->cnt = 0; //首次出现记0
//tm->index[(tm->cnt)++] = i;
tm->rsz = (*returnSize)++; //记录返回下标,并更新*returnSize
tm->index[(tm->cnt)++] = i; //记录出现在该key中的单词对应的 strs中的下标
HASH_ADD_STR(USER, key, tm); //加入链表
}
else
tm->index[(tm->cnt)++] = i; //重复出现只更新出现次数 和 记录单词在strs中的下标
}
/*返回数组的操作*/
char*** res = (char***)malloc(sizeof(char**) * (*returnSize)); //开辟空间
*returnColumnSizes = (int*)malloc(sizeof(int) * (*returnSize)); //该数组记录每行出现单词的个数
Node* tm; //临时节点指向s->next
Node* s; //起始指向头节点
HASH_ITER(hh, USER, s, tm) {
res[s->rsz] = (char**)malloc(sizeof(char*) * (s->cnt)); //为每一行字符串指针分配空间
for (int j = 0; j < s->cnt; j++)
{
//res[s->rsz][j] = (char*)malloc(sizeof(char) * (strlen(s->key)+1));
//strcpy(res[s->rsz][j], strs[s->index[j]]);
res[s->rsz][j] = strs[s->index[j]];
}
(*returnColumnSizes)[s->rsz] = s->cnt; //每行的单词数量就是该节点出现的次数
HASH_DEL(USER,s);
free(s);
}
return res;
}
拓展题438(中等)找到字符串中所有字母异位词
注意要点:
- 通过数组作为哈希表进行运算;
- 使用滑动窗口,固定每一次的比较哈希表长度;每次把最左边的元素删除并加上滑移后的元素,进行哈希表的比较;
- C++可以直接把vector<int>进行指针的比较,C只能for循环比较每个元素。
下面贴出代码:
CPP版本
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
if (s.size() < p.size()) {return vector<int> {};}
vector<int> ans;
vector<int> sHash(26);
vector<int> pHash(26);
for (int i = 0; i < p.size(); i++)
{
sHash[s[i] - 'a']++;
pHash[p[i] - 'a']++;
}
if (sHash == pHash) {ans.push_back(0);}
for (int i = 0; i < s.size() - p.size(); i++)
{
sHash[s[i] - 'a']--;
sHash[s[i + p.size()] - 'a']++;
if (sHash == pHash) {ans.push_back(i + 1);}
}
return ans;
}
};
C版本
/**
1. Note: The returned array must be malloced, assume caller calls free().
*/
int* findAnagrams(char * s, char * p, int* returnSize){
int* ret = (int* )malloc(0);
*returnSize = 0;
if (strlen(s) < strlen(p)) {return ret;}
int* sHash[26] = {0};
int* pHash[26] = {0};
for (int i = 0; i < strlen(p); i++)
{
sHash[s[i] - 'a']++;
pHash[p[i] - 'a']++;
}
for (int i = 0; i < 26; i++)
{
if (sHash[i] != pHash[i]) {break;}
if (i == 25)
{
ret = (int* )realloc(ret, sizeof(int) * (*returnSize + 1));
ret[(*returnSize)++] = 0;
}
}
for (int i = 0; i < strlen(s) - strlen(p); i++)
{
sHash[s[i] - 'a']--;
sHash[s[i + strlen(p)] - 'a']++;
for (int j = 0; j < 26; j++)
{
if (sHash[j] != pHash[j]) {break;}
if (j == 25)
{
ret = (int* )realloc(ret, sizeof(int) * (*returnSize + 1));
ret[(*returnSize)++] = i + 1;
}
}
}
return ret;
}
两个数组的交集
例题349(简单)两个数组的交集
注意要点:
- 可以使用unordered_set作为哈希表,并且可以用unordered_set直接拷贝数组,达到降重的效果;
- 可以使用内置的find()函数进行set中的寻值,找到了就插入到结果之中;
- C语言直接只用数组作为哈希表
下面贴出代码:
CPP版本
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> ret;
unordered_set<int> nums(nums1.begin(), nums1.end());
for (int num : nums2)
{
//找到了就会返回迭代器,如果没找到就会返回end
if (nums.find(num) != nums.end()) {ret.insert(num);}
}
return vector<int> (ret.begin(), ret.end());
}
};
C版本
/**
1. Note: The returned array must be malloced, assume caller calls free().
*/
int* intersection(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
int nums1Hash[1000] = {0};
*returnSize = 0;
int lessSize = fmin(nums1Size, nums2Size);
int* ret = (int *)malloc(sizeof(int) * lessSize);
for(int i = 0; i < nums1Size; i++) {nums1Hash[nums1[i]]++;}
for(int i = 0; i < nums2Size; i++)
{
if(nums1Hash[nums2[i]])
{
ret[(*returnSize)++] = nums2[i];
nums1Hash[nums2[i]] = 0;
}
}
return ret;
}
拓展题350(简单)两个数组的交集II
注意要点:
- 与349相比,需要一个值来记录出现次数,所以使用map来进行计算,其余操作均类似。
下面贴出代码:
CPP版本
class Solution {
public:
vector<int> intersect(vector<int>& nums1, vector<int>& nums2) {
vector<int> ret;
unordered_map<int, int> hash;
for (int num : nums1)
{
if (hash.find(num) != hash.end()) {hash[num]++;}
else {hash[num] = 1;}
}
for (int num : nums2)
{
if (hash.find(num) != hash.end())
{
if (hash[num])
{
ret.push_back(num);
hash[num]--;
}
}
}
return ret;
}
};
C版本
/**
1. Note: The returned array must be malloced, assume caller calls free().
*/
int* intersect(int* nums1, int nums1Size, int* nums2, int nums2Size, int* returnSize){
int nums1Hash[1001] = {0};
*returnSize = 0;
int lessSize = fmin(nums1Size, nums2Size);
int* ret = (int *)malloc(sizeof(int) * lessSize);
for(int i = 0; i < nums1Size; i++) {nums1Hash[nums1[i]]++;}
for(int i = 0; i < nums2Size; i++)
{
if(nums1Hash[nums2[i]])
{
ret[(*returnSize)++] = nums2[i];
nums1Hash[nums2[i]]--;
}
}
return ret;
}
快乐数
例题202(简单)快乐数
注意要点:
- 题目已经明确表示会有无限循环,则明显要用哈希表来解决问题;
- unordered_set只有insert;
- C需要用UT_hash_handle hh来解决(没有办法确定数组的大小和映射关系),利用HASH_FIND_INT以及HASH_ADD_INT;
- 本题需要用while(1)死循环来确保计算直到到1或者死循环。
下面贴出代码:
CPP版本
class Solution {
public:
int getSum(int n)
{
int sum = 0;
while (n)
{
sum += pow(n % 10, 2);
n /= 10;
}
return sum;
}
bool isHappy(int n) {
unordered_set<int> set;
while (1)
{
int sum = getSum(n);
if (sum == 1) {return 1;}
if (set.find(sum) != set.end()) {return 0;}
else {set.insert(sum);}
n = sum;
}
}
};
C版本
typedef struct listnode
{
int key;
UT_hash_handle hh;
} Node;
int getsum(int n)
{
int sum = 0;
int now = n;
while (now)
{
sum += (now % 10) * (now % 10);
now /= 10;
}
return sum;
}
bool isHappy(int n){
int sum = getsum(n);
Node* USER = NULL;
while (1)
{
if (sum == 1) {return true;}
else
{
int num = sum;
Node* tm = NULL;
HASH_FIND_INT(USER, &num, tm);
if (tm) {return false;}
else
{
tm = (Node* )malloc(sizeof(Node));
tm->key = num;
HASH_ADD_INT(USER, key, tm);
sum = getsum(sum);
}
}
}
}
两数之和
注意要点:
- 由于需要两个值,所以使用map来解题;
- 可以使用auto进行迭代器的定义;
- 在存入结果是,使用**pair<int,int>()**进行存储
下面贴出代码:
CPP版本:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> map;
for (int i = 0; i < nums.size(); i++)
{
//遍历寻找是否有满足条件的数
auto iter = map.find(target - nums[i]);
if (iter != map.end()) {return {iter->second, i};}
map.insert(pair<int, int> (nums[i], i));
}
return {};
}
};
C版本
/**
* Note: The returned array must be malloced, assume caller calls free().
*/
struct hashTable
{
int key;
int count;
UT_hash_handle hh;
};
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
struct hashTable* USER = NULL;
for (int i = 0; i < numsSize; i++)
{
struct hashTable* tmp = NULL;
int num = target - nums[i];
HASH_FIND_INT(USER, &num, tmp);
if (!tmp)
{
tmp = (struct hashTable* )malloc(sizeof(struct hashTable));
tmp->key = nums[i];
tmp->count = i;
HASH_ADD_INT(USER, key, tmp);
}
else
{
*returnSize = 2;
int* ans = (int* )malloc(sizeof(int) * 2);
ans[0] = tmp->count;
ans[1] = i;
return ans;
}
}
return NULL;
}
四数相加
例题454(中等)四数相加II
注意要点:
- 需要记录键值以及出现次数,所以用unordered_map;
- 计算其中两个数组的和并用map记录,然后通过寻找另外两个数组的和的相反数,统计出现次数。
下面贴出代码:
CPP版本
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int ret = 0;
unordered_map <int, int> map;
for (int a : nums1)
{
for (int b : nums2)
{
map[a + b]++;
}
}
for (int c : nums3)
{
for (int d : nums4)
{
auto iter = map.find(0 - c - d);
if (iter != map.end()) {ret += iter->second;}
}
}
return ret;
}
};
C版本
struct hashTable
{
int key;
int count;
UT_hash_handle hh;
};
int fourSumCount(int* nums1, int nums1Size, int* nums2, int nums2Size, int* nums3, int nums3Size, int* nums4, int nums4Size){
int result = 0;
struct hashTable* USER = NULL;
for (int i = 0; i < nums1Size; i++)
{
for (int j = 0; j < nums2Size; j++)
{
int num = nums1[i] + nums2[j];
struct hashTable* tmp = NULL;
HASH_FIND_INT(USER, &num, tmp);
if (!tmp)
{
tmp = (struct hashTable* )malloc(sizeof(struct hashTable));
tmp->key = num;
tmp->count = 1;
HASH_ADD_INT(USER, key, tmp);
}
else {tmp->count++;}
}
}
for (int k = 0; k < nums3Size; k++)
{
for (int l = 0; l < nums4Size; l++)
{
int num = 0 - nums3[k] - nums4[l];
struct hashTable* tmp = NULL;
HASH_FIND_INT(USER, &num, tmp);
if (tmp) {result += tmp->count;}
}
}
return result;
}
赎金信
在 有效的字母异位词 中已经有过记录。
三数之和、四数之和
哈希表处理还需降重操作,很难把所有细节都写出来,更适合双指针做,之后的双指针章节会再行整理!
总结
这里我直接把代码随想录的一部分我认为的重点拖了过来,真的总结的特别好!!!
一般来说哈希表都是用来快速判断一个元素是否出现集合里。
对于哈希表,要知道哈希函数和哈希碰撞在哈希表中的作用。
哈希函数是把传入的key映射到符号表的索引上。
哈希碰撞处理有多个key映射到相同索引上时的情景,处理碰撞的普遍方式是拉链法和线性探测法。
接下来是常见的三种哈希结构:
- 数组
- set(集合)
- map(映射)
在C++语言中,set 和 map 都分别提供了三种数据结构,每种数据结构的底层实现和用途都有所不同。
例如什么时候用std::set,什么时候用std::multiset,什么时候用std::unordered_set,都是很有考究的。
只有对这些数据结构的底层实现很熟悉,才能灵活使用,否则很容易写出效率低下的程序。
使用set
没有限制数值的大小,就无法使用数组来做哈希表了。
主要因为如下两点:
- 数组的大小是有限的,受到系统栈空间(不是数据结构的栈)的限制。
- 如果数组空间够大,但哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。
所以此时一样的做映射的话,就可以使用set了。
std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希, 使用unordered_set 读写效率是最高的,本题并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。
使用map
使用数组和set来做哈希法的局限。
-
数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
-
set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x和y的下标。所以set 也不能用。
map是一种<key, value>的结构,本题可以用key保存数值,用value在保存数值所在的下标。所以使用map最为合适。
C++提供如下三种map:
- std::map
- std::multimap
- std::unordered_map
std::unordered_map 底层实现为哈希,std::map 和std::multimap 的底层实现是红黑树。
同理,std::map 和std::multimap 的key也是有序的!
做题的总结
C
C中,能用数组就用数组,会比较方便,但是malloc大小的时候注意不要溢出;不然就用可以直接用的UT_hash_handle hh,里面可以自行定义需要些什么参数,而且可以hh->next进行迭代
CPP
同理尽量用数组,如果只需要key就用set,如果需要映射关系就用map;
没有顺序要求全部用unordered,底层实现是哈希表,效率高;如果要求有序那只能用map和set。