算法体系-20 第二十节暴力递归到动态规划

前言 动态规划模型从尝试暴力递归到傻缓存到动态规划

四种模型和体系班两种模型一共六种模型

0.1 从左往右模型

0.2 范围讨论模型范围尝试模型 (这种模型特别在乎讨论开头如何如何 结尾如何如何)

玩家博弈问题,玩家玩纸牌只能那左或者右

0.3 样本对应样本对应模型(特别在乎两个样本结尾如何如何 最长公共子序列)

0.4 业务限制模型

动态规划只是暴力尝试的一个缓存
 

一 背包问题

1.1 描述

给定两个长度都为N的数组weights和values,

weights[i]和values[i]分别代表 i号物品的重量和价值。

给定一个正数bag,表示一个载重bag的袋子,

你装的物品不能超过这个重量。

返回你能装下最多的价值是多少?

1.2 分析

到当前货物的时候有两种选择,要么选择当前货物,要么不选择当前货物

base 条件的判断分析

if (rest < 0) {

return -1;}

这里为什么不能取return 0,因为上由传下来的剩下的bags的重量要大于0上由的值才是有意义的;

递归改动态规划

第一步找确定的值

if (index == w.length) {

return 0;

}

第二步找动态的值喝确定值之间的关系,动态的值时如何根据静态值退出来的

int p1 = process(w, v, index + 1, rest);

int next = process(w, v, index + 1, rest - w[index]);

这辆动态函数都需要依赖他的一行,最后一行又是确定值

1.3 尝试递归代码

// 所有的货,重量和价值,都在w和v数组里
    // 为了方便,其中没有负数
    // bag背包容量,不能超过这个载重
    // 返回:不超重的情况下,能够得到的最大价值
    public static int maxValue(int[] w, int[] v, int bag) {
        if (w == null || v == null || w.length != v.length || w.length == 0) {
            return 0;
        }
        // 尝试函数!
        return process(w, v, 0, bag);
    }

    // index 0~N
    // rest 负~bag
    public static int process(int[] w, int[] v, int index, int rest) {
        if (rest < 0) {
            return -1;
        }
        if (index == w.length) {
            return 0;
        }
        //不选择当前的货物
        int p1 = process(w, v, index + 1, rest);
        int p2 = 0;
        //要选择当前的货物
        int next = process(w, v, index + 1, rest - w[index]);
        if (next != -1) {
            p2 = v[index] + next;
        }
        return Math.max(p1, p2);
    }

1.4 改动态规划

递归改动态规划

第一步找确定的值

第二步找动态的值喝确定值之间的关系,动态的值时如何根据静态值退出来的

改动态规划 看是否有重复的情况

下面的p(3,10)都会重复

1.5 动态规划代码

public static int dp(int[] w, int[] v, int bag) {
        if (w == null || v == null || w.length != v.length || w.length == 0) {
            return 0;
        }
        int N = w.length;
        int[][] dp = new int[N + 1][bag + 1];
        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= bag; rest++) {
                int p1 = dp[index + 1][rest];
                int p2 = 0;
                int next = rest - w[index] < 0 ? -1 : dp[index + 1][rest - w[index]];
                if (next != -1) {
                    p2 = v[index] + next;
                }
                dp[index][rest] = Math.max(p1, p2);
            }
        }
        return dp[0][bag];
    }

    public static void main(String[] args) {
        int[] weights = { 3, 2, 4, 7, 3, 1, 7 };
        int[] values = { 5, 6, 3, 19, 12, 4, 2 };
        int bag = 15;
        System.out.println(maxValue(weights, values, bag));
        System.out.println(dp(weights, values, bag));
    }

}

二 字符对应数字对应的转换

2.1 描述

规定1和A对应、2和B对应、3和C对应...26和Z对应

那么一个数字字符串比如"111”就可以转化为:

"AAA"、"KA"和"AK"

给定一个只有数字字符组成的字符串str,返回有多少种转化结果

2.2 分析

第一步 到最后一个位置的时候前面做的决定共同构成收获了一个有效的点数

第二步 当前位置是0的是说明前面的做的决定策略错了,0是无效的转化,0没有对应的i字符

第三步 i位置不为0的时候说明该位置可以单转然后轮到 i+1位置去转

第四步 i和i+1位置共同决定,注意越界

2.3 代码

