动态规划系列(1)——动态规划入门

一般的,我们常用的解决问题的方法有暴力解决法、分而治之、二分法、贪心法和动态规划法。在你遇到一个问题怎么想都想不出其解法的时候,很可能就需要用到动态规划了;在你的题目中出现最优、最多、最好等字眼的时候,很可能可以使用动态规划问题来解决了。

那么什么是动态规划(Dynamic Programming)呢?动态规划和分治思想、递归有着千丝万缕的关系。简单来说,分治思想是把一个问题分成一个一个的互不相关小问题,小问题再细分直至不可分(类似于把一根木棍切啊切);递归就是在程序运行的过程中调用自身的一种编程技巧;动态规划通过寻找过程状态转移方程,将一个问题分解为子问题求解,但是子问题之间可能会有重复,因此如果单纯的使用递归方法来实现动态规划问题时间复杂度会比较高。不过动态规划问题的本质就是递归,这是因为我们在分析动态规划问题的过程中,需要状态转移方程,这个状态转移方程本质上就是递归。后面实现的过程中是否使用递归只是实现的不同而已,其本质就是递归。

动态规划有三个最基本的元素:最优子结构、状态转移方程和边界。状态转移方程用于描述将当前状态的解分解为更小状态的关系式;边界即状态转移方程的截止条件;最优子结构即确保通过状态转移方程所选择的子问题也能给出最优的解。

以最最基础的fibnacci问题为例:其基础的递推关系式为:

fib(i)=max\left\{\begin{matrix} 1 & i<3\\ fib(i-1)+fib(i-2) & otherwise \end{matrix}\right.

如果现在需要计算fib(5),按照规则需要计算fib(4) + fib(3),然后分别计算fib(4)和fib(3),具体的计算过程如下图所示:

动态规划系列(1)——动态规划入门_第1张图片

这一个递归的,层层向下的调用过程就是动态规划解fibnacci问题的过程,一般的,动态规划问题的展开图就像上面这样呈树状结构。而这样的结构示意图只是我们分析动态规划问题的第一步(通过写出的状态转移方程对问题进行结构上的分析)。接下来通过编程解决这样一个问题的时候有两种选择:一种是自顶向下,也就是通过递归的方式来解决;一种是自下向上,通过记录每一步的状态量来减少时间复杂度(用空间换时间)。下面分别对这两种方法进行讲解:

方法一:递归

采用递归实质上就是按照上图中的二叉树的先序遍历的顺序执行,其中每有一个节点就代表执行一次,因此其时间复杂度是O(2^n),这种递归解法太暴力了,时间复杂度太高。在LeetCode上上传这样的代码即使结果正确也不能Accept。

int fib(int idx)
{
    if(idx < 1)
    {
        return 0;
    }
    
    if(idx < 3)
    {
        return 1;
    }
    
    return fib(idx - 1) + fib(idx - 2);

}

方法二:添加备忘录的递归

按照二叉树的递归方法会存在很多重复的计算,如何避免这些重复的计算呢?我们提前申请一个数组,将已经计算过的fib(i)的值存在arr[i]中,这样的做法形象的称为备忘录。在后面调用fib(i)函数时,若arr[i] != -1则代表这个数已经计算过了,直接取其值就好了。如此一来每个数的fib函数只需要计算一次,因此其时间复杂度和空间复杂度均为O(n)。

// global array
int arr[10];

int fib(int idx)
{
    if(idx < 1)
    {
        return 0;
    }
    
    if(idx < 3)
    {
        return 1;
    }
    
    if(arr[idx] != 0)
    {
        return arr[idx];
    }
    else
    {
        arr[idx] = fib(idx - 1) + fib(idx - 2);
        return arr[idx];
    }

}

方法三:自底向上的迭代法

采用递归的方法始终会有较大的空间消耗,而采用自底向上的迭代方法可以将空间复杂度降低到O(1)级别。 

int fib(int idx)
{
    if(idx < 1)
    {
        return 0;
    }
    
    if(idx < 3)
    {
        return 1;
    }
    
    int temp_a = 1;
    int temp_b = 1;
    int temp_c;
    
    for(int i = 3; i < idx; i++)
    {
        temp_a = temp_b;
        temp_c = temp_a + temp_b;
        temp_b = temp_c;
    }
    
    return temp_c;
}

 这只是最简单的动态规划为题,举这个例子只是为了直观的介绍动态规划的概念,在后面会介绍一些常见的动态规划问题的解决思路和实现代码,并且难度会有很大程度的提升,从一维问题升级到二级问题。

你可能感兴趣的:(数据结构与算法,Dynamic,Programming,动态规划)