代码随想录day6 | LeetCode242. 有效的字母异位词、LeetCode349. 两个数组的交集、LeetCode202. 快乐数、LeetCode1. 两数之和

代码随想录day6 | LeetCode242. 有效的字母异位词、LeetCode349. 两个数组的交集、LeetCode202. 快乐数、LeetCode1. 两数之和

day5为周日,休息日
今天是哈希表开篇

哈希表基础

链接

  • 哈希表是根据关键码的值而直接进行访问的数据结构。

  • 一般哈希表都是用来快速判断一个元素是否出现集合里。

  • 空间换时间的一种策略

  • 将元素映射到哈希表上就涉及到了hash function (哈希函数)

  • 一般hash code是通过特定编码方式,可以将其他数据格式转化为不同的数值

  • 通过取模解决hash Code得到的数值大于哈希表大小情况

  • 哈希碰撞:不同元素映射到同一下标位置(哈希函数选择不好会提升出现概率,元素数量大于哈希表的大小时一定会出现)

  • 哈希碰撞解决方法:

    • 拉链法

      选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间。

    • 线性探测法

      一定要保证tableSize大于dataSize。 我们需要依靠哈希表中的空位来解决碰撞问题。

  • 三种哈希结构

    无论底层采用什么实现形式,对外提供的都是哈希法的使用方式,即依靠键(key)来访问值(value),所以使用这些数据结构来解决映射问题的方法,我们依然称之为哈希法

    • 数组

    • set (集合)

      集合底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
      std::set红黑树有序O(log n)O(log n)
      std::multiset红黑树有序O(logn)O(logn)
      std::unordered_set哈希表无序O(1)O(1)

      红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加

    • map(映射)

      加。

      映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
      std::map红黑树key有序key不可重复key不可修改O(logn)O(logn)
      std::multimap红黑树key有序key可重复key不可修改O(log n)O(log n)
      std::unordered_map哈希表key无序key不可重复key不可修改O(1)O(1)

      std::map 和std::multimap 的key是有序的(红黑树)(这个问题也经常作为面试题,考察对语言容器底层的理解)。

  • 使用集合时,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset

总结:

快速判断一个元素是否出现集合里

判断一个元素是否出现过

第一时间想到哈希法

有效的字母异位词

题目链接:LeetCode242. 有效的字母异位词

自己敲

判断t字符串中字符是否在s字符串中字符构成的集合中出现过,采用哈希

未要求集合有序或有重复数据,采用multiset,存26个连续的0表示a-z的初始数量为0

class Solution {
public:
    bool isAnagram(string s, string t) {
        multiset_set<int> set = //初始化a-z个0;
        for(int i=0;i<s.size();i++){
            set(s[i]-a)++;
        }
        for(int i=0;i<t.size();i++){
            set(t[i]-a)--;
        }
        for(int i;i<set.size();i++){
            //如果一个元素不为0,则返回false
        }
        //返回true

    }
};

看题解

链接

暴力解法

暴力的解法,两层for循环,同时还要记录字符是否重复出现

哈希表

别忘了数组!

这道题目可以定义一个数组,来记录字符串s里字符出现的次数。

因为字符a到字符z的ASCII是26个连续的数值,所以字符a映射为下标0,相应的字符z映射为下标25

再遍历 字符串s的时候,只需要将 s[i] - ‘a’ 所在的元素做+1 操作即可,并不需要记住字符a的ASCII,只要求出一个相对数值就可以了

class Solution {
public:
    bool isAnagram(string s, string t) {
        int record[26] = {0};
        for (int i = 0; i < s.size(); i++) {
            // 并不需要记住字符a的ASCII,只要求出一个相对数值就可以了
            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) {
                // record数组如果有的元素不为零0,说明字符串s和t 一定是谁多了字符或者谁少了字符。
                return false;
            }
        }
        // record数组所有元素都为零0,说明字符串s和t是字母异位词
        return true;
    }
};

总结

  • 求字符串长度采用size()方法

    • sizeof() 可以以类型、指针、数组和函数等作为参数,其值在编译时计算,用于获取操作数所占空间的字节数大小。
    • strlen()函数其参数只能为char* (字符型指针),所以在计算字符数组(char str[ ])时,一般用strlen()函数(也只能用于计算字符数组了)。
    • size()函数以及length()函数都用于计算字符串(string)长度,不能用char* 作为参数。除此之外,size()函数还可以获取vector类型的长度。

    原文参考:C++求字符串长度————sizeof()、size()、strlen()以及length()详解_c++字符串长度-CSDN博客

