一刷Day7|454.四数相加II 15. 三数之和 18. 四数之和

文章目录

  • 454.四数相加II
    • 识别
    • 核心/易错
    • 难点/亮点
    • 算法设计思路
    • 代码实现
    • 代码实现分析
  • 15. 三数之和 (排序数组后左右双指针法,abc均去重)## 识别
    • 核心/易错
    • 难点/亮点
    • 算法设计思路
    • 代码实现
      • 代码注释
  • 18. 四数之和 (在三数之和的基础上套了一层for循环numsk,剪枝&去重)
    • 识别
    • 核心/易错
    • 难点/亮点
    • 算法设计思路
    • 代码实现
      • 代码注释
  • 383. 赎金信

454.四数相加II

识别

本题有4个数组,较四数之和(一个数组),不用去重。
a+b的值先放入一个集合中,后遍历c+d的集合时判断,
元素数值可能很大,统计if出现及次数2个值。
计算四个数组中和为零的四元组数量。它使用了 uthash 库来实现哈希表,以高效地存储和查找数组元素的和。

核心/易错

核心部分在于正确使用 uthash 库来处理哈希表的创建、插入、查找和删除操作。
易错点包括:

  1. 正确初始化和释放哈希表,避免内存泄漏。
  2. 正确处理 malloc 分配内存失败的情况。
  3. 正确使用 HASH_FIND_INTHASH_ADD_INT 宏来查找和添加哈希表项。

难点/亮点

难点在于理解和使用 uthash 库的宏来操作哈希表。
亮点是使用哈希表来减少查找时间,从而提高算法的效率。

算法设计思路

  1. 初始化哈希表:创建一个空的哈希表来存储 nums1nums2 数组中所有可能和的出现次数。
  2. 构建哈希表:遍历 nums1nums2 数组,计算它们的和,并使用 insertOrUpdateHashTable 函数将这些和及其出现次数存储在哈希表中。
  3. 计算四元组数量:遍历 nums3nums4 数组,计算它们的和的相反数,并查找这个值在哈希表中的计数。如果找到,则将这个计数累加到结果中。
  4. 释放哈希表:在计算完成后,释放哈希表中的所有元素,以避免内存泄漏。

代码实现

#include 
#include 

struct hashTable {
    int key;
    int val;
    UT_hash_handle hh;
};

// 释放哈希表中的所有元素
void freeHashTable(struct hashTable* ht) {
    struct hashTable *current, *tmp;
    HASH_ITER(hh, ht, current, tmp) {
        HASH_DEL(ht, current);
        free(current);
    }
}

// 在哈希表中插入或更新元素
void insertOrUpdateHashTable(struct hashTable** ht, int key, int val) {
    struct hashTable *tmp;
    HASH_FIND_INT(*ht, &key, tmp);
    if (tmp == NULL) {
        tmp = malloc(sizeof(struct hashTable));
        if (tmp == NULL) {
            fprintf(stderr, "Memory allocation failed\n");
            exit(EXIT_FAILURE);
        }
        tmp->key = key;
        tmp->val = val;
        HASH_ADD_INT(*ht, key, tmp);
    } else {
        tmp->val += val;
    }
}

int fourSumCount(int* nums1, int nums1Size, int* nums2, int nums2Size, int* nums3, int nums3Size, int* nums4, int nums4Size) {
    struct hashTable* ht = NULL;
    for (int i = 0; i < nums1Size; i++) {
        for (int j = 0; j < nums2Size; j++) {
            int ikey = nums1[i] + nums2[j];
            insertOrUpdateHashTable(&ht, ikey, 1);
        }
    }

    int res = 0;
    for (int i = 0; i < nums3Size; i++) {
        for (int j = 0; j < nums4Size; j++) {
            int ikey = -nums3[i] - nums4[j];
            struct hashTable *tmp;
            HASH_FIND_INT(ht, &ikey, tmp);
            if (tmp != NULL) {
                res += tmp->val;
            }
        }
    }

    freeHashTable(ht);  // 释放哈希表中的所有元素
    return res;
}

// 示例
int main() {
    int nums1[] = {1, 2};
    int nums2[] = {-2, -1};
    int nums3[] = {-1, 2};
    int nums4[] = {0, 2};
    int result = fourSumCount(nums1, 2, nums2, 2, nums3, 2, nums4, 2);
    printf("%d\n", result);
    return 0;
}

代码实现分析

  1. 哈希表结构定义:定义了一个包含 keyvalUT_hash_handle 的结构体 hashTable
  2. 释放哈希表函数freeHashTable 函数使用 HASH_ITER 宏遍历哈希表,并释放每个元素。
  3. 插入或更新哈希表函数insertOrUpdateHashTable 函数用于在哈希表中插入新元素或更新现有元素的值。
  4. 四数之和计数函数fourSumCount 函数实现了主要的算法逻辑,使用哈希表来统计和为零的四元组数量。
  5. 主函数main 函数提供了一个示例,调用 fourSumCount 函数并打印结果。

