算法11.从暴力递归到动态规划4

news2024/11/22 10:42:10

算法|11.从暴力递归到动态规划4

1.最长公共子序列

题意:给定两个字符串str1和str2,返回这两个字符串的最长公共子序列长度
比如 : str1 = “a12b3c456d”,str2 = “1ef23ghi4j56k”
最长公共子序列是“123456”,所以返回长度6

解题思路:

  • 每个字符串都有一个指针,两个可变参数
  • 边界条件——两个指针都为0时;第一个为0时;第二个为0时。因为决策方向是从右向左
  • 子过程分析的时候有四种情况:str1当前不要str2当前不要;str1当前不要str2要;str1当前要str2不要;str1和str2当前都要
  • 取四种情况的最大值

核心代码:

递归代码:

private static int lsc(String s1, String s2) {
        char[] str1 = s1.toCharArray();
        char[] str2 = s2.toCharArray();
        return process(str1, str2, str1.length - 1, str2.length - 1);
    }

    /**
     * @param str1
     * @param str2
     * @param i:str1考虑[0..i]
     * @param j:str2考虑[0..j]
     * @return
     */
    private static int process(char[] str1, char[] str2, int i, int j) {
        if (i == 0 && j == 0) {
            return str1[i] == str2[j] ? 1 : 0;
        } else if (i == 0) {
            return str1[0] == str2[j] ? 1 : process(str1, str2, i, j - 1);
        } else if (j == 0) {
            return str1[i] == str2[0] ? 1 : process(str1, str2, i - 1, j);
        } else {
//            int p4=process(str1,str2,i-1,j-1);决策四不写是因为已经知道4一定干不过1和2,所以直接不写,不考虑这种可能性
            int p1 = process(str1, str2, i - 1, j);
            int p2 = process(str1, str2, i, j - 1);
            int p3 = str1[i] == str2[j] ? 1 + process(str1, str2, i - 1, j - 1) : 0;
            return Math.max(p1, Math.max(p3, p2));
        }
    }

dp代码:

private static int dp(String s1, String s2) {
    char[] str1 = s1.toCharArray();
    char[] str2 = s2.toCharArray();
    //由尝试可以知道,可变参数为每次开始决策的下标位置,且范围分别是两个数组的长度-1,所以这里抓一下
    int N = str1.length;
    int M = str2.length;
    int[][] dp = new int[N][M];
    //由尝试的base case可以看出,依赖关系是右依赖左,初始条件是第一行第一列
    dp[0][0] = str1[0] == str2[0] ? 1 : 0;
    //第一行
    for (int i = 1; i < M; i++) {
        dp[0][i] = str1[0] == str2[i] ? 1 : dp[0][i - 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];//在这里因为行列不对应,参数j是0-5,但是数组是0-3,所以有个越界问题,依赖的思路从列变成行即可
            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(p3, p2));
        }
    }
    return dp[N - 1][M - 1];
}

测试代码:略

测试结果:在这里插入图片描述

2.分裂数字问题

题意:给定一个正数n,求n的裂开方法数,规定:后面的数不能比前面的数小
比如4的裂开方法有:1+1+1+1、1+1+2、1+3、2+2、4。5种,所以返回5

解题思路:

核心代码:

递归代码:

public static int ways(int n){
    if(n<0){
        return 0;
    }
    if(n==1){
        return 1;
    }
    return process(1,n);//为什么不是1,n-1
}

/**
 *
 * @param pre 上一个拆出来的数
 * @param rest 还剩rest需要拆
 * @return 返回拆解的方法数
 */
public static int process(int pre,int rest){
    //base case 这条路走不通  pre=3  rest=2(前置的数比后边的数大)
    if(rest==0){
        return 1;
    }
    if(pre>rest){
        return 0;
    }
    if(pre==rest){//相等的时候就提前结束
        return 1;
    }
    int ways=0;
    for (int first=pre;first <=rest ; first++) {//(2,7)的例子
        ways+=process(first,rest-first);
    }
    return ways;
}

dp代码:

public static int dp(int n){
    if(n<0){
        return 0;
    }
    if(n==1){
        return 1;
    }
    int[][] dp=new int[n+1][n+1];
    for (int pre = 1; pre <= n; pre++) {
        dp[pre][0] = 1;
        dp[pre][pre] = 1;
    }
    for (int pre = n-1; pre >=1 ; pre--) {//第n行已经填好了,第0号无效,从n-1行到第1行
        for (int rest = pre+1; rest <=n ; rest++) {//pre的位置已经填过了
            int ways=0;
            for (int first=pre;first <=rest ; first++) {//(2,7)的例子
                ways+=dp[first][rest-first];
            }
            dp[pre][rest]=ways;
        }
    }
    return dp[1][n];
}