2.3.1尝试递归

    // str只含有数字字符0~9
    // 返回多少种转化方案
    public static int number(String str) {
        if (str == null || str.length() == 0) {
            return 0;
        }
        return process(str.toCharArray(), 0);
    }

    // str[0..i-1]转化无需过问
    // str[i.....]去转化,返回有多少种转化方法
    public static int process(char[] str, int i) {
        if (i == str.length) {
            return 1;
        }
        // i没到最后,说明有字符
        if (str[i] == '0') { // 之前的决定有问题
            return 0;
        }
        // str[i] != '0'
        // 可能性一,i单转
        int ways = process(str, i + 1);
        if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) {
        //可能性二 i和i+1组合成算一种    
                  ways += process(str, i + 2);
        }
        return ways;
    }

2.3.2 递归改动态规划

1.找到base条件

2.根据base条件推出其他情况

为什么取的是dp[0] 是因为递归尝试是从0开始的,递归最终往上返回的0

// 从右往左的动态规划
    // 就是上面方法的动态规划版本
    // dp[i]表示:str[i...]有多少种转化方式
    public static int dp1(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }
        char[] str = s.toCharArray();
        int N = str.length;
        int[] dp = new int[N + 1];
        dp[N] = 1;
        for (int i = N - 1; i >= 0; i--) {
            if (str[i] != '0') {
                int ways = dp[i + 1];
                if (i + 1 < str.length && (str[i] - '0') * 10 + str[i + 1] - '0' < 27) {
                    ways += dp[i + 2];
                }
                dp[i] = ways;
            }
        }
        return dp[0];
    }

三 给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文

3.1 描述

给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文

arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来

返回需要至少多少张贴纸可以完成这个任务。

例子:str= "babac",arr = {"ba","c","abcd"}

ba + ba + c 3 abcd + abcd 2 abcd+ba 2

所以返回2 张贴纸

3.2 分析

就把每一张贴纸作为第一张,有几张贴纸我就给你试多少种情况,答案必在其中,因为肯定有一张贴纸作为了第一张,我就试每一个第一张是哈,我把所有可能第一张都试了,后续最少张数你告诉我,所有分支中最少的那就是我要的

为什么排序?为了命中率高一点,不排序也可以

3.3 代码

public class Code03_StickersToSpellWord {

    public static int minStickers1(String[] stickers, String target) {
        int ans = process1(stickers, target);
        return ans == Integer.MAX_VALUE ? -1 : ans;
    }

    // 所有贴纸stickers,每一种贴纸都有无穷张
    // target
    // 最少张数
    public static int process1(String[] stickers, String target) {
        if (target.length() == 0) {
            return 0;
            //这里第一步返回
        }
        int min = Integer.MAX_VALUE;
        for (String first : stickers) {
            //假如贴纸stickers里面有5个,将这五个都作为第一个去试减去之后看target里面还剩多少target需要拼接的
            String rest = minus(target, first);
            //假如第一轮target里面试完了还有target需要再去贴的话进行下一轮
            if (rest.length() != target.length()) {
                //进行第一次后的下轮贴纸尝试
                 //这里第一步返回到这里
                min = Math.min(min, process1(stickers, rest));
            }
        }
        //要将第一次的加进来,每次递归process1没有走到上面的判断都会走到这里返回1或者Integer.MAX_VALUE
        return min + (min == Integer.MAX_VALUE ? 0 : 1);
    }
public static String minus(String s1, String s2) {
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        int[] count = new int[26];
        for (char cha : str1) {
            count[cha - 'a']++;
        }
        for (char cha : str2) {
            count[cha - 'a']--;
        }
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < 26; i++) {
            if (count[i] > 0) {
                for (int j = 0; j < count[i]; j++) {
                    builder.append((char) (i + 'a'));
                }
            }
        }
        return builder.toString();
    }

3.4 改进 高效做法 优化

四 列子 两个字符串的最长公共子序列 使用的模型的是 (样本对应模型)

4.1 描述

给定两个字符串str1和str2,

返回这两个字符串的最长公共子序列长度

比如 : str1 = “a12b3c456d”,str2 = “1ef23ghi4j56k”

最长公共子序列是“123456”,所以返回长度6

4.2 分析 样本对应模型的经验是从最后一个开始对应

样本对应模型,他往往讨论结尾该如何组织可能性(经验)

4.3 最长公共子序列改动态规划分析

4.4代码

public class Code04_LongestCommonSubsequence {

    public static int longestCommonSubsequence1(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
            return 0;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        // 尝试
        return process1(str1, str2, str1.length - 1, str2.length - 1);
    }

