1.1 LeetCode总结(线性表)_枚举技巧

零、常用枚举技巧

§0.1 枚举右,维护左

对于 双变量问题,例如两数之和 ai + aj = t,可以枚举右边的 aj,转换成 单变量问题,也就是在 aj

​左边查找是否有 ai = t − aj,这可以用哈希表维护。

我把这个技巧叫做 枚举右,维护左。

1. 两数之和

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

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

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

示例 1:

输入:nums = 2,7,11,15, target = 9

输出:0,1

解释:因为 nums0 + nums1 == 9 ,返回 0, 1

示例 2:

输入:nums = 3,2,4, target = 6

输出:1,2

示例 3:

输入:nums = 3,3, target = 6

输出:0,1

c 复制代码
typedef struct {
    int num; // 哈希表的 key
    int idx; // 哈希表的 value
    UT_hash_handle hh; // uthash 需要
} hashTable;
hashTable *g_hashTable = NULL; // 创建一个空哈希表
int *twoSum(int *nums, int numsSize, int target, int *returnSize)
{
    hashTable *tmp;
    for (int j = 0; ; j++) { // 枚举 j
        // 在左边找 nums[i],满足 nums[i]+nums[j]=target
        int t = target - nums[j];
        HASH_FIND_INT(g_hashTable, &t, tmp);
        if (tmp != NULL) { // 找到了
            int *ans = (int *)malloc(2 * sizeof(int)); // 分配返回结果的内存空间
            *returnSize = 2; // ans 的长度
            ans[0] = tmp->idx; // 存储第一个数的下标
            ans[1] = j; // 存储第二个数的下标
            // 释放哈希表中所有节点,防止内存泄漏
            hashTable *tmp_del;
            HASH_ITER(hh, g_hashTable, tmp, tmp_del) {
                HASH_DEL(g_hashTable, tmp);
                free(tmp);
            }
            return ans; // 返回结果数组的指针
        }
        HASH_FIND_INT(g_hashTable, &nums[j], tmp);
        if (tmp == NULL) { // nums[j] 不在哈希表中
            // 保存 nums[j] 和 j 到哈希表中
            tmp = (hashTable *)malloc(sizeof(hashTable));
            tmp->num = nums[j];
            tmp->idx = j;
            HASH_ADD_INT(g_hashTable, num, tmp);
        } // else 哈希表有 nums[j],无需更新
    }
}

2342. 数位和相等数对的最大和

给你一个下标从 0 开始的数组 nums ,数组中的元素都是 正 整数。请你选出两个下标 i 和 j(i != j),且 numsi 的数位和 与 numsj 的数位和相等。

请你找出所有满足条件的下标 i 和 j ,找出并返回 numsi + numsj 可以得到的 最大值。如果不存在这样的下标对,返回 -1。

示例 1:

输入:nums = 18,43,36,13,7

输出:54

解释:满足条件的数对 (i, j) 为:

  • (0, 2) ,两个数字的数位和都是 9 ,相加得到 18 + 36 = 54 。
  • (1, 4) ,两个数字的数位和都是 7 ,相加得到 43 + 7 = 50 。
    所以可以获得的最大和是 54 。
c 复制代码
typedef struct {
    int num; // 哈希表的 key
    int bit; // 哈希表的 value
    UT_hash_handle hh; // uthash 需要
} hashTable;
hashTable *g_hashTable = NULL; // 创建一个空哈希表
int Judge(int num) 
{
    int cnt = 0;
    while (num > 0) {
        cnt = cnt + num % 10;
        num = num / 10;
    }
    return cnt;
}
int maximumSum(int *nums, int numsSize)
{
    hashTable *tmp;
    int res = -1;
    for (int i = 0; i < numsSize; i++) {
        int bit = Judge(nums[i]);
        HASH_FIND_INT(g_hashTable, &bit, tmp);
        if (tmp == NULL) {
            tmp = (hashTable *)malloc(sizeof(hashTable));
            tmp->num = nums[i];
            tmp->bit = bit;
            HASH_ADD_INT(g_hashTable, bit, tmp);
        } else {
            res = fmax(nums[i] + tmp->num, res);
            tmp->num = fmax(nums[i], tmp->num);
        }
    }
    // 释放哈希表中所有节点,防止内存泄漏
    hashTable *tmp_del;
    HASH_ITER(hh, g_hashTable, tmp, tmp_del) {
        HASH_DEL(g_hashTable, tmp);
        free(tmp);
    }
    return res;
}
int main()
{
    int nums[] = {229,398,269,317,420,464,491,218,439,153,482,169,411,93,147,50,347,210,251,366,401};
    int ret = maximumSum(nums, sizeof(nums)/sizeof(int));
    printf("%ld ", ret);
    return 0;
}

