递归学习

引用自 https://leetcode-cn.com/explore/featured/card/recursion-i/257/recurrence-relation/1208/

递归原理

为了确保递归函数不会导致无限循环,它应具有以下属性:

一个简单的基本案例(basic case)(或一些案例) —— 能够不使用递归来产生答案的终止方案。
一组规则,也称作递推关系(recurrence relation),可将所有其他情况拆分到基本案例。

基础的例子

以相反的顺序打印字符串。

public class Test {
    public static void printReverse(char[] str){
        helper(str,0);
    }

    public static void helper(char[] str,int index){
        //基线条件  大于的情况是呼应下面的 index +1
        if (index<0||index>str.length-1){
            return;
        }
        //递归调用
        helper(str,index+1);
        System.out.print(str[index]);
    }

    public static void main(String[] args) {
        char[] chars={'1','2','3'};

        printReverse(chars);
    }
}

运行结果
在这里插入图片描述

利用递推关系

在实现递归函数之前,有两件重要的事情需要弄清楚:

递推关系: 一个问题的结果与其子问题的结果之间的关系。
基本情况: 不需要进一步的递归调用就可以直接计算答案的情况。 有时,基本案例也被称为 bottom cases,因为它们往往是问题被减少到最小规模的情况,也就是如果我们认为将问题划分为子问题是一种自上而下的方式的最下层。
一旦我们计算出以上两个元素,再想要实现一个递归函数,就只需要根据递推关系调用函数本身,直到其抵达基本情况。

为了解释以上几点,让我们来看一个经典的问题,帕斯卡三角(Pascal’s Triangle):

帕斯卡三角形是排列成三角形的一系列数字。 在帕斯卡三角形中,每一行的最左边和最右边的数字总是 1。 对于其余的每个数字都是前一行中直接位于它上面的两个数字之和。
在这里插入图片描述
递推关系
让我们从帕斯卡三角形内的递推关系开始。
首先,我们定义一个函数 f(i, j)f(i,j),它将会返回帕斯卡三角形第 i 行、第 j 列的数字。

我们可以用下面的公式来表示这一递推关系:
f(i, j) = f(i - 1, j - 1) + f(i - 1, j)
f(i,j)=f(i−1,j−1)+f(i−1,j)

基本情况
可以看到,每行的最左边和最右边的数字是基本情况,在这个问题中,它总是等于 1。
因此,我们可以将基本情况定义如下:

f(i, j) = 1 \quad where \quad j = 1 \enspace or \enspace j = i
f(i,j)=1wherej=1orj=i

public class YangHui02 {
    public int getResultOfIJ(int i,int j){ // i 是行 j 是列
        if ((i<j)&&(i!=1&&j==1)){  //行数小于列数
            return 0;
        } else if ((i<j)&&(i==1||j==1)) {
            return 1;
        }else {
            return getResultOfIJ(i-1,j-1)+getResultOfIJ(i-1,j);
        }
    }



    public List<List<Integer>> generate(int numRows) {
        List<List<Integer>> listToReturn=new LinkedList<List<Integer>>();
        //最外层的是 控制行数  和 numRows 一样
        for (int i=1;i<=numRows;i++){  //行
            List<Integer> temp=new LinkedList<Integer>();
//            temp.removeAll();

            //控制每一行的 内容
            for (int j=1;j<=i;j++){ //列
                temp.add(getResultOfIJ(i,j));
            }
            listToReturn.add(temp);
        }
        return listToReturn;
    }
   }
}

注释

以上的代码在 https://leetcode-cn.com/problems/pascals-triangle/ 中需要优化性能,可以输出 28以内,性能可以达到需求,但是29之后性能不满足了

在这里插入图片描述
29不行
在这里插入图片描述
之后笔者会优化
优化 算法到 O(k) 空间复杂度
在这里插入图片描述

递归中的重复计算

以下引用自
https://leetcode-cn.com/explore/featured/card/recursion-i/258/memoization/1211/
通常情况下,递归是一种直观而有效的实现算法的方法。 但是,如果我们不明智地使用它,可能会给性能带来一些不希望的损失,例如重复计算。 在前一章的末尾,我们遇到了帕斯卡三角的重复计算问题,其中一些中间结果被多次计算。

