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

day6 记录代码随想录

哈希表

哈希表是一种可以用来快速判断元素是否出现的集合。

生成哈希表的过程:将元素映射为哈希表上的索引,然后可以通过查询索引下标快速查找元素。

映射函数被称为哈希函数,通过hashcode的特定编码方式将元素转化为数值。

哈希碰撞:

如果元素数量大于哈希表的大小,就会导致几个元素映射到同一个索引下标处,这一现象称为哈希碰撞

应对哈希碰撞通常由两种方法,拉链法和线性探测法。

拉链法:

拉链法是将发生冲突的元素储存在对应元素下标的链表中,由此可以通过索引找到。

线性探测法:

线性探测法是将冲突的元素放到对应元素的下一个空位,因此哈希表一定要比元素数组要大。

常见的三种哈希结构

当想要使用哈希法来解决问题的时候,一般会选择三种数据结构:数组,set(集合),map(映射)

建议后补一下树的相关知识点。

总结:数据量小(1000以内)或者跨度小用数组,判断是否出现时数据量大用set,数据对集合用map,首先考虑用unordered ,重复记录用multi,有序不重复用 set/map。

第一题 242.有效的字母异位词 

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

示例 1: 输入: s = "anagram", t = "nagaram" 输出: true

示例 2: 输入: s = "rat", t = "car" 输出: false

说明: 你可以假设字符串只包含小写字母。

题目链接:力扣题目链接

暴力解法:

直接排序,然后对比两个数组是否一样就可以,代码如下:

class Solution {
public:
    bool isAnagram(string s, string t) {
        sort(s.begin(),s.end());
        sort(t.begin(),t.end());
        return s==t;
    }
};

严格来说这不算暴力解法,时间复杂度为O(nlogn),因为真正暴力的是使用双重for循环来做,时间复杂度为O(n^2)。

哈希表解法:

因为字母只有26个,数据量比较小,可以考虑使用数组来做哈希表。

初始化一个26大小的数组A,初始化为0,遍历一遍s数组,每遍历一个字母,就把A中对应下标的元素+1。

那么如何将26个字母映射为26个字符下标呢?

字母本身在系统中的存储方式是ASCII码,且是相邻的,A是65,然后依次增加,a是97,依次增加。

本题中只考虑小写字母,那么只需要把每个元素 - 'a' (或者-97)就可以得到0-25的数字,可以直接存到对应下标。

然后遍历另外一个数组,每扫描到一个元素就对A中对应下标的元素-1。

最后,遍历一遍A,如果存在不为0的元素,那么直接输出false就可以,否则输出true。

代码如下:

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

因为这里只用了一重循环,所以时间复杂度为O(n)。

第二题 349. 两个数组的交集 

题意:给定两个数组,编写一个函数来计算它们的交集。

题目链接:力扣题目链接

暴力求解:

首先考虑一下暴力求解,直接两重for循环,分别遍历nums1和nums2,找到重复元素就存到新的数组中。

为了确保结果是去重的,可以定义一个标识flag=0,在二重循环中如果遍历到了相同元素,就将flag=1,然后将这个相同元素删去,遍历完一遍之后,如果flag==1,就将对应的nums[i]存到数组result中。同时下一次再有相同的元素出现时,由于nums2中的元素已经删去了,就不会再记录。如此就实现了功能。直接输出result即可。

result.push_back(A),是vector容器的用法语句,将A存到result的最后面。

代码如下:

// 暴力
class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        vector<int> result;
        int flag = 0;
        for (int i = 0; i < nums1.size(); i++) {
            flag = 0;
            for (int j = 0; j < nums2.size(); j++) {
                if (nums1[i] == nums2[j]) {
                    flag = 1;
                    nums2[j] = -1;
                }
            }
            if (flag == 1)
                result.push_back(nums1[i]);
        }
        return result;
    }
};

哈希表解法:

判断元素的重复与否,可以优先考虑使用哈希表解决,由于数据范围较大,采用set(集合)的方法,且题目没要求顺序和重复,那么采用 unordered_set

定义两个unordered_set 集合,定义语句:unordered_set<int> result;

定义并初始化语句:unordered_set<int> set_nums1(nums1.begin(),nums1.end());

