动态规划(Dynamic-Programming)问题讲解

动态规划类问题

  • 从已知子问题的解,推导出当前问题的解 推导过程可以表达为一个数学公式
  • 用一维或二维数组来保存之前的计算结果(可以进一步降维优化)

        将当前问题 分解成子问题 ,找出递归公式,分阶段进行求解 求解过程中缓存子问题的解,避免重复计算。

以一个简单例子Fibonacci数列为例,求Fibonacci数列第 n 项的

从第二项开始,每一项的值都等于前两项的值之和
0 1 1 2 3 5 8
dp[i] = dp[i-1] + dp[i-2]

思路:

        1.若求第 0 项或第 1 项的值,直接返回对应的值 0 或 1

        2.创建一个一维数组缓存第n项数值之前的求解结果,并初始化第一项和第二项的值

        3.使用循环计算处每一项的值,直到第 n 项,最后返回一维数组的第n项值即可

代码:

public static int fibonacci(int n) {
        if (n == 0) {
            return 0;
        } else if (n == 1) {
            return 1;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
}

测试:

public static void main(String[] args) {
        // 0 1 1 2 3 5 8
        System.out.println(fibonacciDown(13)); //求第 7 项值
}

降维优化:对于每个子问题只需要三个值参与,何不用三个变量代替一维数组进行优化:

    /**
     * 求 Fibonacci 的第 n 项 (降维)
     *
     * @param n 第几项
     * @return
     */
    public static int fibonacciDown(int n) {
        if (n == 0) {
            return 0;
        } else if (n == 1) {
            return 1;
        }

        int a = 0, b = 1;
        for (int i = 2; i <= n; i++) {
            int c = a + b; //记录第i项的值
            //更新值    
            a = b;
            b = c;
        }
        return b;
    }

Leecode62. 不同路径 题

从start走到finish有多少种走法(只能向右向下走)

 将每个格子的走法都记录出来,标识数字为 start 到该格子上的有多少走法,,找出规律

可看出规律为:dp[i][j] = dp[i][j - 1] + dp[i -1][j],并且第一行和第一列的值都为1,即走法只有一种

思路:

        1.以一个二维数组缓存每个格子的走法数

        2.遍历每行每列,求出每个格子的走法数

        3.最后返回第m行第n列的值,即为最终结果

代码:

    /**
     * 求到第m行n列有多少种走法,只能向右和向下
     *
     * @param m
     * @param n
     * @return
     */
    public static int uniquePaths2(int m, int n) {
        int[][] dp = new int[m][n];
        //初始化第一行和第一列的值为 1(其走法只有一种)
        for (int i = 0; i < m; i++) {
            dp[i][0] = 1;
        }
        for (int i = 0; i < n; i++) {
            dp[0][i] = 1;
        }
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[i][j] = dp[i][j - 1] + dp[i - 1][j];
            }
        }
        print(dp);
        return dp[m - 1][n - 1];
    }

降维优化:

每次计算当前格子的走法数时,只需要上一个格子和左边格子的走法之和,何不使用一维数组,上一个格子的走法即为当前格子的做法,将dp[i][j] = dp[i][j - 1] + dp[i -1][j]改为dp[j] = dp[j] + dp[j - 1],实现降维优化的目的,以第二行到第三行为例:

代码:

    /**
     * 求到第m行n列有多少种走法,只能向右和向下 (降维,二维 变 一维)
     *
     * @param m
     * @param n
     * @return
     */
    public static int uniquePaths(int m, int n) {
        int[] dp = new int[n];
        //初始化第一行和第一列的值为 1(其走法只有一种)
        Arrays.fill(dp, 1);
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[j] = dp[j] + dp[j - 1]; //自己加上 上一列的
            }
        }
//        System.out.println(Arrays.toString(dp));
        return dp[n - 1];
    }

2. 01背包问题 - AcWing题库

从N个物体中选择物体放入体积为V的背包中,使得价值最大,每种物品只能选择一次

以一下测试示例:

4 5    //物体数量为 4,背包体积为 5
1 2   //第一个物体的体积 1 和价值 2
2 4
3 4
4 5

以一个二维数组缓存第一行只有物品A的选择,第二行只有物体A、B时的选择等..,

ABCD分别表示四个物体

二维数组 dp 中:

第一行中选择物体A,体积为1,在体积为0时不能放下为0,其它都能放下A

第二行中选择物体B,体积为2,在背包体积为0、1时不能放下,将上一行数据复制下来,背包体积为2时能放下,价值为2比上一行的A更大,为3、4、5时可以放下B此外还能放下一个物体A

