代码随想录算法训练营第六天|242.有效的字母异位词 349. 两个数组的交集 202. 快乐数 1. 两数之和

文章通过多个编程题目的解决方案,介绍了哈希表在解决字符串比较、交集查找、快乐数判断以及两数之和问题中的应用。哈希表能提供快速的查找和判断,通过哈希函数将元素映射到数组下标,利用拉链法和线性探测法解决哈希碰撞问题。文章还讨论了不同数据结构如set和unordered_set在解决特定问题时的效率和适用场景。
摘要由CSDN通过智能技术生成

先了解一下什么是哈希表

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

所以数组就是哈希表

盗个卡哥的图
哈希表中关键码就是数组的索引下标,然后通过下标直接访问数组中的元素,如下图所示:
在这里插入图片描述

哈希表能解决的问题:

一般哈希表都是用来快速判断一个元素是否出现集合里。
如果要查询一个名字是否在这所学校里,要枚举的话时间复杂度是O(n),但如果使用哈希表的话, 只需要O(1)就可以做到。

实际上我连枚举都没咋用过。。。

将学生姓名映射到哈希表上就涉及到了hash function ,也就是哈希函数。

哈希函数如下图所示,通过hashCode把名字转化为数值,一般hashcode是通过特定编码方式,可以将其他数据格式转化为不同的数值,这样就把学生名字映射为哈希表上的索引数字了。
在这里插入图片描述

哈希碰撞 数据结构上叫冲突

如果学生的数量大于哈希表的大小怎么办,此时就算哈希函数计算的再均匀,也避免不了会有几位学生的名字同时映射到哈希表 同一个索引下标的位置。

可以用拉链法和线性探测法解决

拉链法,就是在冲突的位置加个链表
在这里插入图片描述在这里插入图片描述其实拉链法就是要选择适当的哈希表的大小,这样既不会因为数组空值而浪费大量内存,也不会因为链表太长而在查找上浪费太多时间

而使用线性探测法就是需要有足够的表空间,来存放那些数据

哈希表教程

常用的三种哈希结构
数组,
set
map


题目链接

异位词的判断
判断两个字符串组成的字母是否相同,每个字母出现的次数是否一样,但位置可以不一样
比较好的暴力思维
第一种:先排序,用sort函数,然后再逐一对比,这就可以达到O(nlogn)了.

class Solution {
public:
    bool isAnagram(string s, string t) {
        //暴力解法
        //检验所给字符串的长度是否一致,这条你在考试的时候想不到也没事
        if(s.size()!=t.size())return false;
        //先排序
        sort(s.begin(),s.end());
        sort(t.begin(),t.end());
        for(int i=0;i<=s.size();i++){
            if(s[i]!=t[i])return false;
        }return true;

    }
};

第二种暴力思路,我也不知道这叫啥,枚举?
自己还没学过枚举…
就是建立两个数组,用来记录每个字符串中26个字母里的每个字母的出现次数,就是两个循环.最后再注意对比这两个数组的次数,感觉也是从无序到有序

class Solution {
public:
    bool isAnagram(string s, string t) {
        //暴力解法2
        //检验所给字符串的长度是否一致,这条你在考试的时候想不到也没事
        if(s.size()!=t.size())return false;
        //先建立两个数组
        vector <int> ss(26,0);//容器咋建数组又忘了
        vector <int> tt(26,0);
        //从头遍历字符串,统计每个字母出现的次数
        for(int i=0;i<s.size();i++){
            //ASCII码里,先是大写字母,然后是小写字母
            ss[s[i]-'a']++;
            tt[t[i]-'a']++;
        }
        for(int i=0;i<ss.size();i++){
            if(ss[i]!=tt[i])return false;
        }return true;

    }
};

有点牺牲空间换时间的意思但是这里的数组是固定的26,不随着字符串的长度儿变化,时间复杂度O(n)。空间复杂度O(1)
要记住时间复杂度不是开辟的空间大小比n大就是O(n)了而是与输入规模n有关的是O(n)否则是个无关常量不管大小为多少都为O(1)

总结这两种暴力思路我认为都是讲字符串先排好队(无序到有序)然后再进行比较

然后就是哈希的方法
看了看,跟第二个暴力也差不多少啊。。。。
就是处理方法不是比对相等,而是比对数组里的元素是否为0
这算是用数组下标来访问数组的元素,我想关键字是字符串的字符的值吧。
把字符映射到数组也就是哈希表的索引下标上

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

    }
};


用了上面之前的暴力思想,把第一个数组可能出现的值放在统计数字出现次数的数组里,然后在第二个数组里检验,如果第二个数组中出现的数字在第一个数组里也出现了,那么这个数字就存在与交集, 但要特别注意的是要避免重复,两个数组都会有重复的数字,而在统计 交集的时候重复只能算一个,所以赋值只能是1,在比对第二个数组的时候,遇到了相同的数字就得改变其在记录数组的值,以免重复记录。

