问题描述
给定一个整数数组,找出其中两个数,使得它们的和等于一个目标值。返回这两个数的下标,从1开始计数。假设数组中只有一个唯一的解。例如,给定数组[2, 7, 11, 15],目标值为9,返回[1, 2],因为2 + 7 = 9。
解决方案
这个问题可以用哈希表来解决。哈希表是一种数据结构,它可以在常数时间内查找一个键是否存在,以及对应的值是什么。我们可以用一个哈希表来存储数组中的每个元素及其下标。然后,我们遍历数组,对于每个元素,我们计算它与目标值的差,然后在哈希表中查找是否有这个差值。如果有,就说明我们找到了一对解,返回它们的下标。如果没有,就把当前元素及其下标加入哈希表,继续遍历。这个算法的时间复杂度是O(n),空间复杂度也是O(n),其中n是数组的长度。
代码
以下是用C语言实现的代码,假设数组的长度不超过100,目标值不超过200。
#include <stdio.h>
#include <stdlib.h>
// 定义一个哈希表的结构体,包含键、值和指向下一个节点的指针
struct hash_node {
int key;
int value;
struct hash_node *next;
};
// 定义一个哈希表的大小,可以根据需要调整
#define HASH_SIZE 201
// 定义一个哈希函数,将一个整数映射到一个下标
int hash(int key) {
return (key + HASH_SIZE) % HASH_SIZE;
}
// 定义一个函数,向哈希表中插入一个键值对,如果键已存在,更新值
void hash_insert(struct hash_node **table, int key, int value) {
// 计算键的哈希值
int index = hash(key);
// 在哈希表的对应位置查找键是否存在
struct hash_node *node = table[index];
while (node != NULL) {
// 如果键存在,更新值,返回
if (node->key == key) {
node->value = value;
return;
}
// 否则,继续查找下一个节点
node = node->next;
}
// 如果键不存在,创建一个新的节点,插入到链表的头部
node = (struct hash_node *)malloc(sizeof(struct hash_node));
node->key = key;
node->value = value;
node->next = table[index];
table[index] = node;
}
// 定义一个函数,从哈希表中查找一个键对应的值,如果键不存在,返回-1
int hash_find(struct hash_node **table, int key) {
// 计算键的哈希值
int index = hash(key);
// 在哈希表的对应位置查找键是否存在
struct hash_node *node = table[index];
while (node != NULL) {
// 如果键存在,返回值
if (node->key == key) {
return node->value;
}
// 否则,继续查找下一个节点
node = node->next;
}
// 如果键不存在,返回-1
return -1;
}
// 定义一个函数,释放哈希表占用的内存
void hash_free(struct hash_node **table) {
// 遍历哈希表的每个位置
for (int i = 0; i < HASH_SIZE; i++) {
// 释放链表的每个节点
struct hash_node *node = table[i];
while (node != NULL) {
struct hash_node *temp = node;
node = node->next;
free(temp);
}
}
}
// 定义一个函数,解决给定的问题,返回一个包含两个下标的数组,如果没有解,返回NULL
int *two_sum(int *nums, int nums_size, int target) {
// 创建一个哈希表
struct hash_node **table = (struct hash_node **)malloc(sizeof(struct hash_node *) * HASH_SIZE);
// 初始化哈希表为空
for (int i = 0; i < HASH_SIZE; i++) {
table[i] = NULL;
}
// 遍历数组
for (int i = 0; i < nums_size; i++) {
// 计算当前元素与目标值的差
int diff = target - nums[i];
// 在哈希表中查找是否有这个差值
int j = hash_find(table, diff);
// 如果有,说明找到了一对解,返回它们的下标
if (j != -1) {
// 创建一个数组,存储结果
int *result = (int *)malloc(sizeof(int) * 2);
result[0] = j + 1;
result[1] = i + 1;
// 释放哈希表占用的内存
hash_free(table);
// 返回结果
return result;
}
// 如果没有,把当前元素及其下标加入哈希表
hash_insert(table, nums[i], i);
}
// 释放哈希表占用的内存
hash_free(table);
// 如果遍历完数组都没有找到解,返回NULL
return NULL;
}
// 定义一个函数,打印一个数组
void print_array(int *array, int size) {
printf("[");
for (int i = 0; i < size; i++) {
printf("%d", array[i]);
if (i < size - 1) {
printf(", ");
}
}
printf("]\n");
}
// 定义一个主函数,测试上述函数
int main() {
// 定义一个测试用的数组和目标值
int nums[] = {2, 7, 11, 15};
int nums_size = sizeof(nums) / sizeof(nums[0]);
int target = 9;
// 打印数组和目标值
printf("数组:");
print_array(nums, nums_size);
printf("目标值:%d\n", target);
// 调用解决问题的函数,得到结果
int *result = two_sum(nums, nums_size, target);
// 打印结果
if (result != NULL) {
printf("结果:");
print_array(result, 2);
// 释放结果占用的内存
free(result);
} else {
printf("没有结果\n");
}
// 返回0,表示程序正常结束
return 0;
}
运行结果:
总结
这个问题的核心是如何在常数时间内查找一个数是否存在于一个数组中,以及它的下标是什么。如果用暴力的方法,就需要遍历整个数组,时间复杂度是O(n^2),效率很低。如果用排序的方法,就需要先对数组进行排序,然后用双指针或二分查找的方法,时间复杂度是O(nlogn),空间复杂度也增加了。如果用哈希表的方法,就可以在O(1)的时间内完成查找,只需要一次遍历数组,时间复杂度是O(n),空间复杂度也是O(n)。哈希表的优势在于它可以将一个复杂的问题转化为一个简单的问题,即将一个数映射到一个下标,然后用数组的方式存储和访问。哈希表的缺点在于它需要额外的空间,而且可能会出现哈希冲突的情况,需要用链表或其他方法解决。总的来说,哈希表是一种非常实用的数据结构,可以解决很多问题,值得学习和掌握。