算法很美笔记(Java)——动态规划

news/2025/2/23 11:12:32

解重叠子问题(当前解用到了以前求过的解)

形式:记忆型递归或递推(dp)

动态规划本质是递推,核心是找到状态转移的方式,也就是填excel表时的逻辑(填的方式),而后代码就按填表的逻辑写即可

可以先写递归解法,发现有很多重叠子问题时,再改dp

递归是从大规模到小规模,记忆型递归或dp都是小规模到大规模

大多数情况,题中要求什么,dp的值就代表什么,比如求最大长度,dp值就是最大长度

01背包问题

使用dfs,分为选和不选

递归

static int[] wi = {1,5,10,50,100,500};
    static int[] vi = {1,5,1,0,10,2};
    public static int t1(int w,int curr) {
        if (w <= 0)  return 0;
        if (curr == wi.length) return 0;
        if (w < wi[curr]) {
            //            重量不够,选不上了
            return t1(w,curr++);
        } else {
//        不选
            int choice1 = t1(w,curr++);
            //         选
            int choice2 =  vi[curr] + t1(w - wi[curr],curr++);
//            返回的是选择树当前层以下的所有层的最优解
//            与本层的选择无关,所以不会出现因为不知道选还是不选比较大而导致的w混乱
            return Math.max(choice1,choice2);
        }
    }

dp

因为后面的行需要借助第一行的值填,所以先初始化第一行,再填后面的

物品编号也就是当前可选什么物品,编号以前的物品都能选,比如编号2,则第1~3个物品都能选。但是我们因为是借助前面填过的数据填,所以实际上,只需要考虑当前编号的物品的选与不选,而编号以前的物品通过“借助前面填过的数据填”时就已经选上了。

价值计算= 当前物品价值 +(上一行,可用重量)----->选

              =(上一行,可用重量)----->不选

eg:

表格(2,3),当前容量为3,物品重量为3,要的起,所以分为选与不选。

选:价值 = 4 +(1,0 )= 4----->选

               =(1,3 )= 5----->不选

因为每格需要借助其他格的值填,所以不能只考虑w的容量,而是要考虑0~w 之间的所有容量,而最终所求,是右下角的那个值。因为我们计算的是考虑所有物品的情况下(最后一行),容量为w(最后一列)的最大利润

static int[] wi = {1,5,10,50,100,500};
    static int[] vi = {1,5,1,0,10,2};
    public static int _01bag(int w) {
//        二维数组存储数据
//        重量0~w
        int[][] arr = new int[wi.length][w + 1];
//        初始化第一行数据
        for (int j = 0; j < w + 1; j++) {
            if (j >= wi[0]) {
//                要的起
                arr[0][j] = vi[0];
            } else {
//                要不起
                arr[0][j] = 0;
            }
        }
//利用前一行填当前行
        for (int i = 1; i < wi.length; i++) {
            for (int j = 0; j < w + 1; j++) {
                if (j >= wi[i]) {
//                要的起
//                    要
                    int get = vi[i] + arr[i - 1][j - wi[i]];
//                    不要
                    int noGet = arr[i - 1][j];
                    arr[i][j] = Math.max(get,noGet);
                } else {
//                要不起
                    arr[i][j] = arr[i - 1][j];
                }
            }
        }
//        取右下角作为结果
        return arr[wi.length - 1][w];
    }

完全背包

比01背包多了个条件:每种物品可以挑选任意多件。

dp

仍然初始化第一行,用以供给后面计算

虽然一个物品可以选多次,但是我们还是只分为选和不选

当不选时,思路和01背包相同(剩余重量在上一行中寻找最优解)

当选时,选一个后,剩余重量在同行中寻找最优解。

因为只要是填过的就是已经找到最优解的了,同一个物品,我们只需要选一次,至于最终要选几次,交给同行的最优解来解决

钢条切割

第一刀可以切1+9、2+8、3+7……

1+9的第二刀切9,可以切成1+8、2+7……

2+8的第二刀切8,可以切成1+7、2+6……

也就是,固定前面的一段,切后面一段

因为如果两段都切会导致有很多重复结果比如2+8-->1+1 + 2+6和3+7-->1+2 + 1+6

因为是固定前面的一段,切后面一段,所以固定的的那一段长度可以直接得到价值,而只有后面那段价值不确定

递归

每切一刀就是一个递归,因为有不同切法,所以是for循环(控制切法)里写递归(控制刀数)

这样他们之间求max,就是结果(类似01背包问题的max,只不过01背包同层间都是两种选择,而本问题同层之间是不止两种)