这个代码里用到了C++的容器的push_back()函数,将某数或某元素插入到数组末尾。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        //自己的想法就是还是用一个1001个数的数组来记录两个数组的里的值
        //有点哈希的思想
        vector<int> record(1001,0);
        vector<int> result;
        for(int i=0;i<nums1.size();i++){
            record[nums1[i]]=1;
        }
        for(int i=0;i<nums2.size();i++){
            if(record[nums2[i]]==1)
            //别忘了nums2里也会有重复的元素
           { record[nums2[i]]=2;
            result.push_back(nums2[i]);}
        }return result;
        
    }
};

哈希:容器不能再选数组了
而且如果哈希值比较少、特别分散、跨度非常大,使用数组就造成空间的极大浪费

此时就要使用另一种结构体了,set ,关于set,C++ 给提供了如下三种可用的数据结构:

std::set   元素不能重复  有序集合
std::multiset   有序多重集合   元素可以重复
std::unordered_set   无序非重复集合   查找比set更快

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

本题交集结果不用考虑顺序

unordered_set 是 C++ 中的一个容器,它是哈希表的一种实现。与 set 相比,unordered_set 具有更快的查找速度,但是对于元素的顺序没有保证。unordered_set 中的元素是无序的,而且不能有重复的元素。

unordered_set 支持以下操作:

  • 插入元素:insert(key)
  • 删除元素:erase(key)
  • 查找元素:find(key),如果元素存在,则返回元素的迭代器,否则返回 unordered_set::end()
  • 判断元素是否存在:count(key),如果元素存在,则返回 1,否则返回 0;
  • 清空容器:clear()
  • 获取容器大小:size()
  • 判断容器是否为空:empty()

unordered_set 的底层实现是哈希表,因此插入、删除和查找元素的时间复杂度都是常数级别的 O(1),但是需要注意的是,如果哈希表出现冲突,这些操作的时间复杂度可能会退化为 O(n),其中 n 是哈希表中元素的个数。

给第一个数组的元素放入unordered_set里去重,
然后用第二个里的每个值与unordered_set 里的进行对比,如果这个值

   if (nums_set.find(num) != nums_set.end()) //这个啥意思啊

这段代码用来判断 nums2 中的元素是否在 nums1 中出现过,具体实现是通过在 nums_set 中查找是否存在该元素来判断。unordered_set 的 find 方法会返回指向存储该元素位置的迭代器,如果元素不存在,会返回一个指向 unordered_set 结尾的迭代器 end(),因此 nums_set.find(num) != nums_set.end() 的意思是:在 nums_set 中查找 num 元素,如果返回的迭代器不等于 end(),说明 num 存在于 nums_set 中,也就是 nums1 中出现过。

  for (int num : nums2)这个啥意思啊

这是C++11中的一种for循环语法,称为range-based for循环,也叫foreach循环,它可以用来遍历一个容器(如数组、vector等)中的所有元素。在这个例子中,nums2是一个容器(可能是数组或vector等),for循环会依次取出其中的每一个元素,赋值给变量num直接把数组里的值取出来赋给num了,而不是下标了。因此,这行代码的意思是依次取出nums2中的每一个元素,对于每一个元素,执行循环体中的语句。

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
      unordered_set<int>res;
      unordered_set<int>num(nums1.begin(),nums1.end());
      for(int i:nums2){
          if(num.find(i)!=num.end())
          res.insert(i);
      }return vector<int>(res.begin(),res.end());
    }
};


题目链接

主要是有一个数学上的问题,就是他这个会不会无限循环到无穷大
要从最大m位数即每位都是9,那么三位数就是243,4位是81*4.。。。。
这个会进入一个循环

在这里插入图片描述进入循环的就会出不去,这时我们选择用set来收集这些值,若有重复的就说明这个数不是快乐数,应该返回false

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) {
        set<int>num;
          int m=getsum(n);
        while(m!=1){
            if(num.find(m)!=num.end())//判断这个数在没在集合里
            return false;
            else {num.insert(m);
            m=getsum(m);//算新求的和的各位数的平方和,一直循环下去
            }
        }return true;

    }
};


题目链接

给你一个数,让你从数组里找出来两个数的和等于所给的这个数
1.这个数组是无序的,数组里允许有重复的值
如果有序还可以用这个前后指针,并且判断和的大小来决定移动哪个指针

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        //sort(nums.begin(),nums.end());
        int i=0,j=nums.size()-1;
        while(i<j){
        if(nums[i]+nums[j]==target)
            return {i,j};
        else if(nums[i]+nums[j]<target)
            i++;
            else j--;
        
    }return {i,j};
    }
    
};

无序的话用暴力就只能双重for循环了,这个题不包括用数组里的一个数加两次的情况

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
       int i,j;
       for( i=0;i<nums.size()-1;i++)
            for(int j=i+1;j<nums.size();j++)
                if(nums[i]+nums[j]==target)return {i,j};
                return {i,j};//不return 两次不通过啊,不知道为啥
      
       } 
    
};

