242.有效的字母异位词

题目链接

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

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:

输入: s = “anagram”, t = “nagaram”
输出: true
示例 2:

输入: s = “rat”, t = “car”
输出: false

数组哈希

字母的范围从a到b只有26个,可以使用数组来实现哈希表,扫描s并记录每一个字母的出现频率,扫描t进行比较

注意:统计字母出现频率的数组需要进行初始化,否则在进行判断时会出错

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
bool isAnagram(string s, string t)
{
if (s.length() != t.length())//长度不等必然不符合条件
return false;
int a[26] = {0};
for (auto i : s)
{
a[i - 'a']++;
}
for (auto i : t)
{
if (a[i - 'a'] > 0)
a[i - 'a']--;
else
return false;
}
return true;
}

349. 两个数组的交集

题目链接

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]
示例 2:

输入:nums1 = [4,9,5], nums2 = [9,4,9,8,4]
输出:[9,4]
解释:[4,9] 也是可通过的

使用unordered_set

思路

​ 要求输出的结果中每一个元素时唯一的且不要求顺序,考虑使用unordered_set

​ 先将一个数组初始化unordered_set,比较另一个数组中的每一个元素是否存在于set,如果存在则放入一个unordered_map,由于其特性,他会自动去除重复的元素

1
2
3
4
5
6
7
8
9
10
11
vector<int> intersection(vector<int> &nums1, vector<int> &nums2)
{
unordered_set<int> uset(nums1.begin(), nums1.end());//将nums1初始化unordered_set
unordered_set<int> res;
for (auto i : nums2)
{
if (uset.find(i) != uset.end())//uset.find(i) == uset.end() 代表没有找到这个元素
res.insert(i);
}
return vector<int>(res.begin(), res.end());//将unordered_set转化为vector格式
}

第202题. 快乐数

题目链接

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

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
示例 2:

输入:n = 2
输出:false

思想

在重复进行替换正整数的过程中,只会出现两个结果:进入无限循环;或者结果为1

而当相同的数字出现两次及以上的时候,说明这个过程进入了无限循环。我们以此为依据,建立一个哈希表

将每次替换后的数字在哈希表中查找,如果曾经出现过,说明进入了无限循环,这个数不是快乐数;如果没有出现过,我们将数字录入哈希表中,再次进行替换过程,直至替换结果为1(快乐数)或者新数字在哈希表中出现(非快乐数)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//将数字n替换为每个位置上数字的平方和
int Change(int n)
{
int sum = 0;
while (n)
{
sum += (n % 10) * (n % 10);
n /= 10;
}
return sum;
}
//判断是否为快乐数
bool isHappy(int n)
{
unordered_set<int> uset;
while (n != 1)
{
if (uset.find(n) != uset.end())
{
return false;
}
else
{
uset.insert(n);
}
n = Change(n);
}
return true;
}

1. 两数之和

题目链接

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

你可以按任意顺序返回答案。

示例 1:

输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

思路

遍历nums数组,查找umap哈希中是否存在一个key恰好为target-nums[i]的元素,如果有则为答案,如果没有,则将遍历到的元素存入umap当中,注意:umap存入元素时,key值设为元素的值的大小,value设置为元素的数组下标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
vector<int> twoSum(vector<int> &nums, int target)
{
unordered_map<int, int> umap;
for (int i = 0; i < nums.size(); i++)
{
if (umap.find(target - nums[i]) != umap.end())
{
return {umap[target - nums[i]], i};
}
else
{
umap[nums[i]] = i;
}
}
return {};
}

454.四数相加

题目链接

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:

  1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
  2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
    示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

思路

本题与1.两数之和的思路相似,只需要将前两个数组看成一部分,后两个数组看成另一部分,就可以拆解为两数之和的问题,进行求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
int fourSumCount(vector<int> &nums1, vector<int> &nums2, vector<int> &nums3, vector<int> &nums4)
{
unordered_map<int, int> sum;
int cnt = 0;
for (auto i : nums1)
for (auto j : nums2)
{
sum[i + j]++;
}
for (auto i : nums3)
for (auto j : nums4)
{
int temp = i + j;
auto it = sum.find(-temp);
if (it != sum.end())
{
cnt += it->second;
}
}
return cnt;
}

