1. 项目背景与目标
作为一名非科班出身的C语言开发者,我决定系统性地通过力扣平台提升算法能力。这个系列记录了我从零开始刷题的全过程,特别适合没有算法背景但具备基础C语言能力的同行参考。选择C语言而非更现代的Python或Java,一方面是巩固已有技能,另一方面也是想验证C语言在算法题中的实际表现。
2. 环境准备与工具链配置
2.1 开发环境搭建
我使用的是VSCode + MinGW的组合方案:
- 安装MinGW-w64(版本8.1.0)
- 配置环境变量PATH添加gcc路径
- VSCode安装C/C++扩展包
- 创建tasks.json配置编译任务
c复制// 示例:测试环境是否正常工作
#include <stdio.h>
int main() {
printf("Hello LeetCode!\n");
return 0;
}
注意:MinGW安装时建议选择posix线程模型和seh异常处理,这是最兼容的配置方案。
2.2 力扣刷题工作流
我的典型解题流程:
- 阅读题目并手写伪代码
- 在本地创建.c文件实现
- 使用自定义测试用例调试
- 提交到力扣平台验证
- 记录解题思路和优化过程
3. 基础算法实现与优化
3.1 哈希表应用实例
以经典的"两数之和"为例,分享C语言的实现方案:
c复制/**
* Note: The returned array must be malloced, assume caller calls free().
*/
typedef struct {
int key;
int value;
UT_hash_handle hh;
} HashNode;
int* twoSum(int* nums, int numsSize, int target, int* returnSize) {
HashNode *hashTable = NULL, *tmp = NULL;
int* ret = (int*)malloc(2 * sizeof(int));
for (int i = 0; i < numsSize; i++) {
int complement = target - nums[i];
HASH_FIND_INT(hashTable, &complement, tmp);
if (tmp) {
ret[0] = tmp->value;
ret[1] = i;
*returnSize = 2;
return ret;
}
tmp = (HashNode*)malloc(sizeof(HashNode));
tmp->key = nums[i];
tmp->value = i;
HASH_ADD_INT(hashTable, key, tmp);
}
*returnSize = 0;
return NULL;
}
关键点说明:
- 使用uthash第三方库实现哈希表
- 内存管理需要特别注意malloc/free
- 结构体设计包含键值对和uthash必需字段
3.2 快速排序的边界处理
实现快速排序时最容易出错的就是边界条件:
c复制void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}
int partition(int arr[], int low, int high) {
int pivot = arr[high];
int i = low - 1;
for (int j = low; j <= high - 1; j++) {
if (arr[j] < pivot) {
i++;
swap(&arr[i], &arr[j]);
}
}
swap(&arr[i + 1], &arr[high]);
return i + 1;
}
void quickSort(int arr[], int low, int high) {
if (low < high) {
int pi = partition(arr, low, high);
quickSort(arr, low, pi - 1);
quickSort(arr, pi + 1, high);
}
}
常见陷阱:
- 基准元素选择不当导致最坏时间复杂度
- 递归终止条件遗漏等于情况
- 分区时索引越界
4. 算法优化实战案例
4.1 二分查找的变形题
以"搜索旋转排序数组"为例,展示如何修改标准二分查找:
c复制int search(int* nums, int numsSize, int target) {
int left = 0, right = numsSize - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) return mid;
if (nums[left] <= nums[mid]) {
if (target >= nums[left] && target < nums[mid]) {
right = mid - 1;
} else {
left = mid + 1;
}
} else {
if (target > nums[mid] && target <= nums[right]) {
left = mid + 1;
} else {
right = mid - 1;
}
}
}
return -1;
}
优化要点:
- 判断哪半边是有序的
- 检查目标值是否在有序范围内
- 处理重复元素的特殊情况
4.2 滑动窗口技巧
"无重复字符的最长子串"的滑动窗口解法:
c复制int lengthOfLongestSubstring(char * s) {
int charSet[256] = {0};
int left = 0, maxLen = 0;
for (int right = 0; s[right] != '\0'; right++) {
while (charSet[s[right]]) {
charSet[s[left]] = 0;
left++;
}
charSet[s[right]] = 1;
maxLen = (right - left + 1) > maxLen ? (right - left + 1) : maxLen;
}
return maxLen;
}
性能考量:
- 使用数组而非哈希表存储字符出现位置
- 窗口滑动时的双指针移动逻辑
- ASCII码直接作为数组索引的技巧
5. 调试技巧与性能分析
5.1 自定义测试用例生成
我常用的测试框架:
c复制#define TEST_CASE(input, expect) do { \
int returnSize; \
int* result = twoSum(input, sizeof(input)/sizeof(int), 9, &returnSize); \
assert(returnSize == 2); \
assert(input[result[0]] + input[result[1]] == 9); \
free(result); \
} while(0)
int main() {
int case1[] = {2,7,11,15};
int case2[] = {3,2,4};
TEST_CASE(case1, );
TEST_CASE(case2, );
return 0;
}
5.2 性能测量方法
使用<time.h>进行基准测试:
c复制#include <time.h>
void benchmark() {
clock_t start, end;
double cpu_time_used;
int largeArray[10000];
// 初始化测试数据...
start = clock();
quickSort(largeArray, 0, 9999);
end = clock();
cpu_time_used = ((double) (end - start)) / CLOCKS_PER_SEC;
printf("Time used: %f seconds\n", cpu_time_used);
}
6. 进阶挑战与学习路线
6.1 内存管理最佳实践
在解决"合并K个排序链表"时的内存优化:
c复制typedef struct {
int val;
struct ListNode *next;
} ListNode;
ListNode* mergeKLists(ListNode** lists, int listsSize) {
// 使用最小堆优化合并过程
// 特别注意每个节点的内存释放
}
关键点:
- 链表节点的动态创建与释放
- 递归解法可能导致的栈溢出
- 尾指针优化减少遍历次数
6.2 从算法题到工程实践
将刷题中学到的技巧应用到实际项目中:
- 哈希表用于缓存实现
- 快速排序优化日志分析
- 二分查找加速配置检索
7. 资源推荐与持续学习
7.1 推荐学习资料
书籍:
- 《算法导论》经典理论
- 《C Interfaces and Implementations》实践指南
在线资源:
- LeetCode官方C语言题解
- GeeksforGeeks算法专栏
- uthash官方文档
7.2 我的刷题计划
当前阶段:
- 每日1-2道中等难度题目
- 每周复习错题集
- 每月专项突破(如动态规划)
下一步目标:
- 系统学习图算法
- 深入理解动态规划
- 尝试参加周赛