第三行中选择物体C,体积为3,在背包体积为0、1、2时不能放下,将上一行数据复制下来,在背包体积 为3是虽然能放下C,但是上一行的BA价值是6,比C的价值大,所以直接复制下来,在体积为5时,当前背包除了能放下BA外还能放下一个C

第四行选择物体D,体积为4,在背包体积为0、1、2、3时不能放下,将上一行数据复制下来,在背包体积 为4是虽然能放下D,但是上一行的BA价值是6,比D的价值大,所以直接复制下来,在体积为5时同理

            编号 体积(g)  价值(元)     物品名
            1     1         2         A
            2     2         4         B
            3     3         4         C
            4     4         5         D
二维数组 dp :
           0   1   2   3   4    5  --> 背包体积
        0  0   A   A   A   A    A       A
        1  0   A   B   BA  BA   BA      B
        2  0   A   B   BA  BA   BAC     C
        3  0   A   B   BA  BA   BAC     D

        0, 2, 2, 2, 2, 2
        0, 2, 4, 6, 6, 6
        0, 2, 4, 6, 6, 8
        0, 2, 4, 6, 6, 8
        结果:8 (BAC)

总结一个规律:

1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总体积 - 当前物品体积得到的体积值列中最大价值得物品,加到当前处
    dp[i][j] = Math.max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如dp数组中:dp[1][3] = Max(dp[0][3],B + dp[0][3-2]) = BA
2.装不下,将上一行物品复制下来
    dp[i][j] = dp[i-1][j]

代码二维:

import java.util.Scanner;

/**
 * 0 - 1背包问题
 */
public class Main {
       public static void main(String[] args) {
/*
            编号 体积(g)  价值(元)     物品名
            1     1         2         A
            2     2         4         B
            3     3         4         C
            4     4         5         D

           0   1   2   3   4    5  --> 体积
        0  0   A   A   A   A    A       A
        1  0   A   B   BA  BA   BA      B
        2  0   A   B   BA  BA   BAC     C
        3  0   A   B   BA  BA   BAC     D

        0, 2, 2, 2, 2, 2
        0, 2, 4, 6, 6, 6
        0, 2, 4, 6, 6, 8
        0, 2, 4, 6, 6, 8
        结果:8 (BAC)
        1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总体积 - 当前物品体积量得到得体积列中最大价值得物品,加到当前处
            dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
        2.装不下,将上一行物品复制下来
            dp[i][j] = dp[i-1][j]
 */
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); //物品数量
        int V = sc.nextInt(); //背包容积

        int[] vArr = new int[N]; //N个物品的体积
        int[] pArr = new int[N]; //N个物品的价值
        for (int i = 0; i < N; i++) {
            vArr[i] = sc.nextInt();
            pArr[i] = sc.nextInt();
        }

        System.out.println(knapsackProblem01(V, vArr, pArr, N));
    }

    public static int knapsackProblem01(int V, int[] vArr, int[] pArr, int N) {
        int[][] dp = new int[N][V + 1];

        for (int j = 0; j < V + 1; j++) {
            if (vArr[0] <= j) { //装得下
                dp[0][j] = pArr[0];
            }
        }
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < V+1; j++) {
                int x = dp[i - 1][j]; //上一行的价值
                if (vArr[i] <= j) { //装得下
                    //                      当前物品价值      剩余物品价值
                    dp[i][j] = Math.max(x, pArr[i] + dp[i-1][j - vArr[i]]);
                } else { //装不下
                    dp[i][j] = x;
                }
            }
        }
        return dp[N - 1][V];
    }

}

 代码(降维成一维数组):

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); //物品数量
        int V = sc.nextInt(); //背包容积

        int[] vArr = new int[N]; //N个物品的体积
        int[] pArr = new int[N]; //N个物品的价值
        for (int i = 0; i < N; i++) {
            vArr[i] = sc.nextInt();
            pArr[i] = sc.nextInt();
        }

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

        for (int j = 0; j < V + 1; j++) {
            if (vArr[0] <= j) { //装得下
                dp[j] = pArr[0];
            } else { //装不下
                dp[j] = 0;
            }
        }
        //System.out.println(Arrays.toString(dp));
        for (int i = 1; i < N; i++){
            for (int j = V; j > 0; j--) {
                if (vArr[i] <= j) { //装得下
                    //     pArr[i]当前物品价值   dp[j - vArr[i]]剩余空间在上次中(避免同一物品重复使用)能装的最大值
                    dp[j] = Math.max(dp[j], pArr[i] + dp[j - vArr[i]]);
                }
            }
            //System.out.println(Arrays.toString(dp));
        }

        System.out.println(dp[V]);
    }

