【递归、搜索与回溯】专题一:递归(一)
📝前言说明:
- 本专栏主要记录本人递归,搜索与回溯算法的学习以及LeetCode刷题记录,按专题划分
- 每题主要记录:(1)本人解法 + 本人屎山代码;(2)优质解法 + 优质代码;(3)精益求精,更好的解法和独特的思想(如果有的话)
- 文章中的理解仅为个人理解。如有错误,感谢纠错
🎬个人简介:努力学习ing
📋本专栏:C++刷题专栏
📋其他专栏:C语言入门基础,python入门基础,C++学习笔记,Linux
🎀CSDN主页 愚润泽
你可以点击下方链接,进行该专题内不同子专题的学习
点击链接 | 开始学习 |
---|---|
导论 | 递归 (一)、递归 (二) |
二叉树的深搜 | 穷举 vs 暴搜 vs 深搜 |
回溯 vs 剪枝 | 综合练习 |
FloodFill算法 | 记忆化搜索 |
题目
- 面试题 08.06. 汉诺塔问题
- 个人解
- 21. 合并两个有序链表
- 优质解
- 总结
- 循环(迭代) vs 递归
- 递归 vs 深搜
面试题 08.06. 汉诺塔问题
题目链接:https://leetcode.cn/problems/hanota-lcci/description/
个人解
思路:
- 分析问题
- n == 1 时,直接把盘子从 A 移到 C;(递归出口)
- n > 1 时
- 先把上面 n - 1 个盘子从 A 移到 B(子问题);
- 再将最大的盘子从 A 移到 C;
- 再将 B 上 n - 1 个盘子从 B 移到 C(子问题)。
- 子问题确定函数头:把 n - 1个盘子从 A,借助 C,移动到 B
move(int n, vector<int>& source, vector<int>& auxiliary, vector<int>& target)
- 单个子问题解决方法:即上面
n > 1
的处理流程 - 递归出口:即:
n == 1
的处理流程
用时:10:00
屎山代码:
class Solution {
public:void move(int n, vector<int>& source, vector<int>& auxiliary, vector<int>& target){if(n == 1) // 当只有一个盘子,直接移动{target.emplace_back(source.back());source.pop_back();return;}// 把前 n - 1 个盘子移动到辅助柱子move(n - 1, source, target, auxiliary); // 移动第 n 个盘子target.push_back(source.back());source.pop_back();// 在把 前 n - 1 个盘子从辅助盘子移动回来move(n - 1, auxiliary, source, target);}void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {move(A.size(), A, B, C);}
};
时间复杂度:O( 2 n 2^n 2n)
空间复杂度:O(n)
21. 合并两个有序链表
题目链接:https://leetcode.cn/problems/merge-two-sorted-lists/description/
优质解
思路:
- 分析问题:
- 每次进行两个链表的头结点比较,然后提取出较小的头结点
- 再用提出的头结点,链接两个链表(其中一个的头结点被提出)合并好后的结果
- 子问题:合并有序链表
- 函数头:
Node* dfs(l1, l2); // 给两个链表,返回提出的头结点
- 函数头:
- 单个子问题解决方法:
- 比较大小,记录小的头结点
Node
- 链接,假如
l1->val < l2 -> val
,则l1->next = dfs(l1 -> next, l2);
- 返回合并后链表的头结点,
return Node;
- 比较大小,记录小的头结点
- 递归出口:链表为空
代码:
class Solution {
public:ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {if(list1 == nullptr || list2 == nullptr)return list1 == nullptr? list2 : list1; // 返回不为空那个,把没连接的部分链上if(list1 -> val < list2 -> val){list1 -> next = mergeTwoLists(list1 -> next, list2);return list1;}else{list2 -> next = mergeTwoLists(list1, list2 -> next);return list2;}}
};
时间复杂度:O(n1 + n2)
空间复杂度:O(min(n1, n2)),递归最深的那一次调用链的长度
总结
循环(迭代) vs 递归
循环和递归都是在处理重复子问题,那什么时候用循环,什么时候用递归?
适用场景:
- 循环,当展开时,调用都是同一方向的调用(此时可以写成循环),如:迭代处理集合元素(i 的行为只有 – )
- 递归,出现多种不同的调用自身(会选择不同方向的调用自身),如:树形结构的遍历,分治策略…
循环和递归展开图的特点:
循环遍历数组:
递归遍历二叉树:
循环和递归之间的代码可以相互转换。但是,对于递归转换成循环来说:因为函数体内部递归返回结果的时候,函数还没有完全执行完,所以要用一个栈来存储信息。就会有极大的消耗。
简单的转换代码:
递归 vs 深搜
递归的展开图,其实就是对一棵树做一次深度优先遍历(dfs)
🌈我的分享也就到此结束啦🌈
要是我的分享也能对你的学习起到帮助,那简直是太酷啦!
若有不足,还请大家多多指正,我们一起学习交流!
📢公主,王子:点赞👍→收藏⭐→关注🔍
感谢大家的观看和支持!祝大家都能得偿所愿,天天开心!!!