Two Sum(C语言实现)

Two Sum(C语言实现)

哈希(hash)表,亦称散列表,是通过函数映射的方式将关键字和存储位置建立联系,进而实现快速查找。

问题描述】给定一个整数数组 nums 和一个目标值 target,请给出该数组中和为目标值的两个元素的下标。
每组数组只需求出一种答案。
示例:
给定 nums = [7, 11, 2, 15], target = 9
因为 nums[0] + nums[2] = 7 + 2 = 9
所以返回 [0, 2]

算法实现】最简单的方法莫过于暴力求解,设计双重循环,直到找到问题的解,而双重循环对应的时间复杂度为O(n^2)。有没有可能降低其复杂度呢?

暴力求解的问题在于盲目遍历,如果我们提前将数组排好序,那么就可以有方向性的进行对比试探,直到找到问题的解。这种查找方式只需要一次循环就能实现,但常见的排序方式的复杂度也都较高。

Two Sum(C语言实现)_第1张图片

有没有复杂度为O(n)的算法呢?

我们知道,通过映射方式建立的哈希表可以将查找的复杂度从O(n)降至O(1)。对于哈希表而言,最关键的两个问题是:如何设计散列函数,使得发生冲突的概率尽可能小;同时,如何处理好不可避免的冲突。

对于该问题,假设最小元素为min,那么,对于大于target-min的元素是不需要考虑的。若记max =target-min,我们可以建立一个长度为max-min+1的一维数组hash,就足以存放所有可能的元素。

算法实现
将数组中在[min, max]区间的元素从小到大存放在在hash表中,即hash[nums[i]-min]=num[i],表中空余部分填充-1作标记,表示无对应的nums元素。如下表所示:

Two Sum(C语言实现)_第2张图片

那么,我们只需要一次循环,看下对应的hash[target-2×min-i]处是否有对应的nums元素即可,即判断hash[target-2×min-i]是否等于-1。如若不等于-1,则查找完毕,返回nums中对应的i+min和target-min+i的小标即可。
代码如下:

int* twoSum(int* nums, int numsSize, int target)
{
    int min = 2147483647;
    int i;
for (i = 0; i < numsSize; i++)
{
if (nums[i] < min)
            min = nums[i];
    }
    int max = target - min;
    int len = max - min + 1;   //确定hash长度
    int *hash = (int*)malloc(len*sizeof(int));
    int *indice = (int*)malloc(2*sizeof(int));
    for(i=0;i<len;i++)
    {
    	hash[i]=-1;           //初始化hash表
    }
    for(i=0;i<numsSize;i++)
    {
    	if(nums[i]-min<len)
		hash[nums[i]-min]=nums[i];//建立hash表
    }
    for(i=0;i<len;i++)
	{
		if(hash[target-2*min-i]!=-1)
		{
			indice[0] = i+min;
			indice[1]=target-min-i;
			break;
		}
	} 
	for(i=0;i<numsSize;i++)
	{
		if(nums[i]==indice[0])indice[0] = i;
		if(nums[i]==indice[1])indice[1] = i;		
	}
	free(hash);
	return indice;   
}

考虑到题目中需要返回的是元素的下标值,我们可以对散列函数进行如下修改。hash表中存放nums数组中在[min, max]区间的元素的下标值。即,hash[nums[i]-min]=i。如下图所示:

Two Sum(C语言实现)_第3张图片

同样,我们只需要检测对应的hash[target-2×min-i]处是否有对应的nums元素下标,直接返回hash[i]和hash[target-2×min-i]即可。

上述的方法未考虑nums数组中是否有相同元素冲突的问题,即还需要对返回的两个indice进行判断,是否相等,相等的话,还需在[0,indice[1])查找新的indice[0]使得nums[indice[0]]= nums[indice[1]]。

此外,上述方法虽然没有出现两次循环的嵌套,但代码量还是比较大。能否进一步优化呢?
是否可以边建hash表,边比对,找到符合的元素下标就停止查找呢?

于是,就有了下面的思路。
对于初始化后的hash表,先检查与i (hash[m])对应的j (hash[n])是否存在(是否为-1),其中nums[i]+nums[j]==target,则j=hash[n],n=nums[j]-min=target-nums[i]-min,如下图所示:

Two Sum(C语言实现)_第4张图片

如若存在,则停止查找,indice[0]=i,indice[1]=j=hash[target-nums[i]-min];如若不存在,则给hash[m]赋值,即hash[num[i]-min]=i。

此方法先对比后赋值,避免了相同解的冲突,且更为简洁。代码如下:

int* twoSum(int* nums, int numsSize, int target)
{
int min = 2147483647;
    int i = 0;
for (i = 0; i < numsSize; i++) 
    {
if (nums[i] < min)
            min = nums[i];
    }
    int max = target - min;
    int len = max - min + 1;   //确定hash长度
    int *table = (int*)malloc(len*sizeof(int));
    int *indice = (int*)malloc(2*sizeof(int));
for (i = 0; i < len; i++)
{
table[i] = -1;         //hash初值
    }
for (i = 0; i < numsSize; i++) 
    {
if (nums[i]-min < len) 
       {
if (table[target-nums[i]-min] != -1)
{        
indice[0] = table[target-nums[i] - min];
                indice[1] = i;
                return indice;
            }
            table[nums[i]-min] = i;
        }
    }
    free(table);
    return indice;
}

希望本文能够给大家带来一些启发。

同时也欢迎大家关注我的公众号,赵小赵的编程哲学(微信号:zxzdbczx),不定时更新C语言学习过程中的一些心得感悟。

你可能感兴趣的:(Two Sum(C语言实现))