3. 完全背包问题 - AcWing题库

与01背包问题的区别:

dp[i][j] = Math.max(x, pArr[i] + dp[i][j - vArr[i]]); //完全背包中物品数量无限,从本次物品中找,同一物品可重复使用

dp[i][j] = Math.max(x, pArr[i] + dp[i-1][j - vArr[i]]); //01背包中物品数量只有一个,从上次物品中找,同一物品只能用一次

代码(二维):

import java.util.Scanner;

/**
 * 完全背包问题
 */
public class Main {
   public static void main(String[] args) {
/*
            编号 体积(g)  价值(元)     物品名
            1     1         2         A
            2     2         4         B
            3     3         4         C
            4     4         5         D

        完全背包:
           0   1   2    3       4       5  --> 体积
        0  0   A   AA   AAA     AAAA    AAAAA       A
        1  0   A   B    BA      BB      BBA         B
        2  0   A   B    BA      BB      BBA         C
        3  0   A   B    BA      BB      BBA         D

      0      2      4      6      8     10      A
      0      2      4      6      8     10      B
      0      2      4      6      8     10      C
      0      2      4      6      8     10      D
        结果:10 (BAC)
        1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上(总体积 - 当前物品体积)得到的体积列中最大价值得物品,加到当前处
            dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
        2.装不下,将上一行物品复制下来
            dp[i][j] = dp[i-1][j]
 */
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); //物品数量
        int V = sc.nextInt(); //背包容积

        int[] vArr = new int[N]; //N个物品的体积
        int[] pArr = new int[N]; //N个物品的价值
        for (int i = 0; i < N; i++) {
            vArr[i] = sc.nextInt();
            pArr[i] = sc.nextInt();
        }

        System.out.println(knapsackProblemComplete(V, vArr, pArr, N));
    }


    public static int knapsackProblemComplete(int V, int[] vArr, int[] pArr, int N) {
        int[][] dp = new int[N][V + 1];

        for (int j = 0; j < V + 1; j++) {
            if (vArr[0] <= j) { //装得下
                dp[0][j] = pArr[0] + dp[0][j - vArr[0]];
            }
        }
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < V + 1; j++) {
                int x = dp[i - 1][j]; //上一行的价值
                if (vArr[i] <= j) { //装得下
                    //                    当前物品价值      剩余体积的物品价值(从本次中找)
                    dp[i][j] = Math.max(x, pArr[i] + dp[i][j - vArr[i]]);
                } else { //装不下
                    dp[i][j] = x;
                }
            }
        }
        return dp[N - 1][V];
    }
}

降维:

import java.util.Scanner;

/**
 * 完全背包问题
 */
public class Main {
   public static void main(String[] args) {
/*
        1. n个物品都是固体,有重量和价值
        2. 现在你要取走不超过 10克 的物品
        3. 每件物品只能使用一次

            编号 体积(g)  价值(元)     物品名
            1     1         2         A
            2     2         4         B
            3     3         4         C
            4     4         5         D

        完全背包:
           0   1   2    3       4       5  --> 体积
        0  0   A   AA   AAA     AAAA    AAAAA       A
        1  0   A   B    BA      BB      BBA         B
        2  0   A   B    BA      BB      BBA         C
        3  0   A   B    BA      BB      BBA         D

      0      2      4      6      8     10      A
      0      2      4      6      8     10      B
      0      2      4      6      8     10      C
      0      2      4      6      8     10      D
        结果:10 (BAC)
        1.装得下 —— 当前物品价值比上一行价值更大时,选择当前物品,再加上总重量 - 当前物品重量得到得重量列中最大价值得物品,加到当前处
            dp[i][j] = Max(dp[i-1][j],currItemValue + dp[i-1][jTotal - currItem.weight]) 比如:dp[3][8] = Max(dp[2][8],D + dp[2][8-1]) = DA
        2.装不下,将上一行物品复制下来
            dp[i][j] = dp[i-1][j]
4 5
1 2
2 4
3 4
4 5
 */
        Scanner sc = new Scanner(System.in);
        int N = sc.nextInt(); //物品数量
        int V = sc.nextInt(); //背包容积

        int[] vArr = new int[N]; //N个物品的体积
        int[] pArr = new int[N]; //N个物品的价值
        for (int i = 0; i < N; i++) {
            vArr[i] = sc.nextInt();
            pArr[i] = sc.nextInt();
        }

        System.out.println(knapsackProblemComplete2(V, vArr, pArr, N));
    }