15. 三数之和 (排序数组后左右双指针法,abc均去重)## 识别

数组中所有不重复的三元组,使得三数之和为零。使用 qsort 函数对数组进行排序,并使用双指针技术来寻找符合条件的三元组。

核心/易错

核心部分是排序、双指针搜索和去重。
易错点包括:

  1. 正确地实现排序比较函数。
  2. 避免数组索引越界和重复三元组的生成。
  3. 正确地管理动态分配的内存。

难点/亮点

难点在于优化搜索过程以提高效率,例如通过提前终止搜索和跳过重复元素。
亮点是使用了两个优化条件来减少不必要的搜索:

  1. 如果当前元素加上后面两个元素的和大于0,则无需继续搜索。
  2. 如果当前元素加上数组最后两个元素的和小于0,也无需继续搜索。

算法设计思路

  1. 排序:首先对数组进行排序,以便使用双指针技术。
  2. 遍历:遍历排序后的数组,对于每个元素,使用两个指针从其后面开始搜索。
  3. 双指针:对于每个元素,初始化两个指针,一个指向当前元素之后的元素,另一个指向数组末尾。根据三数之和的正负来移动指针。
  4. 去重:在添加三元组到结果数组之前,跳过重复的元素。
  5. 优化:通过检查当前元素与数组首尾元素的和来提前终止搜索或跳过当前元素。

代码实现

int cmp(const void* a, const void* b) {
    // 比较函数,用于 qsort,按升序排列
    return *(int*)a - *(int*)b;
}

int** threeSum(int* nums, int n, int* returnSize, int** returnColumnSizes) {
    // 对数组进行排序
    qsort(nums, n, sizeof(int), cmp);
    
    // 动态分配存储所有可能三元组的空间
    int** ans = malloc(n * n * sizeof(int*));
    // 动态分配存储所有三元组列大小的空间
    *returnColumnSizes = malloc(n * n * sizeof(int));
    int m = 0; // 用于跟踪结果数组中的三元组数量
    for (int i = 0; i < n - 2; i++) {
        int x = nums[i];
        // 跳过重复的数字以避免重复的三元组
        if (i > 0 && x == nums[i - 1]) continue;
        // 如果当前元素加上后面两个元素的和大于0,则无需继续搜索
        if (x + nums[i + 1] + nums[i + 2] > 0) break;
        // 如果当前元素加上数组最后两个元素的和小于0,则无需继续搜索
        if (x + nums[n - 2] + nums[n - 1] < 0) continue;
        int j = i + 1, k = n - 1;
        while (j < k) {
            int s = x + nums[j] + nums[k];
            // 如果三数之和大于0,移动右指针
            if (s > 0) {
                k--;
            } else if (s < 0) {
                // 如果三数之和小于0,移动左指针
                j++;
            } else {
                // 找到三数之和为0的三元组
                int* tuple = malloc(3 * sizeof(int));
                tuple[0] = x;
                tuple[1] = nums[j];
                tuple[2] = nums[k];
                ans[m] = tuple;
                (*returnColumnSizes)[m++] = 3;
                // 跳过重复的数字
                for (j++; j < k && nums[j] == nums[j - 1]; j++);
                for (k--; k > j && nums[k] == nums[k + 1]; k--);
            }
        }
    }
    *returnSize = m; // 设置返回的三元组数量
    return ans; // 返回结果数组
}

代码注释

  • cmp 函数:用于 qsort 的比较函数,按升序排列数组元素。
  • threeSum 函数:实现三数之和的主要逻辑,包括排序、双指针搜索、去重和结果存储。
  • threeSum 函数中,通过 m 变量跟踪结果数组中的三元组数量,通过 *returnSize*returnColumnSizes 向调用者返回结果数组的大小和列大小信息。
  • 使用 malloc 动态分配内存来存储结果数组和列大小数组,确保有足够的空间存储所有可能的三元组。
  • 使用 for 循环和 while 循环结合双指针技术来寻找所有符合条件的三元组,并在找到后进行去重处理。

18. 四数之和 (在三数之和的基础上套了一层for循环numsk,剪枝&去重)

识别

找出数组中所有唯一的四元组,使得四数之和等于给定的目标值 target。该程序使用快速排序 quickSort 对数组进行排序,并使用四层嵌套循环结合剪枝和去重技术来寻找符合条件的四元组。

核心/易错

核心部分是排序、四层循环的嵌套遍历、剪枝优化和去重。易错点包括:

  1. 正确地实现排序和分区函数。
  2. 避免数组索引越界和重复四元组的生成。
  3. 正确地管理动态分配的内存。

难点/亮点