static int[] v = {1 , 5 , 8 , 16 , 10 , 17 , 17 , 20 , 24 , 30};
    public static int cut(int x) {
        if (x == 0) return 0;
//        每刀切的长度1~10
        int maxSum = 0;
        for (int i = 1; i <= x; i++) {
            maxSum = Math.max(maxSum,(v[i-1] + cut(x - i)));
        }
        return maxSum;
    }

记忆型递归

这里会不止一次递归f(1)、f(2)……f(10),显然重复计算了

想改成只算一次,则用一个数组把这些值给记下来,当需要用的时候,如果有就直接用,没有就计算再存起来,以供后面计算使用

代码与直接递归类似

dp

画excel表为一个长度-价值最优表,不同长度的钢条对应不同价值

思路中,组合中第二段长度价值不确定,所以我们画了最优表,第二段价值直接取就行

最后求得长度为10的钢条的最大价值,return 价值

static int[] v = {1 , 5 , 8 , 16 , 10 , 17 , 17 , 20 , 24 , 30};
    public static int cut() {
        int[] sum = new int[11];
        sum[0] = 0;
        sum[1] = 1;
        for (int i = 3; i < 11; i++) {
//            curr初始为钢条不切,直接卖
            int curr = v[i - 1];
            for (int j = 1; j < i; j++) {
                int temp = v[j - 1] + sum[i - j];
                curr = Math.max(curr,temp);
            }
            sum[i] = curr;
        }
        return sum[10];
    }

数字三角形

The Triangle - POJ 1163 - Virtual Judge

在数字三角形中寻找一条从顶部到底边的路径,使得路径上所经过的数字之和最大。

路径上的每一步都只能往左下或 右下走。只需要求出这个最大和即可,不必给出具体路径。

三角形的行数大于1小于等于100,数字为 0 - 99

输入格式:

5 //表示三角形的行数 接下来输入三角形
      7
     3 8
    8 1 0
   2 7 4 4
  4 5 2 6 5
要求输出最大和

只能选左下,右下,而不是一层内可以随便选,所以无法一次性确定选什么最大,所以无法用贪心

那么就使用dfs把所有情况都列出来

可以看到,dfs会重复算中间的数,比如算第二层的3和8时,3算了f(1),8也算了f(1),所以用dp

从下往上填,最后一行没得选,直接初始化,其他行,找加起来更大的填上

static int[][] v = {
            {7},
            {3,8},
            {8,1,0},
            {2,7,4,4},
            {4,5,2,6,5},
    };
    public static int Triangle() {
        int[][] arr = new int[5][5];
//        System.out.println(arr[3].length);
//        初始化一行
        for (int i = 0; i < 5; i++) {
            arr[4][i] = v[4][i];
        }
//        填其他行
        for (int i = arr.length - 2; i >= 0; i--) {
//            列
            for (int j = 0; j < i + 1; j++) {
                arr[i][j] = Math.max(v[i][j] + arr[i+1][j],v[i][j] + arr[i+1][j+1]);
            }
        }
        return arr[0][0];
    }

最长公共子序列(LCS)

子数组只能像截取一段这样,是紧凑的

而子序列可以跳着来,只要前后顺序一致就行

比如AB34CA1BC2 则输出ABC

找出全部公共子序列,然后求max

有串S1 = AB34,S2 = A1BC2

让S1的每一个字母都作为一个子序列的开头

eg:让A作为开头,则去S2里找A,找到了的话,通过将后面未处理的序列进行递归和字符串拼接将后续子序列拼接出来。没找到则不用处理,这样max的时候就不会被取

这样处理完A开头的子序列后,处理B开头的子序列……

将这些子序列取长度max返回

用ArrayList存结果

for i :S1每个开头

        for j:S2 找相同字母

                找到了:当前字母+递归(剩余子串)

        if(上一个子序列.size()<当前子序列.size())res = 当前子序列

public static ArrayList<Character> str(String s1,String s2) {
//        让s1的,每一个字母开头
        ArrayList<Character> endRes = new ArrayList<>();
        for (int i = 0; i < s1.length(); i++) {
            ArrayList<Character> res = new ArrayList<>();
//            去s2找匹配的
            for (int j = 0; j < s2.length(); j++) {
                if (s1.charAt(i) == s2.charAt(j)) {
                    res.add(s1.charAt(i));
                    res.addAll(str(s1.substring(i + 1),s2.substring(j + 1)));
                    break;
                }
            }
            if (res.size() > endRes.size()) {
                endRes = res;
            }
        }
        return endRes;
    }

最长上升子序列问题


有一个长为n的数列a0,a1,...,a(n-1)。请求出这个序列中最长的上升子序列的长度。