    /**
     * 取total重量的物品 并且 价值最大(降维)
     *
     * @return 最大价值
     */
    public static int knapsackProblemComplete2(int V, int[] vArr, int[] pArr, int N) {
        int[] dp = new int[V + 1];

        for (int j = 0; j < V + 1; j++) {
            if (vArr[0] <= j) { //装得下
                dp[j] = pArr[0] + dp[j - vArr[0]];
            }
        }
        for (int i = 1; i < N; i++) {
            for (int j = 0; j < V + 1; j++) {
                int x = dp[j]; //上一行的价值
                if (vArr[i] <= j) { //装得下
                    //                      当前物品价值      剩余物品价值
                    dp[j] = Math.max(x, pArr[i] + dp[j - vArr[i]]);
                } else { //装不下
                    dp[j] = x;
                }
            }
//            System.out.println(Arrays.toString(dp));
        }

        return dp[V];
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/668343.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【scau大数据技术与原理2】综合性实验Spark集群的安装和使用——安装启动spark shell篇

实验内容简介&#xff1a; Spark是一个分布式计算框架&#xff0c;常用于大数据处理。本次实验中&#xff0c;首先设计一个包含主节点和从节点的Spark集群架构&#xff0c;并在CentOS的Linux环境下进行搭建。通过下载并解压Spark安装包&#xff0c;配置环境变量和集群参数&…

python分别保存聚类分析结果+KeyError: ‘CustomerID‘报错

如何在完成聚类分析后按聚类编号保存数据并且带上原数据所属ID # 将每个聚类的数据保存到不同的文件中 for cluster_id in range(6): # 假设共有6个聚类cluster_data data[data[cluster] cluster_id]cluster_data_with_customer_id cluster_data.copy()cluster_data_with_…

量化研究---强大的可转债分析系统上线,提供api,实时数据支持

今天把可转债实盘的分析模型拿出来&#xff0c;放在服务器方便选股分析&#xff0c;方便后面对接大qmt直接选股交易 强大的禄得可转债自定义因子轮动系统完成&#xff0c;可转债三低为例子 自定义因子实盘的框架 自定义因子轮动框架非常强大 网页 http://120.78.132.143:8023/…

MongoDB~俩大特点管道聚合和数据压缩(snappy)

场景 在MySQL中&#xff0c;通常会涉及多个表的一些操作&#xff0c;MongoDB也类似&#xff0c;有时需要将多个文档甚至是多个集合汇总到一起计算分析&#xff08;比如求和、取最大值&#xff09;并返回计算后的结果&#xff0c;这个过程被称为 聚合操作 。 根据官方文档介绍&…

day1、2-数1

数学一主要考查高数、线性代数、概率统计这三个方面&#xff0c;其中高数占比56%、线性代数占比22%、概率统计占比22% 题做完 要产生1套理论 24年真题 1. 选C sinx的话不影响奇偶 奇偶函数的积分 0到a的积分为一个常数 求导的话 奇函数导出来一定是偶函数&#xff0c;偶函…

【机器学习系列】掌握随机森林:从基础原理到参数优化的全面指南

目录 目录 一、随机森林简介 (一)随机森林模型的基本原理如下&#xff1a; (二)随机森林模型的优点包括&#xff1a; (三)森林中的树的生成规则如下&#xff1a; (四)在随机森林中&#xff0c;每棵树都使用不同的训练集进行训练&#xff0c;原因如下 随机森林的分类性能&…

day-36 删除链表的倒数第 N 个结点

思路 首先计算出链表的长度&#xff0c;然后删除第n个节点即可&#xff0c;但要注意考虑特殊情况 解题方法 特殊情况&#xff1a;1.删除节点为最后一个节点 2.删除节点为头结点 Code /*** Definition for singly-linked list.* public class ListNode {* int val;* …

函数:计算数组的元素和

一、计算数组的元素和 参数传递给函数时&#xff0c;实际上只有数组的首地址作为指针传递给了函数。 在函数定义中的int a[ ]等价于int *a。在只有地址信息的情况下&#xff0c;是无法知道数组里有多少个元素的&#xff0c;因此在计算数组中的元素和时&#xff0c;要加一个参…

MongoDB下载安装入门 + SpringBoot简单集成

MongoDB安装入门 SpringBoot简单集成 MongoDB下载安装下载安装连接图形化界面MongoDB Compass Navicat Premium Spring Boot集成API操作添加maven配置数据库连接调用Mongo API MongoDB下载安装 下载安装 MongoDB官网地址&#xff1a;https://www.mongodb.com/ 下载地址&…

CobaltStrike基本渗透

目录 CobaltStrike简介 主要功能&#xff1a; 使用注意&#xff1a; 在使用CobaltStrike进行渗透测试时&#xff0c;务必遵守法律法规&#xff0c;并获得合法授权。 CobaltStrike安装 前提 安装 服务端安装 windows安装 CS基本使用 监听器配置 一些基本的攻击…

UnityAPI学习之游戏物体的方法使用

目录 游戏物体 创建游戏物体的三种方式 组建的获取和查找 游戏物体的方法与其他成员变量 游戏物体的生成 游戏物体的激活状态/标签(tag)/层级(layer) 游戏物体的激活与失活 游戏物体的查找 1. 名称查找(Find) 2. 通过标签查找游戏物体&#xff08;FindGameObjectWithT…

Leecode---动态规划---打家劫舍 / 乘积最大子数组

动态规划法&#xff1a; 思路&#xff1a; &#xff08;1&#xff09;状态定义&#xff1a;dp[i]代表前i家能偷盗的最大金额 &#xff08;2&#xff09;状态初始化&#xff1a;如果只有一家&#xff0c;只能偷这家dp[0]nums[0]&#xff1b;如果有两家&#xff0c;因为是连通的&…

fluent UI v9版本Dialog右上角x按钮聚焦问题解决

右上角x按钮聚焦效果展示 第一次点击不会聚焦&#xff0c;第二次或多次点击会出现这种情况。如果多个地方公用一个页面里&#xff0c;这个页面包含这个组件&#xff0c;那其它页面刚打开弹框就是聚焦状态&#xff0c;是个样式的问题。 解决&#xff1a; import * as React fr…

pytorch-Normalization

目录 1. 为什么Normalization2. Normalization2.1 image Normalization2.2 Batch Normalization 3. Normalization pytorch实现3.1 Normalization标准公式3.2 2d normalization3.3 normalize test 4. 使用normalization的好处 1. 为什么Normalization 下图使用sigmoid激活函数…

【2024新版】银系统源码/超市收银系统/智慧新零售/ERP进销存管理/线上商城/商户助手

>>>系统简述&#xff1a;本系统适用于超吃便利店&#xff0c;美妆母婴行业&#xff0c;服装鞋帽行业&#xff0c;食品零售行业&#xff0c;3C数码电子行业&#xff0c;食品生鲜等一切零售行业&#xff0c;产品功能角色介绍如下 合伙人&#xff1a;无限发展代理商和商…

Jetpack架构组件_1.基本知识

1.什么是Jetpack&#xff1f; Jetpack 是一个由多个库组成的套件&#xff0c;可帮助开发者遵循最佳做法、减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码&#xff0c;让开发者可将精力集中于真正重要的编码工作。Jetpack 包含一系列 Android 库&#xff0c;它…

RTPS协议之Behavior Module

目录 交互要求基本要求RTPS Writer 行为RTPS Reader行为 RTPS协议的实现与Reader匹配的Writer的行为涉及到的类型RTPS Writer实现RTPS WriterRTPS StatelessWriterRTPS ReaderLocatorRTPS StatefulWriterRTPS ReaderProxyRTPS ChangeForReader RTPS StatelessWriter BehaviorBe…

python上位机串行通信接收字节数据的校验处理-以crc16-modbus为例

在串行通信中&#xff0c;接收到的数据是否正确&#xff0c;一般用CRC校码的方式来完成。上位机向下位机发送数据时&#xff0c;需要加上校验码&#xff0c;同理&#xff0c;下位机向上位机上报数据时&#xff0c;也需要加上校验码。 校验码的计算方法有很多&#xff0c;比较简…

el-date-picker 选择日期范围只保存左侧日期面板

需求 日期筛选&#xff0c;但限制只能选择同一个月的数据&#xff0c;故此应该去掉右侧月份面板。 实现 主要是通过 css 样式实现&#xff1a; <style> /* 隐藏右边日期面板 */ .el-picker-panel__content.el-date-range-picker__content.is-right .el-date-table, .…

HTTP基础

一、HTTP协议 1、HTTP协议概念 HTTP的全称是&#xff1a;Hyper Text Transfer Protocol&#xff0c;意为 超文本传输协议。它指的是服务器和客户端之间交互必须遵循的一问一答的规则。形容这个规则&#xff1a;问答机制、握手机制。 它规范了请求和响应内容的类型和格式, 是基于…