在本文中,我们将进一步研究递归可能出现的重复计算问题。 然后我们将提出一种常用的技术,称为记忆化(memoization),可以用来避免这个问题。

为了演示重复计算的另一个问题,让我们看一个大多数人可能都很熟悉的例子,斐波那契数。 如果我们定义函数 F(n) 表示在索引 n 处的斐波那契数,那么你可以推导出如下的递推关系:

F(n) = F(n - 1) + F(n - 2)
基本情况:

F(0) = 0, F(1) = 1
根据斐波那契数列的定义,可以实现下面的函数:

public static int fibonacci(int n) {
  if (n < 2) {
    return n;
  } else {
    return fibonacci(n-1) + fibonacci(n-2);
  }
}

现在,如果你想知道 F(4) 是多少,你可以应用上面的公式并进行展开:

F(4) = F(3) + F(2) = (F(2) + F(1)) + F(2)
正如你所看到的,为了得到 f (4)的结果,我们需要在上述推导之后计算两次数 F(2) : 第一次在 F(4) 的第一次展开中,第二次在中间结果 F(3) 中。

下面的树显示了在计算 F(4) 时发生的所有重复计算(按颜色分组)。
在这里插入图片描述

记忆化

为了消除上述情况中的重复计算,正如许多人已经指出的那样,其中一个想法是将中间结果存储在缓存中,以便我们以后可以重用它们,而不需要重新计算。

这个想法也被称为记忆化,这是一种经常与递归一起使用的技术。

记忆化 是一种优化技术,主要用于加快计算机程序的速度,方法是存储昂贵的函数调用的结果,并在相同的输入再次出现时返回缓存的结果。 (来源: 维基百科)

回到斐波那契函数 F(n)。 我们可以使用哈希表来跟踪每个以 n 为键的 F(n) 的结果。 散列表作为一个缓存,可以避免重复计算。 记忆化技术是一个很好的例子,它演示了如何通过增加额外的空间以减少计算时间。

为了便于比较,我们在下面提供了带有记忆化功能的斐波那契数列解决方案的实现。

作为一种练习,您可以尝试使记忆化更加通用和非侵入性,即应用记忆化技术而不改变原来的功能。 (提示: 可以参考一种被称作 decorator 的设计模式)。

import java.util.HashMap;

public class Main {

  HashMap<Integer, Integer> cache = new HashMap<Integer, Integer>();

  private int fib(int N) {
    if (cache.containsKey(N)) {
      return cache.get(N);
    }
    int result;
    if (N < 2) {
      result = N;
    } else {
      result = fib(N-1) + fib(N-2);
    }
    // keep the result in cache.
    cache.put(N, result);
    return result;
  }
}

下面是自己实现的 记忆化操作

import java.util.HashMap;

/**
 * @description:
 * @author: htb
 * @createDate: 2019/11/26
 * @version: 1.0
 */
public class Solution {
    //存储 记忆 存储算出来的值
    private HashMap<Integer,Integer> map=new HashMap<Integer, Integer>();
    /**
     * @Description: 优先到map中查找
     * @param: [N]
     * @return: int
     * @date: 2019/11/26 20:10
     */
    public int fib(int N) {
        if (map.containsKey(N)){
            return map.get(N);
        }else {
            map.put(N,getfib(N));

            return map.get(N);
        }
    }


    /**
     * @Description: 拿到 第 N个  数
     * @param: [N]
     * @return: int
     * @date: 2019/11/26 20:08
     */
    public int getfib(int N){
        if (N == 0) {
            return 0;
        } else if (N == 1) {
            return 1;
        } else {
            return fib(N - 1) + fib(N - 2);
        }
    }
}

Test 类
  public static void main(String[] args) {
        Solution solution=new Solution();
        for (int i=0;i<10;i++){
            System.out.print(solution.fib(i)+"  ");
        }

输出结果如下
在这里插入图片描述

待续。。。。。。。。。。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值