给定一个整数数组 nums 和一个整数目标值 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 < = n u m s . l e n g t h < = 103 2 <= nums.length <= 103 2<=nums.length<=103
- − 1 0 9 < = n u m s [ i ] < = 1 0 9 -10^9 <= nums[i] <= 10^9 −109<=nums[i]<=109
- − 1 0 9 < = t a r g e t < = 1 0 9 -10^9 <= target <= 10^9 −109<=target<=109
- 只会存在一个有效答案
分析
对于题目中给定的target,只需要逐个遍历数组中的任意两个元素是否满足题意即可:若满足则直接返回下标,若不满足则继续遍历.
实现
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
// 由于题目中确定满足题意的索引一定存在,所以可以提前将returnSize赋值,并开辟索引数组存储结果索引
*returnSize = 2;
int *result = malloc(sizeof(int) * 2);
memset(result, 0, sizeof(int) * 2);
int start = 0;
while(start < numsSize) {
int end = numsSize - 1;
while (start < end) {
if (nums[start] + nums[end] == target) {
result[0] = start;
result[1] = end;
return result;
}
end--;
}
start++;
}
*returnSize = 0;
return NULL;
}
算法复杂度分析
时间复杂度
- 最好时间复杂度:如果在外层循环第一次遍历内层循环结束之前就遍历到了结果,那么循环就会终止,此时酸腐复杂度只需要 O ( 1 ) O(1) O(1);
- 最坏时间复杂度:如果至到最后一次循环才遍历到结果的话,外层循环需要进行n-1次遍历,而内层循环需要进行n-1-start次循环,所以时间复杂度为
F
(
n
)
F(n)
F(n)
F ( n ) = ( n − 1 − 0 ) + ( n − 1 − 1 ) + ( n − 1 − 2 ) + . . . + 2 + 1 = n ∗ ( n − 1 ) / 2 = n 2 / 2 − n / 2 F(n) = (n-1-0) + (n-1-1) + (n-1-2) + ... + 2 + 1 = n*(n-1)/2= n^2/2 - n/2 F(n)=(n−1−0)+(n−1−1)+(n−1−2)+...+2+1=n∗(n−1)/2=n2/2−n/2
所以最坏时间复杂度为 O ( n 2 ) O(n^2) O(n2).
空间复杂度
由于只开辟了一个保存结果的一纬数组,所以空间复杂度为
O
(
1
)
O(1)
O(1).
其他算法
时间复杂度为
O
(
n
2
)
O(n^2)
O(n2)的算法,在数据量较大时消耗的时间会增长较快,所以可以尝试减少查询
t
a
r
g
e
t
−
n
u
m
s
[
i
]
target -nums[i]
target−nums[i]的次数.这里可以尝试使用散列表进行优化.
既然需要使用散列表,首先需要构建散列表:由于
n
u
m
s
[
i
]
nums[i]
nums[i]的取值范围很广,直接使用作为散列值的话在数据量较小时会造成巨大的内存浪费,所以可以尝试使用常用的取模操作来作为散列函数:
#define HASHCOUNT 1000
struct Element {
int key;
int index;
struct Element *next;
};
struct HashTable {
struct Element *list[HASHCOUNT];
int (*hash)(int);
};
struct Element *find(struct HashTable *ht, int key) {
int keyIndex = ht->hash(key);
struct Element *ele = ht->list[keyIndex];
while (ele) {
if (ele->key == key) return ele;
ele = ele->next;
}
return NULL;
}
int hashFun(int key) {
// 细节问题,由于key可能会出现负值,而负值在数组取值时会导致异常,所以这里需要处理一下
int keyIndex = key;
while (keyIndex < 0) {
keyIndex += HASHCOUNT;
}
return keyIndex % HASHCOUNT;
}
// 添加元素
void add(struct HashTable *ht, int key, int index) {
struct Element *ele = find(ht, key);
if (ele != NULL) {
ele->index = index;
} else {
int hashIndex = ht->hash(key);
ele = ht->list[hashIndex];
struct Element *addElement = malloc(sizeof(struct Element));
addElement->key = key;
addElement->index = index;
addElement->next = NULL;
if (ele) {
while (ele->next) ele = ele->next;
addElement->next = ele->next;
ele->next = addElement;
} else {
ht->list[hashIndex] = addElement;
}
}
}
这样就基本完成了函数实现所需要的基本功能,然后对散列表进行初始化,
int* twoSum(int* nums, int numsSize, int target, int* returnSize){
struct HashTable *ht = malloc(sizeof(struct HashTable));
ht->hash = hashFun;
memset(ht->list, 0, sizeof(struct Element *) * HASHCOUNT);
for(int index = 0; index < numsSize; index++) {
int element = nums[index];
struct Element *ele = find(ht, target-element);
if (ele) {
*returnSize = 2;
int *result = malloc(sizeof(int) * 2);
result[0] = ele->index;
result[1] = index;
return result;
} else {
add(ht, element, index);
}
}
*returnSize = 0;
return NULL;
}
这个算法的提交时消耗的时间就会变得相对较少,但是相对来讲消耗的内存会增加。
事实上,C语言有一套开源的散列实现uthash,借助于该框架的实现对该题目做分析会发现可以简化自己对于散列表的实现支持,性能上也表现得更加优异.
struct HTElement {
int key;
int index;
UT_hash_handle hh; // 其实在这里并没有做实际的实现可以理解为使用原始数据做散列函数
};
// 定义全局散列头节点对象
struct HTElement *head = NULL;
// 查找
struct HTElement *find(int key) {
struct HTElement *element = NULL;
HASH_FIND_INT(head, &key, element);
return element
}
// 添加
void add(int key, int index) {
struct HTElement *des = find(key);
if (des) {
des->index = index;
} else {
struct HTElement *addElement = malloc(sizeof(struct HTElement));
addElement->key = key;
addElement->index = index;
HASH_ADD_INT(head, key, addElement);
}
}
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
// reinitialize global variables
// 重新初始化全局变理,全局变理在C语言里是有很大的副作用
head = NULL;
int* result = malloc(sizeof(int) * 2);
*result = *(result + 1) = -1;
*returnSize = 2;
for (int i = 0; i < numsSize; ++i) {
int x = target - nums[i];
struct HTElement * res = find(x);
if (res) {
result[0] = res->index, result[1] = i;
return result;
}
add(nums[i], i);
}
*returnSize = 0;
return NULL;
}
由于在没有冲突的情况下散列表的时间复杂度可以降低到
O
(
1
)
O(1)
O(1),所以整个算法的时间复杂度在较为理想的情况下就可以压缩为
O
(
n
)
O(n)
O(n)。