动态规划笔记(一)

转载自:https://blog.csdn.net/fisherming/article/details/79809071

动态规划

  • 本质:递归
  • 递归的意义在于将原问题(N)拆解为子问题(N-1),用子问题得到答案之后综合得到原问题(N)的答案:原问题–子问题–原问题
  • 动态规划的三要素:最优子结构,边界条件,转移方程。

leetcode 198
题目描述:你是一个专业的强盗,计划抢劫沿街的房屋。每间房都藏有一定的现金,阻止你抢劫他们的唯一的制约因素就是相邻的房屋有保安系统连接,如果两间相邻的房屋在同一晚上被闯入,它会自动联系警方。

给定一个代表每个房屋的金额的非负整数列表,确定你可以在没有提醒警方的情况下抢劫的最高金额。

//暴力搜索
    public int solve(int index, int[] nums){
        if(index < 0){
            return 0;
        }
        int max = Math.max(nums[index] + solve(index - 2, nums), solve(index - 1, nums));
        return max;
    }

    public int rob(int[] nums) {
        return solve(nums.length-1, nums);
    }

例如该题中的问题是我们要抢N家店,这是我们的原问题。如何拆解成子问题呢?就是我们要不要抢这家店,如果抢这家店,它的子问题变成了N-2,如果不抢这家店,它的子问题变成了N-1。

  • 最优子结构

    • 子问题最优决策可导出原问题最优决策
    • 无后效性

    看看这题,要是我们抢N家,那么我们就不能抢N-1家了,最多只能抢N-2家,这个地方的决策就产生了后效性。那么怎么解决这个后效性呢?不是抢了N家之后不可以抢第N-1家么,那我跳过N-1(跳过有后效性的这一步),—>( n u m s [ i d x ] + s o l v e ( i d x 2 , n u m s ) nums[idx]+solve(idx-2,nums) )。要是不抢N家呢,那就没有后效性啦!( s o l v e ( i d x 1 , n u m s ) solve(idx-1,nums) )(放弃做决策了)。

  • 重叠子问题

    • 去冗余

    这道题如果用暴力搜索的话,会有很多重复的地方。
    假设我们抢n-1家,那么接下来的执行方案:
    n-1 ->(n-3, n-4, n-5)
    假设我们抢n-2家,那么接下来的方案为:
    n-2 ->(n-4, n-5)
    那么我的两种决策方式只是影响能不能抢n-3,在n-3之后都是随便抢的;通过观察上述两种方案,我们发现了n-4,n-5被重复计算。因此,每一家都有两种可能可能,抢或者不抢。则该算法的时间复杂度为: O ( 2 n ) O(2^n)

    • 怎么解决冗余呢?

    可以开一个数组,把算过的子问题给记录下来。也叫做空间换时间。

//记忆化搜索
class Solution {
    public static int[] result;

    public int solve(int index, int[] nums){
        if(index < 0){
            return 0;
        }

        if(result[index] >= 0){
            return result[index];
        }

        result[index] = Math.max(nums[index] + solve(index-2 , nums), solve(index-1, nums));
        return result[index];
    }

    public int rob(int[] nums) {
        result = new int[nums.length];
        for(int i=0; i < result.length; i++){
            result[i]=-1;  //给这个用于记录子问题的数组进行初始化为-1
        }
        return solve(nums.length-1, nums);
    }
}

好像递归去掉冗余就变成动态规划了呀!也叫作记忆化搜索。

怎么计算动态规划的复杂度呢?
对于每个状态都只计算了一遍,总共有N个店,总共N个状态,所以复杂度为 O ( n ) O(n)

重要!!

那么怎么知道一个问题可以用动态规划来求解呢?

问题共性:

  • 套路:
    • 最优,最大,最小,最长,计数
  • 离散问题:
    • 容易设计状态(整数01背包问题)

      小数的话,没有办法转换为整数下标。

  • 最优子结构
    • N-1可以推导出N

解题步骤

  1. 设计暴力算法,优化(优化一定是找到冗余)
  2. 设计并存储状态(一维,二维,三维数组,甚至用Map)[备忘录,记忆化搜索]
  3. 递归式(状态转移方程)
  4. 自底向上计算最优解(编程方式)–递推方式
    public int rob(int[] nums) {
        if (nums.length == 0){
            return 0;
        }

        if (nums.length == 1){
            return nums[0];
        }

        if (nums.length == 2){
            return Math.max(nums[0], nums[1]);
        }

        int[] result = new int[nums.length];   
        result[0] = nums[0];
        result[1] = Math.max(nums[0], nums[1]);

        for(int index=2; index < result.length; index++){
            result[index] = Math.max(nums[index] + result[index-2], result[index -1]);
        }

        return result[nums.length -1];
    }
}

总结:

这道题讲了动态规划的一般解法和本质:

先用自顶向下的递归的写法写出一个初步框架,再用自顶向下的记忆化搜索方法(需要数组来存储状态)去除冗余,最后用自底向上的递推方式(需要数组来存储状态)。

  • 记忆化搜索和递推的复杂度是一样的。

猜你喜欢

转载自blog.csdn.net/qq_39504764/article/details/89917145
今日推荐