    // str1[0...i]和str2[0...j],这个范围上最长公共子序列长度是多少?
    // 可能性分类:
    // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾
    // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾
    // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾
    // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾
    // 注意:a)、b)、c)、d)并不是完全互斥的,他们可能会有重叠的情况
    // 但是可以肯定,答案不会超过这四种可能性的范围
    // 那么我们分别来看一下,这几种可能性怎么调用后续的递归。
    // a) 最长公共子序列,一定不以str1[i]字符结尾、也一定不以str2[j]字符结尾
    //    如果是这种情况,那么有没有str1[i]和str2[j]就根本不重要了,因为这两个字符一定没用啊
    //    所以砍掉这两个字符,最长公共子序列 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归)
    // b) 最长公共子序列,可能以str1[i]字符结尾、但是一定不以str2[j]字符结尾
    //    如果是这种情况,那么我们可以确定str2[j]一定没有用,要砍掉;但是str1[i]可能有用,所以要保留
    //    所以,最长公共子序列 = str1[0...i]与str2[0...j-1]的最长公共子序列长度(后续递归)
    // c) 最长公共子序列,一定不以str1[i]字符结尾、但是可能以str2[j]字符结尾
    //    跟上面分析过程类似,最长公共子序列 = str1[0...i-1]与str2[0...j]的最长公共子序列长度(后续递归)
    // d) 最长公共子序列,必须以str1[i]字符结尾、也必须以str2[j]字符结尾
    //    同时可以看到,可能性d)存在的条件,一定是在str1[i] == str2[j]的情况下,才成立的
    //    所以,最长公共子序列总长度 = str1[0...i-1]与str2[0...j-1]的最长公共子序列长度(后续递归) + 1(共同的结尾)
    // 综上,四种情况已经穷尽了所有可能性。四种情况中取最大即可
    // 其中b)、c)一定参与最大值的比较,
    // 当str1[i] == str2[j]时,a)一定比d)小,所以d)参与
    // 当str1[i] != str2[j]时,d)压根不存在,所以a)参与
    // 但是再次注意了!
    // a)是:str1[0...i-1]与str2[0...j-1]的最长公共子序列长度
    // b)是:str1[0...i]与str2[0...j-1]的最长公共子序列长度
    // c)是:str1[0...i-1]与str2[0...j]的最长公共子序列长度
    // a)中str1的范围 < b)中str1的范围,a)中str2的范围 == b)中str2的范围
    // 所以a)不用求也知道,它比不过b)啊,因为有一个样本的范围比b)小啊!
    // a)中str1的范围 == c)中str1的范围,a)中str2的范围 < c)中str2的范围
    // 所以a)不用求也知道,它比不过c)啊,因为有一个样本的范围比c)小啊!
    // 至此,可以知道,a)就是个垃圾,有它没它,都不影响最大值的决策
    // 所以,当str1[i] == str2[j]时,b)、c)、d)中选出最大值
    // 当str1[i] != str2[j]时,b)、c)中选出最大值
    public static int process1(char[] str1, char[] str2, int i, int j) {
        if (i == 0 && j == 0) {
            // str1[0..0]和str2[0..0],都只剩一个字符了
            // 那如果字符相等,公共子序列长度就是1,不相等就是0
            // 这显而易见
            return str1[i] == str2[j] ? 1 : 0;
        } else if (i == 0) {
            // 这里的情况为:
            // str1[0...0]和str2[0...j],str1只剩1个字符了,但是str2不只一个字符
            // 因为str1只剩一个字符了,所以str1[0...0]和str2[0...j]公共子序列最多长度为1
            // 如果str1[0] == str2[j],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
            // 如果str1[0] != str2[j],只是此时不相等而已,
            // 那么str2[0...j-1]上有没有字符等于str1[0]呢?不知道,所以递归继续找
            if (str1[i] == str2[j]) {
                return 1;
            } else {
                return process1(str1, str2, i, j - 1);
            }
        } else if (j == 0) {
            // 和上面的else if同理
            // str1[0...i]和str2[0...0],str2只剩1个字符了,但是str1不只一个字符
            // 因为str2只剩一个字符了,所以str1[0...i]和str2[0...0]公共子序列最多长度为1
            // 如果str1[i] == str2[0],那么此时相等已经找到了!公共子序列长度就是1,也不可能更大了
            // 如果str1[i] != str2[0],只是此时不相等而已,
            // 那么str1[0...i-1]上有没有字符等于str2[0]呢?不知道,所以递归继续找
            if (str1[i] == str2[j]) {
                return 1;
            } else {
                return process1(str1, str2, i - 1, j);
            }
        } else { // i != 0 && j != 0
            // 这里的情况为:
            // str1[0...i]和str2[0...i],str1和str2都不只一个字符
            // 看函数开始之前的注释部分
            // p1就是可能性c)  完全不考虑i 所以减去1,可能考虑j
            int p1 = process1(str1, str2, i - 1, j);
            // p2就是可能性b) 完全不考虑j 所以减去1,可能考虑i
            int p2 = process1(str1, str2, i, j - 1);
            // p3就是可能性d),如果可能性d)存在,即str1[i] == str2[j],那么p3就求出来,参与pk
            // 如果可能性d)不存在,即str1[i] != str2[j],那么让p3等于0,然后去参与pk,反正不影响
            int p3 = str1[i] == str2[j] ? (1 + process1(str1, str2, i - 1, j - 1)) : 0;
            return Math.max(p1, Math.max(p2, p3));
        }
    }

