代码随想录算法训练营第七天 | 454.四数相加II,383. 赎金信,15. 三数之和 ,18. 四数之和[哈希表篇]

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
解释:
两个元组如下:

  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

提示

  • 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;
}

总结

  1. 在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;
    }
  1. 要灵活使用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;
}

总结

  1. 这道题都是哈希表中比较经典的题目,特别适合入门。
    一般哈希表涉及三个数据结构:
  • 哈希数组, 一般适合元素比较少,值的离散程度比较小,这样构造的数组个数比较少。
  • 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;
}

总结

  1. 代码随想录的思想倒是能看懂,自己写的话,还是会卡壳,代码几乎都是抄写过来的,惭愧啊!
  2. 还是不懂参数中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.待补充

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值