遍历一遍nums2,如果元素在set_nums1中存在,就将其存入result。

此处代码有几个需要注意的点:

for(int num : nums2) 语句是将nums2中的元素依次赋给num进行循环操作。

if( set_nums1.find(num) != set.nums1.end() ) 是判断num在不在set_nums1中。

其中set_nums1.find()是寻找语句,会依次遍历set,如果找到会返回一个迭代器指向num,如果没找到,会返回一个指向最后一个元素的下一个地址的迭代器,其结果与set.nums1.end()相等。

result.insert(num)是将num插入到result集合中。

最后因为函数需要返回一个vector<int>,那么需要新建一个vector<int>,将result存到其中,然后返回即可。vector<int>  AA(result.begin(),result.end());

具体代码如下:

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

时间复杂度为O(n)

第三题 202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」定义为:对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和,然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。如果 可以变为  1,那么这个数就是快乐数。

如果 n 是快乐数就返回 True ;不是,则返回 False 。

示例:

输入:19
输出:true
解释:
1^2 + 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1

提示:

  • 1 <= n <= 2^31 - 1

题目链接:力扣题目链接

解题思路:

n最大值为2^31 - 1,大约是21亿多,10位数,那么计算过程中的最大值为10*9*9=810,数值比较小,如果一个数不是快乐数,那么其平方和必定会重复出现,问题就转化为判断其平方和是否重复出现或者是否为1。

这里可以定义一个求平方和的函数tosum(),对n进行对10求余就可以得到最后一位数字,累加其平方,然后n=n/10,循环到n/10==0就可以了。

在主函数中,只需要定义一个unordered_set集合,反复调用tosum,判断输出值是否等于1、是否存在于集合中,否则存入集合中。

代码如下:

class Solution {
public:
    int tosum (int n) {
        int sum = 0;
        while(n/10 != 0) {
            sum += (n%10)*(n%10) ;
            n = n/10;
        }
        sum += n*n ;
        return sum;
    }

    bool isHappy(int n) {
        unordered_set<int> table;
        while(1) {
            n = tosum(n);
            if ( n == 1)
                return 1;
            else if (table.find(n) != table.end())
                return 0;
            else 
                table.insert(n);
        }
    }
};

时间复杂度为O(logn)

第四题 1. 两数之和 

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。

示例:

给定 nums = [2, 7, 11, 15], target = 9

因为 nums[0] + nums[1] = 2 + 7 = 9

所以返回 [0, 1]

题目链接:力扣题目链接

解题思路:

本题涉及到两个变量,数字和数组下标,这显然是对应的数值对,优先考虑map映射。因为无序且不重复,所以使用unordered_map。

无序map映射的定义方式:unordered_map<int,int> AA;

依据题意,需要判断nums[i] + nums[j] = target,考虑公式给出的对应关系,只需要遍历nums,判断 target - nums[i] 是否存在于map中,如果不存在于map中,就把 num[i] 的值 和 下标 i 存入到map对中,如果存在,就输出 i 和 找到的下标。

需要注意几点

  • map的存储方式是 { key , value } 对
  • map.find()的返回值为对应的key的迭代器
  • 如果想要返回对应的value,语句为:map.key->second
  • map存值是成对存入的,语句:map.insert(pair<int,int>(key,value));
  • 通过value找key为value->first;

具体代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> map;
        for(int i = 0 ; i < nums.size() ; i++ ) {
            if ( map.find(target-nums[i]) != map.end() )
                return { i , map.find(target-nums[i])->second };
            map.insert(pair<int,int>(nums[i],i));
        }
        return {};
    }
};

这里map.find()返回值为一个迭代器,可以赋值给iter操作,但是迭代器的格式不固定,可以使用auto关键字定义其格式,修改后代码如下:

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> map;
        auto iter = map.find(target-nums[i]);
        for(int i = 0 ; i < nums.size() ; i++ ) {
            if ( iter != map.end() )
                return { i , iter->second };
            map.insert(pair<int,int>(nums[i],i));
        }
        return {};
    }
};

总结:数组、set、map是实现哈希表的三种数据结构,基于不同的条件选择不同的方法来实现功能,其中各类方法的语句语法相对来说复杂多样,需要多多练习。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值