 //转化动态规划
    public static int longestCommonSubsequence2(String s1, String s2) {
        if (s1 == null || s2 == null || s1.length() == 0 || s2.length() == 0) {
            return 0;
        }
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        int N = str1.length;
        int M = str2.length;
        int[][] dp = new int[N][M];
        dp[0][0] = str1[0] == str2[0] ? 1 : 0;
        for (int j = 1; j < M; j++) {
            dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1];
        }
        for (int i = 1; i < N; i++) {
            dp[i][0] = str1[i] == str2[0] ? 1 : dp[i - 1][0];
        }
        for (int i = 1; i < N; i++) {
            for (int j = 1; j < M; j++) {
                int p1 = dp[i - 1][j];
                int p2 = dp[i][j - 1];
                int p3 = str1[i] == str2[j] ? (1 + dp[i - 1][j - 1]) : 0;
                dp[i][j] = Math.max(p1, Math.max(p2, p3));
            }
        }
        return dp[N - 1][M - 1];
    }

}

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

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

相关文章

Docker Jenkins(改错版本)

Devops:它强调开发(Development)和运维(Operations)团队之间的协作.实现更快,更可靠的软件交付部署. JenKins是一个开源的自动化服务器,广泛用于构建,测试和部署软件项目.它是持续集成(CI)和持续交付/部署(CD)的工具.JenKins是实现DevOps实践的重要工具. 前端项目部署一般流程:…

【javaEE-有关CPU进程和线程实现的并发编程及二者的区别】

&#x1f525;&#x1f525;&#x1f525;有关进程并发编程开发的成本问题 这次之前其实我们所有的写的程序都是使用单核心来运行的&#xff0c;但是一般我们的计算机都有很多核心&#xff0c;如果我们编程的时候&#xff0c;只使用一个核心的话&#xff0c;其实这是一个非常大…

通俗范畴论2 有向图与准范畴

退一步海阔天空&#xff0c;在正式进入范畴论之前&#xff0c;我们可以重新审视一下我们是如何认识世界的&#xff0c;有了这个对人类认识世界过程的底层理解&#xff0c;可以帮助我们更好地理解范畴论。 对于人类认识世界&#xff0c;最神奇的一点就是这个世界居然是可以认识…

【C语言】解决C语言报错:Race Condition

文章目录 简介什么是Race ConditionRace Condition的常见原因如何检测和调试Race Condition解决Race Condition的最佳实践详细实例解析示例1&#xff1a;缺乏适当的同步机制示例2&#xff1a;错误使用条件变量 进一步阅读和参考资料总结 简介 Race Condition&#xff08;竞争条…

element-ui input输入框和多行文字输入框字体不一样

页面中未作样式修改&#xff0c;但是在项目中使用element-ui input输入框和多行文字输入框字体不一样&#xff0c;如下图所示&#xff1a; 这是因为字体不一致引起的&#xff0c;如果想要为Element UI的输入框设置特定的字体&#xff0c;你可以在你的样式表中添加以下CSS代码…

尚品汇-(二)

本地域名解析器&#xff1a;当我们在浏览器输入域名的时候&#xff0c;它首先找的不是远程的DNS&#xff0c;而是去本地的host中去找这个域名有没有对应的&#xff0c;如果有对应的&#xff0c;那么就根据对应的ip进行访问 一&#xff1a;环境安装 1.安装JAVA 运行环境 第一…

MySQL之优化服务器设置(四)

