非科班学习算法day5 | LeetCode242: 有效的字母异位词,Leetcode349: 两个数组的交集,Leetcode202: 快乐数,Leetcode1:两数之和
目录
介绍
包含LC的四道题目,还有相应概念的补充。
相关图解和更多版本:
代码随想录 (programmercarl.com)https://programmercarl.com/#%E6%9C%AC%E7%AB%99%E8%83%8C%E6%99%AF
一、基础概念补充:
1.哈希表
哈希表(Hash Table),也称为散列表,是一种数据结构,它实现了关联数组的抽象数据类型,能够以键值对的形式存储数据。哈希表通过一个特定的函数(哈希函数)将键转换为索引,从而实现快速的查找、插入和删除操作。
哈希表的优点是:
- 平均时间复杂度:在理想情况下,插入、删除和查找操作的时间复杂度可以达到O(1)。
- 动态大小调整:可以根据需要动态调整表的大小,以维持高效的性能。
总结就是:见到需要查找指定元素在不在范围内的时候首先想到哈希表
哈希表的常见 结构:
std::unordered_map
: 键值对存储,键唯一,非有序。std::unordered_set
: 仅存储键,键唯一,非有序。std::unordered_multimap
: 键值对存储,允许键重复。std::unordered_multiset
: 仅存储键,允许键重复。- 数组也是哈希表
2.c++11新特性(部分)
-
范围for循环 (
使用for-each loop
)for(auto& item : container)
语法遍历容器或数组,使代码更加简洁。 -
自动类型推导 (
auto
和decltype
)auto
关键字允许编译器自动推导变量的类型,简化了类型声明,特别是在使用复杂类型时。decltype
用于获取一个表达式的类型,常用于模板编程和函数返回类型推导。
二、LeetCode题目
1.LeetCode242:有效的字母异位词
题目链接:242. 有效的字母异位词 - 力扣(LeetCode)
题目解析
分析题目需求可知:找A里出现的东西B里是不是都出现了,且次数相同!
符合哈希表的应用场景:找有没有。确定了方法之后,我们就要看选择什么容器来作为哈希表。如果无脑选择map肯定是可以的,分别记录值和值出现的次数。但是要知道相对来说数组的消耗性最小,也最简单。这里符合使用数组的条件(因为字母只有26个)。
具体的实现还是依靠循环来查找。
c++代码如下:
class Solution {
public:
bool isAnagram(string s, string t)
{
//建立数组,包含26个字母
int nums[26] = {0};
for (int i = 0; i < s.size(); i++)
{
//记录相对位置的字母的数量增加情况
nums[s[i] - 'a']++;
}
//遍历字符串t
for(int i = 0; i < t.size(); i++)
{
//记录相对位置字母的减少情况
nums[t[i] - 'a']--;
}
//判断异位词
for(int i = 0; i < 26; i++)
{
if(nums[i] != 0)
{
return false;
}
}
//没有判断分支就返回真
return true;
}
};
注意点1:记录方式要注意,因为这里字母的记录方式是ASCII码,不过在本题中,不需要直到具体的值,所以只需要记录相对值就可以,所以采用了-‘a’的操作。
注意点2:注意最后的检验条件,两个字符串里的元素数量必须完全相同。
这里为了节省空间,所以在一个就在哈希表中操作,最后查看存储的数;如果对空间没有限制,我一开始做的是,用两个表,分别记录,然后因为元素相对于a的位置是一样的,然后比对两个表的值,但是这样明显不够简洁。
2.Leetcode349:两个数组的交集
题目链接:349. 两个数组的交集 - 力扣(LeetCode)
题目解析
这题我记录三种不同的方法,这里是方法1,基于上一道题,我们知道在处理有限元素的问题时,完全可以采用数组作为哈希表。本处就是基于leetcode的备注求解。
C++代码如下:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
int c_nums1[1001] = {0};
int c_nums2[1001] = {0};
for(int i = 0; i < nums1.size(); i++)
{
c_nums1[nums1[i] - 0]++;
}
for(int i = 0; i < nums2.size(); i++)
{
c_nums2[nums2[i] - 0]++;
}
vector<int> result;
for(int i = 0; i < 1001; i++)
{
if(c_nums1[i] != 0 && c_nums2[i] != 0)
{
result.push_back(i);
}
}
return result;
}
};
这种写法就比较呆,也是记录相对位置信息,只不过这里是数字,其实就会引出来下面比较简洁的写法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//声明记录hash表
int count[1001] = {0};
//声明unordered_set
unordered_set<int> us1;
//声明记录结果unordered_set
unordered_set<int> us;
//遍历数组1
for(auto num : nums1)
{
us1.insert(num);
count[num]++;
}
//遍历顺组2
for(auto num : nums2)
{
if(count[num] != 0)
{
us.insert(num);
}
}
//声明返回vector
vector<int> result;
result.assign(us.begin(), us.end());
return result;
}
};
注意点1:这里用了auto关键字,这是c++11引入的新特性可以自动推导类型,有两个地方比较适用:
1.初始化就赋值的情况,比如
auto x = 10;
2.对于STL中vector等容器的迭代器,在初始化的时候可以使用auto关键字,很方便
注意点2:for的范围基遍历,这也是c++11引入的新特性,可以完全遍历全部元素,但是不适用于仅仅遍历部分元素!
注意点3: 这里使用set的作用是去重,去重的作用非常明显,能大大提升效率,但是最后需要返回的是vector,所以要利用迭代器复制一份给vector。
但是还是不够简洁,而且依托于数组的思维,如果这里没有限定,那么我们能不能写呢,所以有了完全借助set的方法:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2)
{
//定义数组1的unordered_set
unordered_set<int> c_nums1(nums1.begin(), nums1.end());
//定义储存结果的unordered_set
unordered_set<int> result;
for(auto num : nums2)
{
if(c_nums1.find(num) != c_nums1.end())
{
result.insert(num);
}
}
vector<int> re (result.begin(), result.end());
return re;
}
};
注意点1:这里题目明确说了不要求返回顺序,那么相对来说unordered_set是最好的选择,这也是进一步优化的点。
注意点2:首先我们用unordered_set建立哈希表,记录数组1的情况,在遍历数组2的时候就对照哈希表来看是否符合,进而确定是不是要进行更新结果。这种思路对于两个数组或者字符串来说相当好用。
3.Leetcode202:快乐数
题目解析
难点一:可以知道是要寻找是否出现1,那么怎么建立哈希表
答:还是用set来记录
难点二:怎么进行位数上的操作
答:取余和整除
这里关于官方给出的很多情况的考虑我觉得没有必要,我仅仅是根据题目当中的意思来翻译,没有1,就会出现循环
C++代码如下:
class Solution {
public:
int sumnum(int n)
{
int sum = 0;
while(n)
{
//操作n
sum += (n % 10) * (n % 10);
n /=10;
}
return sum;
}
bool isHappy(int n)
{
//设置set接收计算结果
set<int> result;
while(true)
{
int a = sumnum(n);
if (a == 1)
{
return true;
}
if(result.find(a) != result.end())
{
return false;
}
else
{
result.insert(a);
}
n = a;
}
return false;
}
};
注意点1:为了代码的可读性,先创建了一个求和的成员函数,这里就充分用到了整除和取余的方法来帮助我们获取每一位上的值,这里的循环条件设定就是,除到最后为0,结束循环
注意点2:接下来的计算中,遇到1,返回真;没有1,判断是否已经出现过的值。在最后不要忘记将当前数字更新为计算后的值,才能实现循环更新。
4.Leetcode1:两数之和
题目解析
最直接的想法,用两个循环嵌套一下,结束战斗,很显然复杂度O(n^2),我用python展示一下
python代码如下:
class Solution(object):
def twoSum(self, nums, target):
"""
:type nums: List[int]
:type target: int
:rtype: List[int]
"""
#寻找两数之和都等于target的数
for i in range(len(nums)):
num1 = nums[i]
for j in range(i+1, len(nums)):
num2 = nums[j]
if num1 + num2 == target :
return (i, j)
但是对于这种形式,最好的一定不是这种O(n^2)的方法,这里我们也可以将问题理解成:对于一个确定的值,去寻找一个数,和它加和等于目标值,这正是运用hash的场景,那么问题首先还是:用什么容器?
这里题目要求的是要下标值,所以就要利用map来进行有序的记录,将下标作为键,将对应的值作为键值。
c++代码如下:
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target)
{
//建立map接受结果
unordered_map<int, int> m;
for(int i = 0; i < nums.size(); i++)
{
auto it = m.find(target - nums[i]);
if(it != m.end())
{
return {it->second , i};
}
else
{
m.insert(pair<int, int>(nums[i], i));
}
}
return {};
}
};
总结
打卡第5天,坚持!!!