作为一名从零开始刷LeetCode的开发者,我深知数组作为最基本的数据结构,是算法学习的必经之路。今天我将分享自己第一天刷题的心得体会,重点解析704二分查找、24移除元素和977有序数组平方这三道经典题目。
数组在内存中是连续存储的,这个特性决定了它的随机访问时间复杂度是O(1)。但插入和删除操作可能需要移动大量元素,时间复杂度为O(n)。理解这个底层原理对解题至关重要。
二分查找是算法学习中的"Hello World",但很多初学者容易忽略细节。标准的二分查找要求数组必须是有序的,这是使用二分的前提条件。
c复制int search(int* nums, int numsSize, int target){
int left = 0;
int right = numsSize - 1;
while(left <= right){
int mid = left + (right - left)/2;
if(nums[mid] == target){
return mid;
}else if(nums[mid] < target){
left = mid + 1;
}else{
right = mid - 1;
}
}
return -1;
}
这里有几个关键点需要注意:
left <= right而不是left < right,这样可以确保检查到所有可能的元素left + (right - left)/2而不是(left + right)/2,防止整数溢出mid + 1和mid - 1,而不是直接用mid,避免死循环提示:二分查找的变种很多,比如查找第一个/最后一个等于目标值的位置,这时需要调整判断条件和边界更新逻辑。
双指针是解决数组问题的利器,24题移除元素就是典型应用。我最初尝试暴力解法,时间复杂度O(n²),后来优化为双指针的O(n)解法,效率提升明显。
快慢指针的核心思想是:快指针用于遍历数组,慢指针用于记录有效元素的位置。当快指针指向的元素不等于目标值时,将其复制到慢指针位置,然后两个指针都前进;否则仅快指针前进。
c复制int removeElement(int* nums, int numsSize, int val){
int slow = 0;
for(int fast = 0; fast < numsSize; fast++){
if(nums[fast] != val){
nums[slow++] = nums[fast];
}
}
return slow;
}
实际编码时我踩过几个坑:
双指针不仅可用于同向移动,还可以对向移动。比如在有序数组中找两数之和,就可以使用左右指针从两端向中间逼近。这种技巧在后续的题目中会经常遇到。
这道题看似简单,但直接平方后排序的时间复杂度是O(nlogn)。利用数组已排序的特性,可以优化到O(n)。
由于负数平方后会变成正数,最大的平方值一定出现在数组的两端。因此可以从两端向中间遍历,比较两端的平方值,将较大的放入结果数组的末尾。
c复制int* sortedSquares(int* nums, int numsSize, int* returnSize){
*returnSize = numsSize;
int* result = (int*)malloc(numsSize * sizeof(int));
int left = 0, right = numsSize - 1;
int index = numsSize - 1;
while(left <= right){
int leftSquare = nums[left] * nums[left];
int rightSquare = nums[right] * nums[right];
if(leftSquare > rightSquare){
result[index--] = leftSquare;
left++;
}else{
result[index--] = rightSquare;
right--;
}
}
return result;
}
这里有几个关键点:
left <= right,确保处理所有元素在实际编码过程中,我遇到了几个典型问题,这里分享解决方法:
在二分查找中,如果right初始化为numsSize而不是numsSize-1,可能会导致访问越界。这种错误在C语言中不会立即报错,但会导致不可预知的行为。
调试方法:
在977题中,需要动态分配内存。常见错误包括:
解决方法:
很多错误发生在边界条件下,比如:
测试用例建议:
理解算法的时间复杂度和空间复杂度是刷题的重要部分:
二分查找:
移除元素(双指针法):
有序数组平方(双指针法):
在实际面试中,面试官不仅关心你是否能解决问题,更关心你是否能分析不同解法的优劣,并选择最优方案。
在原始材料中提到的printf格式化输出,虽然与算法关系不大,但在调试时非常有用:
c复制// 调试时打印数组
for(int i = 0; i < numsSize; i++){
printf("%3d ", nums[i]); // 每个数字占3位
if((i+1) % 10 == 0) printf("\n"); // 每10个换行
}
常用格式说明符:
%5d:右对齐,宽度5%-5d:左对齐,宽度5%02d:不足两位前面补零%.2f:保留两位小数掌握这些格式化技巧可以让调试输出更清晰,方便发现问题。
完成这三道题目后,我建议可以继续练习以下相关题目巩固知识:
二分查找变种:
双指针应用:
数组操作:
刷题时建议:
算法学习是个长期过程,保持每天至少一道题的节奏,坚持几个月就会看到明显进步。我在实际工作中发现,这些基础算法思想确实能帮助写出更高效的代码。