递归、搜索与回溯——递归

💁‍♂️个人主页:进击的荆棘

👇作者其它专栏:

《数据结构与算法》《算法》《C++起始之路》


目录

1.总结

2.相关题解


1.总结

在解决一个规模为n的问题时,若满足一下条件,我们可以使用递归来解决:

a.问题可以被划分为规模更小的子问题,并且这些子问题具有与原问题相同的解决问题。

b.当我们知道规模更小的子问题(规模为n-1)的解时,我们可以直接计算出规模为n的问题的解。

c.存在一种简单情况,或者说当问题的规模足够小时,我们可以直接求解问题。

一般的递归求解过程如下:

a.验证是否满足简单情况。

b.假设较小规模的问题已经解决,解决当前问题。

上述步骤可以通过数学归纳法来证明。

2.相关题解

1汉诺塔问题

算法思路:

这是一道递归方法的经典题目,我们可以先从最简单的情况考虑:

●假设n=1,只有一个盘子,很简单,直接把它从A中拿出来,移到C上;

●若n=2呢?这时候就需要借助B,因为小盘子必须在大盘子上面,共需要3步(为方便叙述,记A中的盘子从上到下为1号,2号):

a.1号盘子放到B上;

b.2号盘子放到C上;

c.1号盘子放到C上。

至此,C中的盘子从上到下为1号,2号。

●若n>2呢?这时我们需要用到n=2时的策略,将A上面的两个盘子挪到B上,再将最大的盘子挪到C上,最后将B上的小盘子挪到C上就完成了所有步骤。当n=3时如下:

因为A中最后处理的是最大的盘子,所以再移动过程中不存在大盘子在小盘子上面的情况。

则本题可以被解释为:

1.对于规模为n的问题我们需要将A柱上的n个盘子移动到C柱上。

2.规模为n的问题可以被拆分为规模为n-1的子问题:

a.将A柱上面的n-1个盘子移动到B柱上。

b.将A柱上的最大盘子移动到C柱上,然后将B柱上的n-1个盘子移动到C柱上。

3.当问题的规模变为n=1时,即只有一个盘子时,我们可以直接将其从A柱移动到C柱。

●需要注意的是,步骤2.b考虑的是总体问题中的子问题b情况。在处理子问题的子问题b时,我们应将A柱中最上面的盘子移动到C柱,然后再将B柱上的盘子移动到C柱。在处理总体问题的子问题b时,A柱中的最大盘子仍然是最上面的盘子,因此这种做法是通用的。

算法流程:

递归函数设计:void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n)

1.返回值:无;

2.参数:三个柱子上的盘子,当前需要处理的盘子个数(当前问题规模)。

3.函数作用:将A上面的n个盘子挪到C上。

递归函数流程:

1.当前问题规模为n-1时,直接将A中的最上面盘子挪动到C中并返回;

2.递归将A中最上面的n-1个盘子挪动到B中;

3.将A中最上面的一个盘子挪动到C中;

4.将B中上面的n-1个盘子挪动到C中。

cpp 复制代码
class Solution {
public:
    void hanota(vector<int>& a, vector<int>& b, vector<int>& c) {
        dfs(a,b,c,a.size());
    }
    void dfs(vector<int>& a, vector<int>& b, vector<int>& c,int n){
        //出口
        if(n==1){
            c.push_back(a.back());
            a.pop_back();
            return ;
        }
        //将a上的n-1个盘子借助c移到b上
        dfs(a,c,b,n-1);
        c.push_back(a.back());
        a.pop_back();
        //将b上的n-1个盘子借助a移到c上
        dfs(b,a,c,n-1);
    }
};

2.合并两个有序链表

算法思路:

1.**递归函数的含义:**有两链表的头节点,把它们合并起来,并返回合并后的头节点;

2.**函数体:**选择两个头节点中较小的节点作为最终合并后的头节点,然后将剩下的链表交给递归函数去处理;

3.递归出口:当某一链表尾空时,返回另一链表。

cpp 复制代码
/**
 * 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* list1, ListNode* list2) {
        if(!list1) return list2;
        if(!list2) return list1;
        if(list1->val<=list2->val) {
            list1->next=mergeTwoLists(list1->next,list2);
            return list1;
        }
        else {
            list2->next=mergeTwoLists(list1,list2->next);
            return list2;
        }
    }
};

3.反转链表

算法思路:

1.**递归函数的含义:**交给你一个链表的头指针,逆序后,返回逆序后的头节点;

2.**函数体:**先把当前节点之后的链表逆序,逆序完之后,把当前节点添加到逆序后的链表后面即可;

3.**递归出口:**当前节点为空或者当前只有一个节点的时候,不用逆序,直接返回。

cpp 复制代码
/**
 * 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||!head->next) return head;
        //将此节点后面的链表逆置
        ListNode* newhead=reverseList(head->next);
        head->next->next=head;//后面的节点指向它
        head->next=nullptr;//它指向空,方便将逆置后的尾指向空

        return newhead;
    }
    
};

4.两两交换链表中的节点

算法思路:

1.**递归函数的含义:**有一链表,将这个链表两两交换,然后返回交换后的头节点;

2.**函数体:**先去处理一下第二个节点往后的链表,然后再把当前的两个节点交换,连接上后面处理后的链表;

3.**递归出口:**当前节点为空或者当前只有一个节点的时候,不要交换,直接返回。

cpp 复制代码
/**
 * 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||!head->next) return head;

        //先把第三个往后的节点交换
        ListNode* tmp=swapPairs(head->next->next);
        ListNode* ret=head->next;
        head->next=tmp;
        ret->next=head;

        return ret;
    }
};

5.Pow(x, n)

算法思路:

1.**递归函数的含义:**求出x的n次方是多少,然后返回;

2.函数体:先求出x的n/2次方是多少,然后根据n的奇偶,得出x的n次方是多少;

3.**递归出口:**当n为0的时候,返回1即可。

cpp 复制代码
class Solution {
public:
    double myPow(double x, int n) {
        //return n<0?dfs(x,-n):dfs(x,n);
        //当n为无穷小时,变成正数时会存不下,强转为long long
        return n<0?1.0/dfs(x,-(long long)n):dfs(x,n);
    }
    double dfs(double x,long long n){
        if(n==0) return 1.0;
        double tmp=dfs(x,n/2);
        return n%2==0?tmp*tmp:tmp*tmp*x;
    }
};
相关推荐
2301_822703203 小时前
鸿蒙Flutter第三方库FlutterUnit组件百科适配——具体示例还原演示1
算法·flutter·华为·harmonyos·鸿蒙
2301_764441339 小时前
LISA时空跃迁分析,地理时空分析
数据结构·python·算法
东北洗浴王子讲AI9 小时前
GPT-5.4辅助算法设计与优化:从理论到实践的系统方法
人工智能·gpt·算法·chatgpt
Billlly10 小时前
ABC 453 个人题解
算法·题解·atcoder
玉树临风ives10 小时前
atcoder ABC 452 题解
数据结构·算法
feifeigo12310 小时前
基于马尔可夫随机场模型的SAR图像变化检测源码实现
算法
fengfuyao98511 小时前
基于STM32的4轴步进电机加减速控制工程源码(梯形加减速算法)
网络·stm32·算法
无敌昊哥战神12 小时前
深入理解 C 语言:巧妙利用“0地址”手写 offsetof 宏与内存对齐机制
c语言·数据结构·算法
小白菜又菜12 小时前
Leetcode 2075. Decode the Slanted Ciphertext
算法·leetcode·职场和发展