代码随想录算法训练营第七天
LeetCode 454.四数相加II
题目链接:454.四数相加II
文章讲解:代码随想录#454.四数相加II
视频讲解:学透哈希表,map使用有技巧!LeetCode:454.四数相加II
题目描述
给你四个整数数组 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
解释:
两个元组如下:
- (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
- (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
提示
- n == nums1.length
- n == nums2.length
- n == nums3.length
- n == nums4.length
- 1 <= n <= 200
- -228 <= nums1[i], nums2[i], nums3[i], nums4[i] <= 228
思路
这道题的具体分析思路可以看下代码随想录,这是我就做一下记录,梳理下我的思路。
已知:从四个数组中分别取一个值,有多少种可能使得这些数相加为0,假设从nums1中取出a,从nums2中取出b,从nums3中取出c,从nums4中取出d,即需要求出a+b+c+d=0的组合数。
四个数组是独立的,各种组合不需要考虑去重与减枝,我们将等式转换下:a+b=0-(c+d),(a+b)是从nums1和nums2中取出的两个数相加的值,结果有很多种值。这样的话,等式的两边不就是一个集合吗?
想一下,哈希表能解决什么问题呢?用来快速判断一个元素是否出现集合里。
那么这道题使用哈希算法会更方便些。
首先,对两个数组nums1,nums2进行两层for循环,使用一个map数据结构,key用来存放a+b的值,val用来存放这个值出现的次数。
比如[-1,1]和[-1,1],元素相加有[-2,0,2]三个值,-2出现过1次,0出现过2次,2出现过1次。
用map表示为{<-2, 1>, <0, 2>, <2, 1>}。
接着,对两个数组nums3,nums4进行两层for循环遍历,计算c+d的值。然后在map的key中查找0-(c+d)的值,如果存在则累加其val值,这样就可以统计出a+b+c+d=0的组合数了。
参考代码
// 通过链表实现map
struct key {
int val;
int cnt;
struct key* next;
};
typedef struct key map;
// 初始化map
map* initMap(void) {
map* head = (map*)malloc(sizeof(map));
head->next = NULL;
return head;
}
// 判断map中是否存在val
int isMapExisted(map* head, int val) {
map* cur = head;
while (cur != NULL) {
if (cur->val == val) {
return cur->cnt;
}
cur = cur->next;
}
return -1;
}
// 向map中添加元素
void addMapElement(map* head, int val) {
map* cur = head;
while (cur->next != NULL) {
if (cur->next->val == val) { // map中已经添加过了key,更新cnt
cur->next->cnt++;
return;
}
cur = cur->next;
}
map* node = (map*)malloc(sizeof(map));
node->next = NULL;
node->val = val;
node->cnt = 1;
cur->next = node;
}
int fourSumCount(int* nums1, int nums1Size,
int* nums2, int nums2Size,
int* nums3, int nums3Size,
int* nums4, int nums4Size)
{
map *head = initMap();
int sum = 0;
for (int i = 0; i < nums1Size; i++) {
for (int j = 0; j < nums2Size; j++) {
int cnt = nums1[i] + nums2[j];
addMapElement(head, cnt);
}
}
for (int i = 0; i < nums3Size; i++) {
for (int j = 0; j < nums4Size; j++) {
int diff = 0 - nums3[i] - nums4[j];
int cnt = isMapExisted(head, diff);
if (-1 != cnt) {
sum += cnt;
}
}
}
return sum;
}
总结
- 在addMapElement函数中将条件写错了,cur在遍历到最后一个节点时就跳出了,导致算出来的map中次数不对,对条件判断一定要小心!!!
修改前
while (cur != NULL && cur->next != NULL) {
if (cur->val == val) { // map中已经添加过了key,更新cnt
cur->cnt++;
return;
}
cur = cur->next;
}
修改后
while (cur->next != NULL) {
if (cur->next->val == val) {
cur->next->cnt++;
return;
}
cur = cur->next;
}
- 要灵活使用map。
LeetCode 383. 赎金信
题目链接:383. 赎金信
文章讲解:代码随想录#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
提示
- 1 <= ransomNote.length, magazine.length <= 10^5
- ransomNote 和 magazine 由小写英文字母组成
思路
这道题和有效字母异位词比较像,都是只含小写字母,可以使用哈希数组实现。
对magazine 中出现的字符进行加1,然后对ransomNote 出现的字符进行-1,最后判断哈希数组是否有负数,如果有,返回fasle,否则返回true。
参考代码
bool canConstruct(char* ransomNote, char* magazine) {
int letter[26] = {0};
int lenA = strlen(ransomNote);
int lenB = strlen(magazine);
for (int i = 0; i < lenB; i++) {
letter[magazine[i] - 'a']++;
}
for (int i = 0; i < lenA; i++) {
letter[ransomNote[i] - 'a']--;
}
for (int i = 0; i < 26; i++) {
if (letter[i] < 0) {
return false;
}
}
return true;
}
总结
- 这道题都是哈希表中比较经典的题目,特别适合入门。
一般哈希表涉及三个数据结构:
- 哈希数组, 一般适合元素比较少,值的离散程度比较小,这样构造的数组个数比较少。
- set, 其他C++高级语言中都有set集合,用起来比较方便,C语言的话,我目前没有找到,需要自己用链表实现。
- map,这个就是个键值对,类似于C#中的dictionary类型,C语言中有相应的库函数HashMap.h,后面可以了解下,也可以自己用链表实现。
LeetCode 15. 三数之和
题目链接:15. 三数之和
文章讲解:代码随想录#15. 三数之和
视频讲解:梦破碎的地方!| LeetCode: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 。
思路
在一个大小为n的整数数组nums中,找出满足a+b+c=0的三元组个数,要求不能包含重复的三元组。
比如,数组[-1, -1, -1, 0, 1, 2]中前三个元素都为-1,符合要求的三元组[-1, 0, 1]中-1只能在三元组的第一位出现一次,也就是说前三个-1中两个是重复的,需要去重。
但[-1, -1, 2]三元组也满足要求,并且-1不算重复,因为-1分别三元组的第一位和第二位。
使用哈希法,可以参考四数相加,先使用两层for循环记录a和b的值,再使用0-(a+b)来确实数组中c的值。但这种算法是需要考虑去重的,使用哈希法非常费时。
此题比较推荐使用双指针法,可以很好地解决去重问题。
对于数组nums,首先进行由小到到排序。然后进行一层for循环,索引i从0开始,接着定义双指针,left=i+1, right=nums.size-1。
这样就可以得到a,b,c三个值,a=nums[i], b=nums[left], c=nums[right]。
因为要找到a+b+c=0,所以刚开始就可以有减枝的判断(已经排序过了),如果nums[i]>0,则直接返回。
同时还需要对a进行去重,如果nums[i]==nums[i-1],则跳过本次循环。
为什么会用nums[i-1]进行判断呢,因为a=nums[i-1]时已经判断过了,就像上面例子[-1, 0, -1]的-1一样,需要去重。
当i固定好后,开始移动left和right,求出a+b+c=0时b和c的下标。
如果a+b+c>0,说明b+c的值过大了,需要向前移动c的下标,即rgiht–。
如果a+b+c<0,说明b+c的值过小了,需要向后移动b的下标,即left++。
如果a+b+c=0,需要将这个三元组记录起来,接着还得对b、c进行去重判断。
比如[-1, 0, 0, 0, 1, 1, 1]只有一个三元组[-1, 0, 1]符合要求。当第一次遍历时,i=0, left = 1, right = 6时符合条件。
当left=2和3时,需要去重,当right=4和5时同样需要去重。
参考代码
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
int cmp(const void *a, const void *b)
{
return *(int*)a - *(int*)b;
}
int** threeSum(int* nums, int numsSize, int* returnSize, int** returnColumnSizes) {
int **ret = (int**)malloc(18000*sizeof(int*));
if (numsSize < 3) {
*returnSize = 0;
return ret;
}
int cnt = 0;
qsort(nums, numsSize, sizeof(int), cmp);
for (int i = 0; i < numsSize; i++) {
if (nums[i] > 0)
break;// 减枝
// 从第二项开始去后,如[-1,-1,-1,0,1,2],一旦第一个-1选中后,后面两个都需要跳过
if (i > 0 && nums[i] == nums[i-1])
continue;
int left = i + 1;
int right = numsSize - 1;
while (left < right) { // 条件不能包含=
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0)
left++; // 结果小于0,需要增加left
else if (sum > 0)
right--; // 结果大于0,需要减小left
else {
int *arr = (int*)malloc(3 * sizeof(int));
arr[0] = nums[i];
arr[1] = nums[left];
arr[2] = nums[right];
// 将结果放入二维数组中
ret[cnt++] = arr;
// 接着对righ left去重
while (left < right && nums[left] == nums[left+1])
left++; // 向后滑动
while (left < right && nums[right] == nums[right-1])
right--; // 向前滑动
left++;
right--;
}
}
}
*returnSize = cnt;
*returnColumnSizes = (int*)malloc(sizeof(int) * cnt);
int z;
for(z = 0; z < cnt; z++) {
(*returnColumnSizes)[z] = 3;
}
return ret;
}
总结
- 代码随想录的思想倒是能看懂,自己写的话,还是会卡壳,代码几乎都是抄写过来的,惭愧啊!
- 还是不懂参数中returnColumnSizes的作用是什么。
这其实就是一个二维数组,在子函数中申请空间大小后,需要在主函数中使用
LeetCode 18. 四数之和
题目链接:18. 四数之和
文章讲解:代码随想录#18. 四数之和
视频讲解:难在去重和剪枝!| LeetCode:18. 四数之和
题目描述
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
- 0 <= a, b, c, d < n
- a、b、c 和 d 互不相同
- nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例1
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例2
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示
- 1 <= nums.length <= 200
- -10^9 <= nums[i] <= 10^9
- -10^9 <= target <= 10^9
思路
有时间再补充,这道题比上面第三道稍稍难一些,需要多判断一层,去重和减枝的条件也得改变一下。
参考代码
待补充
总结
1.待补充