动态规划01

1. 什么是动态规划?与递归有什么区别?

先说递归:递归——程序调用自身,也就是函数自己调用自己。递归通常从顶部将问题分解,通过解决掉所有分解出来的小问题,来解决整个问题;

斐波那契数列 f(n) = f(n-1) + f(n-2)   从上往下开始算   从n开始   直至找到n=1 n=2的递归出口

动态规划——通常与递归相反,其从底部开始解决问题。将所有小问题解决掉,进而解决的整个问题。

 

2、动态规划(Dynamic Programming)

一、基本概念

    动态规划过程是:每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划。

二、基本思想与策略

    基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。

    由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每一个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中。

    与分治法最大的差别是:适合于用动态规划法求解的问题,经分解后得到的子问题往往不是互相独立的(即下一个子阶段的求解是建立在上一个子阶段的解的基础上,进行进一步的求解)。

什么题适合用DP解?

使用动态规划特征: 
1. 求一个问题的最优解 
2. 大问题可以分解为子问题,子问题还有重叠的更小的子问题 
3. 整体问题最优解取决于子问题的最优解(状态转移方程) 
4. 从上往下分析问题,从下往上解决问题 
5. 讨论底层的边界问题

DP 一般用一个数组或者二维数组  存储每一次递归过程 或子问题产生的结果

1、构造问题所对应的过程。

2、思考过程的最后一个步骤,看看有哪些选择情况。

3、找到最后一步的子问题,确保符合“子问题重叠”,把子问题中不相同的地方设置为参数。

4、使得子问题符合“最优子结构”。

5、找到边界,考虑边界的各种处理方式。

6、确保满足“子问题独立”,一般而言,如果我们是在多个子问题中选择一个作为实施方案,而不会同时实施多个方案,那么子问题就是独立的。

7、考虑如何做备忘录。

8、分析所需时间是否满足要求。

9、写出转移方程式。

1. 剑指offer   ---剪绳子

给你一根长度为N的绳子,请把绳子剪成M段(m,n都是整数 都大于1),每段绳子的 
长度记为k[0],k[1],k[2]…. 请问如何剪绳子使得k[0],k[1],k[2] 
的乘积最大 
例如 绳子长度8 最大乘积18 = 2*3*3

分析: 求最优  有没有重叠的子问题 ,先剪一刀 i  n-i  (n-i这部分还可以再分)

f(n) = max( f(i)*f(n-i)  ) 这里f(n)长度为n所求的最大乘积  显然需要一个数组去存储 max里面的结果 选择最佳(不同的i对应不同的结果 需要遍历取不同的i)

一般先从最下面开始比如 n=0 n=1 这里 n>1  n=2

代码如下:

public class Main {

    public static int getMax(int length){
        if(length < 2){
            return 0;
        }
        if(length==2) return 1;
        if(length==3) return 2;

        int dp[]=new int[length+1];

        dp[0]=0;
        dp[1]=1;
        dp[2]=2;
        dp[3]=3;
        int max=0;
        for(int i=4; i <= length; i++){
            max=0;
            for(int j=1; j <= i/2; j++){
                int tmp=dp[j]*dp[i-j];
                if(max < tmp){
                    max=tmp;
                }
                dp[i]=max;
            }
        }

        max=dp[length];
        return max;
    }

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

 

2. 最长递增子序列

给定一个长度为N的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为6的数组A{5, 6, 7, 1, 2, 8},则其最长的单调递增子序列为{5,6,7,8},长度为4.

分析:

设长度为N的数组为{a0,a1, a2, ...an-1),则假定以aj结尾的数组序列的最长递增子序列长度为L(j),则L(j)={ max(L(i))+1, i<j且a[i]<a[j] }。也就是说,我们需要遍历在j之前的所有位置i(从0到j-1),找出满足条件a[i]<a[j]的L(i),求出max(L(i))+1即为L(j)的值。最后,我们遍历所有的L(j)(从0到N-1),找出最大值即为最大递增子序列。时间复杂度为O(N^2)。

j=0  aj=5  只有一个       L0=1

j=1  aj=6 aj-1=5  5 <6   L1=2

j=2                                L2=3

j=3     1比前面的小      L3=1 重新开始

j=4   aj=2                     L4=2  //1 2 

j=5    aj=8 比前面大    L5=L2+1=4 // L2是最大的

代码如下:先定义dp数组并赋初值, 用于存放得到的 Lj   

                   从1开始类似于选择排序(从0开始不需要比较 最长子序列就是1),外层遍历1-len

                  内层从 0 --- j-1   比如 5 2 6  3 7 4 8  aj=6的时候     需要与比较5 2比较  

public class Main02 {
    public static int lis(int arr[], int len){
        int dp[]=new int[len];

        for(int i=0; i < len; i++){
            dp[i]=1;
        }

        for(int j=1; j<len; j++){
            for(int i=0; i < j; i++){
                if(arr[j] > arr[i] && dp[j] <dp[i]+1){
                    dp[j]=dp[i]+1;
                }
            }
        }
        int max=0;
        for(int j=0; j<len; j++){
            System.out.print(dp[j]+"");

            if(dp[j]>max){
                max=dp[j];
            }
        }
        System.out.println();
        return max;

    }