时间复杂度O(n^2)
空间复杂度O(1)

1.为什么会想到用哈希表
2.哈希表为什么用map
3.本题map是用来存什么的
4.map中的key和value用来存什么的

当需要查询一个元素是否出现过,或者一个元素是否在集合里的时候

需要用一个集合来存放遍历过的元素,然后在遍历数组的时候去询问这个集合,某元素是否遍历过,也就是 是否出现在这个集合。
key来存元素,value来存下标,那么使用map正合适
map是键值对,一个量保存下标,一个变量保存值
map也和set似的有三种类型
unordered_map为无序的,

所以哈希表的应用在容器里有三种,数组,set,map

map的一些基本用法
map{key 数据元素,value 值(数组元素对应的下标)}
map会自动按键值(key )大括号中的第一部分来排列顺序
其中key用迭代器->first
value 用迭代器->second

C++ STL之映射map的使⽤
map 是键值对,⽐如⼀个⼈名对应⼀个学号,就可以定义⼀个字符串string 类型的⼈名为“键”,学
号int 类型为“值”,如map<string, int> m; 
按照数组来说(前面是数组里的元素,下面是数组里的下标)
当然键、值也可以是其它变量类型~ 
map 会⾃动将所有的键值对按照键从⼩到⼤排序, 

map 使⽤时的头⽂件#include <map> 以下是map 中常⽤的⽅法:

#include <iostream>
#include <map>
#include <string>
using namespace std;
int main() {
	map<string, int> m; // 定义⼀个空的map m,键是string类型的,值是int类型的
	
	m["hello"] = 2; // 将key为"hello", value为2的键值对(key-value)存⼊map中
	
	cout << m["hello"] << endl; // 访问map中key为"hello"的value, 如果key不存在,则返回0
	cout << m["world"] << endl;
	m["world"] = 3; // 将"world"键对应的值修改为3
	m[","] = 1; // 设⽴⼀组键值对,键为"," 值为1
	
// ⽤迭代器遍历,输出map中所有的元素,键⽤it->first获取,值⽤it->second获取
	for (auto it = m.begin(); it != m.end(); it++) {
	cout << it->first << " " << it->second << endl;
	}
	
	// 访问map的第⼀个元素,输出它的键和值
	cout << m.begin()->first << " " << m.begin()->second << endl;
	
	// 访问map的最后⼀个元素,输出它的键和值
	cout << m.rbegin()->first << " " << m.rbegin()->second << endl;

通俗的来讲整个做法就是每从数组里遍历一个数,就看看map里有没有能与之加一起为target的数。

pair<int, int>(nums[i], i):键值对,用pair来表示。<int,int>可以省略
pair是STL库提供的一个模板类,用于将两个值组合在一起,即一个键值对。
其中,pair<int, int>表示键值对中键和值的数据类型都是int,(nums[i], i)表示具体的键值对,
即nums[i]为键,i为值。

时间复杂度为O(n)
不用担心会漏掉元素,因为之前map里没有的元素后续遍历终会遇到,只不过位置换位了一下,去map里相遇了。不会错过只会迟到。

题目要求不能有重复的元素,即你也不能自己与自己匹配,那你匹配完了就不能再录入map里了
这个题就说了,只会有一种答案所以不用担心会有map里的元素被多次匹配的情况

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 it=map.find(target-nums[i]);//迭代器,我就把它当成指针。。。
          if(it!=map.end()){//返回值,就是位置。
              return {it->second,i};
          }else map.insert(pair(nums[i],i));
      }
        return{};
       } 
    
};
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
代码随想录算法训练营是一个优质的学习和讨论平台,提供了丰富的算法训练内容和讨论交流机会。在训练营中,学员们可以通过观看视频讲解来学习算法知识,并根据讲解内容进行刷题练习。此外,训练营还提供了刷题建议,例如先看视频、了解自己所使用的编程语言、使用日志等方法来提高刷题效果和语言掌握程度。 训练营中的讨论内容非常丰富,涵盖了各种算法知识点和解题方法。例如,在第14天的训练营中,讲解了二叉树的理论基础、递归遍历、迭代遍历和统一遍历的内容。此外,在讨论中还分享了相关的博客文章和配图,帮助学员更好地理解和掌握二叉树的遍历方法。 训练营还提供了每日的讨论知识点,例如在第15天的讨论中,介绍了层序遍历的方法和使用队列来模拟一层一层遍历的效果。在第16天的讨论中,重点讨论了如何进行调试(debug)的方法,认为掌握调试技巧可以帮助学员更好地解决问题和写出正确的算法代码。 总之,代码随想录算法训练营是一个提供优质学习和讨论环境的平台,可以帮助学员系统地学习算法知识,并提供了丰富的讨论内容和刷题建议来提高算法编程能力。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [代码随想录算法训练营每日精华](https://blog.csdn.net/weixin_38556197/article/details/128462133)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值