两个数组的交集

题目链接:LeetCode349. 两个数组的交集

自己敲

求两数组交集,即判断一个数组中元素是否在另一个数组元素集合里,采用哈希

要求输出结果唯一且不要求集合有序,采用unordered_set

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //将unordered_set初始化为nums1
        
        //遍历nums2,如果元素在unordered_set,就存入数组s

        //遍历输出数组s

    }
};

由于忘记STL操作,所以只写了思路

看题解

链接

什么时候采用数组?

题目限制了数值大小时

如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费

思路如图所示:
在这里插入图片描述

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());
        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());
    }
};

总结

题目不难,主要是学会使用unordered_set用法

std::unordered_set<string> things {16}; // 16 buckets

std::unordered_set<string> words {"one", "two", "three", "four"};// Initializer list

std::unordered_set<string> some_words {++std::begin(words), std::end (words)};  // Range

std::unordered_set<string> copy_wrds {words}; // Copy constructor

原文参考:C++ unordered_set定义及初始化详解 (biancheng.net)

快乐数

题目链接:LeetCode202. 快乐数

自己敲

判断无限循环,循环,证明n及n变化后的数构成的集合中有重复的数

所以解题要判断当前数是否在历史数据这个集合中重复,采用哈希

不需要存入的元素有序,但要可以重复,存入元素数量不确定,采用vector作为集合

class Solution {
public:
    bool isHappy(int n) {
        vector<int> s;
        while(n!=1){
            for(int i=0;i<s.size();i++){
                if(n==s[i]){
                    return false;
                }
            }
            s.push_back(n);
            int sum=0;
            //将n各个位上的数求和
            while(n>0){
                sum+=(n%10)*(n%10);
                n/=10;
            }
            n=sum;
        }
        return true;
    }
};

看题解

链接

和我思路一致,只是题解集合用的时unordered_set

题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,抓住这个条件题目迎刃而解

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;
        }
    }
};

总结

  • set.find(sum) != set.end(),注意此处unordered_set用法,这个判断条件为真,即在set集合中找到了sum

两数之和

题目链接:LeetCode1. 两数之和

自己敲

没啥思路,暴力求解

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int> s;
        for(int i=0;i<nums.size();i++){
            if(nums[i]<=target){
                s.push_back(nums[i]);
            }
        }
        vector<int> result;
        for(int i=0;i<s.size()-1;i++){
            for(int j=i+1;j<s.size();j++){
                if(s[i]+s[j]==target){
                    result.push_back(i);
                    result.push_back(j);
                    return result;
                }
            }
        }
        return result;
    }
};

看题解

链接

本题我们需要一个集合来存放我们遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合

本题,我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适

再来看一下使用数组和set来做哈希法的局限。

  • 数组的大小是受限制的,而且如果元素很少,而哈希值太大会造成内存空间的浪费。
  • set是一个集合,里面放的元素只能是一个key,而两数之和这道题目,不仅要判断y是否存在而且还要记录y的下标位置,因为要返回x 和 y的下标。所以set 也不能用。

这道题目中并不需要key有序,选择std::unordered_map 效率更高

map用来做什么?

map目的用来存放我们访问过的元素,因为遍历数组的时候,需要记录我们之前遍历过哪些元素和对应的下标,这样才能找到与当前元素相匹配的(也就是相加等于target)

map中key和value分别表示什么?

这道题 我们需要 给出一个元素,判断这个元素是否出现过,如果出现过,返回这个元素的下标。

那么判断元素是否出现,这个元素就要作为key,所以数组中的元素作为key,有key对应的就是value,value用来存下标。

所以 map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。

在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。

过程如下:
在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
                return {iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {};
    }
};

总结

auto自动类型推导:使用auto关键字,编译器会根据初始化表达式的类型自动推导出变量的类型

auto x = 10; // x 被推导为 int
auto y = 3.14; // y 被推导为 double
auto str = "Hello, World!"; // str 被推导为 const char*

详见原文:【C++】auto关键字(C++11,超详细解析,小白必看系列)_c++ auto-CSDN博客

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值