代码随想录刷题笔记3——哈希表

有效的字母异位词

例题242(简单)有效的字母异位词

注意要点:

  1. 直接通过一个数组来映射字母(全小写,则一共26个映射关系,使用数组更方便操作);
  2. 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(简单)赎金信

注意要点:

  1. 直接通过数组作为哈希表计算;
  2. 注意覆盖关系。

下面贴出代码:

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(中等)字母异位词分组

注意要点:

  1. C++版本还在研究怎么使用……
  2. 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(中等)找到字符串中所有字母异位词

注意要点:

  1. 通过数组作为哈希表进行运算;
  2. 使用滑动窗口,固定每一次的比较哈希表长度;每次把最左边的元素删除并加上滑移后的元素,进行哈希表的比较;
  3. 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(简单)两个数组的交集

注意要点:

  1. 可以使用unordered_set作为哈希表,并且可以用unordered_set直接拷贝数组,达到降重的效果
  2. 可以使用内置的find()函数进行set中的寻值,找到了就插入到结果之中;
  3. 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

注意要点:

  1. 与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(简单)快乐数

注意要点:

  1. 题目已经明确表示会有无限循环,则明显要用哈希表来解决问题;
  2. unordered_set只有insert
  3. C需要用UT_hash_handle hh来解决(没有办法确定数组的大小和映射关系),利用HASH_FIND_INT以及HASH_ADD_INT
  4. 本题需要用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);
            }
        }
    }
}

两数之和

注意要点:

  1. 由于需要两个值,所以使用map来解题;
  2. 可以使用auto进行迭代器的定义
  3. 在存入结果是,使用**pair<int,int&gt()**进行存储

下面贴出代码:

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

注意要点:

  1. 需要记录键值以及出现次数,所以用unordered_map;
  2. 计算其中两个数组的和并用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。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值