代码随想录算法训练营第六天
LeetCode 242.有效的字母异位词
题目链接:242.有效的字母异位词
文章讲解:代码随想录#242.有效的字母异位词
视频讲解:学透哈希表,数组使用有技巧!Leetcode:242.有效的字母异位词
题目描述
给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。
注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。
示例1
输入: s = “anagram”, t = “nagaram”
输出: true
示例2
输入: s = “rat”, t = “car”
输出: false
提示
- 1 <= s.length, t.length <= 5 * 104
- s 和 t 仅包含小写字母
思路
这是一道比较典型的使用哈希表的简单算法题。
因为要计算字符串中每个字符出现的次数,很容易想到使用哈希表太记录次数。
一般元素个数有限,会使用数组作为哈希表。
题目中已知字符串中只包含小写字母,所以可以构建一个数组letter[26],下标为字符c-‘a’,元素的值为字符c出现的次数。
首先遍历字符串s,字符c出现后,值加1,然后再遍历字符串t,字符c出现后,值减1。
最后重新遍历数组letter,如果有元素非零,说明s和t互为字母异位词。
参考代码
bool isAnagram(char* s, char* t) {
int letter[26] = {0};
for (int i = 0; i < strlen(s); i++) {
letter[s[i] - 'a']++;
}
for (int i = 0; i < strlen(t); i++) {
letter[t[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (letter[i] != 0) {
return false;
}
}
return true;
}
总结
- 要清楚哈希表使用的场景,一般哈希表都是用来快速判断一个元素是否出现集合里,可以查看这篇文章了解哈希表。
LeetCode 349. 两个数组的交集
题目链接:349. 两个数组的交集
文章讲解:代码随想录#349. 两个数组的交集
视频讲解:学透哈希表,set使用有技巧!Leetcode: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] 也是可通过的
提示
- 1 <= nums1.length, nums2.length <= 1000
- 0 <= nums1[i], nums2[i] <= 1000
思路
需要求出交集的元素值,由于两个数组的值<=1000,所以可以构造一个哈希数组arr[1000],哈希数组的下标为原始数组的元素值,哈希数组的值为原始数组出现的次数。
同时还需要构建一个数组,用来记录交集。数组大小为原始两个数组中的最小的长度。
参考代码
int* intersection(int* nums1, int nums1Size, int* nums2, int nums2Size,
int* returnSize) {
int arr[1000] = {0}; // 构建一个哈希级数,大小为1000
int sizeNum = 0;
int size = nums1Size > nums2Size
? nums2Size
: nums1Size; // 取两者最小值用来构建交集数组的大小
int* ret = (int*)malloc(size * sizeof(int));
for (int i = 0; i < nums1Size; i++) {
arr[nums1[i]]++;
}
for (int i = 0; i < nums2Size; i++) {
if (arr[nums2[i]] != 0) {
ret[sizeNum++] = nums2[i];
arr[nums2[i]] = 0; // 清除,防止后面出现相同的数组,重复计算
}
}
*returnSize = sizeNum;
return ret;
}
总结
- 对于哈希表时刻要明白哈希数组下标代表什么含义,其值又代表什么含义。
- 哈希数组的限定要求就是数量不能太大,以及值不能离散得太厉害了。
LeetCode 202. 快乐数
题目链接:202. 快乐数
文章讲解:代码随想录#202. 快乐数
题目描述
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
示例1
输入:n = 19
输出:true
解释:
1^2+ 9^2 = 82
8^2 + 2^2 = 68
6^2 + 8^2 = 100
1^2 + 0^2 + 0^2 = 1
示例2
输入:n = 2
输出:false
思路
这道题的题眼是可能存在无限循环,意味着在求和的过程中有些值会重复出现,那么我们就可以考虑使用哈希法了,这样就可以快速判断在集合中某个值是否重复出现。
但是这个集合大小是未知的,不能直接使用哈希数组,而C语言中不像C++中直接可以使用set,需要自己封装set集合,所以处理起来稍微麻烦一些。
参考代码
// 通过链表实现set
struct key {
int val;
struct key* next;
};
typedef struct key Set;
// 初始化Set
Set* initSets(void) {
Set* head = (Set*)malloc(sizeof(Set));
head->next = NULL;
return head;
}
// 给Set中添加元素
void addSetsElement(Set* head, int val) {
Set* cur = head;
while (cur->next != NULL) {
cur = cur->next;
}
Set* node = (Set*)malloc(sizeof(Set));
node->next = NULL;
node->val = val;
cur->next = node;
}
// 从Set中判断是否存在val
int isSetsExisted(Set* head, int val) {
Set* cur = head;
while (cur != NULL) {
if (cur->val == val) {
return 1;
}
cur = cur->next;
}
return 0;
}
int getSum(int n) {
int sum = 0;
do { // 拆分数字每位,计算平方和
sum += (n % 10) * (n % 10);
} while (n /= 10);
return sum;
}
bool isHappy(int n) {
Set* head = initSets();
int num = 0;
while (1) {
num = getSum(n);
if (num == 1) { // 如果平方和为1,则为快乐数
return true;
}
if (isSetsExisted(head, num)) { // 如果num已经存在于Set中,说明已经开始重复循环了
return false;
}
// 给Set中添加num
addSetsElement(head, num);
n = num; // 更新n值,进行下一次计算
}
}
总结
- 链表就这么用起来了,set集合的实现应该也可以通过一些头文件来实现,后面有机会再优化下。
LeetCode 1. 两数之和
题目链接:1. 两数之和
文章讲解:代码随想录#1. 两数之和
视频讲解:梦开始的地方,Leetcode: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]
提示
- 2 <= nums.length <= 104
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
- 只会存在一个有效答案
思路
这道题中元素中可能存在比较大的值,就不能再单纯使用哈希数组了,那么就需要用到map。
C语言中使用哈希map需要用到一些宏和头文件,可以参考这篇文章
假如有数组[2, 7, 11, 15],目标target为9。
首先构建一个map,key为数组元素,value为元素下标。
接着开始遍历数组,当i=0时,值为2,与目标9比较,还差7,
然后去map中查找是否存在7,如果存在7,则返回map中value即下标,以及当前索引0。
如果不存在7,则将数值2存入到key中,将索引0存入到value中。
接着遍历i=1,数值为7,差值为2,则去map中查找key中是否存在值为2的数,如果存在,则返回2的下标,以及当前下标。
如果不存在,则将数值7存入到key中,将索引1存入到value中,以此类推…
参考代码
// 通过链表实现map
struct key {
int val;
int idx;
struct key* next;
};
typedef struct key map;
// 初始化map
map* initMap(void) {
map* head = (map*)malloc(sizeof(map));
head->next = NULL;
return head;
}
// 向map中添加元素
void addMapElement(map* head, int val, int idx) {
map* cur = head;
while (cur->next != NULL) {
cur = cur->next;
}
map* node = (map*)malloc(sizeof(map));
node->next = NULL;
node->val = val;
node->idx = idx;
cur->next = node;
}
// 判断map中是否存在val
int isMapExisted(map* head, int val) {
map* cur = head;
while (cur != NULL) {
if (cur->val == val) {
return cur->idx;
}
cur = cur->next;
}
return -1;
}
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
int diff = 0;
int j = 0;
int *ret = (int*)malloc(2*sizeof(int));
*returnSize = 2;
map* head = initMap();
for (int i = 0; i < numsSize; i++) {
diff = target - nums[i];
j = isMapExisted(head, diff);
if (j != -1) {
ret[0] = i;
ret[1] = j;
return ret;
}
addMapElement(head, nums[i], i);
}
return NULL;
}
总结
- 这块是我的知识盲区,需要多看多练