自从了解动态规划的思想,我开启了新世界的大门

文章目录

  • 前言
  • 简介
  • 最优子结构
  • 重复子问题
  • 最优子结构和重复子问题
  • 例子:加深理解
    • 动态规划求解

前言

今天学习了动态规划算法,整理了一些大佬的笔记。就决定写一个博客,用来给想学习动态规划算法的同学,带大家了解一下什么是动态规划。

简介

动态规划(Dynamic Programming)(以下简称DP)算法的核心思想是:将大问题划分为小问题进行解决,从而一步步获取最优解的处理算法。通过把原问题分解位相对简单的问题的方式来解决复杂的问题。

动态规划不是一种具体的算法,而是一种思想:若要解一个给定问题,我们需要解其子问题,再根据子问题得出原问题的解。

可能有的人说这和分治好像,区别就在于分治算法的求解不依赖于子问题,DP是依赖于子问题的求解。

最优子结构

规定的是原问题与子问题之间的关系

DP要解决的都是一些问题的最优解,即从很多解决问题的方案中找出最优的那一个。当我们求解一个问题的最优解的时候,我们将其分为子问题,转换位求解子问题的最优解,最后通过对子问题的最优解进行组合来得到最终的结果。用的来说就是一个问题的最优解是由它的各个子问题的最优解来决定的。

重复子问题

规定的是子问题与子问题的关系

在我们递归的寻找每个子问题的最优解的时候,有可能会出现一些重复的子问题,如果只是普通的递归,则可能出现很多重复的计算,而DP可以保证每个重叠的子问题只会被求解一次。当重复的问题很多的时候,DP可以减少很多重复的计算。

例如:斐波那契问题:求f(5)

  			f(5)
  		  /      \
  		f(4)	 f(3)
  	   /	\	 /   \
  	f(3)   f(2)f(2)  f(1)
    /  \
  f(2) f(1)

f(3) 是重复计算的,对于DP来说我们可以建表存储子问题的答案,以避免重复计算

最优子结构和重复子问题

  1. 证明问题的方案中包含一种选择,选择之后留下一和或多个子问题
  2. 设计子问题的递归描述方式
  3. 证明对原问题的最优解包括了对所有子问题的最优解
  4. 证明子问题是重叠的(不是必须,但如果无重叠就和普通的递归效率相同)

例子:加深理解

力扣第300题最长递增子序列

给你一个整数数组nums,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

例如:
自从了解动态规划的思想,我开启了新世界的大门_第1张图片

思路:

每次减少一个:记 f(n)f(n) 为以第 nn 个数结尾的最长子序列,每次减少一个,将原问题分为 f(n-1)f(n−1), f(n-2)f(n−2), …, f(1)f(1),共 n - 1n−1 个子问题。n - 1 = 7n−1=7 个子问题以及答案如下:
[10, 9, 2, 5, 3, 7, 101] -> [2, 5, 7, 101]
[10, 9, 2, 5, 3, 7] -> [2, 5, 7]
[10, 9, 2, 5, 3] -> [2, 3]
[10, 9, 2, 5] -> [2, 5]
[10, 9, 2] -> [2]
[10, 9] -> [9]
[10] -> [10]

已经有 7 个子问题的最优解之后,可以发现一种组合方式得到原问题的最优解:f(6)f(6) 的结果 [2,5,7], 7 < 187<18,同时长度也是f(1)~f(7) 中,结尾小于 18 的结果中最长的。f(7) 虽然长度为 4 比f(6) 长,但结尾是不小于 18 的,无法组合成原问题的解。最后的结果取最长序列的个数。

以上组合方式可以写成一个式子,即状态转移方程:
f(n)=maxf(i)+1 其中 i

动态规划求解

[10, 9, 2, 5, 3, 7, 101] -> [2, 5, 7, 101]
[10, 9, 2, 5, 3, 7] -> [2, 5, 7]
[10, 9, 2, 5, 3] -> [2, 3]
[10, 9, 2, 5] -> [2, 5]
[10, 9, 2] -> [2]
[10, 9] -> [9]
[10] -> [10]

从这里我们可以看出->右边的序列是如何得到得呢?
我们从下往上看

  1. 第一个是10,所以最长序列得长度记录为 1
  2. 第二个是9,它得前面只有10 且 9 < 10 ,所以当前子序列得长度依然为 1
  3. 第三个数是2,同理前面得数都比它大所以当前子序列长度仍然是 1
  4. 第四个数是5,前面得2比他小,而二得子序列长度为1,加上5 所以长度变为2
  5. 第五个数为3,前面得2比他小,而二得子序列长度为1,加上3 所以长度变为2
  6. 第六个数为7,很明显前面的2,5,3都比7小,所以我们加上前面比它小的最长序列,3或者5,所以到7,他的序列长度变为了3.
  7. 第七个数位101,同理前面比他小的最长序列为3,加上它本身长度为4
  8. 最后一位数18,,前面比他小的最长序列为数字7的一行长度为3,所以它的子序列为4
  9. 最终的最长的子序列就是所有子序列中最长的。为4
 public static int lengthOfLIS(int[] nums) {
        if (nums.length==0){
            return 0;
        }
        //定义一个等长的数组 来存储序列长度
        int[] arr = new  int[nums.length];
        arr[0] = 1;
        //最短的序列长度为1
        int ans = 1;
        //用来计算子序列长度
        for (int i = 1;i<nums.length;i++){
            //最短就是自身
            arr[i] = 1;
            //寻找前面比i小的数
            for (int j =0;j<i;j++){
                //如果找到比当前值小的值 则+1就是当 i 之前的最长序列
                if (nums[j]<nums[i]){
                    //比较所有比他小的 找到最长的
                    arr[i] = Math.max(arr[j]+1,arr[i]);
                }
            }
            //当退出for循环时 说明已经找到包含nuns[i]的最长序列了 更新ans
            ans = Math.max(ans,arr[i]);
        }

        //返回结果即可
        return ans;
    }

DP可以通过填表的方式来逐步推进,从而得到最优解

对于递归求解,我也不会,哈哈哈哈

你可能感兴趣的:(JAVA,动态规划,算法,java)