测试代码:略

测试结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-KJVI3cuQ-1685262811633)(F:\typora插图\image-20230528162948144.png)]

3.Bob生存概率问题

题意:给定5个参数,N,M,row,col,k
表示在NM的区域上,醉汉Bob初始在(row,col)位置
Bob一共要迈出k步,且每步都会等概率向上下左右四个方向走一个单位
任何时候Bob只要离开N
M的区域,就直接死亡
返回k步之后,Bob还在N*M的区域的概率

解题思路:

  • 概率问题也可以动态规划问题,算出的总方法数/总的个数(4^k次方)
  • 问题转换成动态规划,转化成实验,收集生存点数
  • 可变参数有三个,分别是行列剩余的步数
  • 子过程中边界条件有行列有效判定,有rest=0两个边界
  • 子过程分析有四个情况,返回四种情况下的总方法数

核心代码:

递归代码:

public static double livePosibility1(int row,int col,int k,int N,int M){
    return (double)process(row,col,k,M,N)/Math.pow(4,k);
}

/**
 *
 * @param row 当前在(row,col)位置,
 * @param col
 * @param rest 走完k步,如果还在棋盘就获得一个生存点
 * @param m 棋盘的大小是n行m列
 * @param n
 * @return
 */
private static long process(int row, int col, int rest, int n, int m) {
    //这里就相当于是走出去了,剪枝了,获得生存点数0
    if(row<0||row==n||col<0||col==m){
        return 0;
    }
    //base case
    if(rest==0){
        return 1;
    }
    //普遍问题
    long up=process(row-1,col,rest-1,n,m);
    long down=process(row+1,col,rest-1,n,m);
    long left=process(row,col-1,rest-1,n,m);
    long right=process(row,col+1,rest-1,n,m);
    return up+down+left+right;
}

dp代码:

public static double livePosibility2(int row,int col,int k,int n,int m){
    if(row<0||row==n||col<0||col==m){
        return 0;
    }
    long[][][] dp=new long[n][m][k+1];
    //这里边只要还在范围内,就有一个生命值
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < m; j++) {
            dp[i][j][0] = 1;
        }
    }
    //这里x-y的范围是0~边界-1
    for (int rest = 1; rest <=k ; rest++) {
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < m; j++) {
                //                    这里也是需要判断越界,同象棋跳马一样,所以采用pick方法
                dp[i][j][rest]+=pick(dp,i-1,j,rest-1,n,m);
                dp[i][j][rest]+=pick(dp,i+1,j,rest-1,n,m);
                dp[i][j][rest]+=pick(dp,i,j-1,rest-1,n,m);
                dp[i][j][rest]+=pick(dp,i,j+1,rest-1,n,m);
            }
        }
    }
    return (double)dp[row][col][k]/Math.pow(4,k);
}

private static long pick(long[][][] dp, int i, int j, int k,int n,int m) {
    if(i<0||j<0||k<0||i>=n||j>=m||k<0){
        return 0;
    }
    return dp[i][j][k];
}

测试代码:

public static void main(String[] args) {
    System.out.println(livePosibility1(6, 6, 10, 50, 50));
    System.out.println(livePosibility2(6, 6, 10, 50, 50));
}

测试结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-u0vydRV8-1685262811634)(F:\typora插图\image-20230528154950215.png)]

4.砍死怪兽概率问题

题意:给定3个参数,N,M,K,怪兽有N滴血,等着英雄来砍自己
英雄每一次打击,都会让怪兽流失[0M]的血量,到底流失多少?每一次在[0M]上等概率的获得一个值,求K次打击之后,英雄把怪兽砍死的概率

解题思路:

  • 总方法数是M+1次展开,M+1层
  • 同上,这里是实验问题
  • 可变参数有三个N,M,K,返回的是k次之后怪兽存活的方法数

核心代码:

递归代码:

public static double right(int N,int M,int K){
    if(N<1||M<1||K<1){
        return 0;
    }
    long all=(long) Math.pow(M+1,K);
    long kill=process(K,M,N);
    return (double) kill/all;
}

/**
 *
 * @param times 剩余的次数
 * @param M 每次伤害的最高值
 * @param hp 剩余的生命值
 * @return
 */
private static long process(int times, int M, int hp) {
     if(times==0){
         return hp<=0?1:0;
     }
     //但是这里好像又是一个剪枝,当现在已经小于等于0了,那么这条支路下边的展开必然是满足题意的
     if(hp<=0){
         //返回的方法数就应该是这样
         return (long)Math.pow(M+1,times);
     }
     long ways=0;
     //普遍情况
    for (int i = 0; i <=M ; i++) {
        ways+=process(times-1,M,hp-i);
    }
    return ways;
}

