C语言与数据结构及算法经典面试题目
C语言基础
1. int main() 和 void main()的区别是什么?
答案解析: int main()是C语言标准定义的主函数形式, 返回值表示程序执行状态 (0表示成功, 非0表示错误)。void main()非标准, 可能导致未定义行为, 部分编译器接受但不推荐。
示例代码:
int main() {
return 0; // 表示程序正常退出
}
2. const 关键字的作用是什么?
答案解析: const 用于定义常量, 防止变量被修改。修饰指针时, 需区分const int *p (指针指向的数据不可改) 和 int *const p (指针本身不可改)。
示例代码:
const int a = 10; // a不可修改
int b = 20;
const int *p = &b; // p指向的数据不可改
// *p = 30; // 错误
p = &a; // 正确
3. static 关键字在C语言中的作用?
答案解析: static 修饰局部变量时, 使其生命周期延长至程序结束, 存储在静态存储区; 修饰全局变量或函数时, 限制其作用域为当前文件。
示例代码:
#include <stdio.h>
void func() {
static int count = 0; // 保留值
count++;
printf("%d\n", count);
}
4. volatile 关键字的作用?
答案解析: volatile 告诉编译器变量可能被外部修改, 防止优化 (如寄存器缓存)。常用于多线程或硬件寄存器访问。
示例代码:
volatile int flag = 0; // 防止编译器优化
5. sizeof 和 strlen 的区别?
答案解析: sizeof 是操作符, 返回变量或类型的字节大小; strlen 是函数, 返回字符串长度 (不含\0)。
示例代码:
#include <stdio.h>
#include <string.h>
char str[] = "hello";
// printf("%zu\n", sizeof(str)); // 6 (含\0)
// printf("%zu\n", strlen(str)); // 5
指针与内存管理
6. 什么是野指针?如何避免?
答案解析: 野指针是指向非法内存的指针, 可能导致程序崩溃。避免方法: 初始化指针为NULL, 使用后置空, 检查指针有效性。
示例代码:
int *p = NULL; // 初始化为NULL
if (p != NULL) {
*p = 10; // 安全访问
}
7. 指针和引用的区别?
答案解析: 指针是存储地址的变量, 可变且可为空; 引用 (C++特性, C无引用) 是变量别名, 不可变且不可为空。C中常用指针模拟引用行为。
示例代码:
void swap(int *a, int *b) {
int temp = *a;
*a = *b;
*b = temp;
}
8. 解释内存泄漏及其检测方法。
答案解析: 内存泄漏指动态分配的内存未释放。检测方法: 使用工具如Valgrind, 手动检查malloc/free配对。
示例代码:
#include <stdlib.h>
int *p = malloc(sizeof(int));
if (p) {
*p = 10;
free(p); // 防止泄漏
p = NULL;
}
9. malloc 和 calloc 的区别?
答案解析: malloc 分配指定字节内存, 未初始化; calloc 分配并初始化为0。
示例代码:
#include <stdlib.h>
int *p1 = malloc(10 * sizeof(int)); // 未初始化
int *p2 = calloc(10, sizeof(int)); // 初始化为0
10. 什么是段错误 (Segmentation Fault)?
答案解析: 段错误通常由访问非法内存引起, 如解引用NULL指针、越界访问数组等。
示例代码:
int *p = NULL;
// *p = 10; // 将导致段错误
数据结构
链表
11. 实现单链表的逆序。
答案解析: 遍历链表, 逐个改变指针方向, 需三个指针 (prev, curr, next) 防止断链。时间复杂度O(n), 空间复杂度O(1)。
示例代码:
#include <stdlib.h>
struct ListNode {
int val;
struct ListNode *next;
};
struct ListNode* reverseList(struct ListNode* head) {
struct ListNode *prev = NULL, *curr = head, *next;
while (curr) {
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
12. 检测链表中是否有环。
答案解析: 使用快慢指针(Floyd算法), 快指针每次走两步, 慢指针走一步, 若相遇则有环。时间复杂度O(n), 空间复杂度O(1)。
示例代码:
#include <stdbool.h>
#include <stdlib.h>
struct ListNode {
int val;
struct ListNode *next;
};
bool 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 true;
}
return false;
}
13. 合并两个有序链表。
答案解析: 递归或迭代比较节点值, 构造新链表。时间复杂度O(n+m), 空间复杂度O(1)(迭代)或O(n+m)(递归)。
示例代码:
struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2) {
struct ListNode dummy;
struct ListNode* tail = &dummy;
dummy.next = NULL;
while (l1 && l2) {
if (l1->val < l2->val) {
tail->next = l1;
l1 = l1->next;
} else {
tail->next = l2;
l2 = l2->next;
}
tail = tail->next;
}
tail->next = l1 ? l1 : l2;
return dummy.next;
}
14. 删除链表倒数第N个节点。
答案解析: 使用双指针, 快指针先走N步, 慢指针跟进, 找到倒数第N个节点前驱, 删除目标节点。时间复杂度O(n), 空间复杂度O(1)。
示例代码:
struct ListNode* removeNthFromEnd(struct ListNode* head, int n) {
struct ListNode dummy = {0, head};
struct ListNode *fast = &dummy, *slow = &dummy;
for (int i = 0; i <= n; i++) fast = fast->next;
while (fast) {
fast = fast->next;
slow = slow->next;
}
struct ListNode* temp = slow->next;
slow->next = slow->next->next;
free(temp); // 在C中,记得释放被删除节点的内存
return dummy.next;
}
15. 查找链表中间节点。
答案解析: 快慢指针法, 慢指针走一步, 快指针走两步, 快指针到末尾时慢指针指向中间。时间复杂度O(n), 空间复杂度O(1)。
示例代码:
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;
}
栈与队列
16. 用两个栈实现队列。
答案解析: 一个栈用于入队, 另一个栈用于出队, 需将入队栈元素倒序压入出队栈。入队O(1), 出队均摊O(1)。
示例代码:
// 伪代码,具体实现依赖栈的API
typedef struct {
Stack *stack1; // 入队栈
Stack *stack2; // 出队栈
} MyQueue;
void myQueuePush(MyQueue* obj, int x) {
stackPush(obj->stack1, x);
}
int myQueuePop(MyQueue* obj) {
if (stackIsEmpty(obj->stack2)) {
while (!stackIsEmpty(obj->stack1)) {
stackPush(obj->stack2, stackPop(obj->stack1));
}
}
return stackPop(obj->stack2);
}
17. 实现一个栈,支持获取最小元素 (MinStack)。
答案解析: 用两个栈, 一个存数据, 一个存当前最小值。每次压入时, 更新最小值栈。时间复杂度O(1)。
示例代码:
// 伪代码,具体实现依赖栈的API
typedef struct {
Stack *dataStack;
Stack *minStack;
} MinStack;
void minStackPush(MinStack* obj, int x) {
stackPush(obj->dataStack, x);
int currentMin = stackIsEmpty(obj->minStack) ? x : stackTop(obj->minStack);
stackPush(obj->minStack, (x < currentMin) ? x : currentMin);
}
int minStackGetMin(MinStack* obj) {
return stackTop(obj->minStack);
}
18. 判断括号字符串是否有效。
答案解析: 用栈检查括号匹配, 遇到左括号入栈, 右括号检查栈顶是否匹配。时间复杂度O(n), 空间复杂度O(n)。
示例代码:
#include <stdbool.h>
#include <string.h>
bool isValid(char *s) {
int n = strlen(s);
if (n % 2 != 0) return false;
char stack[n];
int top = -1;
for (int i = 0; s[i]; i++) {
if (s[i] == '(' || s[i] == '[' || s[i] == '{') {
stack[++top] = s[i];
} else {
if (top == -1) return false;
if (s[i] == ')' && stack[top] != '(') return false;
if (s[i] == ']' && stack[top] != '[') return false;
if (s[i] == '}' && stack[top] != '{') return false;
top--;
}
}
return top == -1;
}
树与图
19. 实现二叉树的前序遍历。
答案解析: 前序遍历(根-左-右)可用递归或迭代(栈)。时间复杂度O(n), 空间复杂度O(h), h为树高。
示例代码 (递归):
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
void preorderTraversal(struct TreeNode* root, int* res, int* returnSize) {
if (!root) return;
res[(*returnSize)++] = root->val;
preorderTraversal(root->left, res, returnSize);
preorderTraversal(root->right, res, returnSize);
}
20. 判断二叉树是否为二叉搜索树 (BST)。
答案解析: 中序遍历BST应为递增序列, 检查遍历结果是否满足。时间复杂度O(n), 空间复杂度O(h)。
示例代码:
#include <stdbool.h>
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
};
bool isValidBSTHelper(struct TreeNode* root, long min, long max) {
if (!root) return true;
if (root->val <= min || root->val >= max) return false;
return isValidBSTHelper(root->left, min, root->val) &&
isValidBSTHelper(root->right, root->val, max);
}
bool isValidBST(struct TreeNode* root) {
return isValidBSTHelper(root, -2147483649L, 2147483648L); // LONG_MIN, LONG_MAX
}
... 更多问题填充 ...