【代码随想录】Day 6 哈希法(字母异位词、数组交集、快乐数)

文章讨论了哈希表的基础知识,包括哈希碰撞的解决方法如拉链法和线性探测法,并通过C++代码示例展示了如何使用哈希表解决异位词判断和数组交集问题。此外,还提到了红黑树作为平衡二叉搜索树的特点,以及在C++编程中如何利用set和unordered_set进行数据处理。文章强调了在特定情况下选择合适的数据结构的重要性。
摘要由CSDN通过智能技术生成

哈希表基础:

快速判断一个元素是否出现集合里的时候,就要考虑哈希法,哈希法是牺牲了空间换取了时间

哈希表:将数据映射到哈希表中存储,当数据量大于哈希表的容量时可能会出现两个数据映射到表中同一个索引位,如此称为哈希碰撞哈希冲突),解决方法为拉链法线性探测法

拉链法:在冲突位置拉出一条链表,存储冲突的多个数据;

线性探测法:保证哈希表容量大于数据总量,给冲突的数据分配空位。

常见的哈希结构:数组、set和map

 红黑树是一种平衡二叉搜索树,所以key值是有序的且key不可以修改,只能删除和增加。

 第一题

力扣

 GPT解答,比较清晰

class Solution {
public:
    bool isAnagram(string s, string t) {
        if (s.length() != t.length()) {
            return false; // 长度不相等,不可能是异位词
        }

        std::map<char, int> count;
        // 遍历字符串 s,统计每个字符的出现次数
        for (char c : s) {
            count[c]++;
        }

        // 遍历字符串 t,减去每个字符的出现次数
        for (char c : t) {
            count[c]--;
        }

        // 如果两个字符串是异位词,则 count 中所有字符的计数应为 0
        for (const auto& pair : count) {
            if (pair.second != 0) {
                return false;
            }
        }
        return true;
    }
};

自己第二次做的:(注意是对table的索引进行操作,string可以用length()直接取长度)

class Solution {
public:
    bool isAnagram(string s, string t) {
        if ( s.length() != t.length()) {
            return false;
        }
        vector<int> table(26, 0);
        for (int i=0; i<s.length(); i++) {
            table[s[i] - 'a']++; //注意这里是对table的索引进行操作!
        }
        for (int i=0; i<t.length(); i++) {
            table[t[i] - 'a']--;
        }
        for (int i=0;i < 26; i++) {
            if (table[i] != 0) {
                return false;
            }
        }
        return true;
    }
};

第二题:

力扣

 第三道AC的题,需要注意的是,auto i:nums1这个写法是C++11引入的一种“范围基于循环”,表示遍历容器中的元素,auto关键字用于自动推导迭代器的类型,对于容器nums1,循环将遍历其中每个元素并赋值给i,因此直接写table[i]而不是table[nums1[i]]。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        std::map<int, int> table;
        for (auto i : nums1) {
                table[i] = 1;
        }
        for (auto i : nums2) {
            if (table[i] == 1) {
                table[i] = -1;
            }
        }
        vector<int> num;
        for (auto& pair : table) {
            if (pair.second == -1) {
                num.push_back(pair.first);
            }
        }
        return num;
    }
};

其实可以将第二个循环和三个合并:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        std::map<int, int> table;
        vector<int> num;
        for (auto i : nums1) {
                table[i] = 1;
        }
        for (auto i : nums2) {
            if (table[i] == 1) {
                num.push_back(i);
                table[i] = 0; //去重
            }
        }
        return num;
    }
};

学习记录:

使用数组来做哈希的题目,是因为题目都限制了数值的大小。没有限制数值的大小不适合用数组做。而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费。此时就要使用另一种结构体了,set。但是直接使用set不仅占用空间比数组大,而且速度要比数组慢,set把数值映射到key上都要做hash计算的,因此需要综合考虑。

  • std::set
  • std::multiset
  • std::unordered_set

std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set。

因此如果使用set,写法跟map差不多,但它存储不重复的元素,并且按照元素的排序规则自动进行排序。因此,当使用 std::set 来存储结果时,重复元素会自动被剔除,无需手动去重。

ChatGPT给出的解,更理解了set的用法,其中count 是 std::set 和 std::unordered_set 等关联容器的一个成员函数,用于统计容器中指定元素的个数。使用 count 函数,你可以判断某个元素是否存在于 std::set 或 std::unordered_set 中。如果返回值为 1,则表示元素存在;如果返回值为 0,则表示元素不存在。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        std::set<int> set1(nums1.begin(), nums1.end());
        std::set<int> set2(nums2.begin(), nums2.end());
        
        vector<int> num;
        for (int i : set1) {
            if (set2.count(i) > 0) {
                num.push_back(i);
            }
        }
        return num;
    }
};

代码随想录和我自己写的差不多,但是他用了insert函数,解决了手动赋值去重的操作:

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        int hash[1005] = {0}; // 默认数值为0
        for (int num : nums1) { // nums1中出现的字母在hash数组中做记录
            hash[num] = 1;
        }
        for (int num : nums2) { // nums2中出现话,result记录
            if (hash[num] == 1) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

insert 函数的定义如下:

std::pair<iterator,bool> insert (const value_type& val);

其中,val 是要插入的元素值。insert 函数会将 val 插入到集合中,并返回一个 std::pair 对象,其中包含一个迭代器(iterator)和一个布尔值(bool)。迭代器指向插入的元素(或者指向已经存在的相同元素),而布尔值表示插入是否成功。

如果集合中已经存在与 val 相同的元素,则插入操作不会生效,此时返回的迭代器指向已经存在的相同元素,而布尔值为 false。如果集合中不存在与 val 相同的元素,则插入操作会生效,此时返回的迭代器指向插入的新元素,而布尔值为 true。

第三题:

力扣

自己写的暴力解法: 

C++11可以用to_string将int转化为string。

class Solution {
public:
    bool isHappy(int n) {
        std::string num = std::to_string(n);
        std::map<int, int> num_map;
        int sum = 0;
        int i = 0;
        while (i<10000) {
            i++;
            sum = 0;
            for (char c : num) {
                num_map[c] = (c - '0') * (c - '0');            
                sum = sum + num_map[c];
            }
            if (sum == 1) {
                return true;
            }
            num = std::to_string(sum);
        }
        return false; 
    }
};

学习记录:

 记住!遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法!

用哈希法判断sum是否重复出现,如果重复了就是return false, 否则一直找到sum为1为止。(而我自己是用的循环10000次。。。)

判断sum是否重复出现就可以使用unordered_set。用的函数是set.find(sum)是否等于set.end()

代码随想录中取单数是用的常规思路,n%10后n/10再循环。(而我用的是将n转成string,然后遍历string,将每个char再通过 -'0' 的方式转回来。

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);
            if (sum == 1) {
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
                return false;
            } else {
                set.insert(sum);
            }
            n = sum;
        }
    }
};

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值