文章目录
一、二叉树的前序遍历

前序遍历的核心顺序是:先访问根节点,再递归 / 遍历左子树,最后递归 / 遍历右子树。
递归法
终止条件:当前节点为null时,直接返回(无子树可遍历).
遍历顺序:
- 先访问当前节点(记录节点值);
- 递归遍历当前节点的左子树;
- 递归遍历当前节点的右子树。
int idx;
typedef struct TreeNode TreeNode;
void prev(TreeNode* root, int* res){
if(root == NULL){
return;
}
res[idx++] = root->val;
prev(root->left, res);
prev(root->right, res);
}
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
idx = 0;
int* res = (int*)malloc(200 * sizeof(int));
prev(root, res);
*returnSize = idx;
return res;
}
迭代法
迭代法需要手动用栈模拟递归的调用流程,核心是控制 “根→左→右” 的顺序。
因为前序遍历是 “根→左→右”,而栈是 “后进先出” 结构,因此入栈顺序需先压右子树,再压左子树(这样出栈时左子树先被访问,符合 “根→左→右”)。
- 步骤 1:初始化栈,将根节点入栈;
- 步骤 2:循环取栈顶节点:
- 弹出栈顶节点,访问并记录其值;
- 若该节点有右子树,将右子树入栈;
- 若该节点有左子树,将左子树入栈;
- 步骤 3:栈为空时,遍历结束。
int* preorderTraversal(struct TreeNode* root, int* returnSize) {
if (root == NULL) {
*returnSize = 0;
return NULL;
}
int* res = (int*)malloc(sizeof(int) * 200);
*returnSize = 0;
struct TreeNode* stack[100];
int top = 0;
stack[top++] = root; // 根节点入栈
while (top > 0) {
struct TreeNode* curr = stack[--top]; // 弹出栈顶
res[(*returnSize)++] = curr->val; // 访问节点
// 先压右子树(后进先出,左子树会先出栈)
if (curr->right != NULL) {
stack[top++] = curr->right;
}
// 再压左子树
if (curr->left != NULL) {
stack[top++] = curr->left;
}
}
return res;
}
二、用栈实现队列

要实现 “用两个栈模拟队列”,核心是利用栈的 “后进先出” 特性,通过 “倒栈” 转换为队列的 “先进先出” 特性。
队列是「先进先出」,栈是「后进先出」—— 用两个栈分工实现顺序转换:
- 输入栈(inStack):仅负责接收新元素(
push操作),元素入栈后,栈顶是 “最新加入的元素”; - 输出栈(outStack):仅负责提供待取出的元素(
pop/peek操作),栈顶是 “最早加入的元素”; - 倒栈时机:当
outStack为空时,将inStack的所有元素依次弹出并压入outStack—— 此时inStack的 “栈底元素(最早加入)” 会变成outStack的 “栈顶元素(最早取出)”,实现顺序反转。
1. push(int x):将元素加入队列尾部
直接将元素压入inStack(输入栈负责接收新元素)。
- 示例:
push(1) → push(2) → push(3),inStack状态:[1,2,3](栈顶是 3)。
2. pop():移除并返回队列头部元素
- 先检查
outStack是否为空:若空,将inStack的所有元素倒栈到outStack; - 弹出
outStack的栈顶元素(即队列头部)。 - 示例:执行
pop()时,outStack为空 → 把inStack的3、2、1依次弹出,压入outStack→inStack空,outStack状态:[3,2,1](栈顶是 1)→ 弹出 1(队列头部)。
3. peek():返回队列头部元素
逻辑与pop()一致,但不弹出元素,仅返回outStack的栈顶值。
4. empty():判断队列是否为空
当inStack和outStack都为空时,队列才为空(所有元素已被处理)。
// 定义栈的结构(用数组模拟)
typedef struct Stack {
int* data;
int top; // 栈顶指针(指向栈顶元素的下一个位置)
int capacity;// 栈容量
} Stack;
// 初始化栈
Stack* stackInit(int capacity) {
Stack* s = (Stack*)malloc(sizeof(Stack));
s->data = (int*)malloc(sizeof(int) * capacity);
s->top = 0;
s->capacity = capacity;
return s;
}
// 压栈
void stackPush(Stack* s, int x) {
s->data[s->top++] = x;
}
// 弹栈(返回栈顶元素)
int stackPop(Stack* s) {
return s->data[--s->top];
}
// 获取栈顶元素
int stackPeek(Stack* s) {
return s->data[s->top - 1];
}
// 判断栈是否为空
bool stackEmpty(Stack* s) {
return s->top == 0;
}
// 定义队列结构(包含两个栈)
typedef struct {
Stack* inStack;
Stack* outStack;
} MyQueue;
// 初始化队列
MyQueue* myQueueCreate() {
MyQueue* q = (MyQueue*)malloc(sizeof(MyQueue));
q->inStack = stackInit(100); // 假设最大容量100
q->outStack = stackInit(100);
return q;
}
// 倒栈:将inStack的元素转移到outStack
void transfer(MyQueue* q) {
if (stackEmpty(q->outStack)) { // 仅当outStack为空时倒栈
while (!stackEmpty(q->inStack)) {
int val = stackPop(q->inStack);
stackPush(q->outStack, val);
}
}
}
// push操作
void myQueuePush(MyQueue* obj, int x) {
stackPush(obj->inStack, x);
}
// pop操作
int myQueuePop(MyQueue* obj) {
transfer(obj);
return stackPop(obj->outStack);
}
// peek操作
int myQueuePeek(MyQueue* obj) {
transfer(obj);
return stackPeek(obj->outStack);
}
// empty操作
bool myQueueEmpty(MyQueue* obj) {
return stackEmpty(obj->inStack) && stackEmpty(obj->outStack);
}
// 释放资源
void myQueueFree(MyQueue* obj) {
free(obj->inStack->data);
free(obj->inStack);
free(obj->outStack->data);
free(obj->outStack);
free(obj);
}
因为每个元素最多被 “压栈 2 次、弹栈 2 次”(一次在inStack,一次在outStack),因此每个操作的均摊时间复杂度为 O (1)。
三、无重复字符的最长字串

