目录
本篇文章将是我们回溯专题的第一篇文章,在这里我先浅浅讲一下什么是回溯
其实就是递归,只不过递归的时候还有一个搜索的目的,也叫做深度优先遍历,dfs
所以大家对于递归不用太过于害怕,因为各位害怕的其实是递归展开图的复杂,至于递归本身,如果将其看做一个黑盒,从宏观角度去看待这个问题的话,那么递归其实清晰特别逻辑
接下来我们就用 5 道题目来带各位初识一下递归
(下文中的标题都是leedcode对应题目的链接)
leetcode 面试题08.06.汉诺塔问题
这道题目非常的简单
首先我们先从宏观看一下这道问题,我们要从A柱移动到C柱,那么我们就应该是将最后一个盘上面的所有盘都当成一个整体,然后就是将上面所有的盘移动到B柱,最后一个盘移动到C柱,最后再将一刚开始移动到B柱的盘全部移动到C柱
这其实就是重复子问题了,因为我们每一种情况都是将最下面上面的所有和最下面
所以代码就是:
class Solution {
public:
void dfs(vector<int>& begin, vector<int>& mid, vector<int>& end, int n)
{
if(n == 1)
{
end.push_back(begin.back());
begin.pop_back();
return;
}
dfs(begin, end, mid, n-1);
end.push_back(begin.back());
begin.pop_back();
dfs(mid, begin, end, n-1);
}
void hanota(vector<int>& A, vector<int>& B, vector<int>& C)
{
dfs(A, B, C, A.size());
}
};
我们来细细解析一下:
首先,if 是递归出口,也就是递归到这个条件的时候,就没有办法再进行递归了
放在这道题目上面就是,当A柱上面只有一个盘子的时候,我们就直接将这个盘放到C柱上面
接着第一个dfs代表将最下面那个盘上面的所有盘,将这些盘放到B柱上面
接着就是将最后一个盘移动到C柱上的工作
最后一个dfs就是将那 n-1 个盘全部递归式地放到C柱上面
leetcode 21.合并两个有序链表
这道题其实有更简单的解法,也就是一个一个节点的对比,设两个指针分别指向两个链表的头节点
对比完之后,再看两个指针还有哪个没有到结尾的,再将那一段结尾全部加进返回的结果里就可以了
但是这题也是可以用递归来做的,我们可以用这道题目锻炼以下我们的递归思维
首先我们要先找到重复子问题,因为只有找到了我们才知道怎么递归
假设上面那个链表节点的第一个小一点,那么我们就把这个节点先屏蔽
那么除开这个节点之后,我们的任务就变成了将剩下的所有节点全部合并
再拿开一个节点,还是将剩下的全部节点合并,我们的重复子问题就出现了
由于这是两个升序链表,所以我们可以直接改变原链表的指向
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2)
{
if(!l1)return l2;
if(!l2)return l1;
if(l1->val < l2->val) l1->next = mergeTwoLists(l1->next, l2);
else l2->next = mergeTwoLists(l1, l2->next);
return l1->val < l2->val ? l1 : l2;
}
};
最上面两个 if 是递归出口
下来的递归,就是,如果第一个小,那么我们就将第一个作为头节点,然后后面的递归函数会帮我们将后面的所有节点合并为有序的,然后我们第一个节点和他链接起来,最后返回第一个节点即可
第二个小的情况同理
leetcode 206.反转链表
这道反转链表我们可以用递归的方式完成
首先是重复子问题,当我们第一个节点排除的时候,我们剩下的任务就是将除了第一个之外的所有节点反转
那我们可以将第一个节点单独拿出来,让head->next放进递归中
接着这个递归函数过后,后面的链表肯定被反转好了,那么我们要做的其实就是让第二个节点的next指针指向第一个,接着第一个的指针指向空这样我们的数组就被反转了
最后我们要一直返回最后一个元素的指针,所以我们可以再碰到最后一个节点的时候,返回他自己,接着剩下所有的情况我们都那一个指针接受返回值,然后将这个指针一路返回回去
或可以搞一个全局指针,当碰到最后一个节点的时候赋值给这个指针,最后返回这个节点即可
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head)
{
if(head == nullptr || head->next == nullptr) return head;
ListNode* ret = reverseList(head->next);//将head之后的链表全部反转
head->next->next = head;// 将head放到反转后链表的最后面
head->next = nullptr;
return ret;
}
};
leetcode 24.两两交换链表中的节点
这道题目和反转链表差不多,但是我们是将当前节点的下下个节点放入递归中,然后我们执行当前两个节点的交换逻辑
然后递归出口就是,当我们当前节点的下一个节点为空(只有一个,不是一对),或者当前节点就是空(刚好都是成双成对)
代码如下:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* swapPairs(ListNode* head)
{
if(head == nullptr || head->next == nullptr)
return head;
ListNode* ret = swapPairs(head->next->next);
ListNode* n1 = head;
ListNode* n2 = head->next;
n1->next = ret;
n2->next = n1;
return n2;
}
};
leetcode 50. Pow(x, n)
题意很简单,其实就是求一个数的多少次方而已
但是我们直接遍历 n 次的话,一定会超时,因为这道题目的 n 最大是非常大的,包超时的
所以我们需要想一个对策
试想一下,我们需要求出 3^8,正常是直接乘 8 次,但是我们可以先将 3^4 求出来,这样我们只用循环 4 次,接下来就是结果乘两边就行了
而 3^4 可以再分为 3^2,这样循环下去
所以我们的重复子问题就出来了
因为我们可以判断以下当前数是否是奇数,如果是的话那就乘 待平方数(假设是3),然后再拿一个数来接收返回值,假设是ret
那么结果就是ret * ret * 3,而如果是偶数的话,就是 ret * ret
代码如下:
class Solution {
public:
double dfs(double x, long n)
{
if(n == 0) return 1;
if(n == 1) return x;
double ret = dfs(x, n/2);
if(n % 2 == 1) return ret*ret*x;
return ret*ret;
}
double myPow(double x, int n)
{
int flag = 0;
long n1 = (long)n;
if(n < 0)
{
flag = 1;
n1 = -n1;
}
double ret = dfs(x, n1);
if(flag == 1) return 1/ret;
return ret;
}
};
这篇文章到这里就结束了
我们这篇文章主要是讲一讲简单的递归,用一些简单递归来帮助大家消除恐惧的
接下来我们还会有更多递归系列的博客,如果觉得对你有帮助的可以关注一下!!~( ̄▽ ̄)~*