移动应用开发实验室大一上考核

一、二叉树的前序遍历

在这里插入图片描述
前序遍历的核心顺序是:先访问根节点,再递归 / 遍历左子树,最后递归 / 遍历右子树。

递归法

终止条件:当前节点为null时,直接返回(无子树可遍历).
遍历顺序

  1. 先访问当前节点(记录节点值);
  2. 递归遍历当前节点的左子树;
  3. 递归遍历当前节点的右子树。
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:循环取栈顶节点:
    1. 弹出栈顶节点,访问并记录其值;
    2. 若该节点有右子树,将右子树入栈;
    3. 若该节点有左子树,将左子树入栈;
  • 步骤 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;
}

二、用栈实现队列

在这里插入图片描述
要实现 “用两个栈模拟队列”,核心是利用栈的 “后进先出” 特性,通过 “倒栈” 转换为队列的 “先进先出” 特性
队列是「先进先出」,栈是「后进先出」—— 用两个栈分工实现顺序转换:

  1. 输入栈(inStack):仅负责接收新元素(push操作),元素入栈后,栈顶是 “最新加入的元素”;
  2. 输出栈(outStack):仅负责提供待取出的元素(pop/peek操作),栈顶是 “最早加入的元素”;
  3. 倒栈时机:当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为空 → 把inStack3、2、1依次弹出,压入outStackinStack空,outStack状态:[3,2,1](栈顶是 1)→ 弹出 1(队列头部)。

3. peek():返回队列头部元素

逻辑与pop()一致,但不弹出元素,仅返回outStack的栈顶值。

4. empty():判断队列是否为空

inStackoutStack都为空时,队列才为空(所有元素已被处理)。

// 定义栈的结构(用数组模拟)
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)


三、无重复字符的最长字串

在这里插入图片描述
本题是滑动窗口哈希表搭配的经典例题,核心是通过双指针维护一个 “无重复字符的连续子串窗口”,结合哈希 / 数组快速判断字符重复.

  1. 初始化
    • 左指针left = 0(窗口左边界);
    • 最大长度max_len = 0(记录最长无重复子串的长度);
    • 哈希集合 / 数组container(存储当前窗口内的字符)。
  2. 遍历右指针
    • 右指针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间房屋,有两种选择:

  1. 偷第i:则第i-1间不能偷,总金额 = 第i间金额 + 前i-2间的最大金额;
  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[i1], dp[i2]+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 个链表的合并问题,拆解为多个 “合并两个链表” 的子问题

  1. 拆分:将链表数组分成左右两部分,递归合并左半部分和右半部分,得到两个有序链表;
  2. 合并:将这两个有序链表合并,得到最终结果。

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);
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值