1128. 等价多米诺骨牌对的数量

给你一组多米诺骨牌 dominoes 。

形式上,dominoesi = a, b 与 dominoesj = c, d 等价 当且仅当 (a == c 且 b == d) 或者 (a == d 且 b == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。

在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoesi 和 dominoesj 等价的骨牌对 (i, j) 的数量。

示例 1:

输入:dominoes = \[1,2,2,1,3,4,5,6]

输出:1

示例 2:

输入:dominoes = \[1,2,1,2,1,1,1,2,2,2]

输出:3

提示:

1 <= dominoes.length <= 4 * 10^4

dominoesi.length == 2

1 <= dominoesij <= 9

对于每个 dominoesi=a,b,如果 a>b,我们就交换 a 和 b,从而保证 a≤b。这样就可以用 1512. 好数对的数目 题的算法了。注:这个思路和 49. 字母异位词分组 是一样的,如果两个骨牌在排序后相等,那么就是一对符合题目要求的骨牌对。

实现细节:可以用哈希表 cnt 存储 dominoesi 的出现次数,也可以利用 dominoesij≤9 的性质,用 10×10 的数组存储 dominoesi 的出现次数。

c 复制代码
int numEquivDominoPairs(int **dominoes, int dominoesSize, int *dominoesColSize)
{
    int ans = 0;
    int cnt[10][10] = {0};
    for (int i = 0; i < dominoesSize; i++) {
        int a = dominoes[i][0], b = dominoes[i][1];
        if (a <= b) {
            ans = ans + cnt[a][b]++;
        } else {
            ans = ans + cnt[b][a]++;
        }
    }
    return ans;
}

§0.2 枚举中间

对于有三个或者四个变量的问题,枚举中间的变量往往更好算。

为什么?比如问题有三个下标,需要满足 0≤i<j<k<n,对比一下:

枚举 i,后续计算中还需保证 j<k。

枚举 j,那么 i 和 k 自动被 j 隔开,互相独立,后续计算中无需关心 i 和 k 的位置关系。

所以枚举中间的变量更简单。

2909. 元素和最小的山形三元组 II

给你一个下标从 0 开始的整数数组 nums 。

如果下标三元组 (i, j, k) 满足下述全部条件,则认为它是一个 山形三元组 :

i < j < k

numsi < numsj 且 numsk < numsj

请你找出 nums 中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回 -1 。

示例 1:

输入:nums = 8,6,1,5,3

输出:9

解释:三元组 (2, 3, 4) 是一个元素和等于 9 的山形三元组,因为:

  • 2 < 3 < 4
  • nums2 < nums3 且 nums4 < nums3
    这个三元组的元素和等于 nums2 + nums3 + nums4 = 9 。可以证明不存在元素和小于 9 的山形三元组。
c 复制代码
int minimumSum(int *nums, int numsSize)
{
    int ans = INT_MAX;
    int *leftArr  = (int *) malloc(numsSize * sizeof(int));
    int *rightArr = (int *) malloc(numsSize * sizeof(int));
    // 1.左边界的最小值
    leftArr[0] = nums[0];
    for(int i = 1; i < numsSize; i++) {
        leftArr[i] = fmin(nums[i], leftArr[i - 1]);
    }
    // 2.右边界的最小值
    rightArr[numsSize - 1] = nums[numsSize - 1];
    for (int i = numsSize - 2; i >= 0; i --) {
        rightArr[i] = fmin(nums[i], rightArr[i + 1]);
    }
    for(int i = 1; i < numsSize - 1; i ++) {
        // [0, i - 1] 的最小值
        int minl = leftArr[i - 1];
        // [i + 1, numsSize - 1] 的最小值
        int minr = rightArr[i + 1];
        if (minl >= nums[i] || minr >= nums[i]) {
            continue;
        }
        ans = fmin(ans, minl + nums[i] + minr);
    }
    if (ans == INT_MAX) return -1;
    return ans;
}
int main()
{
    int nums[6] = {5,4,8,7,10,2};
    printf("%d\n", minimumSum(nums, 6));
    return 0;
}

3446. 按对角线进行矩阵排序

给你一个大小为 n x n 的整数方阵 grid。返回一个经过如下调整的矩阵:

左下角三角形(包括中间对角线)的对角线按 非递增顺序 排序。

右上角三角形 的对角线按 非递减顺序 排序。

示例 1:

输入: grid = \[1,7,3,9,8,2,4,5,6]

输出: \[8,2,3,9,6,7,4,5,1]

解释:

标有黑色箭头的对角线(左下角三角形)应按非递增顺序排序:

1, 8, 6 变为 8, 6, 1

9, 54 保持不变。

标有蓝色箭头的对角线(右上角三角形)应按非递减顺序排序:

7, 2 变为 2, 7

3 保持不变。

c 复制代码
int cmp_less(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}
int cmp_greater(const void* a, const void* b) {
    return (*(int*)b - *(int*)a);
}
int **sortMatrix(int **grid, int gridSize, int *gridColSize, int *returnSize, int **returnColumnSizes)
{
    int m = gridSize, n = gridColSize[0];
    int *a = malloc(MIN(m, n) * sizeof(int));
    // k = n - j + i, 右上角 k=1,左下角 k=m+n-1
    for (int k = 1; k < m + n; k++) {
        int min_j = MAX(n - k, 0); // i=0 时 j=n-k,但不能是负数
        int max_j = MIN(m + n - 1 - k, n - 1); // i=m-1 时 j=m+n-1-k,但不能超过 n-1
        int idx = 0;
        for (int j = min_j; j <= max_j; j++) {
            a[idx++] = grid[k + j - n][j]; // 根据 k 的定义 i=k+j-n
        }
        qsort(a, max_j - min_j + 1, sizeof(int), min_j > 0 ? cmp_less : cmp_greater);
        for (int j = min_j; j <= max_j; j++) {
            grid[k + j - n][j] = a[j - min_j];
        }
    }
    free(a);
    *returnSize = m;
    *returnColumnSizes = malloc(m * sizeof(int));
    for (int i = 0; i < m; i++) {
        (*returnColumnSizes)[i] = n;
    }
    return grid;
}
相关推荐
阿正的梦工坊7 分钟前
【Rust】06-函数、控制流与模块组织
开发语言·算法·rust
阿正的梦工坊13 分钟前
【Rust】16-async/await、Future 与执行器模型
网络·算法·rust
阿正的梦工坊17 分钟前
【Rust】11-Rust 所有权模型的编译期推理机制
开发语言·算法·rust
风筝在晴天搁浅20 分钟前
LeetCode CodeTop 88.合并两个有序数组
算法·leetcode·职场和发展
GuWen_yue21 分钟前
吃透二叉树与递归!60分钟掌握树结构核心+解题思路
javascript·算法
happymaker062623 分钟前
LeetCodeHot100——3.无重复字符的最长子串
算法
nice_lcj52027 分钟前
排序(2)-选择排序专题——简单选择排序与堆排序的结构优化
数据结构·算法·排序算法
nice_lcj52029 分钟前
排序(4)-归并排序专题——归并排序的分治美学
java·数据结构·算法·排序算法
洛水水43 分钟前
【力扣100题】83.最小栈
算法·leetcode·职场和发展
Zhang~Ling1 小时前
哈希表底层详解:从哈希函数到冲突处理的原理与实现
开发语言·c++·算法·哈希算法·散列表