递归专题练习1(附代码解析)

1、lintcode375克隆二叉树(简单)

375 · 克隆二叉树 - LintCode

代码:

class Solution {
public:
    /**
     * @param root: The root of binary tree
     * @return: root of new tree
     */
    TreeNode * cloneTree(TreeNode * root) {
        // write your code here
        if(root==NULL) return NULL;
        TreeNode* newTree=new TreeNode(root->val);
        //以相同方法对左子树和右子树继续进行复制
        newTree->right=cloneTree(root->right);
        newTree->left=cloneTree(root->left);
        return newTree;
        
    }
};

 解析:

本题的核心在于深度拷贝,即要new一个新的空间出来存放新的二叉树,并且新的二叉树和原二叉树保持一致。采用递归,先对当前节点开辟空间,然后左右子节点用相同的方法向下递归即可。

2、lintcode2尾部的0(简单)

2 · 尾部的零 - LintCode

代码:

class Solution {
public:
    /**
     * @param n: A long integer
     * @return: An integer, denote the number of trailing zeros in n!
     */
    long long trailingZeros(long long n) {
        // write your code here
        //递归结束条件
        if(n==0) return 0;
        //继续判断有几个5
        return n/5+trailingZeros(n/5);
    }
};

解析

写本题时要注意思维的巧妙转换,虽然题目要求计算的是n的阶乘中末尾0的个数,但其实n的阶乘可以不用直接计算,因为末尾0的个数只取决于式中含5的个数。因为5和任意偶数相乘可以得10,而分析易知偶数的个数肯定比5的个数要多,换句话说也就是任意的5都能找到偶数和它匹配,所以这里只用统计阶乘的式子中5的个数。故我们只用从n到1,依次判断是否可以被5整除,将结果进行累加即可。

3、lintcode92背包问题(简单)

代码:

① 迭代做法,设置数组dp,通过动态规划的记忆化方法实现

class Solution {
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param a: Given n items with size A[i]
     * @return: The maximum size
     */
    int backPack(int m, vector<int> &a) {
        // write your code here
        int len=a.size();
        //滚动数组dp[j]:容量为j的情况下的最大装载量
        vector<int> dp(m+1,0);
        //从头遍历每一个物体
        for(int i=0;i<len;i++)
        {
            //遍历背包容量的每种状态
            for(int j=m;j>=a[i];j--)
            {
                //更新(不放or放)
                dp[j]=max(dp[j],dp[j-a[i]]+a[i]);
            }
        }
        return dp[m];
    }
};

②递归做法

class Solution {
    //声明全局数组dp
    //dp的作用:避免重复计算子问题
    vector<vector<int>> dp;
    int len;
public:
    /**
     * @param m: An integer m denotes the size of a backpack
     * @param a: Given n items with size A[i]
     * @return: The maximum size
     */
    int backPack(int m, vector<int> &a) {
        // write your code here
        len=a.size();
        //初始化dp的大小
        dp=vector<vector<int>>(len+1,vector<int>(m+1,-1));
        //目标容量:m
        return dfs(a,0,m);
    }
    //i:第i个物品,j:背包容量
     int dfs(vector<int> &v,int i,int j)
    {
        //说明该组合已经计算过,直接返回
        if(dp[i][j]!=-1) return dp[i][j];
        //异常情况
        //避免后面一致重复计算异常情况
        if(i>=len||j<0)
        {
            dp[i][j]=0;
            return 0;
        }
        int max1=0,max2=0;
        //可以容纳v[i]
        if(j-v[i]>=0) max1=dfs(v,i+1,j-v[i])+v[i];
        //不能容纳,和前一个物品保持一致,代表不选
        //注意这里不要写成s
        //因为无论怎样都要考虑不选的情况
        //而不是只有背包容纳不下才不选
        max2=dfs(v,i+1,j);
        //选取最大的
        dp[i][j]= max(max1,max2);
        return dp[i][j];
    }
};

 解析:

1、为什么可以使用 i + 1?
在递归函数中,i 表示当前正在考虑的物品索引。使用 i + 1 意味着我们在递归调用中考虑下一个物品。这种方式实际上是在问:

当前选择:对于第 i 个物品,我们有两个选择:
选中当前物品:将当前物品的重量加入总重量,然后在剩余容量 j - v[i] 下,考虑下一个物品 i + 1
不选当前物品:直接在容量 j 下,考虑下一个物品 i + 1。
这种方法的本质是自顶向下的递归,每个状态依赖于其 后续状态。虽然这看起来有些反直觉,但在递归树中,每个节点的选择都会影响其子节点,而不是父节点。

2、与使用 i - 1 的区别
当你使用 i - 1 时,表示你从最后一个物品开始,向前遍历。这种方法的逻辑是:

当前选择:对于第 i 个物品,我们有两个选择:
选中当前物品:将当前物品的重量加入总重量,然后在剩余容量 j - v[i] 下,考虑前一个物品 i - 1。
不选当前物品:直接在容量 j 下,考虑前一个物品 i - 1。
这种方法是自底向上的递归,每个状态依赖于其 前序状态。

3、两种方法的等价性
无论你选择从前往后(i + 1)还是从后往前(i - 1),最终的结果应该是一致的。关键在于:

初始调用和终止条件需要与遍历方向匹配。
状态转移方程需要正确地反映物品的选择和剩余容量的变化。


4、举例说明
假设我们有物品集合 A = [2, 3, 5, 7],背包容量 m = 10。

使用 i + 1:

从物品索引 i = 0 开始,考虑物品 A[0] = 2。
递归调用 dfs(v, i + 1, j),表示考虑下一个物品 A[1]。
每个递归层级向下,索引增加,直到超过物品数量。
使用 i - 1:

从物品索引 i = len - 1 开始,考虑物品 A[3] = 7。
递归调用 dfs(v, i - 1, j),表示考虑前一个物品 A[2]。
每个递归层级向下,索引减少,直到小于 0。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值