上升子序列指的是对于任意的i&lt;j都满足ai&lt;aj的子序列。

1≤n≤1000

0≤ai≤1000000

输入

n=5
a={4,2,3,1,5}
输出

3(注:2,3,5)

因为是子序列,所以可以跳着取

暴力法

类似上一题的思路

以每一个数字开头,遍历向后找比这个数字大的数字,剩余数字用递归找

而后所有子序列求max 

dp

dp的每个值代表:以这个数字作为结尾,由它前面的数字组成的最长子序列的长度

curr是当前数字,

与前面每个数字做比较,自己较大,则curr的一个dp值等于 前面的数字的dp值+1,自己较小则urr的一个dp值等于 1(子序列只有自己,长度就是1)

再与更前面的数字作比较,求出一个dp值

…………

等与前面的每个数字作比较求出dp值后,取最大的dp值为最终dp值

求完所有dp值后,取最大的值作为结果返回

public static int maxLen(int[] a) {
        if (a.length == 0) return 0;
        int[] dp = new int[a.length];
        dp[0] = 1;
        for (int i = 1; i < dp.length; i++) {
//            子序列只有自己的时候的length
            int res = 1;
//            与前面的元素作比较
            for (int j = i - 1; j >= 0 ; j--) {
//                较小,子序列length就是1
//                较大
                if (a[j] < a[i]) {
                    int temp = dp[j] + 1;
//                    取max
                    if (res < temp) {
                        res = temp;
                    }
                }
            }
            dp[i] = res;
        }
        return max(dp);
    }
    public static int max(int[] dp) {
        int maxVal = 0;
        for (int i = 0; i < dp.length; i++) {
            if (dp[i] > maxVal) {
                maxVal = dp[i];
            }
        }
        return maxVal;
    }
    public static void main(String[] args) {
        System.out.println(maxLen(new int[]{4,2,3,1,5}));
    }


http://www.niftyadmin.cn/n/5863360.html

相关文章

[MDM 2024]Spatial-Temporal Large Language Model for Traffic Prediction

论文网址&#xff1a;[2401.10134] Spatial-Temporal Large Language Model for Traffic Prediction 论文代码&#xff1a;GitHub - ChenxiLiu-HNU/ST-LLM: Official implementation of the paper "Spatial-Temporal Large Language Model for Traffic Prediction" …

3damx 发动机活塞运动动画

使用HD解算器绑定&#xff1a;点(绑定的最终目标对象)→曲柄→活塞&#xff08;子控父&#xff0c;反向解算&#xff09; 点:绑定到轮子上的连接点

C语言:什么是动态内存管理?

动态内存管理是指在程序运行期间&#xff0c;根据实际需要动态地分配和释放内存空间的过程。与静态内存分配在编译时就确定内存大小不同&#xff0c;动态内存管理允许程序在运行时根据具体情况灵活地申请和释放内存。 主要通过 malloc 、 calloc 、 realloc 和 free 函数来实现…

04-DevOps-安装并初始化Jenkins

Jenkins由Java语言开发&#xff0c;用于监控持续重复的工作&#xff0c;包括&#xff1a;持续的软件版本发布/测试项目&#xff0c;监控外部调用执行的工作。 Jenkins主要起到一个驱动者&#xff0c;流水线的工作&#xff0c;下游代码拉取&#xff0c;上游生产环境发布、构建&…

PHP2(WEB)

##解题思路 打开页面什么线索都没有&#xff0c;目录扫描只是扫出来一个index.php&#xff0c;而源代码没有东西&#xff0c;且/robots.txt是不允许访问的 于是一番查询后发现&#xff0c;有个index.phps的文件路径&#xff0c;里头写着一段php的逻辑&#xff0c;对url的id参数…

kafka+spring cloud stream 发送接收消息

方案 1&#xff1a;使用旧版 StreamListener&#xff08;适用于 Spring Cloud Stream < 2.x&#xff09; 1. 添加依赖&#xff08;pom.xml&#xff09; <!-- Spring Cloud Stream Kafka Binder --> <dependency> <groupId>org.springframework.clo…

leetcode 题目解析 第3题 无重复字符的最长子串

给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的 最长 子串的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所以其长度为 3。 示例 2: 输入: s “bbbbb” 输出: 1 解释: 因为无重复字符的最长子串是 “b”…

C# 从基础神经元到实现在0~9数字识别

训练图片:mnist160 测试结果:1000次训练学习率为0.1时,准确率在60%以上 学习的图片越多&#xff0c;训练的时候越长(比如把 epochs*10 10000或更高时)效果越好 using System; using System.Collections.Generic; using System.Drawing; using System.IO; using System.Windo…