本篇整理了 100 道 C 语言面试高频手撕代码题,覆盖字符串操作、链表、排序查找、双指针、递归、矩阵、数据结构、嵌入式常用代码等核心考点,代码简洁规范,适合面试前背诵默写、笔试刷题。
目录
一、字符串与内存操作
1. 实现 strlen(求字符串长度)
int my_strlen(const char *s) {
int len = 0;
while (*s++) len++;
return len;
}
思路:遍历字符串直到遇到结束符 '\0',统计字符个数。
2. 实现 strcpy(字符串拷贝)
char *my_strcpy(char *dest, const char *src) {
char *d = dest;
while ((*d++ = *src++));
return dest;
}
思路:逐个字符拷贝,直到拷贝完结束符。
3. 实现 strncpy(指定长度字符串拷贝)
char *my_strncpy(char *dest, const char *src, int n) {
int i;
for (i = 0; i < n && src[i]; i++)
dest[i] = src[i];
for (; i < n; i++)
dest[i] = '\0';
return dest;
}
思路:最多拷贝 n 个字符,不足部分用 '\0' 补齐。
4. 实现 strcmp(字符串比较)
int my_strcmp(const char *a, const char *b) {
while (*a && *b && *a == *b) {
a++;
b++;
}
return *a - *b;
}
思路:逐字符比较,出现不同或结束时返回差值。
5. 实现 strcat(字符串拼接)
char *my_strcat(char *dest, const char *src) {
char *d = dest;
while (*d) d++;
while ((*d++ = *src++));
return dest;
}
思路:先找到目标串末尾,再从末尾开始追加源串。
6. 实现 strstr(查找子串)
char *my_strstr(const char *str, const char *sub) {
while (*str) {
const char *s = str, *p = sub;
while (*s && *p && *s == *p) {
s++; p++;
}
if (!*p) return (char*)str;
str++;
}
return NULL;
}
思路:朴素匹配法,遍历主串逐个匹配子串。
7. 实现 memcpy(内存拷贝)
void *my_memcpy(void *dest, const void *src, int n) {
char *d = (char *)dest;
const char *s = (const char *)src;
while (n--)
*d++ = *s++;
return dest;
}
思路:按字节逐位拷贝,不处理内存重叠。
8. 实现 memmove(安全内存拷贝,处理重叠)
void *my_memmove(void *dest, const void *src, int n) {
char *d = (char *)dest;
const char *s = (const char *)src;
if (d < s) {
while (n--) *d++ = *s++;
} else {
d += n; s += n;
while (n--) *--d = *--s;
}
return dest;
}
思路:内存重叠时从后往前拷贝,避免数据覆盖。
9. 字符串逆序
void reverse_str(char *s) {
if (!s) return;
char *l = s, *r = s + my_strlen(s) - 1;
while (l < r) {
char c = *l;
*l = *r;
*r = c;
l++; r--;
}
}
思路:双指针首尾交换,向中间靠拢。
10. 字符串空格替换为 %20
void replaceSpace(char *str, int len) {
int space = 0, i;
for (i = 0; i < len; i++)
if (str[i] == ' ') space++;
int new_len = len + 2 * space;
for (i = len - 1; i >= 0; i--) {
if (str[i] == ' ') {
str[new_len--] = '0';
str[new_len--] = '2';
str[new_len--] = '%';
} else {
str[new_len--] = str[i];
}
}
}
思路:先统计空格数计算新长度,从后往前填充,避免覆盖。
二、链表专题
先定义链表节点结构:
struct ListNode {
int val;
struct ListNode *next;
};
11. 单链表反转
struct ListNode* reverseList(struct ListNode *head) {
struct ListNode *prev = NULL, *cur = head, *next;
while (cur) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
return prev;
}
思路:三指针遍历,逐个反转节点指向。
12. 判断链表是否有环
int hasCycle(struct ListNode *head) {
struct ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) return 1;
}
return 0;
}
思路:快慢指针,快指针每次走两步,相遇则有环。
13. 找链表环的入口节点
struct ListNode* detectCycle(struct ListNode *head) {
struct ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
if (slow == fast) {
slow = head;
while (slow != fast) {
slow = slow->next;
fast = fast->next;
}
return slow;
}
}
return NULL;
}
思路:相遇后慢指针回到起点,两指针同速走,相遇点即为入口。
14. 合并两个有序链表
struct ListNode* mergeTwoLists(struct ListNode *l1, struct ListNode *l2) {
struct ListNode dummy, *p = &dummy;
dummy.next = NULL;
while (l1 && l2) {
if (l1->val < l2->val) {
p->next = l1; l1 = l1->next;
} else {
p->next = l2; l2 = l2->next;
}
p = p->next;
}
p->next = l1 ? l1 : l2;
return dummy.next;
}
思路:虚拟头节点 + 双指针,按大小依次拼接。
15. 删除链表倒数第 n 个节点
struct ListNode* removeNthFromEnd(struct ListNode *head, int n) {
struct ListNode dummy, *fast, *slow;
dummy.next = head;
fast = slow = &dummy;
while (n--) fast = fast->next;
while (fast->next) {
fast = fast->next;
slow = slow->next;
}
slow->next = slow->next->next;
return dummy.next;
}
思路:快慢指针,快指针先走 n 步,再同步走到末尾。
16. 删除链表中所有值等于 val 的节点
struct ListNode* removeElements(struct ListNode *head, int val) {
struct ListNode dummy, *p = &dummy;
dummy.next = head;
while (p->next) {
if (p->next->val == val) {
p->next = p->next->next;
} else {
p = p->next;
}
}
return dummy.next;
}
思路:虚拟头节点遍历,遇到目标值直接跳过节点。
17. 找链表中间节点
struct ListNode* middleNode(struct ListNode *head) {
struct ListNode *slow = head, *fast = head;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
思路:快慢指针,快指针走到末尾时慢指针在中间。
18. 反转链表前 n 个节点
struct ListNode* reverseN(struct ListNode *head, int n) {
struct ListNode *prev = NULL, *cur = head, *next;
while (n--) {
next = cur->next;
cur->next = prev;
prev = cur;
cur = next;
}
head->next = cur;
return prev;
}
思路:反转前 n 个节点后,将原头节点接到剩余链表前。
19. 两个链表找第一个公共节点
struct ListNode* getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
struct ListNode *a = headA, *b = headB;
while (a != b) {
a = a ? a->next : headB;
b = b ? b->next : headA;
}
return a;
}
思路:双指针分别遍历两个链表,走到末尾换另一条,相遇点即为公共节点。
20. 链表排序(归并排序)
struct ListNode* sortList(struct ListNode *head) {
if (!head || !head->next) return head;
struct ListNode *slow = head, *fast = head->next;
while (fast && fast->next) {
slow = slow->next;
fast = fast->next->next;
}
struct ListNode *mid = slow->next;
slow->next = NULL;
return mergeTwoLists(sortList(head), sortList(mid));
}
思路:快慢指针切分链表,递归排序后合并。
三、排序与查找
21. 冒泡排序
void bubbleSort(int a[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-1-i; j++) {
if (a[j] > a[j+1]) {
int t = a[j]; a[j] = a[j+1]; a[j+1] = t;
}
}
}
}
思路:相邻元素两两比较交换,每轮把最大值沉到末尾。
22. 选择排序
void selectSort(int a[], int n) {
for (int i = 0; i < n-1; i++) {
int min = i;
for (int j = i+1; j < n; j++)
if (a[j] < a[min]) min = j;
int t = a[i]; a[i] = a[min]; a[min] = t;
}
}
思路:每轮选出未排序部分最小值,放到已排序末尾。
23. 插入排序
void insertSort(int a[], int n) {
for (int i = 1; i < n; i++) {
int t = a[i], j;
for (j = i-1; j >=0 && a[j]>t; j--)
a[j+1] = a[j];
a[j+1] = t;
}
}
思路:将当前元素插入到前面已排序序列的正确位置。
24. 快速排序
void quickSort(int a[], int l, int r) {
if (l >= r) return;
int i = l, j = r, pivot = a[l];
while (i < j) {
while (i < j && a[j] >= pivot) j--;
a[i] = a[j];
while (i < j && a[i] <= pivot) i++;
a[j] = a[i];
}
a[i] = pivot;
quickSort(a, l, i-1);
quickSort(a, i+1, r);
}
思路:选基准值,小值放左大值放右,递归分区排序。
25. 归并排序
void merge(int a[], int l, int mid, int r, int tmp[]) {
int i = l, j = mid+1, k = 0;
while (i <= mid && j <= r)
tmp[k++] = a[i] < a[j] ? a[i++] : a[j++];
while (i <= mid) tmp[k++] = a[i++];
while (j <= r) tmp[k++] = a[j++];
for (i = 0; i < k; i++) a[l+i] = tmp[i];
}
void mergeSort(int a[], int l, int r, int tmp[]) {
if (l >= r) return;
int mid = l + (r-l)/2;
mergeSort(a, l, mid, tmp);
mergeSort(a, mid+1, r, tmp);
merge(a, l, mid, r, tmp);
}
思路:二分拆分数组,排序后合并两个有序区间。
26. 基础二分查找
int binarySearch(int a[], int n, int target) {
int l = 0, r = n-1;
while (l <= r) {
int mid = l + (r-l)/2;
if (a[mid] == target) return mid;
else if (a[mid] < target) l = mid+1;
else r = mid-1;
}
return -1;
}
思路:有序数组折半查找,每次缩小一半搜索范围。
27. 二分查找左边界
int searchLeft(int a[], int n, int target) {
int l = 0, r = n;
while (l < r) {
int mid = l + (r-l)/2;
if (a[mid] < target) l = mid+1;
else r = mid;
}
return l < n && a[l] == target ? l : -1;
}
思路:找到目标值时继续向左收缩,锁定第一个出现位置。
28. 二分查找右边界
int searchRight(int a[], int n, int target) {
int l = 0, r = n;
while (l < r) {
int mid = l + (r-l)/2;
if (a[mid] <= target) l = mid+1;
else r = mid;
}
return l-1 >=0 && a[l-1] == target ? l-1 : -1;
}
思路:找到目标值时继续向右收缩,锁定最后一个出现位置。
29. 查找第一个大于等于目标值的位置
int searchFirstGe(int a[], int n, int target) {
int l = 0, r = n;
while (l < r) {
int mid = l + (r-l)/2;
if (a[mid] < target) l = mid+1;
else r = mid;
}
return l;
}
思路:二分查找左边界的通用形式,可用于插入位置计算。
30. 数组中第 k 大元素(快排思想)
int findKthLargest(int a[], int n, int k) {
k = n - k;
int l = 0, r = n-1;
while (l <= r) {
int i = l, j = r, pivot = a[l];
while (i < j) {
while (i < j && a[j] >= pivot) j--;
a[i] = a[j];
while (i < j && a[i] <= pivot) i++;
a[j] = a[i];
}
a[i] = pivot;
if (i == k) return a[i];
else if (i < k) l = i+1;
else r = i-1;
}
return -1;
}
思路:快排分区思想,根据基准位置缩小搜索第 k 大的区间。
四、数组与数学题
31. 有序数组去重
int removeDuplicates(int a[], int n) {
if (n <= 1) return n;
int slow = 0;
for (int fast = 1; fast < n; fast++) {
if (a[fast] != a[slow]) {
slow++;
a[slow] = a[fast];
}
}
return slow + 1;
}
思路:快慢指针,快指针遍历,遇到新元素慢指针前移并赋值。
32. 数组向右旋转 k 位
void reverse(int a[], int l, int r) {
while (l < r) {
int t = a[l]; a[l] = a[r]; a[r] = t;
l++; r--;
}
}
void rotate(int a[], int n, int k) {
k %= n;
reverse(a, 0, n-1);
reverse(a, 0, k-1);
reverse(a, k, n-1);
}
思路:三次反转法,整体反转 + 前 k 个反转 + 后段反转。
33. 找出数组中重复的数字
int findDuplicate(int a[], int n) {
for (int i = 0; i < n; i++) {
while (a[i] != i) {
if (a[i] == a[a[i]]) return a[i];
int t = a[i]; a[i] = a[t]; a[t] = t;
}
}
return -1;
}
思路:原地交换法,把数字放到对应下标位置,重复时会相遇。
34. 两数之和
int* twoSum(int a[], int n, int target, int *returnSize) {
static int res[2];
*returnSize = 2;
for (int i = 0; i < n; i++) {
for (int j = i+1; j < n; j++) {
if (a[i] + a[j] == target) {
res[0] = i; res[1] = j;
return res;
}
}
}
return NULL;
}
思路:暴力枚举两数组合,匹配目标和返回下标。
35. 最大子序和
int maxSubArray(int a[], int n) {
int max = a[0], cur = a[0];
for (int i = 1; i < n; i++) {
cur = cur + a[i] > a[i] ? cur + a[i] : a[i];
max = max > cur ? max : cur;
}
return max;
}
思路:动态规划思想,维护当前子数组和,遇负则重新开始计算。
36. 斐波那契数列
int fib(int n) {
if (n <= 1) return n;
int a = 0, b = 1, t;
for (int i = 2; i <= n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
思路:迭代滚动计算,避免递归重复计算,效率更高。
37. 判断素数
int isPrime(int n) {
if (n < 2) return 0;
for (int i = 2; i*i <= n; i++) {
if (n % i == 0) return 0;
}
return 1;
}
思路:遍历到根号 n 即可,能整除则不是素数。
38. 十进制转二进制字符串
void decToBin(int num, char *s) {
char *p = s;
if (num == 0) {
*p++ = '0';
*p = '\0';
return;
}
while (num) {
*p++ = num % 2 + '0';
num /= 2;
}
*p = '\0';
reverse_str(s);
}
思路:除 2 取余法,逆序拼接后反转得到正确二进制。
39. 实现 atoi(字符串转整数)
int my_atoi(const char *str) {
int num = 0, sign = 1;
while (*str == ' ') str++;
if (*str == '+' || *str == '-') {
sign = (*str == '-') ? -1 : 1;
str++;
}
while (*str >= '0' && *str <= '9') {
num = num * 10 + (*str - '0');
str++;
}
return num * sign;
}
思路:跳过空格,处理符号,逐位计算数值。
40. 实现 itoa(整数转字符串)
char *my_itoa(int num, char *str) {
char *start = str;
int sign = num;
if (num < 0) num = -num;
do {
*str++ = num % 10 + '0';
} while ((num /= 10) > 0);
if (sign < 0) *str++ = '-';
*str = '\0';
char *l = start, *r = str - 1;
while (l < r) {
char c = *l; *l = *r; *r = c;
l++; r--;
}
return start;
}
思路:取模逐位得到逆序数字,补符号位后反转字符串。
五、数据结构与位运算
41. 数组实现栈
#define STACK_SIZE 100
typedef struct {
int data[STACK_SIZE];
int top;
} Stack;
void stackInit(Stack *s) { s->top = 0; }
int stackEmpty(Stack *s) { return s->top == 0; }
int stackPush(Stack *s, int v) {
if (s->top >= STACK_SIZE) return -1;
s->data[s->top++] = v;
return 0;
}
int stackPop(Stack *s, int *v) {
if (stackEmpty(s)) return -1;
*v = s->data[--s->top];
return 0;
}
思路:数组模拟栈,top 指向栈顶下一个位置,入栈 ++ 出栈 --。
42. 数组实现循环队列
#define QUEUE_SIZE 10
typedef struct {
int data[QUEUE_SIZE];
int front, rear;
} Queue;
void qInit(Queue *q) { q->front = q->rear = 0; }
int qFull(Queue *q) { return (q->rear+1)%QUEUE_SIZE == q->front; }
int qEmpty(Queue *q) { return q->front == q->rear; }
int qPush(Queue *q, int v) {
if (qFull(q)) return -1;
q->data[q->rear] = v;
q->rear = (q->rear+1) % QUEUE_SIZE;
return 0;
}
int qPop(Queue *q, int *v) {
if (qEmpty(q)) return -1;
*v = q->data[q->front];
q->front = (q->front+1) % QUEUE_SIZE;
return 0;
}
思路:牺牲一个位置区分空和满,取模实现循环下标。
43. 用两个栈实现队列
Stack s1, s2;
void pushQueue(int x) { stackPush(&s1, x); }
int popQueue(int *x) {
if (stackEmpty(&s2)) {
while (!stackEmpty(&s1)) {
int t;
stackPop(&s1, &t);
stackPush(&s2, &t);
}
}
return stackPop(&s2, x);
}
思路:入栈放 s1,出栈时 s1 全部倒入 s2,s2 为空再倒。
44. 统计二进制中 1 的个数
int countOne(int x) {
int c = 0;
while (x) {
c++;
x &= x - 1;
}
return c;
}
思路:x & x-1 消除最低位的 1,消除次数就是 1 的个数。
45. 不使用临时变量交换两个数
void swap(int *a, int *b) {
*a ^= *b;
*b ^= *a;
*a ^= *b;
}
思路:异或运算性质,相同数异或为 0,0 异或任何数为自身。
46. 判断一个数是不是 2 的幂
int isPowerOfTwo(int n) {
return n > 0 && (n & (n-1)) == 0;
}
思路:2 的幂二进制只有一个 1,n&n-1 会消除唯一的 1,结果为 0。
47. 判断系统大小端
int isLittleEndian() {
int a = 1;
return *(char*)&a;
}
思路:int 低字节存放在低地址则小端,强转 char 读取首字节判断。
48. 32 位整数大小端转换
unsigned int bswap32(unsigned int v) {
return ((v >> 24) & 0xff) |
((v >> 8) & 0xff00) |
((v << 8) & 0xff0000) |
((v << 24) & 0xff000000);
}
思路:四个字节分别移位到对应位置,按位或拼接。
49. 固定大小内存池实现
#define BLOCK_SIZE 64
#define POOL_NUM 100
typedef struct {
char data[BLOCK_SIZE];
int used;
} MemBlock;
MemBlock pool[POOL_NUM];
void poolInit() {
for (int i = 0; i < POOL_NUM; i++)
pool[i].used = 0;
}
void *poolAlloc() {
for (int i = 0; i < POOL_NUM; i++) {
if (!pool[i].used) {
pool[i].used = 1;
return pool[i].data;
}
}
return NULL;
}
void poolFree(void *ptr) {
MemBlock *p = (MemBlock*)((char*)ptr - (size_t)&(((MemBlock*)0)->data));
p->used = 0;
}
思路:预分配固定块内存,标记使用状态,避免频繁 malloc/free。
50. 回调函数实现通用冒泡排序
typedef int (*cmpFunc)(const void*, const void*);
void bubbleSortCb(int arr[], int n, cmpFunc cmp) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-1-i; j++) {
if (cmp(&arr[j], &arr[j+1]) > 0) {
int t = arr[j];
arr[j] = arr[j+1];
arr[j+1] = t;
}
}
}
}
int cmpAsc(const void *a, const void *b) {
return *(int*)a - *(int*)b;
}
思路:比较逻辑通过函数指针传入,实现排序逻辑与比较规则解耦。
六、双指针与滑动窗口
51. 移动零(将数组所有 0 移到末尾,保持非零顺序)
void moveZeroes(int* nums, int n) {
int slow = 0;
for (int fast = 0; fast < n; fast++) {
if (nums[fast] != 0) {
nums[slow++] = nums[fast];
}
}
for (int i = slow; i < n; i++) {
nums[i] = 0;
}
}
思路:快慢指针收集所有非零元素,末尾统一补零。
52. 验证回文字符串(忽略大小写和非字母数字)
int isChar(char c) {
return (c>='a'&&c<='z') || (c>='A'&&c<='Z') || (c>='0'&&c<='9');
}
char toLower(char c) {
return c>='A'&&c<='Z' ? c+32 : c;
}
int isPalindrome(char *s) {
int l = 0, r = my_strlen(s)-1;
while (l < r) {
while (l<r && !isChar(s[l])) l++;
while (l<r && !isChar(s[r])) r--;
if (toLower(s[l]) != toLower(s[r])) return 0;
l++; r--;
}
return 1;
}
思路:左右双指针跳过无效字符,统一小写后比较。
53. 合并两个有序数组(从后往前填充)
void merge(int* nums1, int m, int* nums2, int n) {
int i = m-1, j = n-1, k = m+n-1;
while (i>=0 && j>=0) {
nums1[k--] = nums1[i] > nums2[j] ? nums1[i--] : nums2[j--];
}
while (j >= 0) nums1[k--] = nums2[j--];
}
思路:从后往前双指针填充,避免覆盖 nums1 未处理元素。
54. 长度最小的子数组(和≥target 的最短连续子数组)
int minSubArrayLen(int target, int* nums, int n) {
int l = 0, sum = 0, res = n+1;
for (int r = 0; r < n; r++) {
sum += nums[r];
while (sum >= target) {
res = res < r-l+1 ? res : r-l+1;
sum -= nums[l++];
}
}
return res == n+1 ? 0 : res;
}
思路:滑动窗口,右指针扩大窗口,满足条件时左指针收缩求最小值。
55. 移除数组中所有等于 val 的元素
int removeElement(int* nums, int n, int val) {
int slow = 0;
for (int fast = 0; fast < n; fast++) {
if (nums[fast] != val) {
nums[slow++] = nums[fast];
}
}
return slow;
}
思路:快慢指针,快指针遍历,非目标值则覆盖慢指针位置。
56. 有序数组的平方(按非递减顺序返回平方数组)
void sortedSquares(int* nums, int n, int* res) {
int l = 0, r = n-1, k = n-1;
while (l <= r) {
if (nums[l]*nums[l] > nums[r]*nums[r]) {
res[k--] = nums[l]*nums[l];
l++;
} else {
res[k--] = nums[r]*nums[r];
r--;
}
}
}
思路:两端平方值最大,双指针从两端向中间遍历,倒序存入结果。
57. 盛最多水的容器
int maxArea(int* height, int n) {
int l = 0, r = n-1, res = 0;
while (l < r) {
int area = (r-l) * (height[l] < height[r] ? height[l] : height[r]);
res = res > area ? res : area;
if (height[l] < height[r]) l++;
else r--;
}
return res;
}
思路:双指针从两端向中间,每次移动较短的那条边,寻找更大面积。
58. 反转字符串中的单词(不使用额外空间)
void reverseWords(char *s) {
int len = my_strlen(s);
reverse_str(s);
int start = 0;
for (int i = 0; i <= len; i++) {
if (s[i] == ' ' || s[i] == '\0') {
s[i] = '\0';
reverse_str(s + start);
s[i] = ' ';
start = i + 1;
}
}
}
思路:先整体反转,再逐个单词反转,恢复单词顺序。
59. 查找两个有序数组的中位数
double findMedianSortedArrays(int* nums1, int m, int* nums2, int n) {
int total = m + n;
int i = 0, j = 0, prev = 0, cur = 0;
for (int k = 0; k <= total/2; k++) {
prev = cur;
if (i<m && (j>=n || nums1[i]<nums2[j]))
cur = nums1[i++];
else
cur = nums2[j++];
}
if (total % 2 == 1) return cur;
return (prev + cur) / 2.0;
}
思路:双指针归并遍历,走到中间位置记录数值计算中位数。
60. 三数之和(找出所有和为 0 的不重复三元组)
void threeSum(int* nums, int n) {
quickSort(nums, 0, n-1);
for (int i = 0; i < n; i++) {
if (nums[i] > 0) break;
if (i>0 && nums[i]==nums[i-1]) continue;
int l = i+1, r = n-1;
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum == 0) {
// 输出或保存三元组
while (l<r && nums[l]==nums[l+1]) l++;
while (l<r && nums[r]==nums[r-1]) r--;
l++; r--;
} else if (sum < 0) l++;
else r--;
}
}
}
思路:排序 + 固定第一个数 + 双指针找另外两个数,跳过重复元素。
七、递归与分治专题
61. 递归实现阶乘
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n-1);
}
思路:n! = n × (n-1)!,终止条件 n=1 返回 1。
62. 递归反转单链表
struct ListNode* reverseListRecur(struct ListNode* head) {
if (!head || !head->next) return head;
struct ListNode* newHead = reverseListRecur(head->next);
head->next->next = head;
head->next = NULL;
return newHead;
}
思路:递归到最后一个节点作为新头,回溯时反转相邻节点指向。
63. 递归实现汉诺塔
void hanoi(int n, char a, char b, char c) {
if (n == 1) {
printf("%c -> %c\n", a, c);
return;
}
hanoi(n-1, a, c, b);
printf("%c -> %c\n", a, c);
hanoi(n-1, b, a, c);
}
思路:n 个盘子先把 n-1 个移到辅助柱,最大盘移到目标柱,再移 n-1 个过去。
64. 递归求数组最大值
int maxArr(int a[], int l, int r) {
if (l == r) return a[l];
int mid = l + (r-l)/2;
int leftMax = maxArr(a, l, mid);
int rightMax = maxArr(a, mid+1, r);
return leftMax > rightMax ? leftMax : rightMax;
}
思路:二分拆分数组,分别求左右最大值再比较。
65. 递归实现二分查找
int binarySearchRecur(int a[], int l, int r, int target) {
if (l > r) return -1;
int mid = l + (r-l)/2;
if (a[mid] == target) return mid;
if (a[mid] < target)
return binarySearchRecur(a, mid+1, r, target);
else
return binarySearchRecur(a, l, mid-1, target);
}
思路:根据中间值大小递归搜索左半或右半区间。
66. 递归实现字符串逆序
void reverseStrRecur(char *s, int l, int r) {
if (l >= r) return;
char t = s[l]; s[l] = s[r]; s[r] = t;
reverseStrRecur(s, l+1, r-1);
}
思路:交换首尾字符,递归处理中间子串。
67. 爬楼梯(每次走 1 或 2 步,n 阶多少种走法)
int climbStairs(int n) {
if (n <= 2) return n;
int a = 1, b = 2, t;
for (int i = 3; i <= n; i++) {
t = a + b;
a = b;
b = t;
}
return b;
}
思路:本质斐波那契数列,f (n)=f (n-1)+f (n-2),迭代避免递归超时。
68. 快速幂(递归版)
double myPow(double x, int n) {
if (n == 0) return 1;
if (n == 1) return x;
double half = myPow(x, n/2);
if (n % 2 == 0) return half * half;
else return half * half * x;
}
思路:分治思想,n 次方拆成两个 n/2 次方相乘,减少乘法次数。
69. 递归实现全排列
void swapNum(int *a, int *b) {
int t = *a; *a = *b; *b = t;
}
void permute(int a[], int start, int end) {
if (start == end) {
// 输出一组排列
return;
}
for (int i = start; i <= end; i++) {
swapNum(&a[start], &a[i]);
permute(a, start+1, end);
swapNum(&a[start], &a[i]);
}
}
思路:固定首位,递归排列剩余元素,回溯还原数组。
70. 二叉树前序遍历(递归,数组模拟二叉树)
void preOrder(int tree[], int index, int n) {
if (index >= n || tree[index] == -1) return;
printf("%d ", tree[index]);
preOrder(tree, 2*index+1, n);
preOrder(tree, 2*index+2, n);
}
思路:根左右顺序,先访问当前节点,再递归左右子树。
八、矩阵与二维数组
71. 矩阵转
void transpose(int a[][3], int row, int col, int res[][3]) {
for (int i = 0; i < row; i++) {
for (int j = 0; j < col; j++) {
res[j][i] = a[i][j];
}
}
}
思路:行列下标互换,原 [i][j] 放到结果 [j][i]。
72. 螺旋矩阵遍历
void spiralOrder(int matrix[][4], int row, int col, int res[]) {
int top = 0, bottom = row-1, left = 0, right = col-1;
int k = 0;
while (top <= bottom && left <= right) {
for (int i = left; i <= right; i++) res[k++] = matrix[top][i];
top++;
for (int i = top; i <= bottom; i++) res[k++] = matrix[i][right];
right--;
if (top <= bottom) {
for (int i = right; i >= left; i--) res[k++] = matrix[bottom][i];
bottom--;
}
if (left <= right) {
for (int i = bottom; i >= top; i--) res[k++] = matrix[i][left];
left++;
}
}
}
思路:按层遍历,上下左右四条边依次遍历,边界向内收缩。
73. 杨氏矩阵查找(每行每列递增,查找目标值)
int searchMatrix(int matrix[][4], int row, int col, int target) {
int i = 0, j = col-1;
while (i < row && j >= 0) {
if (matrix[i][j] == target) return 1;
if (matrix[i][j] > target) j--;
else i++;
}
return 0;
}
思路:从右上角开始,大于目标左移,小于目标下移,逐步缩小范围。
74. 顺时针旋转矩阵 90 度
void rotateMatrix(int matrix[][3], int n) {
// 先转置
for (int i = 0; i < n; i++)
for (int j = i+1; j < n; j++) {
int t = matrix[i][j];
matrix[i][j] = matrix[j][i];
matrix[j][i] = t;
}
// 再每行反转
for (int i = 0; i < n; i++) {
int l = 0, r = n-1;
while (l < r) {
int t = matrix[i][l];
matrix[i][l] = matrix[i][r];
matrix[i][r] = t;
l++; r--;
}
}
}
思路:先转置矩阵,再逐行反转,完成顺时针 90 度旋转。
75. 矩阵对角线求和
int diagonalSum(int mat[][4], int n) {
int sum = 0;
for (int i = 0; i < n; i++) {
sum += mat[i][i];
if (i != n-1-i) sum += mat[i][n-1-i];
}
return sum;
}
思路:主副对角线同时累加,奇数阶矩阵中心去重。
76. 零矩阵(遇零则整行整列置零)
void setZeroes(int matrix[][4], int row, int col) {
int rowFlag[10] = {0}, colFlag[10] = {0};
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (matrix[i][j] == 0) {
rowFlag[i] = 1;
colFlag[j] = 1;
}
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
if (rowFlag[i] || colFlag[j])
matrix[i][j] = 0;
}
思路:先标记所有含零的行和列,再统一置零,避免边遍历边修改影响结果。
77. 矩阵加法
void matrixAdd(int a[][3], int b[][3], int res[][3], int row, int col) {
for (int i = 0; i < row; i++)
for (int j = 0; j < col; j++)
res[i][j] = a[i][j] + b[i][j];
}
思路:对应位置元素相加,存入结果矩阵。
78. 矩阵乘法
void matrixMul(int a[][3], int b[][2], int res[][2], int m, int k, int n) {
for (int i = 0; i < m; i++)
for (int j = 0; j < n; j++) {
res[i][j] = 0;
for (int t = 0; t < k; t++)
res[i][j] += a[i][t] * b[t][j];
}
}
思路:m×k 矩阵乘 k×n 矩阵得 m×n 矩阵,每个元素是行列点积。
79. 每行最大值
void rowMax(int matrix[][4], int row, int col, int res[]) {
for (int i = 0; i < row; i++) {
int max = matrix[i][0];
for (int j = 1; j < col; j++)
if (matrix[i][j] > max) max = matrix[i][j];
res[i] = max;
}
}
思路:逐行遍历,记录每行最大值存入结果数组。
80. 二维数组按列求和
void colSum(int matrix[][4], int row, int col, int res[]) {
for (int j = 0; j < col; j++) {
res[j] = 0;
for (int i = 0; i < row; i++)
res[j] += matrix[i][j];
}
}
思路:按列遍历,累加每列所有行的元素。
九、经典算法与思维题
81. 约瑟夫环问题
int josephus(int n, int m) {
int res = 0;
for (int i = 2; i <= n; i++) {
res = (res + m) % i;
}
return res + 1;
}
思路:数学递推公式,从 1 个人逐步推导到 n 个人的幸存者位置。
82. 洗牌算法(随机打乱数组)
void shuffle(int a[], int n) {
srand(time(NULL));
for (int i = n-1; i > 0; i--) {
int j = rand() % (i+1);
int t = a[i]; a[i] = a[j]; a[j] = t;
}
}
思路:从后往前,每个位置与前面随机位置交换,保证概率均等。
83. 大整数加法(字符串实现)
void bigAdd(char *a, char *b, char *res) {
int i = my_strlen(a)-1, j = my_strlen(b)-1, carry = 0, k = 0;
while (i>=0 || j>=0 || carry) {
int sum = carry;
if (i >= 0) sum += a[i--] - '0';
if (j >= 0) sum += b[j--] - '0';
res[k++] = sum % 10 + '0';
carry = sum / 10;
}
res[k] = '\0';
reverse_str(res);
}
思路:字符串模拟逐位相加,处理进位,结果反转得到正序。
84. 有效的括号(栈实现)
int isValid(char *s) {
Stack st; stackInit(&st);
for (int i = 0; s[i]; i++) {
if (s[i]=='(' || s[i]=='[' || s[i]=='{') {
stackPush(&st, s[i]);
} else {
int top;
if (stackEmpty(&st)) return 0;
stackPop(&st, &top);
if ((s[i]==')' && top!='(') ||
(s[i]==']' && top!='[') ||
(s[i]=='}' && top!='{'))
return 0;
}
}
return stackEmpty(&st);
}
思路:左括号入栈,右括号匹配栈顶,不匹配或栈空则无效。
85. 整数反转
int reverseInt(int x) {
int res = 0;
while (x != 0) {
res = res * 10 + x % 10;
x /= 10;
}
return res;
}
思路:逐位取模得到末位,拼接到结果末尾。
86. 回文数判断
int isPalindromeNum(int x) {
if (x < 0) return 0;
int tmp = x, rev = 0;
while (tmp) {
rev = rev * 10 + tmp % 10;
tmp /= 10;
}
return x == rev;
}
思路:反转整数,与原数比较是否相等。
87. 最长公共前缀
char* longestCommonPrefix(char** strs, int strsSize) {
if (strsSize == 0) return "";
for (int i = 0; strs[0][i]; i++) {
for (int j = 1; j < strsSize; j++) {
if (strs[j][i] != strs[0][i]) {
strs[0][i] = '\0';
return strs[0];
}
}
}
return strs[0];
}
思路:以第一个串为基准,逐列对比所有字符串,遇不同则截断。
88. 字符串转浮点数
double my_atof(const char *s) {
double res = 0, dec = 0.1;
int sign = 1;
while (*s == ' ') s++;
if (*s == '+' || *s == '-') {
sign = (*s == '-') ? -1 : 1;
s++;
}
while (*s >= '0' && *s <= '9') {
res = res * 10 + (*s - '0');
s++;
}
if (*s == '.') {
s++;
while (*s >= '0' && *s <= '9') {
res += (*s - '0') * dec;
dec *= 0.1;
s++;
}
}
return res * sign;
}
思路:先算整数部分,遇小数点后按小数位逐步累加。
89. 计算后缀表达式(逆波兰表达式)
int evalRPN(char** tokens, int tokensSize) {
Stack st; stackInit(&st);
for (int i = 0; i < tokensSize; i++) {
if (tokens[i][0]>='0' && tokens[i][0]<='9' ||
(tokens[i][0]=='-' && tokens[i][1]>='0')) {
stackPush(&st, atoi(tokens[i]));
} else {
int b, a;
stackPop(&st, &b);
stackPop(&st, &a);
switch(tokens[i][0]) {
case '+': stackPush(&st, a+b); break;
case '-': stackPush(&st, a-b); break;
case '*': stackPush(&st, a*b); break;
case '/': stackPush(&st, a/b); break;
}
}
}
int res; stackPop(&st, &res);
return res;
}
思路:数字入栈,遇运算符弹出两个数计算,结果再入栈。
90. 格雷码生成
void grayCode(int n, int res[], int *size) {
*size = 1 << n;
for (int i = 0; i < *size; i++) {
res[i] = i ^ (i >> 1);
}
}
思路:公式法,第 i 个格雷码 = i ^ (i>>1),直接计算生成。
十、嵌入式常用代码专题
91. 寄存器指定位置 1
#define SET_BIT(reg, bit) (reg |= (1U << bit))
思路:1 左移到对应位,按位或操作,不影响其他位。
92. 寄存器指定位清 0
#define CLR_BIT(reg, bit) (reg &= ~(1U << bit))
思路:1 左移取反得到掩码,按位与清除指定位。
93. 寄存器指定几位赋值
#define SET_VAL(reg, mask, shift, val) (reg = (reg & ~(mask<<shift)) | ((val&mask)<<shift))
思路:先清除目标位段,再写入新值,不影响其他位。
94. 软件延时函数(毫秒级)
void delay_ms(unsigned int ms) {
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 1000; j++);
}
思路:循环空转占用时间,嵌入式无操作系统时常用,需根据主频校准。
95. 环形缓冲区(串口 FIFO 常用)
#define RING_SIZE 128
typedef struct {
unsigned char buf[RING_SIZE];
int head, tail;
} RingBuf;
void ringInit(RingBuf *rb) { rb->head = rb->tail = 0; }
int ringFull(RingBuf *rb) { return (rb->head+1)%RING_SIZE == rb->tail; }
int ringEmpty(RingBuf *rb) { return rb->head == rb->tail; }
int ringWrite(RingBuf *rb, unsigned char data) {
if (ringFull(rb)) return -1;
rb->buf[rb->head] = data;
rb->head = (rb->head+1) % RING_SIZE;
return 0;
}
int ringRead(RingBuf *rb, unsigned char *data) {
if (ringEmpty(rb)) return -1;
*data = rb->buf[rb->tail];
rb->tail = (rb->tail+1) % RING_SIZE;
return 0;
}
思路:数组 + 头尾指针实现循环,用于串口收发缓存、数据解耦。
96. 按键消抖代码
int keyScan(void) {
if (KEY_PIN == 0) {
delay_ms(20);
if (KEY_PIN == 0) {
while (KEY_PIN == 0);
return 1;
}
}
return 0;
}
思路:检测到按下后延时 20ms 再判断,松手后返回,消除机械抖动误触发。
97. 通用大小端转换宏
#define BSWAP16(x) (((x) << 8) | ((x) >> 8))
#define BSWAP32(x) ((((x) & 0xff000000) >> 24) | \
(((x) & 0x00ff0000) >> 8) | \
(((x) & 0x0000ff00) << 8) | \
(((x) & 0x000000ff) << 24))
思路:宏实现字节交换,适配不同大小端平台数据传输。
98. 整数转十六进制字符串
char* itoh(unsigned int num, char *str) {
char *start = str;
const char *hex = "0123456789ABCDEF";
do {
*str++ = hex[num % 16];
num /= 16;
} while (num);
*str = '\0';
reverse_str(start);
return start;
}
思路:除 16 取余得到逆序十六进制字符,反转后得到正序。
99. 简单定时器超时判断
unsigned int getTick(void); // 系统滴答定时器,毫秒计数
int isTimeout(unsigned int start, unsigned int timeout) {
return getTick() - start >= timeout;
}
思路:利用系统时基差值判断超时,无阻塞,嵌入式常用非阻塞延时。
100. 位带操作模拟(置位 / 清位 / 读位)
#define BIT_ADDR(addr, bit) (*(volatile unsigned long *)((addr & 0xF0000000) + 0x2000000 + ((addr & 0xFFFFF) << 5) + ((bit) << 2)))
思路:将寄存器每个 bit 映射到独立地址,直接读写地址操作单个 bit,STM32 位带原理。
以上就是 100 道 C 语言必刷手撕代码题,覆盖通用算法、数据结构、嵌入式实操等面试高频场景,建议自己动手敲一遍加深记忆。
制作不易,如果对你有用,希望能点赞收藏支持一下。

4368

被折叠的 条评论
为什么被折叠?