优化服务器设置 InnoDB的IO配置 双写缓冲(Doublewrite Buffer) InnoDB用双写缓冲来避免页没写完整所导致的数据损坏。当一个磁盘写操作不能完整地完成时&#xff0c;不完整的页写入就可能发生&#xff0c;16KB的页可能只有一部分被写到磁盘上。有多种多样的原因(崩溃、Bug&am…

Obsidian 工作区Workspace:实现切换和管理工作区的多任务处理插件

工作区 工作区是Obsidian 的核心插件之一&#xff0c;旨在帮助用户更好地管理和组织他们的工作环境。 功能简介 工作区保存和切换&#xff1a;Workspace 插件允许用户保存当前的窗口布局和打开的笔记状态&#xff0c;用户可以随时切换到不同的工作区&#xff0c;这样可以根据…

Matlab|基于手肘法的kmeans聚类数的精确识别【K-means聚类】

主要内容 在电力系统调度研究过程中&#xff0c;由于全年涉及的风、光和负荷曲线较多&#xff0c;为了分析出典型场景&#xff0c;很多时候就用到聚类算法&#xff0c;而K-means聚类就是常用到聚类算法&#xff0c;但是对于K-means聚类算法&#xff0c;需要自行指定分类数&…

Google Earth Engine(GEE)——计算闪闪红星的ndvi的值和折线图(时序分析)

函数: ui.Chart.image.doySeries(imageCollection, region, regionReducer, scale, yearReducer, startDay, endDay)

中小学电子教材下载办法(202406最简单的)

官方版本 现在能阅读电子教材的官方网站挺多的&#xff0c;例如 人民教育出版社-电子教材&#xff0c;还有 国家中小学智慧教育平台 &#xff0c;其他还有很多可在阅读的网站。由于平台的原因不能直接贴链接&#xff0c;大家可以通过搜索关键词找到网站。 如何下载 据我所知…

idea搜索只显示100条、如何修改idea搜索的条数

文章目录 一、老版本的IDEA&#xff08;2021年之前的版本&#xff09;二、新版本的IDEA&#xff08;2021年及之后的版本&#xff09;2.1、方式一2.2、方式二 如下图&#xff1a;idea搜索的时候默认只显示100条 要解决IDEA搜索只显示100条的问题&#xff0c;可以通过修改搜索结…

【推荐】Perl入门教程特点功能文本处理读取文件替换文本写入文件分割字符数据库处理环境准备安装(包含示咧)

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

HashMap详解(含动画演示)

目录 HashMap1、HashMap的继承体系2、HashMap底层数据结构3、HashMap的构造函数①、无参构造②、有参构造1 和 有参构造2 (可以自定义初始容量和负载因子)③、有参构造3(接受一个Map参数)JDK 8之前版本的哈希方法&#xff1a;JDK 8版本的哈希方法 4、拉链法解决哈希冲突什么是拉…

监控室,屏幕显示不支持码流

1号屏&#xff0c;出现不支持码流 如下原因 老是录像机 无法关闭自动添加摄像头功能&#xff0c; 其他杂牌摄像头 会自动还ip 最终导致 ip冲突 更换ip 可以解决

Arnoldi Iteration 思考

文章目录 1. 投影平面2. Arnoldi Iteration3. python 代码 1. 投影平面 假设我们有一个向量q,我们需要关于向量q&#xff0c;构建一个投影平面P&#xff0c;使得给定任何向量v,可以通过公式 p P v pPv pPv&#xff0c;快速得到向量v在投影平面P上的投影向量p. 计算向量内积,…

部署LVS-DR群集...

目录 最后一台主机&#xff08;第四台&#xff09; 本地yum源安装httpd&#xff08;非必做&#xff09; 继续开始从最后一台主机开始&#xff08;第四台&#xff09; 转第二台主机 转第三台主机 回第二台 上传 转第三台主机 上传 回第二台 转第三台 转第一台主机…

Parallelize your massive SHAP computations with MLlib and PySpark

https://medium.com/towards-data-science/parallelize-your-massive-shap-computations-with-mllib-and-pyspark-b00accc8667c (能翻墙直接看原文&#xff09; A stepwise guide for efficiently explaining your models using SHAP. Photo by Pietro Jeng on Unsplash Int…

前端:鼠标点击实现高亮特效

一、实现思路 获取鼠标点击位置 通过鼠标点击位置设置高亮裁剪动画 二、效果展示 三、按钮组件代码 <template><buttonclass"blueBut"click"clickHandler":style"{backgroundColor: clickBut ? rgb(31, 67, 117) : rgb(128, 128, 128),…