给定一个整数数组 nums
和一个整数目标值 target
,请你在该数组中找出 和为目标值 target
的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [3,2,4], target = 6 输出:[1,2]
这道题我们通常有两种解法:
第一种是暴力法,就是我们常见的思路双for循环,我们来分析一下为什么我们能这么简单地想出这个办法?其实就是遍历的思想,当你锚定第一个元素的时候,然后以此遍历接下来的每一个元素,其本质是:主动的寻找元素和第一个元素进行比较,所以第一种方式的比较次数会从高到低依次降低,即第一次是两次,第二次就是1次,从共是C32,也就是3次。但是一旦数组非常大的时候,那么遍历所花费的时间就很多了,时间复杂度是O(n2)。所以有没有时间复杂度更少的解法呢?
第二种是哈希表,这个解法有什么优势呢?其本质是什么呢?其实就是换了一种比较的思维,上一种方式中,都是锚定一个元素然后主动地和剩下的元素去比较,这种方式是主动的,比较次数依次减少,而哈希表是空间换时间,先建立一个哈希表,先让第一个元素自己躺进去,然后等别人和他来比较,换句话说就是从“主动”变为“被动”,那变成被动的就有优势啦?这个时候就要说说哈希表的特点了。不管是暴力法还是哈希表,都是有第一轮遍历的,但是在遍历的时候,两者做的事情确实不一样的!暴力法是在遍历第一个元素的时候,继续使用遍历,继续看剩下的元素,而哈希表是直接拉着第一轮遍历的元素来哈希表比较,在这个示例中,我们要求和,那么这个时候就要拿着第一轮遍历元素和target的差值来到哈希表中寻找,而哈希表寻找的时间复杂度是O(1),所以这个时候时间复杂度就比第二次遍历的时间复杂度要低很多了,如果在哈希表中没有找到的话,由于该元素只比较了第一个元素,没有和之后的元素进行比较,所以也要扔到哈希表中,这样话,到最后,比较的次数是逐次增加的,第一次是1次,第二次是两次,总共是C32,也就是3次。其中这种先比较后扔进去的方式也是很巧妙的,一开始的时候也很难想出来,因为如果知道用哈希表计算的话,很多人都会想着一开始就将所有的元素一起扔进哈希表中,然后再来遍历第一轮元素,然后一个个比较就好了。总之,哈希表的前种方式是将后种的两个步骤合二为一了。
那怎么实现呢?
在C语言中,需要手搓哈希表,所以非常不友好,不过奈何本人水平有限,只会C,所以借鉴了一下其他人的代码,代码如下:
// 写一个判断是否是质数的函数
int IsPrime(int num)
{
for (int i = 2; i <= num / 2; i++)
{
if (num % i == 0)
return 0;
}
return 1;
}
int GetLen(int num) // 装入元素的个数,装填因子是0.7,取到的整数是最好是质数,默认越大越好
{
int result = (int)(num / 0.7);
while (1)
{
if (IsPrime(result))
return result;
else
{
result++;
}
}
}
// 有了最佳的长度,那么就要有自家的除数,除数的大小常见的选择是使用小于表长的最接近表长一半的质数作为除数
int GetDivisor(int len)
{
int result = len / 2;
while (1)
{
if (IsPrime(result))
{
return result;
}
else
{
result++;
}
}
}
// 用哈希表的方式来解答一下下面的题目
// 首先建立一个结构体来存储哈希表
// 建立一个哈希表数组的哈希节点,里面存其值和其在数组中的下标
typedef struct HashNode
{
int data;
int index;
} HashNode;
typedef struct Hashlist
{
HashNode *array; // 其中的哈希表数组
int num; // 来定义一下哈希表里面的元素有多少个
} Hash;
// 在来建立一个初始化哈希表的函数
Hash *Hashinit(int num) // 其中的num表示的是数组的元素个数
{
Hash *initlist = (Hash *)malloc(sizeof(Hash)); // 先分配一个空间
initlist->array = (HashNode *)malloc(sizeof(HashNode) * GetLen(num)); // 给数组分配空间
initlist->num = 0;
for (int i = 0; i < GetLen(num); i++)
{
initlist->array[i].data = 100;
initlist->array[i].index = -2;
} // 初始化让其中的值都为0
return initlist;
}
// 怎么计算哈希值呢?
int hash1(int key, int num)
{
return key % GetDivisor(num);
}
int hash2(int key, int i, int num) // 其中的i是表示前进几步,num表示的是数组的长度
{
return (hash1(key, num) + i) % GetLen(num);
}
// 初始化哈希表之后,怎么放元素呢?
void put(Hash *list, int key, int num,int *c) // 因为都是遍历将元素放入哈希表中的,所以索引
{
int count = 0;//细节决定成败啊,现在的水平还不高,写代码的时候应该一步一步来,先将步骤拆分出来,然后看看是否可以合并,否则有你受的。
// 我们放元素的时候采用除留取余的方式,解决冲突的方式是线性探查
if (list->array[hash1(key, num)].data == 100) // 这里面的100是不太可能与放入的元素重复的一个元素,表示的是空
{
list->array[hash1(key, num)].data = key;
*c = count;
list->num++;
}
else
{ count = 1;
while (list->array[hash2(key, count, num)].data != 100)
{
// if (key == list->array[hash2(key, count, num)].data)
// {
// printf("在一个哈希表中输入了同一个数字,输入错误!");
// return;
// }
// else
// {
count++;
// }
}
list->array[hash2(key, count, num)].data = key;// 当找到这样的位置的时候
*c = count; //将count传递出去
list->num++; //
}
}
// 怎么快速查找这个元素是否在哈希表中呢,然后返回其在数组中的下标呢?
int find(Hash *list, int key, int num) // 其中的num还是数组的长度
{
int count = 1;
if (list->array[hash1(key, num)].data == key)
{
return list->array[hash1(key, num)].index;
}
else
{
while (list->array[hash2(key, count, num)].data != key)
{
if (list->array[hash2(key, count, num)].data == 100)
{
return -1; // 当搜索当空白位置的时候,返回-1说明这个数一定不在这个表里面
}
count++;
}
return list->array[hash2(key, count, num)].index;
}
}
int *twoSum(int *nums, int numsSize, int target, int *returnSize)
{
int *temp = (int *)malloc(sizeof(int) * 2);
Hash *HashTable = Hashinit(numsSize);
int *c1 = (int*)malloc(sizeof(int));
put(HashTable, nums[0], numsSize,c1);
HashTable->array[hash1(nums[0], numsSize)].index= 0;
for (int i = 1; i < numsSize; i++)
{
int search = target - nums[i];
int finda = find(HashTable, search, numsSize);
if (finda == -1)
{
int *c = (int*)malloc(sizeof(int));
put(HashTable, nums[i], numsSize,c);
HashTable->array[hash2(nums[i], *c, numsSize)].index= i;
}
else
{
if (i > finda)
{
temp[0] = finda;
temp[1] = i;
}
else if (i < finda)
{
temp[0] = i;
temp[1] = finda;
}
*returnSize = 2;
return temp;
}
}
*returnSize = 0;
return NULL;
}
代码多主要是有很多辅助函数,其实主框架还是很少的。
其实还有一种运用C语言库函数的方式,这样的相当于封装了一些内容,让代码看的更精简了。