本题是滑动窗口、哈希表搭配的经典例题,核心是通过双指针维护一个 “无重复字符的连续子串窗口”,结合哈希 / 数组快速判断字符重复.
- 初始化:
- 左指针
left = 0(窗口左边界); - 最大长度
max_len = 0(记录最长无重复子串的长度); - 哈希集合 / 数组
container(存储当前窗口内的字符)。
- 左指针
- 遍历右指针:
- 右指针
right从 0 遍历到字符串末尾,逐个处理每个字符s[right]; - 若
s[right]已在窗口内:移动左指针left,并从container中移除s[left],直到窗口内无s[right]; - 将
s[right]加入窗口:把s[right]存入container; - 更新最大长度:计算当前窗口长度(
right - left + 1),若大于max_len则更新。
- 右指针
int lengthOfLongestSubstring(char* s) {
int n = strlen(s);
bool exist[128] = {false}; // 用数组代替哈希集合,判断字符是否存在
int left = 0, max_len = 0;
for (int right = 0; right < n; right++) {
// 若当前字符已在窗口内,移动左指针
while (exist[s[right]]) {
exist[s[left]] = false;
left++;
}
// 加入当前字符
exist[s[right]] = true;
// 更新最大长度
int len = right - left + 1;
max_len = len > max_len ? len : max_len;
}
return max_len;
}
四、打家劫舍

本题是典型的动态规划问题,核心是通过 “状态转移” 决策 “偷 / 不偷当前房屋”
由于相邻房屋不能同时偷,因此对第i间房屋,有两种选择:
- 偷第
i间:则第i-1间不能偷,总金额 = 第i间金额 + 前i-2间的最大金额; - 不偷第
i间:总金额 = 前i-1间的最大金额。
1. 状态定义
dp[i]:表示偷前i+1间房屋(即下标0~i)能得到的最大金额。
2. 状态转移方程
基于两种选择,取最大值: d p [ i ] = max ( d p [ i − 1 ] , d p [ i − 2 ] + n u m s [ i ] ) dp[i] = \max(dp[i-1],\ dp[i-2] + nums[i]) dp[i]=max(dp[i−1], dp[i−2]+nums[i])
3. 边界条件
- 若只有 1 间房屋(
i=0):dp[0] = nums[0](只能偷这一间); - 若有 2 间房屋(
i=1):dp[1] = max(nums[0], nums[1])(选金额大的那间)。
int rob(int* nums, int numsSize) {
if (numsSize == 0) {
return 0;
}
if (numsSize == 1) {
return nums[0];
}
int dp[numsSize];
dp[0] = nums[0];
dp[1] = nums[0] > nums[1] ? nums[0] : nums[1];
for (int i = 2; i < numsSize; i++) {
dp[i] = dp[i-1] > (dp[i-2] + nums[i]) ? dp[i-1] : (dp[i-2] + nums[i]);
}
return dp[numsSize - 1];
}
五、合并K个升序链表

“合并 K 个升序链表” 的核心其实还是基于 “合并两个升序链表”,通过归并法 扩展到 K 个链表
合并两个升序链表
用递归 / 迭代法,比较两个链表的头节点,取较小的节点,再递归 / 迭代合并剩余部分。
递归
struct ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) {
return l2;
}
if (l2 == NULL) {
return l1;
}
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else {
l2->next = mergeTwoLists(l2->next, l1);
return l2;
}
}
迭代
ListNode* mergeTwolists(ListNode* l1, ListNode* l2){
ListNode* dummy = (ListNode*)malloc(sizeof(ListNode));
ListNode* cur = dummy;
while(l1 != NULL && l2 != NULL){
if(l1->val > l2->val){
cur->next = l2;
cur = l2;
l2 = l2->next;
}
else{
cur->next = l1;
cur = l1;
l1 = l1->next;
}
}
cur->next = (l1 != NULL) ? l1 : l2;
return dummy->next;
}
归并
核心思路:分而治之
将 K 个链表的合并问题,拆解为多个 “合并两个链表” 的子问题:
- 拆分:将链表数组分成左右两部分,递归合并左半部分和右半部分,得到两个有序链表;
- 合并:将这两个有序链表合并,得到最终结果。
typedef struct ListNode ListNode;
struct ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if (l1 == NULL) {
return l2;
}
if (l2 == NULL) {
return l1;
}
if (l1->val < l2->val) {
l1->next = mergeTwoLists(l1->next, l2);
return l1;
}
else {
l2->next = mergeTwoLists(l2->next, l1);
return l2;
}
}
struct ListNode* mergeKLists(ListNode** lists, int listsSize) {
// 终止条件
if (listsSize == 0) {
return NULL;
}
if (listsSize == 1) {
return lists[0];
}
// 拆分
int mid = listsSize / 2;
ListNode* left = mergeKLists(lists, mid);
ListNode* right = mergeKLists(lists + mid, listsSize - mid);
// 合并左右结果
return mergeTwoLists(left, right);
}


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