383.赎金信

题目链接

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = “a”, magazine = “b”
输出:false
示例 2:

输入:ransomNote = “aa”, magazine = “ab”
输出:false
示例 3:

输入:ransomNote = “aa”, magazine = “aab”
输出:true

思路

与242.有效的字母异位词相似,不同的是ransomNote中的所有字母都在magazine存在即可,不需要后者的字母都在前者中存在

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
bool canConstruct(string ransomNote, string magazine)
{
int cnt[26]={0};
for (auto i : magazine)
{
cnt[i - 'a']++;
}
for (auto i : ransomNote)
{
if (cnt[i - 'a'])
{
cnt[i - 'a']--;
}
else
{
return false;
}
}
return true;
}

15.三数之和

题目链接

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

双指针法

这道题的哈希解法比较困难,去重逻辑较为复杂,使用双指针法进行求解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
vector<vector<int>> threeSum(vector<int> &nums)
{
vector<vector<int>> res;
sort(nums.begin(), nums.end());//先对nums进行排序,使相同的数字排列在一起,为之后的去重做好准备
for (int i = 0; i < nums.size(); i++)
{
if (nums[0] > 0)
break; // 排序后最小的数大于0,无论如何都无法三个数相加等于零
if (i > 0 && nums[i] == nums[i - 1])
continue; // i去重,去除两个连续的相同i的情况
int left = i + 1;
int right = nums.size() - 1;

while (left < right)
{
int sum = nums[i] + nums[left] + nums[right];
if (sum == 0)
{
res.push_back({nums[i], nums[left], nums[right]});
left++;
// 去重,防止left与之前的相同
while (left < right)
{
if (nums[left] == nums[left - 1])
left++;
else
break;
}
}
else if (sum > 0)
{
// 每次移动都检查会浪费时间
right--;
/* while (left < right)
{
if (nums[right] == nums[right + 1])
right--;
else
break;
}*/
}
else
{
// 每次移动都检查会浪费时间
left++;
/* while (left < right)
{
if (nums[left] == nums[left - 1])
left++;
else
break;
}*/
}
}
}
return res;
}

注意

1.基于范围的for循环(C++ 11 )

1
2
for (int x : nums)
cout<<x<<endl;

该循环可以显示数组的每一个值,但是无法通过这种方法对元素进行修改
需要使用引用

1
2
for(int &x : nums)
x=x*2;

基于范围的for循环与迭代器遍历的不同:
for循环返回元素
迭代器为指针
例如:遍历map

1
2
3
4
5
6
7
8
如果it为基于范围的for循环
for(auto it:umap)
key = it.first
value=it.second
如果it为迭代器
for(auto it=umap.begin();it!=umap.end();it++)
key=it->first
value=it->second

2.C++三个set的区别

三个set分别为set,unordered_set,multiset

1、unordered_set的底层实现是通过哈希表的方式实现的
2、set和multiset的底层是通过红黑树实现的***(红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。)***

3.集合来解决哈希问题的时候,优先使用unordered_set,因为它的查询和增删效率是最优的,如果需要集合是有序的,那么就用set,如果要求不仅有序还要有重复数据的话,那么就用multiset。

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

3.C++三个map的区别

三个map分别为map,multimap,unordered_map

1.unordered_map的底层实现是通过哈希表的方式实现的

2.map,multimap底层是通过红黑树实现的***(红黑树是一种平衡二叉搜索树,所以key值是有序的,但key不可以修改,改动key值会导致整棵树的错乱,所以只能删除和增加。)***

3.map可以修改value值,不可以修改key值

映射底层实现是否有序数值是否可以重复能否更改数值查询效率增删效率
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)

4.去除vector中的重复元素

1.vector<int> 类型

可以利用unordered_set不存储重复key值的特性,先将vector转化为set,再转化回来

2.vector<vector<int>> 类型

因为unordered_set底层通过哈希方法实现对key值的查找,而C++没有为vector提供默认的哈希方法,导致第一种方法失效。
所以我们需要利用unique函数

1
2
3
sort(res.begin(), res.end());
auto it=unique(res.begin(), res.end());
res.erase(it,res.end());