递归-学习笔记

本文是《数据结构与算法之美》中递归内容的学习笔记,介绍了递归需满足的三个条件、编写递归代码的方法,指出递归存在堆栈溢出、重复计算等问题及解决办法,还说明了可将递归代码改成非递归代码,最后给出几道LeetCode递归相关题目。

介绍

本文是学习《数据结构与算法之美》中关于递归的学习笔记,后面是刷了几道leetCode中跟递归相关的算法题目。

int f(int n) {
  if (n == 1) return 1;
  return f(n-1) + 1;
}

复制代码

递归需要满足的三个条件

  1. 一个问题的解可以分解成几个子问题的解
  2. 这个问题与分解后的子问题,除了数据规模不同,求解思路完全一样
  3. 存在递归终止条件

如何编写递归代码

写递归代码最关键的是写出递推公式,找到终止条件。 举例子:这里有 n 个台阶,每次你可以跨 1 个台阶或者 2 个台阶,请问走这n个台阶有多少种走法

int f(int n) {
  if (n == 1) return 1;
  if (n == 2) return 2;
  return f(n-1) + f(n-2);
}
复制代码

递归代码的一些问题

  • 要警惕堆栈溢出的问题

函数调用会使用栈来保存临时变量。每调用一个函数,都会将临时变量封装为栈帧,压入内存栈中,等函数执行完成返回时,才执行出栈。系统栈或者虚拟机栈空间一般不大,如果递归求解的调用层次很深的话,就会有堆栈溢出的风险。

避免堆栈溢出:在代码中限制递归调用的最大深度,但是这种做法并不能完全解决问题

  • 重复计算的问题

为了避免重复计算的问题,可以通过一个数据结构来保存已经求解过的值f(k)。当递归调用到f(k)的时候,先看下是否已经求结过。如果是,直接从散列表中取值返回,不需要重复计算。

  • 空间复杂度

在空间复杂度上, 因为递归调用一次就会在内存栈中保存一次数据,所以在分析递归代码的空间复杂度的时候,需要额外考虑这部分的开销。空间复杂度并不是O(1)。

将递归代码改成非递归代码

笼统地讲,所有的递归代码都可以改成迭代循环的非递归写法。

leetcode上的一些题目

  • 258 各位相加
  • 144 二叉树的前序遍历 : 递归方法实现和非递归方法的实现
  • 70 爬楼梯问题
/**
 *  258 各位相加
 *
 *  给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。
 *
 * 示例:
 *
 * 输入: 38
 * 输出: 2
 * 解释: 各位相加的过程为:3 + 8 = 11, 1 + 1 = 2。 由于 2 是一位数,所以返回 2。
 */
public class AddDigits {

    public static void main(String[] args) {
        System.out.println(addDigits(38));
    }

    /**
     *  递推公式:f(n)=f(n)进行处理后的结果;
     *  终止条件:f(n)的结果是一位数
     * @return
     */
    public static int addDigits(int num) {
        return fun(num);
    }

    public static int fun(int n) {
        if (n >= 0 && n < 10) {
            return n;
        }
        int sum = 0;
        while (n != 0) {
            sum = sum + n % 10;
            n = n / 10;
        }
        return fun(sum);
    }


    /**
     * 不使用循环或者递归,且在 O(1) 时间复杂度内解决这个问题:
     * 找出规律,进行换算:
     *
     * 给定一个非负整数 num,反复将各个位上的数字相加,直到结果为一位数。
     * 分析:ab=a*10+b
     * ab%9=(a*10+b)%9=(9a+a+b)%9=(a+b)%9
     * abc=a*100+b*10+c
     * 同理abc%9=(a+b+c)%9
     * 观察题目规律,可以看出0…9结果为0…9
     * 10…19结果为1…9…1
     * ….
     * 可以看出除了0以外,所有的结果都是1…9,可以num%9,比如38%9=2;但是18%9=0,18=1+8=9;我们可以做个简单的变换(num-1)%9+1;
     */
    public static int addDigits2(int num){
        return (num-1)%9+1;
    }

}
复制代码
    /**
     * 前序遍历
     * 递归方法:
     * * 1.访问根节点
     * * 2.前序遍历左子树
     * * 3.前序遍历右子树
     */
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        add(result, root);
        return result;
    }

    public void add(List<Integer> list, TreeNode root) {
        if (root == null) {
            return;
        }
        list.add(root.val);
        if (root.left != null) {
            add(list, root.left);
        }
        if (root.right != null) {
            add(list, root.right);
        }
    }

    /**
     * 前序遍历:非递归方法
      * 需要利用栈:
     *      1)访问结点P,并将结点P入栈;
     *
     *      2)判断结点P的左孩子是否为空,若为空,则取栈顶结点并进行出栈操作,并将栈顶结点的右孩子置为当前的结点P,循环至1);若不为空,则将P的左孩子置为当前的结点P;
     *
     *      3)直到P为NULL并且栈为空,则遍历结束。
     */
    public List<Integer> preorderTraversal2(TreeNode root) {
        List<Integer> result = new ArrayList<>();
        Stack<TreeNode> stack = new Stack<>();
        TreeNode node = root;

        while (node != null || !stack.empty()) {
            while (node != null) {
                result.add(node.val);
                stack.push(node);
                //访问左孩子
                node = node.left;
            }
            node = stack.pop();
            node = node.right;
        }
        return result;
    }
复制代码
/**
 * 70 爬楼梯
 * 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
 * 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
 * 注意:给定 n 是一个正整数。
 */
public class climbStairs {
    public static void main(String[] args) {
        System.out.println(climbStairs(10));
        System.out.println(climbStairs1(10));
    }

    /**
     * 利用递归的方法实现,不过比较耗时,leetcode运行超时
     */
    public static int climbStairs(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        return climbStairs(n - 1) + climbStairs(n - 2);
    }

    /**
     *  利用临时变量来存放结果,比用临时的数组存储结果快一些
     */
    public static int climbStairs1(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 2;
        }
        int a=1;
        int b=2;
        int result=0;
        while (n>=3){
            result=a+b;
            a=b;
            b=result;
            n--;
        }
        return result;
    }
}
复制代码
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值