    public static void main(String[] args) {
        int arr[]={5,6,7,1,2,8};

        System.out.println(lis(arr,arr.length));
    }
}

3. 最长公共子序列

子串应该比较好理解,至于什么是子序列,这里给出一个例子:有两个母串

  • cnblogs
  • belong

比如序列bo, bg, lg在母串cnblogs与belong中都出现过并且出现顺序与母串保持一致,我们将其称为公共子序列。最长公共子序列(Longest Common Subsequence,LCS),顾名思义,是指在所有的子序列中最长的那一个。子串是要求更严格的一种子序列,要求在母串中连续地出现。在上述例子的中,最长公共子序列为blog(cnblogs,belong),最长公共子串为lo(cnblogs, belong)。

如何求:详细的看这一篇https://www.cnblogs.com/hapjin/p/5572483.html

还是要写下最长公共子序列的递归式才完整。

c[i,j]表示:(x1,x2....xi) 和 (y1,y2...yj) 的最长公共子序列的长度。(是长度哦,就是一个整数嘛

代码如下:

根据得到的递推式进行求解

定义存储数组时的长度要注意下  尽可能长一点0-len   长度为len+1, 直接定义为c[1000][1000]也是一样的

public class TestLcs {
    public static int LCS(String str1, String str2){
        int len1=str1.length();
        int len2=str2.length();

        int [][]c=new int[len1 + 1][len2 +1];

        for(int i=0; i <= len1; i++){
            for(int j=0; j <= len2; j++){
                if(i==0 || j==0){
                    c[i][j]=0;
                }else if(str1.charAt(i-1) == str2.charAt(j-1)){
                    c[i][j]= c[i-1][j-1] +1;
                }else{
                    c[i][j]= Math.max(c[i-1][j], c[i][j-1]);
                }

            }
        }
        return c[len1][len2];
    }
    public static void main(String[] args) {
        String str1 = "abcdeaafgacb";
        String str2 = "acbfg";//acfg
        System.out.println(LCS(str1, str2));
    }
}

4. 最长公共子串(这个不会的话就比较麻烦)

(1)子序列要连续

DP求解最长公共子串
子串是一种特殊的子序列,因此同样可以用DP来解决。定义数组的存储含义对于后面推导转移方程显得尤为重要,糟糕的数组定义会导致异常繁杂的转移方程。考虑到子串的连续性,将二维数组c[i][j]用来记录具有这样特点的子串——结尾同时也为为串x1x2⋯xi与y1y2⋯yj的结尾——的长度。
得到转移方程:


最长公共子串的长度为 max(c[i,j]), i∈{1,⋯,m},j∈{1,⋯,n}。

代码如下所示

public class TestCommonStr {
    public static int CommonStr(String str1, String str2){
        int len1=str1.length();
        int len2=str2.length();
        int result=0;

        int c[][]=new int[len1 +1][len2 +1];
        for(int i=0; i <= len1; i++){
            for(int j=0; j <= len2; j++){
                if(i==0 || j==0){
                    c[i][j]=0;
                }else if(str1.charAt(i-1)== str2.charAt(j-1)){
                    c[i][j]=c[i-1][j-1]+1;
                    result= Math.max(c[i][j], result);
                }else{
                    c[i][j]=0;
                }
            }
        }
        return result;
    }
    public static void main(String[] args) {
        String str1 = "abcdeaafgacb";
        String str2 = "acbfg";//
        System.out.println(CommonStr(str1, str2));//3
    }
}

5 .怎么找出这个公共子串?

直接给出代码如下

public class FIndSonStr {
    public static String findSonStr(String str1, String str2){
        int len1=str1.length();
        int len2=str2.length();

        String strTmp=str1;
        int maxLen=0;
        int index=0;
        String str="";

        int c[][]=new int[len1 +1][len2 +1];

        for(int i=0; i <= len1; i++){
            for(int j=0; j <= len2; j++){
                if(i==0 || j==0){
                    c[i][j]=0;

                }else if(str1.charAt(i-1)== str2.charAt(j-1)){
                    c[i][j]=c[i-1][j-1]+1;


                }else{
                    c[i][j]=0;
                }

                if(c[i][j]>maxLen){
                    maxLen=c[i][j];
                    index=i;
                }
            }
        }

        // charAt()返回的是char   char+char=int
        for(int k=index-maxLen; k < index; k++){
               char ch=strTmp.charAt(k);
               str+=String.valueOf(ch);
        }
            return str;


    }
    public static void main(String[] args) {
        String str1 = "abcdeaafgacb";
        String str2 = "acbfg";//
        System.out.println(findSonStr(str1, str2));//acb
    }
}

 

<think>嗯,用户想了解动态规划中的01背包问题的解决方法或实现方式。让我先回忆一下相关的知识点。根据提供的引用内容,动态规划主要用于解决最优化问题,特别是像01背包这样的组合优化问题。01背包问题的核心是在有限的背包容量内,选择物品使得总价值最大,每个物品只能选一次。 首先,我需要明确问题的定义。引用[3]中给了一个例子,有三个物品,重量分别为20、15、10,价值为20、30、25,背包最大承重25。目标是通过动态规划找到最大价值。那么动态规划的关键在于状态转移方程。根据引用[1]和[2],动态规划需要定义状态和状态转移。通常,状态是一个二维数组dp[i][w],表示前i个物品在背包容量为w时的最大价值。 接下来,状态转移方程需要考虑是否选择当前物品。如果不选,那么dp[i][w] = dp[i-1][w];如果选,那么需要当前背包容量足够,即w >= weight[i-1],此时dp[i][w] = dp[i-1][w - weight[i-1]] + value[i-1]。两者取最大值。 然后,初始化条件也很重要。当物品数量为0或背包容量为0时,最大价值都是0。这对应dp数组的第一行和第一列初始化为0。 接下来要考虑实现方式。引用[4]提到用JavaScript实现,但用户可能需要通用的代码示例。Python可能更易懂。例如,创建一个二维数组,然后双重循环遍历物品和容量,逐步填充dp表。最后返回右下角的值作为最大价值。 此外,空间优化也是一个点,因为二维数组可能占用较大内存。可以优化为一维数组,逆序遍历容量,避免覆盖之前的状态。这样空间复杂度从O(nW)降到O(W),其中n是物品数量,W是背包容量。 用户可能需要具体的例子来理解。比如引用[3]的例子,手动推导一下dp表的填充过程,能更直观。例如,当处理第三个物品(重量10,价值25)时,在容量为25的情况下,是否选择该物品,比较不选时的dp[2][25](即前两个物品的最大价值)和选择时的dp[2][15]+25。根据例子中的数值,正确结果应该是55,即选择第二和第三个物品,总重量25,总价值55。 代码部分需要确保正确,比如循环的顺序,数组索引是否正确。例如,物品的索引是从0开始还是1开始,在代码中如何处理。同时,测试用例是否覆盖了用户提供的例子,确保代码的正确性。 可能用户还会关心时间复杂度和空间复杂度。动态规划解决01背包的时间复杂度是O(nW),其中n是物品数量,W是背包容量。这在W较大时可能不够高效,但对于整数容量且W不太大的情况,是可行的。 最后,根据用户提供的引用,需要正确标注引用来源,比如状态转移方程和最优性原理引用[1]和[2],实例问题引用[3],实现思路引用[4]。这样确保回答的权威性和准确性。</think>### 动态规划解决01背包问题的分步说明 01背包问题要求在背包容量限制下选择物品组合,使总价值最大。每个物品只能选或不选,不能分割。以下是具体解决步骤: #### 1. **定义状态** 设$dp[i][w]$表示前$i$个物品在背包容量为$w$时的最大价值[^1]。 - $i$:前$i$个物品(范围:$0 \leq i \leq n$) - $w$:当前背包容量(范围:$0 \leq w \leq W$,$W$为最大容量) #### 2. **状态转移方程** - **不选第$i$个物品**:$dp[i][w] = dp[i-1][w]$ - **选第$i$个物品**(需满足$w \geq weight[i-1]$): $$dp[i][w] = \max(dp[i-1][w], \ dp[i-1][w - weight[i-1]] + value[i-1])$$ 其中$weight[i-1]$和$value[i-1]$是第$i$个物品的重量和价值[^2]。 #### 3. **初始化** - $dp[0][w] = 0$(没有物品时价值为0) - $dp[i][0] = 0$(背包容量为0时无法装物品) #### 4. **实现代码(Python)** ```python def knapsack(weights, values, capacity): n = len(weights) dp = [[0] * (capacity + 1) for _ in range(n + 1)] for i in range(1, n + 1): for w in range(1, capacity + 1): if weights[i-1] > w: dp[i][w] = dp[i-1][w] else: dp[i][w] = max(dp[i-1][w], dp[i-1][w - weights[i-1]] + values[i-1]) return dp[n][capacity] # 示例(引用[3]的实例) weights = [20, 15, 10] values = [20, 30, 25] capacity = 25 print(knapsack(weights, values, capacity)) # 输出:55(选择后两个物品) ``` #### 5. **空间优化** 将二维数组优化为一维数组,逆序遍历容量: ```python def knapsack_optimized(weights, values, capacity): dp = [0] * (capacity + 1) for i in range(len(weights)): for w in range(capacity, weights[i] - 1, -1): dp[w] = max(dp[w], dp[w - weights[i]] + values[i]) return dp[capacity] # 相同示例输出55 ``` #### 6. **实例推导(引用[3])** | 容量\物品 | 0 | 1(20/20) | 2(15/30) | 3(10/25) | |---------|---|------------|------------|------------| | 25 | 0 | 20 | 30 → 30+25=55 | 55 | 最终选择物品2和3,总重量$15+10=25$,总价值$30+25=55$[^3]。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值