dp代码:

private static double dp(int K, int M, int N) {
        if(N<1||M<1||K<1){
            return 0;
        }
        long all=(long) Math.pow(M+1,K);
        long[][] dp=new long[K+1][N+1];
        //hp都没了还砍只存在于尝试思路,但是最后也是剪掉了
        dp[0][0]=1;
        for (int times = 1; times <= K ; times++) {
            dp[times][0] = (long) Math.pow(M + 1, times);
            for (int hp = 1; hp <=N ; hp++) {

                long ways=0;
                for (int i = 0; i <=M ; i++) {
//                    这里边hp-i可能会越界,所以需要分情况讨论一下
//                    ways+=dp[times-1][hp-i];
                    if(hp-i>=0){
                        ways+=dp[times-1][hp-i];
                    }else{
                        ways+=(long)Math.pow(M+1,times-1);
                    }
                }
                dp[times][hp]=ways;
            }
        }
        return (double)dp[K][N]/all;
    }

测试代码:略

测试结果:[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SN10fnPa-1685262811634)(F:\typora插图\image-20230528155605004.png)]

5.最长回文子串

题意前边已经说过,这里只是另一种解法,所以只放代码了。

这里的思路是拿到原串的逆串,并且将这两个串作为最大公共子串的参数,这里就是使用样本对应尝试模型了。

核心代码:

public static int lps(String s){
    if(s==null||s.length()==0){
        return 0;
    }
    if(s.length()==1){
        return 1;
    }
    char[] str1=s.toCharArray();
    char[] str2=reverse(str1);
    return process(str1,str2,str1.length-1,str2.length-1);
}
private static char[] reverse(char[] s) {
    char[] str=new char[s.length];
    for (int i = 0; i < s.length; i++) {
        str[i]=s[s.length-1-i];
    }
    return str;
}

private static int process(char[] str1, char[] str2, int i, int j) {
    if(i==0&&j==0){
        return str1[i]==str2[j]?1:0;
    }else if(i==0){
        return str1[0]==str2[j]?1: process(str1,str2,i,j-1);
    }else if(j==0){
        return str1[i]==str2[0]?1: process(str1,str2,i-1,j);
    }else{
        int p1= process(str1,str2,i-1,j);
        int p2= process(str1,str2,i,j-1);
        int p3=str1[i]==str2[j]?1+ process(str1,str2,i-1,j-1):0;
        return Math.max(p1,Math.max(p3,p2));
    }
}

测试代码:

public static String randomString(int len) {
    char[] str = new char[len];
    for (int i = 0; i < len; i++) {
        str[i] = (char) ((int) (Math.random() * 10) + 'a');
    }
    return String.valueOf(str);
}
private static int dp(String s) {
    if(s==null||s.length()==0){
        return 0;
    }
    char[] str=s.toCharArray();
    //可变参数范围为0-N-1,所以抓虾数组长度
    int N=str.length;
    int[][] dp=new int[N][N];
    dp[N-1][N-1]=1;
    //由两个base case 可以得出第一条对角线和挨着的斜线,另外可以得知主对角线下的数据没意义
    for (int i = 0; i < N-1; i++) {
        dp[i][i]=1;
        dp[i][i + 1] = str[i] == str[i + 1] ? 2 : 1;//这个时候已经知道空间关系了
    }
    //普遍情况,根据上边可以知道它是依赖左下,左上,左,下的,根据已知,只能从右下角开始推
    for (int i =N - 3; i >= 0; i--) {//从倒数第三列开始(别忘了边界坐标是N-1)
        //一条斜线一条斜线弄的
        for (int j = i+2; j < N ; j++) {//起始位置得推,暂时先记着
            int p1=dp[i+1][j-1];
            int p2=dp[i+1][j];
            int p3=dp[i][j-1];
            int p4=str[i]==str[j]?2+dp[i+1][j-1]:0;
            dp[i][j]=Math.max(Math.max(p1,p2),Math.max(p4,p3));//有次因为这里的最大值没改过来一直错
        }

    }
    return dp[0][N-1];
}

public static void main(String[] args) {
    int len = 10;
    int testTime = 1000;
    System.out.println("测试开始");
    for (int i = 0; i < testTime; i++) {
        String s = randomString(len);
        int ans0 = lps(s);
        int ans1 = dp(s);
        if (ans0 != ans1) {
            System.out.println(s);
            System.out.println(ans0);
            System.out.println(ans1);
            System.out.println("Oops!");
            break;
        }
    }
    System.out.println("测试结束");
}