难点在于优化遍历过程以提高效率,例如通过剪枝和去重。亮点是使用了快速排序和剪枝条件来减少不必要的搜索:

  1. 如果当前两个元素之和大于目标值,则无需继续搜索。
  2. 如果当前两个元素之和小于目标值,则也无需继续搜索。

算法设计思路

  1. 排序:首先对数组进行快速排序,以便使用双指针技术。
  2. 遍历:通过两层外层循环遍历数组,对于每对元素,使用两个指针从其后面开始搜索。
  3. 双指针:对于每对元素,初始化两个指针,一个指向当前元素之后的元素,另一个指向数组末尾。根据四数之和的正负来移动指针。
  4. 去重:在添加四元组到结果数组之前,跳过重复的元素。
  5. 剪枝:通过检查当前元素与数组首尾元素的和来提前终止搜索或跳过当前元素。

代码实现

/**
 * 交换两个整数的值
 */
void swap(int* a, int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

/**
 * 快速排序的分区函数
 */
int partition(int* nums, int low, int high) {
    int pivot = nums[high];
    int i = low - 1;
    for (int j = low; j < high; j++) {
        if (nums[j] <= pivot) {
            i++;
            swap(&nums[i], &nums[j]);
        }
    }
    swap(&nums[i + 1], &nums[high]);
    return i + 1;
}

/**
 * 快速排序函数
 */
void quickSort(int* nums, int low, int high) {
    if (low < high) {
        int PI = partition(nums, low, high);
        quickSort(nums, low, PI - 1);
        quickSort(nums, PI + 1, high);
    }
}

/**
 * 主函数,找出所有和为 target 的四元组
 */
int** fourSum(int* nums, int numsSize, int target, int* returnSize, int** returnColumnSizes) {
    int i = 0, j = 0, k = 0, z = 0, m = 0;
    int** Nums = (int**)malloc(sizeof(int*) * 1001); // 存储结果的数组
    *returnSize = 0;
    *returnColumnSizes = (int*)malloc(sizeof(int) * 1001); // 存储每组的大小
    if (numsSize < 4) return Nums;
    quickSort(nums, 0, numsSize - 1); // 对数组进行快速排序
    for (int i = 0; i < numsSize - 3; i++) { // 第一层循环
        if (i > 0 && nums[i] == nums[i - 1]) {
            continue; // 去重
        }
        if ((long) nums[i] + nums[i + 1] + nums[i + 2] + nums[i + 3] > target) {
            break; // 剪枝
        }
        if ((long) nums[i] + nums[numsSize - 3] + nums[numsSize - 2] + nums[numsSize - 1] < target) {
            continue; // 剪枝
        }
        for (int j = i + 1; j < numsSize - 2; j++) { // 第二层循环
            if (j > i + 1 && nums[j] == nums[j - 1]) {
                continue; // 去重
            }
            if ((long)nums[i] + nums[j] + nums[j + 1] + nums[j + 2] > target) {
                break; // 剪枝
            }
            if ((long)nums[i] + nums[j] + nums[numsSize - 2] + nums[numsSize - 1] < target) {
                continue; // 剪枝
            }
            int k = j + 1, z = numsSize - 1;
            while (k < z) { // 双指针
                long sum = (long)nums[i] + nums[j] + nums[k] + nums[z];
                if (sum == target) {
                    (*returnColumnSizes)[m] = 4;
                    Nums[m] = (int*)malloc(sizeof(int) * 4);
                    Nums[m][0] = nums[i];
                    Nums[m][1] = nums[j];
                    Nums[m][2] = nums[k];
                    Nums[m][3] = nums[z];
                    m++;
                    while (k < z && nums[k] == nums[k + 1]) {
                        k++;
                    }
                    k++;
                    while (k < z && nums[z] == nums[z - 1]) {
                        z--;
                    }
                    z--;
                } else if (sum < target) {
                    k++;
                } else {
                    z--;
                }
            }
        }
    }
    if (Nums == NULL) {
        *returnSize = 0;
        **returnColumnSizes = 0;
        return Nums;
    }
    *returnSize = m;
    return Nums; // 返回结果数组
}

代码注释

  • swap 函数:用于交换两个整数的值。
  • partition 函数:快速排序的分区函数,选择一个枢轴元素并将数组分为两部分。
  • quickSort 函数:快速排序函数,递归地对数组进行排序。
  • fourSum 函数:实现四数之和的主要逻辑,包括排序、四层循环的嵌套遍历、剪枝优化和去重。
  • fourSum 函数中,通过 m 变量跟踪结果数组中的四元组数量,通过 *returnSize*returnColumnSizes 向调用者返回结果数组的大小和列大小信息。
  • 使用 malloc 动态分配内存来存储结果数组和列大小数组,确保有足够的空间存储所有可能的四元组。
  • 使用 for 循环和 while 循环结合双指针技术来寻找所有符合条件的四元组,并在找到后进行去重处理。

383. 赎金信

你可能感兴趣的:(哈希算法,c语言)