算法入门打卡Day5
今日收获
- set容器的概念与使用
- map容器的概念与使用
- 了解迭代器
- auto关键字的功能
- 操作数字各位数的方法
学习时长:2.3h
正文
有效的字母异位词
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词(字母异位词是通过重新排列不同单词或短语的字母而形成的单词或短语,并使用所有原字母一次)。
class Solution {
public:
bool isAnagram(string s, string t) {
int arr[26] = {0};
for (int i = 0; i < s.size(); i++) {
arr[s[i] - 'a']++;
}
for (int i = 0; i < t.size(); i++) {
arr[t[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (arr[i] != 0) return false;
}
return true;
}
};
- 用一个数组储存字符串中字母出现的次数
- 不需要记住字母的ASCII码,可以巧妙地运用
s[i] - 'a'来将字母a对齐下标0的位置 - 两个字符串一加一减最后判断数组是否有非零元素即可
两个数组的交集(set)
给定两个数组 nums1 和 nums2 ,返回 它们的 交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。
示例:
输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
set容器介绍
和数学中的集合一样,C++中的集合set用于允许存储一组不重复的元素, 并且元素的值按照有序排列, set基于红黑树实现,支持高效的关键字查询操作, 可以用来检查一个给定关键字是否在set中。
无序集合unordered-set类似于集合(Set),但不会按照元素的值进行排序,而是由哈希函数的结果决定的。
multiset 则是一个用于存储一组元素,允许元素重复,并按照元素的值进行有序排列的集合。
(Tip:unordered_set有下划线,multiset没有下划线别写错了)
使用该容器需要引入头文件:
// 引入<unordered_set>头文件
#include <unordered_set>
// 引入set头文件
#include <set>
set容器基本操作:
// 创建一个存储整数的无序集合
unordered_set<int> mySet;
// 创建一个存储整数的set
set<int> mySet;
// 创建一个存储整数的 multiset
multiset<int> myMultiSet;
// 向集合中插入元素
mySet.insert(1);
mySet.insert(2);
mySet.insert(3);
//在集合中删除元素
mySet.erase(1);
//这里要注意,在multiset中,以上删除方式会删除容器中所有值为1的元素
mySet.erase(mySet.find(1)); //这种使用迭代器的方式就只会删除找到的第一个值为1的元素
find() 方法用于查找特定元素是否存在于集合中,如果 find() 方法找到了要查找的元素,它会返回指向该元素的迭代器,如果未找到要查找的元素,它会返回一个指向集合的 end() 的迭代器,表示未找到。通过比较find()方法返回的迭代器是否等于 end(),可以确定集合中是否有查找的元素。
// 判断元素是否在集合中, 只要不等于end(), 说明元素在集合中
if (mySet.find(i) != mySet.end()) {
}
解题
这里我们使用哈希表set:
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
unordered_set<int> index(nums1.begin(), nums1.end());
vector<int> result;
for (int &x : nums2) {
if (index.erase(x)) {
result.push_back(x);
}
}
return result;
}
};
- 创建 set 容器时可以利用迭代器初始化 unordered_set
index(nums1.begin(), nums1.end()); - 增强for循环(也叫范围for循环)简化写法,
for (int &x : nums2),定义一个循环变量x,每次循环会自动从nums2中取出一个元素,赋值给x(这里int是元素的类型,要和nums2的元素类型匹配),用引用节省内存空间 if (index.erase(x)),如果擦除了index里的x,则返回 擦除的个数 否则返回0
迭代器(iterator)
迭代器iterator提供了一种类似指针的接口,可以用来遍历访问容器(比如数组、集合)中的元素,并执行各种操作。
可以理解为,迭代器和下标运算符的作用一样,用来访问容器中的元素,并且迭代器可以从一个元素移动到另外一个元素。
迭代器都拥有名为begin()和end()的成员,表示指向第一个元素和最后一个元素的下一个元素的迭代器(尾后迭代器),如果容器为空,则begin和end返回的是同一个迭代器。
可以使用比较运算符来判断两个迭代器是否相等,如果迭代器想要从一个元素移动到另外一个元素,可以使用递增++运算符和递减--运算符,表示向前(后)移动一个位置。
通过解引用*可以获取迭代器所指的对象,下面的示例表示了vector的遍历。
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> myVector = {1, 2, 3, 4, 5};
// 使用迭代器遍历容器
// vector<int>::iterator it 用于创建一个能读取<vector>int 元素的迭代器it,最初指向begin()
// ++it表示迭代器的移动
for (vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
cout << *it << " "; // 通过解引用获取迭代器所指的对象
}
cout << endl;
return 0;
}
- 创建迭代器(iterator)必须在容器的作用域下(别忘了带上类型),迭代器可以用于访问
pair的first和second
map<int, int> iterator it = myMap.find(x); //假设myMap里有pair<int, int> = {x, y};
那么我可以用it->first访问x,用it->second访问y。
自动判断数据类型初始化变量(auto)
比如我们可以把上面的代码这样写:
auto it = myMap.find(x);
it->first;
it->second;
- 注意在C++11及以上版本有效
- 创建时一定要初始化,顺便一提const静态值也是创建时一定要初始化!
快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
- 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
- 如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 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) {
unordered_set<int> record;
while (1) {
if (n == 1) return true;
if (record.find(n) != record.end()) {
return false;
}
record.insert(n);
n = getSum(n);
}
}
};
- 由于题目明确表示只有最后是
1和循环两种情况,所以只需要考虑这两种即可 - set应用的部分不必多说,说说求每一位和的过程
- 如以上代码所示,利用一个
while(n)循环来对每一位数进行操作,千万不要再像以前傻傻地看着用例范围枚举。。。
两数之和(map)
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。
你可以按任意顺序返回答案。
map容器介绍
我们常常把map称之为映射,就是将一个元素(通常称之为key键)与一个相对应的值(通常称之为value)关联起来,比如说一个学生的姓名(key)有与之对应的成绩(value),它们是一一对应的,就好像一把钥匙开一扇门,在map中键是唯一的,也只有一个唯一的确定的值。
和 set 类似,unordered_map 的key值存储是无序的,底层实现为哈希表,查找速度更快,如果不需要排序而只是快速查找键对应的值,可以考虑使用。
map 和 multimap 的底层实现是红黑树,它的key值存储是有序的,如果需要对键值对进行自定义排序,可以考虑使用map。
使用映射容器需要引入头文件<unordered_map>或者<map>
// 引入unordered_map头文件,包含unordered_map类型
#include <unordered_map>
// 引入map头文件,包含map类型和multimap类型
#include <map>
想要声明map映射关系,需要指定键的类型和值的类型。
// 声明一个整数类型映射到整数类型的 无序映射
unordered_map<int, int> uMap;
// 声明一个将字符串映射到整数的map,可以这样声明:
map<string, int> myMap;
想要插入键值对key-value, 需要使用insert()函数或者使用[]操作符来插入。如果键不存在,[]操作符将会创建一个新的键值对,将其插入到map中,并将值初始化为默认值(对于整数来说,默认值是0)。
my_map.insert(pair<int, int>(1, 10)); //注意insert的时候需要用到pair类型
// 方式2:C++11 及以上支持——列表初始化(最简洁,实际开发常用)
my_map.insert({3, 30});
uMap[0] = 10;
uMap[10] = 0;
myMap["math"] = 100;
myMap["english"] = 80;
和set类似,可以使用find函数来检查某个键是否存在于map中,它会返回一个迭代器。如果键存在,迭代器指向该键值对,否则指向map的末尾。
if (myMap.find("math") != myMap.end()) {
// 键存在
} else {
// 键不存在
}
你可以使用范围for循环来遍历map中的所有键值对,进行各种操作。
for(const pair<int,int>& kv:umap) {
}
当使用范围for循环遍历map时,我们需要声明一个变量kv来存储每个键值对。这个变量的类型通常是pair类型,下面就让我们详细解释一下const pair<int,int>& kv:umap
const用于声明一个不可修改的变量,这意味着一旦变量被初始化,就不能再修改其值。常量通常用大写字母表示
因为const声明的变量一旦创建后就无法修改值,所以必须初始化。
而pair<int, int>定义了kv也就是键值对的数据类型是pair, C++中的pair类型会将两个不同的值组合成一个单元, 常用于存储键值对,创建pair的时候,也必须提供两个类型名,比如上面的pair对象,两个值的类型都是int, 在使用时通过first 和 second 成员来访问 pair 中的第一个和第二个元素, 它的 first 成员存储键,而 second 成员存储值。
pair 的使用示例:
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
// 定义一个 unordered_map(键是 int,值是 int)
unordered_map<int, int> umap = {{1, 10}, {2, 20}, {3, 30}};
// 遍历 umap,用 const pair<int,int>& kv 接收每个元素
for (const pair<int, int>& kv : umap) {
// 访问键:kv.first,访问值:kv.second
cout << "键:" << kv.first << ",值:" << kv.second << endl;
}
return 0;
}
解题
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
unordered_map<int, int> record;
for (int i = 0; i < nums.size(); i++) {
if (record.find(target - nums[i]) != record.end()) {
return {i, record[target - nums[i]]};
}
record[nums[i]] = i;
}
return {};
}
};
- 一开始我想到的是先把所有数组元素及其下标存入
unordered_map<int, int> record;,再遍历数组看是否有配对的数,但是这样无法避免重复找到自己,所以最终选择先遍历再将自己加入record中。 - 注意这里的非
if情况要加上return值。 - 数组的空
ruturn值可以用{}表示。

浙公网安备 33010602011771号