100 道 C 语言高频手撕代码

本篇整理了 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 语言必刷手撕代码题,覆盖通用算法、数据结构、嵌入式实操等面试高频场景,建议自己动手敲一遍加深记忆。

制作不易,如果对你有用,希望能点赞收藏支持一下。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

天空'之城

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值