LeetCode 219题"存在重复元素 II"是一道经典的数组处理题目,题目要求我们判断一个整数数组中是否存在两个相同的元素,且这两个相同元素的索引差不超过给定的k值。这道题在面试中经常出现,因为它很好地考察了对基础数据结构的理解和应用能力。
题目给出的函数原型是:
c复制bool containsNearbyDuplicate(int* nums, int numsSize, int k)
其中:
nums是输入的整数数组numsSize是数组的长度k是允许的最大索引差举个例子,对于输入nums = [1,2,3,1],k = 3,因为第一个1和最后一个1的索引差是3(0和3),所以返回true。如果k = 2,则返回false。
最直观的解法是使用双重循环遍历数组,对于每个元素,检查其后k个元素中是否有相同的值。这种方法的时间复杂度是O(n*k),在k较大时效率很低。
c复制// 暴力解法示例
bool containsNearbyDuplicate(int* nums, int numsSize, int k) {
for (int i = 0; i < numsSize; i++) {
for (int j = i + 1; j <= i + k && j < numsSize; j++) {
if (nums[i] == nums[j]) {
return true;
}
}
}
return false;
}
更高效的解法是使用哈希表来记录元素及其索引。我们可以在遍历数组时,维护一个大小为k+1的滑动窗口,使用哈希表存储窗口内的元素。这样可以将时间复杂度降低到O(n)。
具体步骤:
在C语言中,标准库没有提供现成的哈希表实现,我们需要自己实现一个简单的哈希表。这里我们使用开放寻址法来处理哈希冲突。
c复制#define HASH_SIZE 10000
typedef struct {
int key;
int val;
} HashItem;
typedef struct {
HashItem* items[HASH_SIZE];
} HashTable;
// 哈希函数
unsigned int hash(int key) {
return (unsigned int)(key) % HASH_SIZE;
}
// 初始化哈希表
void hashInit(HashTable* table) {
for (int i = 0; i < HASH_SIZE; i++) {
table->items[i] = NULL;
}
}
// 插入元素
void hashInsert(HashTable* table, int key, int val) {
unsigned int h = hash(key);
while (table->items[h] != NULL) {
h = (h + 1) % HASH_SIZE;
}
HashItem* item = (HashItem*)malloc(sizeof(HashItem));
item->key = key;
item->val = val;
table->items[h] = item;
}
// 查找元素
HashItem* hashFind(HashTable* table, int key) {
unsigned int h = hash(key);
while (table->items[h] != NULL) {
if (table->items[h]->key == key) {
return table->items[h];
}
h = (h + 1) % HASH_SIZE;
}
return NULL;
}
// 删除元素
void hashRemove(HashTable* table, int key) {
unsigned int h = hash(key);
while (table->items[h] != NULL) {
if (table->items[h]->key == key) {
free(table->items[h]);
table->items[h] = NULL;
return;
}
h = (h + 1) % HASH_SIZE;
}
}
基于上述哈希表实现,我们可以写出完整的解题代码:
c复制bool containsNearbyDuplicate(int* nums, int numsSize, int k) {
if (numsSize <= 1 || k <= 0) return false;
HashTable table;
hashInit(&table);
for (int i = 0; i < numsSize; i++) {
HashItem* item = hashFind(&table, nums[i]);
if (item != NULL && i - item->val <= k) {
// 清理哈希表内存
for (int j = 0; j < HASH_SIZE; j++) {
if (table.items[j] != NULL) {
free(table.items[j]);
}
}
return true;
}
hashInsert(&table, nums[i], i);
// 维护滑动窗口大小不超过k
if (i >= k) {
hashRemove(&table, nums[i - k]);
}
}
// 清理哈希表内存
for (int j = 0; j < HASH_SIZE; j++) {
if (table.items[j] != NULL) {
free(table.items[j]);
}
}
return false;
}
哈希表解法需要额外的O(min(n,k))空间来存储哈希表,因为滑动窗口的大小最多为k+1。
c复制void test() {
int nums1[] = {1,2,3,1};
assert(containsNearbyDuplicate(nums1, 4, 3) == true);
int nums2[] = {1,0,1,1};
assert(containsNearbyDuplicate(nums2, 4, 1) == true);
int nums3[] = {1,2,3,1,2,3};
assert(containsNearbyDuplicate(nums3, 6, 2) == false);
int nums4[] = {99,99};
assert(containsNearbyDuplicate(nums4, 2, 2) == true);
int nums5[] = {1};
assert(containsNearbyDuplicate(nums5, 1, 1) == false);
printf("All test cases passed!\n");
}
在C语言实现哈希表时,容易忘记释放内存。确保在函数返回前释放所有分配的哈希表项。
开放寻址法可能导致聚集现象,可以通过以下方式优化:
这类滑动窗口与哈希表结合的技术在实际开发中有广泛应用:
本题体现了两个重要的算法模式:
掌握这两种技术可以解决一大类数组/字符串处理问题。
在Python中,借助字典可以更简洁地实现:
python复制def containsNearbyDuplicate(nums, k):
seen = {}
for i, num in enumerate(nums):
if num in seen and i - seen[num] <= k:
return True
seen[num] = i
return False
这种实现更简洁,但理解底层原理(哈希表实现)对于性能优化和解决更复杂问题很有帮助。