样本对应尝试模型总结

改写Dp:

  • 无法改写成dp,不是因为是字符串,而是之前那个贴纸问题可能性太多了,而这个最长公共子序列同样是两个字符串,但是极限可能也是两个字符串中最大的长度相同。
  • 概率问题,其实可以通过实验获得生存点数,然后除以总可能展开的点数,可以得到计算
  • 最长公共子序列和最长回文子串问题都可以通过样本对应尝试模型完成

例题总结:

  • 最长公共子序列——四种情况(00,10,01,11);边界条件(i=0,j=0,i=0&j=0)
  • 分裂数字问题
  • Bob生存概率问题——边界条件rest、位置有效性;四种可能收集总情况
  • 砍死怪兽概率问题——边界条件、位置有效性;可能收集总情况(略)
  • 最长回文子串——逆串+调用最长公共子序列函数

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【PowerShell】PowerShell 7.1 之后版本的安装

当前以下操作系统支持PowerShell 7.1 版本的安装,非Windows 系统支持的版本和要求有一定的限制。 Windows 8.1/10 (including ARM64)Windows Server 2012 R2, 2016, 2019, and Semi-Annual Channel (SAC)Ubuntu 16.04/18.04/20.04 (including ARM64)Ubuntu 19.10 (via Snap pa…

图的邻接矩阵表示

设图有n个顶点&#xff0c;则邻接矩阵是一个n*n的方阵&#xff1b;若2个顶点之间有边&#xff0c;则方阵对应位置的值为1&#xff0c;否则为0&#xff1b; 看几个例子&#xff1b; 此图的邻接矩阵是 0 1 1 1 1 0 1 0 1 1 0 1 1 0…

学习 xss+csrf 组合拳

目录 1.xss基础铺垫 1.1反射型xss 1.2存储型xss 1.3基于DOM的xss 1.4xss漏洞的危害 1.5xss漏洞的黑盒测试 1.6xss漏洞的白盒测试 2.csrf基础铺垫 2.1csrf攻击原理 2.2csrf攻击防护 3.应用案例 3.1存储型xsscsrf组合拳 3.2csrfselfxss组合拳 1.xss基础铺垫 跨站脚…

线程和进程

进程和线程的区别(超详细) 与进程不同的是同类的多个线程共享进程的堆和方法区资源&#xff0c;但每个线程有自己的程序计数器、虚拟机栈和本地方法栈&#xff0c;所以系统在产生一个线程&#xff0c;或是在各个线程之间作切换工作时&#xff0c;负担要比进程小得多&#xff0…

【架构】常见技术点--服务治理

导读&#xff1a;收集常见架构技术点&#xff0c;作为项目经理了解这些知识点以及解决具体场景是很有必要的。技术要服务业务&#xff0c;技术跟业务具体结合才能发挥技术的价值。 目录 1. 微服务 2. 服务发现 3. 流量削峰 4. 版本兼容 5. 过载保护 6. 服务熔断 7. 服务…

微服务之流量控制

Informal Essay By English I have been thinking about a question recently, what is the end of coding? 参考书籍&#xff1a; “凤凰架构” 流量控制 任何一个系统的运算、存储、网络资源都不是无限的&#xff0c;当系统资源不足以支撑外部超过预期的突发流量时&…

数字信号处理8:利用Python进行数字信号处理基础

我前两天买了本MATLAB信号处理&#xff0c;但是很无语&#xff0c;感觉自己对MATLAB的语法很陌生&#xff0c;看了半天也觉得自己写不出来&#xff0c;所以就对着MATLAB自己去写用Python进行的数字信号处理基础&#xff0c;我写了两天左右&#xff0c;基本上把matlab书上的代码…

【数据结构】轻松掌握二叉树的基本操作及查找技巧

二叉树的基本操作 ​ 在学习二叉树的基本操作前&#xff0c;需先要创建一棵二叉树&#xff0c;然后才能学习其相关的基本操作。由于现在大家对二 叉树结构掌握还不够深入&#xff0c;为了降低学习成本&#xff0c;此处手动快速创建一棵简单的二叉树&#xff0c;快速进入二叉树操…

【自然语言处理】不同策略的主题建模方法比较

不同策略的主题建模方法比较 本文将介绍利用 LSA、pLSA、LDA、NMF、BERTopic、Top2Vec 这六种策略进行主题建模之间的比较。 1.简介 在自然语言处理&#xff08;NLP&#xff09;中&#xff0c;主题建模一词包含了一系列的统计和深度学习技术&#xff0c;用于寻找文档集中的隐…

【刷题之路】LeetCode 2073. 买票需要的时间

【刷题之路】LeetCode 2073. 买票需要的时间 一、题目描述二、解题1、方法1——记录每个人需要的时间1.1、思路分析1.2、代码实现 2、方法2——队列记录下标2.1、思路分析2.2、先将队列实现一下2.3、代码实现 一、题目描述 原题连接&#xff1a; 2073. 买票需要的时间 题目描述…

Linux---用户组命令(groupadd、groupdel、groupmod、newgrp、getent)

1. groupadd命令 [rootlocalhost ~]# groupadd [选项] 组名 [rootlocalhost ~]# groupadd group1 --向系统中增加了一个新组group1&#xff0c;新组的组标识号是在当前已有的最大组标识号的基础上加1。 [rootlocalhost ~]# groupadd -g 101 group2 --向系统中增加了一个新组gr…

MySQL5.7递归查询与CTE递归查询

文章目录 一、8.0版本的递归1、CTE递归2、举例3、递归CTE的限制 二、5.7版本的递归1、find_in_set 函数2、concat函数3、自定义函数实现递归查询4、向上递归5、可能遇到的问题 一、8.0版本的递归 1、CTE递归 先看8.0版本的递归查询CET。语法规则&#xff1a; WITH RECURSIVE…

深入浅出解析Stable Diffusion完整核心基础知识 | 【算法兵器谱】

Rocky Ding 公众号&#xff1a;WeThinkIn 写在前面 【算法兵器谱】栏目专注分享AI行业中的前沿/经典/必备的模型&论文&#xff0c;并对具备划时代意义的模型&论文进行全方位系统的解析&#xff0c;比如Rocky之前出品的爆款文章Make YOLO Great Again系列。也欢迎大家提…

笔试强训错题总结(一)

笔试强训错题总结 文章目录 笔试强训错题总结选择题编程题连续最大和不要二最近公共祖先最大连续的bit数幸运的袋子手套 选择题 以下程序的运行结果是&#xff08;&#xff09; #include <stdio.h> int main(void) {printf("%s , %5.3s\n", "computer&q…

<Linux开发>驱动开发 -之-基于pinctrl/gpio子系统的beep驱动

&#xff1c;Linux开发&#xff1e;驱动开发 -之-基于pinctrl/gpio子系统的beep驱动 交叉编译环境搭建&#xff1a; &#xff1c;Linux开发&#xff1e; linux开发工具-之-交叉编译环境搭建 uboot移植可参考以下&#xff1a; &#xff1c;Linux开发&#xff1e; -之-系统移植…

如何在华为OD机试中获得满分?Java实现【人民币转换】一文详解!

✅创作者&#xff1a;陈书予 &#x1f389;个人主页&#xff1a;陈书予的个人主页 &#x1f341;陈书予的个人社区&#xff0c;欢迎你的加入: 陈书予的社区 &#x1f31f;专栏地址: Java华为OD机试真题&#xff08;2022&2023) 文章目录 1. 题目描述2. 输入描述3. 输出描述…

认识Servlet---1

hi ,大家好,今天为大家带来Servlet相关的知识,并且实现第一个程序 &#x1f389;1.什么是Servlet &#x1f389;2.使用Servlet写一个hello程序 &#x1f33b;&#x1f33b;&#x1f33b;1.创建项目 &#x1f33b;&#x1f33b;&#x1f33b;2.引入依赖 &#x1f33b;&…

GitHub基本概念

创建日期: 2018-09-22 09:50:06 Git & GitHub Git是一个版本控制软件&#xff1a; 读作[gɪt] ,拼音读作gē y te。 Git is a free and open source distributed version control system designed to handle everything from small to very large projects with speed an…

STM32之温湿度LCD显示并上传服务器

目录 项目需求 项目框图 硬件清单 LCD1602介绍及实战 硬件接线 引脚封装 代码实现 DHT11介绍及实战 硬件接线 引脚封装 代码实现 项目设计及实现 项目设计 项目实现 项目需求 使用温湿度传感器模块&#xff08; DHT11 &#xff09;获取温度及湿度&#xff0c…

推荐计算机领域的几本入门书籍

人工智能入门&#xff1a; 人工智能&#xff1a;现代方法&#xff08;第4版&#xff09;揭示AI与chatgpt的奥秘&#xff0c;详解人工智能的发展与未来&#xff01; 推荐理由&#xff1a;系统性总结人工智能的方方面面&#xff0c;国际人工智能领域专家斯图尔特罗素撰写人工智能…