和为 K 的子数组
建议参考下列视频:
【560. 和为 K 的子数组 Subarray Sum Equals K【LeetCode 力扣官方题解】】 https://www.bilibili.com/video/BV13t4y1y7ya/?share_source=copy_web&vd_source=b1f5728f3a9c87006fa48f39e09acbab
问题描述
给定一个整数数组 nums
和一个整数 k
,请统计并返回该数组中和为 k
的子数组的个数。子数组是数组中元素的连续非空序列。
示例
示例 1
输入:nums = [1, 1, 1]
, k = 2
输出:2
解释:子数组 [1, 1]
和 [1, 1]
的和为 2。
示例 2
输入:nums = [1, 2, 3]
, k = 3
输出:2
解释:子数组 [1, 2]
和 [3]
的和为 3。
约束条件
1 <= nums.length <= 2 * 10^4
-1000 <= nums[i] <= 1000
-10^7 <= k <= 10^7
算法思路
本题的核心是利用前缀和和哈希表来高效统计和为 k
的子数组个数。
前缀和
前缀和是指从数组的起始位置到当前位置的所有元素之和。通过计算前缀和,可以快速判断任意子数组的和是否等于目标值 k
。
哈希表
哈希表用于存储每个前缀和出现的次数。通过查找 pre - k
是否存在于哈希表中,可以快速判断是否存在一个子数组的和为 k
。
算法步骤
- 初始化哈希表,插入键为 0 的条目,值为 1。
- 遍历数组,计算前缀和
pre
。 - 查找
pre - k
是否存在于哈希表中,如果存在,累加其值到结果中。 - 更新哈希表,插入当前前缀和
pre
。 - 返回结果。
代码实现
以下是使用 C 语言实现的代码,包含详细注释。
使用 uthash.h
的实现
#include "uthash.h"
#include <stdio.h>
#include <stdlib.h>
// 哈希表结构
typedef struct {
int key; // 前缀和
int value; // 前缀和出现的次数
UT_hash_handle hh; // uthash 需要的句柄
} hashTable;
int subarraySum(int* nums, int numsSize, int k) {
hashTable* hashMap = NULL; // 创建一个空哈希表
hashTable* hashNode;
int result = 0; // 结果计数器
int pre = 0; // 前缀和
// 初始化哈希表,插入键为 0 的条目
hashNode = (hashTable*)malloc(sizeof(hashTable));
hashNode->key = 0;
hashNode->value = 1;
HASH_ADD_INT(hashMap, key, hashNode);
for (int i = 0; i < numsSize; i++) {
pre += nums[i]; // 更新前缀和
int target = pre - k; // 计算目标值
HASH_FIND_INT(hashMap, &target, hashNode); // 查找 pre - k 是否存在
if (hashNode != NULL) {
result += hashNode->value; // 如果存在,累加其值
}
// 更新哈希表,插入当前前缀和
HASH_FIND_INT(hashMap, &pre, hashNode);
if (hashNode == NULL) {
hashNode = (hashTable*)malloc(sizeof(hashTable));
hashNode->key = pre;
hashNode->value = 1;
HASH_ADD_INT(hashMap, key, hashNode);
} else {
hashNode->value++; // 如果已存在,值加 1
}
}
// 释放哈希表
hashTable* current;
hashTable* tmp;
HASH_ITER(hh, hashMap, current, tmp) {
HASH_DEL(hashMap, current);
free(current);
}
return result;
}
int main() {
int nums1[] = {1, 1, 1};
int k1 = 2;
printf("Result 1: %d\n", subarraySum(nums1, 3, k1)); // 输出 2
int nums2[] = {1, 2, 3};
int k2 = 3;
printf("Result 2: %d\n", subarraySum(nums2, 3, k2)); // 输出 2
return 0;
}
自定义哈希表的实现
#include <stdio.h>
#include <stdlib.h>
// 哈希表节点
typedef struct HashNode {
int key; // 前缀和
int value; // 前缀和出现的次数
struct HashNode* next; // 指向下一个节点
} HashNode;
// 哈希表
typedef struct HashTable {
HashNode** table; // 哈希表数组
int size; // 哈希表大小
} HashTable;
// 初始化哈希表
HashTable* initHashTable(int size) {
HashTable* table = (HashTable*)malloc(sizeof(HashTable));
table->size = size;
table->table = (HashNode**)calloc(size, sizeof(HashNode*));
return table;
}
// 插入或更新哈希表
void insertOrUpdate(HashTable* table, int key, int value) {
int index = (key % table->size + table->size) % table->size; // 确保索引为非负值
HashNode* node = table->table[index];
while (node != NULL) {
if (node->key == key) {
node->value += value; // 如果键已存在,更新值
return;
}
node = node->next;
}
// 如果键不存在,创建新节点
node = (HashNode*)malloc(sizeof(HashNode));
node->key = key;
node->value = value;
node->next = table->table[index];
table->table[index] = node;
}
// 查找哈希表
int find(HashTable* table, int key) {
int index = (key % table->size + table->size) % table->size; // 确保索引为非负值
HashNode* node = table->table[index];
while (node != NULL) {
if (node->key == key) {
return node->value; // 如果找到键,返回其值
}
node = node->next;
}
return 0; // 如果未找到键,返回 0
}
// 释放哈希表
void freeHashTable(HashTable* table) {
for (int i = 0; i < table->size; i++) {
HashNode* node = table->table[i];
while (node != NULL) {
HashNode* next = node->next;
free(node); // 释放当前节点
node = next;
}
}
free(table->table); // 释放哈希表数组
free(table); // 释放哈希表
}
// 计算和为 K 的子数组个数
int subarraySum(int* nums, int numsSize, int k) {
HashTable* table = initHashTable(20001); // 初始化哈希表,大小为 20001
insertOrUpdate(table, 0, 1); // 插入键为 0 的条目
int count = 0; // 结果计数器
int pre = 0; // 前缀和
for (int i = 0; i < numsSize; i++) {
pre += nums[i]; // 更新前缀和
count += find(table, pre - k); // 查找 pre - k 是否存在
insertOrUpdate(table, pre, 1); // 插入当前前缀和
}
freeHashTable(table); // 释放哈希表
return count;
}
int main() {
int nums1[] = {1, 1, 1};
int k1 = 2;
printf("Result 1: %d\n", subarraySum(nums1, 3, k1)); // 输出 2
int nums2[] = {1, 2, 3};
int k2 = 3;
printf("Result 2: %d\n", subarraySum(nums2, 3, k2)); // 输出 2
return 0;
}
代码说明
使用 uthash.h
- 使用
uthash
提供的哈希表功能,简化了哈希表的实现。 - 通过
HASH_ADD_INT
和HASH_FIND_INT
操作哈希表。
自定义哈希表
- 使用链表解决哈希冲突。
- 通过
(key % size + size) % size
确保索引为非负值。 - 手动管理哈希表的内存分配和释放。
总结
本题通过前缀和和哈希表的结合,实现了高效的子数组和统计。使用 uthash
可以简化代码实现,而自定义哈希表则提供了更多的灵活性。两种实现方式均能达到 O(n) 的时间复杂度,适用于大规模数据的处理。