零、常用枚举技巧
§0.1 枚举右,维护左
对于 双变量问题,例如两数之和 ai + aj = t,可以枚举右边的 aj,转换成 单变量问题,也就是在 aj
左边查找是否有 ai = t − aj,这可以用哈希表维护。
我把这个技巧叫做 枚举右,维护左。
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]
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),且 nums[i] 的数位和 与 nums[j] 的数位和相等。
请你找出所有满足条件的下标 i 和 j ,找出并返回 nums[i] + nums[j] 可以得到的 最大值。如果不存在这样的下标对,返回 -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 。
形式上,dominoes[i] = [a, b] 与 dominoes[j] = [c, d] 等价 当且仅当 (a == c 且 b == d) 或者 (a == d 且 b == c) 。即一张骨牌可以通过旋转 0 度或 180 度得到另一张多米诺骨牌。
在 0 <= i < j < dominoes.length 的前提下,找出满足 dominoes[i] 和 dominoes[j] 等价的骨牌对 (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
dominoes[i].length == 2
1 <= dominoes[i][j] <= 9
对于每个 dominoes[i]=[a,b],如果 a>b,我们就交换 a 和 b,从而保证 a≤b。这样就可以用 1512. 好数对的数目 题的算法了。注:这个思路和 49. 字母异位词分组 是一样的,如果两个骨牌在排序后相等,那么就是一对符合题目要求的骨牌对。
实现细节:可以用哈希表 cnt 存储 dominoes[i] 的出现次数,也可以利用 dominoes[i][j]≤9 的性质,用 10×10 的数组存储 dominoes[i] 的出现次数。
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
nums[i] < nums[j] 且 nums[k] < nums[j]
请你找出 nums 中 元素和最小 的山形三元组,并返回其 元素和 。如果不存在满足条件的三元组,返回 -1 。
示例 1:
输入:nums = [8,6,1,5,3]
输出:9
解释:三元组 (2, 3, 4) 是一个元素和等于 9 的山形三元组,因为:
- 2 < 3 < 4
- nums[2] < nums[3] 且 nums[4] < nums[3]
这个三元组的元素和等于 nums[2] + nums[3] + nums[4] = 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, 5\] 和 \[4\] 保持不变。 标有蓝色箭头的对角线(右上角三角形)应按非递减顺序排序: \[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; } ```