两数之和
- 问题描述
- 示例
- 提示
- 思路分析
- 代码实现
- 代码解析
- 1. 哈希表结构体定义
- 2. 初始化哈希表
- 3. 释放哈希表内存
- 4. 主函数 `twoSum`
- 5. 返回结果
- 复杂度分析
- 第二种解法
- 代码功能概述
- 代码详细注释
- 1. 哈希表结构体定义
- 2. 哈希表指针
- 3. 查找函数
- 4. 插入函数
- 5. 两数之和函数
- 6. 主函数(示例)
- 总结
问题描述
给定一个整数数组 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]
提示
2 <= nums.length <= 10^4
-10^9 <= nums[i] <= 10^9
-10^9 <= target <= 10^9
- 只会存在一个有效答案
思路分析
这个问题可以通过哈希表来高效地解决。具体思路如下:
- 创建一个哈希表,存储已经遍历过的数和它们对应的索引。
- 对于每个数,计算
target
减去当前数的差值,即complement
。 - 如果差值
complement
在哈希表中已经存在,则找到了符合条件的两个数,直接返回它们的下标。 - 如果差值不在哈希表中,则将当前数加入哈希表。
通过哈希表的查找特性,我们可以在一次遍历中完成求解,时间复杂度为 O(n),空间复杂度为 O(n)。
代码实现
#include <stdio.h>
#include <stdlib.h>typedef struct { int* table; // 哈希表数组int size; // 哈希表大小
} HashTable;// 初始化哈希表
void initHash(HashTable* hashTable, int size) {hashTable->size = size;hashTable->table = (int*)malloc(size * sizeof(int)); // 分配内存for (int i = 0; i < size; i++) {hashTable->table[i] = -1; // 初始化哈希表中的值为-1,表示没有存储任何数据}
}// 释放哈希表内存
void freeHash(HashTable* hashTable) { free(hashTable->table); // 释放哈希表中的数组内存free(hashTable); // 释放哈希表结构体内存
}// 查找和为目标值的两个数的下标
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {int size = 20001; // 哈希表的大小,范围[-10^9, 10^9]需要加上10000进行偏移,保证索引为正HashTable* hashTable = (HashTable*)malloc(sizeof(HashTable)); // 分配哈希表结构体内存initHash(hashTable, size); // 初始化哈希表for (int i = 0; i < numsSize; i++) {// 计算当前数的补数int complement = target - nums[i];int index = complement + 10000; // 补数的哈希表索引,偏移+10000确保索引为正// 如果补数已经在哈希表中,说明找到了两个数,它们的和等于目标值if (hashTable->table[index] != -1) {int* result = (int*)malloc(2 * sizeof(int)); // 分配结果数组result[0] = i; // 当前数的下标result[1] = hashTable->table[index]; // 补数的下标freeHash(hashTable); // 释放哈希表内存*returnSize = 2; // 设置返回结果的大小return result; // 返回结果}// 如果补数不在哈希表中,将当前数添加到哈希表int numIndex = nums[i] + 10000; // 当前数的哈希表索引hashTable->table[numIndex] = i; // 将当前数的下标存入哈希表}freeHash(hashTable); // 释放哈希表内存*returnSize = 0; // 没有找到结果,返回0return NULL; // 返回空值
}
代码解析
1. 哈希表结构体定义
我们定义了一个 HashTable
结构体,它包含两个成员:
table
:一个数组,用于存储哈希表中的数据。我们使用一个整数数组,哈希表的大小为 20001,以适应数据范围[-10^9, 10^9]
,通过加上10000
来保证索引为正。size
:哈希表的大小。
2. 初始化哈希表
在 initHash
函数中,我们为哈希表分配内存,并将哈希表中的所有值初始化为 -1
,表示该位置还没有存储数据。
3. 释放哈希表内存
在 freeHash
函数中,我们释放了哈希表的内存,包括哈希表的数组和结构体本身。
4. 主函数 twoSum
- 我们首先计算目标值
target
和当前数的差值complement
。 - 通过偏移量
10000
将补数的范围映射到哈希表的索引范围内,确保索引为正。 - 如果哈希表中已经存在补数,则返回当前数和补数的下标。
- 如果哈希表中不存在补数,则将当前数及其下标存入哈希表。
5. 返回结果
- 如果找到了符合条件的两个数,我们将它们的下标存入一个结果数组并返回。
- 如果没有找到符合条件的数对,返回
NULL
。
复杂度分析
- 时间复杂度:O(n),其中 n 是数组
nums
的长度。我们只需要遍历一次数组,每次操作的时间复杂度为 O(1)(哈希表查找和插入操作)。 - 空间复杂度:O(n),我们需要一个哈希表来存储最多
n
个元素。
可惜,这种解法需要空间过大,无法满足所有案例。
第二种解法
// 定义哈希表的结构体
struct hashTable {int key; // 哈希表的键,用于存储数值int val; // 哈希表的值,用于存储索引UT_hash_handle hh; // UT_hash库提供的宏,用于处理哈希表操作
};// 声明一个全局指针,用于指向哈希表的头节点
struct hashTable* hashtable;// 查找哈希表中是否存在某个键
struct hashTable* find(int ikey) {struct hashTable* tmp; // 定义一个临时指针用于存储查找结果HASH_FIND_INT(hashtable, &ikey, tmp); // 使用UT_hash库提供的宏查找键为ikey的元素return tmp; // 返回查找结果
}// 向哈希表中插入键值对
void insert(int ikey, int ival) {struct hashTable* it = find(ikey); // 首先查找键是否已存在if (it == NULL) { // 如果键不存在struct hashTable* tmp = malloc(sizeof(struct hashTable)); // 分配内存tmp->key = ikey, tmp->val = ival; // 初始化键值对HASH_ADD_INT(hashtable, key, tmp); // 使用UT_hash库提供的宏将新元素添加到哈希表中} else { // 如果键已存在it->val = ival; // 更新值}
}// 解决“两数之和”问题
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {hashtable = NULL; // 初始化哈希表为空for (int i = 0; i < numsSize; i++) { // 遍历数组struct hashTable* it = find(target - nums[i]); // 查找当前元素的补数是否在哈希表中if (it != NULL) { // 如果找到补数int* ret = malloc(sizeof(int) * 2); // 分配内存用于存储结果ret[0] = it->val, ret[1] = i; // 存储两个数的索引*returnSize = 2; // 设置返回结果的大小为2return ret; // 返回结果}insert(nums[i], i); // 将当前元素及其索引插入哈希表}*returnSize = 0; // 如果未找到结果,设置返回结果的大小为0return NULL; // 返回NULL
}作者:力扣官方题解
链接:https://leetcode.cn/problems/two-sum/solutions/434597/liang-shu-zhi-he-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
官方解法虽然看着复杂,实际上最重要的是多了#include “uthash.h”,自己得学会如何调库。
代码功能概述
这段代码使用了UTHash库来实现“两数之和”问题。目标是在给定数组中找到两个数,它们的和等于目标值 target
,并返回这两个数的索引。
代码详细注释
1. 哈希表结构体定义
struct hashTable {int key; // 键:存储数组中的数值int val; // 值:存储数组中数值对应的索引UT_hash_handle hh; // UTHash宏定义的句柄,用于内部管理哈希表节点
};
key
:哈希表的键,用于存储数组中的数值。val
:哈希表的值,用于存储数组中数值对应的索引。UT_hash_handle hh
:这是UTHash库要求的字段,用于管理哈希表的内部结构(例如,存储链表指针、哈希值等)。用户不需要直接操作这个字段。
2. 哈希表指针
struct hashTable* hashtable;
- 这是一个全局变量,指向哈希表的根节点。UTHash库通过这个指针来管理整个哈希表。
3. 查找函数
struct hashTable* find(int ikey) {struct hashTable* tmp;HASH_FIND_INT(hashtable, &ikey, tmp); // 使用UTHash宏查找键为ikey的节点return tmp; // 返回找到的节点,如果未找到则返回NULL
}
HASH_FIND_INT
:这是UTHash库提供的宏,用于在哈希表中查找键为ikey
的节点。- 第一个参数是哈希表的根节点指针(
hashtable
)。 - 第二个参数是指向键值的指针(
&ikey
)。注意,这里传入的是键值的地址。 - 第三个参数是输出参数,用于存储查找结果。
- 第一个参数是哈希表的根节点指针(
- 如果找到键为
ikey
的节点,则返回该节点;否则返回NULL
。
4. 插入函数
void insert(int ikey, int ival) {struct hashTable* it = find(ikey); // 先查找键是否已存在if (it == NULL) { // 如果键不存在struct hashTable* tmp = malloc(sizeof(struct hashTable)); // 分配新节点tmp->key = ikey, tmp->val = ival; // 初始化新节点的键和值HASH_ADD_INT(hashtable, key, tmp); // 使用UTHash宏将新节点插入哈希表} else { // 如果键已存在it->val = ival; // 更新节点的值}
}
HASH_ADD_INT
:这是UTHash库提供的宏,用于将一个新节点插入哈希表。- 第一个参数是哈希表的根节点指针(
hashtable
)。 - 第二个参数是哈希表结构体中用于哈希的字段名(
key
)。 - 第三个参数是要插入的新节点(
tmp
)。
- 第一个参数是哈希表的根节点指针(
- 如果键已存在,则更新该键对应的值。
5. 两数之和函数
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {hashtable = NULL; // 初始化哈希表为空for (int i = 0; i < numsSize; i++) {struct hashTable* it = find(target - nums[i]); // 查找补数是否已存在if (it != NULL) { // 如果找到补数int* ret = malloc(sizeof(int) * 2); // 分配返回数组ret[0] = it->val, ret[1] = i; // 返回补数的索引和当前索引*returnSize = 2; // 设置返回数组的大小return ret; // 返回结果}insert(nums[i], i); // 将当前数值及其索引插入哈希表}*returnSize = 0; // 如果未找到结果,返回大小为0return NULL; // 返回NULL
}
- 逻辑流程:
- 初始化哈希表:将
hashtable
设置为NULL
,表示哈希表为空。 - 遍历数组:
- 对于数组中的每个数字
nums[i]
,计算它的补数(target - nums[i]
)。 - 使用
find
函数查找补数是否已经在哈希表中。 - 如果找到补数:
- 分配一个大小为2的数组
ret
,存储补数的索引和当前索引。 - 设置返回数组的大小为2。
- 返回结果。
- 分配一个大小为2的数组
- 如果未找到补数:
- 调用
insert
函数,将当前数字及其索引插入哈希表。
- 调用
- 对于数组中的每个数字
- 未找到结果:
- 如果遍历结束后仍未找到结果,设置返回大小为0,并返回
NULL
。
- 如果遍历结束后仍未找到结果,设置返回大小为0,并返回
- 初始化哈希表:将
6. 主函数(示例)
int main() {int nums[] = {2, 7, 11, 15}; // 输入数组int target = 9; // 目标值int returnSize; // 返回结果的大小int* result = twoSum(nums, 4, target, &returnSize); // 调用两数之和函数if (result) { // 如果找到结果printf("Result: [%d, %d]\n", result[0], result[1]); // 打印结果free(result); // 释放结果内存} else { // 如果未找到结果printf("No solution found.\n");}return 0;
}
- 输出:
Result: [0, 1]
- 解释:
- 数组
[2, 7, 11, 15]
中,nums[0] + nums[1] = 2 + 7 = 9
,因此返回[0, 1]
。
- 数组
总结
这段代码通过UTHash库实现了“两数之和”问题,具有以下优点:
- 动态哈希表:UTHash库可以动态管理哈希表,适合处理大范围的整数。
- 简洁高效:UTHash提供了简洁的宏来操作哈希表,代码易于理解和维护。
- 时间复杂度低:哈希表操作的时间复杂度为O(1),整体算法时间复杂度为O(n)。