首页 > 其他分享 >二维动态规划(下)

二维动态规划(下)

时间:2024-01-29 21:33:24浏览次数:26  
标签:int s2 s1 char 二维 strlen 动态 规划 dp

二维动态规划(下)

115. 不同的子序列

// 自底向上
int numDistinct(char *s, char *t) {
    const int MOD = 1e9 + 7;
    int lenS = strlen(s);
    int lenT = strlen(t);
    // dp[i][j]表示在s中前缀长度为i的字符串所包含的所有子序列中,有多少个子序列等于t中前缀长度为j的字符串
    // i、j表示前缀长度,而不是字符串中的下标
    int dp[lenS + 1][lenT + 1];
    // 第一列全1,表示空串的时候匹配
    for (int i = 0; i <= lenS; ++i) dp[i][0] = 1;
    // 第一行除了第一个元素,全0,不可能匹配
    for (int i = 1; i <= lenT; ++i) dp[0][i] = 0;

    for (int i = 1; i <= lenS; ++i) {
        for (int j = 1; j <= lenT; ++j) {
            // 情况1:s[i]不选,则问题转化为dp[i - 1][j]
            dp[i][j] = dp[i - 1][j];
            // 情况2:s[i]选,但是前提条件是末尾字符相同,问题转化为dp[i - 1][j - 1],累积这两种情况
            if (s[i - 1] == t[j - 1])
                dp[i][j] = (dp[i][j] + dp[i - 1][j - 1]) % MOD;
        }
    }
    return dp[lenS][lenT];
}
// 空间优化
int numDistinct(char *s, char *t) {
    const int MOD = 1e9 + 7;
    int lenS = strlen(s);
    int lenT = strlen(t);
    // dp[i][j]表示在s中前缀长度为i的字符串所包含的所有子序列中,有多少个子序列等于t中前缀长度为j的字符串
    // i、j表示前缀长度,而不是字符串中的下标
    int dp[lenT + 1];
    // 第一列全1,表示空串的时候匹配
    // 第一行除了第一个元素,全0,不可能匹配
    memset(dp, 0, sizeof(int) * (lenT + 1));
    dp[0] = 1;

    // 当前位置依赖于左上角和上方的格子,所以从上往下,从右往左填
    for (int i = 1; i <= lenS; ++i) {
        for (int j = lenT; j >= 1; j--) {
            // 情况1:s[i]不选,则问题转化为dp[i - 1][j],即上方格子,此时dp[j]不用变动,直接就表示上方格子
            // 情况2:s[i]选,但是前提条件是末尾字符相同,问题转化为dp[i - 1][j - 1],dp[j-1]尚未更新,表示左上方格子
            if (s[i - 1] == t[j - 1])
                dp[j] = (dp[j] + dp[j - 1]) % MOD;
        }
    }
    return dp[lenT];
}

72. 编辑距离

int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    // dp[i][j]表示s1[前缀长度i]变成s2[前缀长度j]的最小代价
    int dp[len1 + 1][len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0][0] = 0;
    // 空字符串到s2[前缀长度i]需要插入相应字符
    for (int i = 1; i <= len2; ++i)
        dp[0][i] = i * a;
    // s1[前缀长度i]到空字符串需要删除相应字符
    for (int i = 1; i <= len1; ++i)
        dp[i][0] = i * b;
    // 首行首列已经填完,剩下的格子和左侧、上方、左上方的格子有关
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            // 1. s1[i-1]参与
            //      1.1 s1[i-1]变成s2[j-1]
            //          1.1.1 末尾字符相同,即s1[i-1]等于s2[j-1]时,代价等于dp[i-1][j-1](情况p1)
            //          1.1.2 末尾字符不相同,代价等于dp[i-1][j-1]加上替换字符的代价(情况p2)
            //      1.2 s1[i-1]不变成s2[j-1]
            //          1.2.1 s1[0~i-1]变成s2[0~j-2]最后再插入字符s2[j-1],代价等于dp[i][j-1]加上插入字符的代价(情况p3)
            // 2. s1[i-1]不参与
            //      2.1 即删除s1[i-1],让s1[0~i-2]变成s2[0~j-1],代价等于dp[i-1][j]加上删除字符的代价(情况p4)
            int p1 = 0x7fffffff;
            if (s1[i - 1] == s2[j - 1])
                p1 = dp[i - 1][j - 1];
            int p2 = 0x7fffffff;
            if (s1[i - 1] != s2[j - 1])
                p2 = dp[i - 1][j - 1] + c;
            int p3 = dp[i][j - 1] + a;
            int p4 = dp[i - 1][j] + b;
            dp[i][j] = min(min(p1, p2), min(p3, p4));
        }
    }
    return dp[len1][len2];
}

int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}
int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    // dp[i][j]表示s1[前缀长度i]变成s2[前缀长度j]的最小代价
    int dp[len1 + 1][len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0][0] = 0;
    // 空字符串到s2[前缀长度i]需要插入相应字符
    for (int i = 1; i <= len2; ++i)
        dp[0][i] = i * a;
    // s1[前缀长度i]到空字符串需要删除相应字符
    for (int i = 1; i <= len1; ++i)
        dp[i][0] = i * b;
    // 首行首列已经填完,剩下的格子和左侧、上方、左上方的格子有关
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            // 根据末尾字符是否相同划分
            if (s1[i - 1] == s2[j - 1]) {
                // 相等时,代价就是之前dp[i - 1][j - 1]的代价
                dp[i][j] = dp[i - 1][j - 1];
            } else {
                // 不等时,分为插入删除和替换三种可能
                dp[i][j] = min(min(dp[i - 1][j - 1] + c, dp[i][j - 1] + a), dp[i - 1][j] + b);
            }
        }
    }
    return dp[len1][len2];
}

// 易理解版
int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}
int min(int a, int b) {
    return a > b ? b : a;
}

// 插入、删除、替换一个字符的代价分别为a,b,c
int editDistance(char *s1, char *s2, int a, int b, int c) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int dp[len2 + 1];
    // 空字符串到空字符串没有代价
    dp[0] = 0;
    // 第一行除了首元素外,其他位置表示从空字符串到s2[前缀长度i]需要插入相应字符的总代价
    for (int i = 1; i <= len2; ++i)
        dp[i] = i * a;

    int leftUp;
    int backup;
    for (int i = 1; i <= len1; ++i) {
        leftUp = (i - 1) * b;
        // s1[前缀长度i]到空字符串需要删除相应字符
        dp[0] = i * b;
        for (int j = 1; j <= len2; ++j) {
            backup = dp[j];
            // 根据末尾字符是否相同划分
            if (s1[i - 1] == s2[j - 1]) {
                // 相等时,代价就是之前dp[i - 1][j - 1]的代价
                dp[j] = leftUp;
            } else {
                // 不等时,分为插入删除和替换三种可能
                dp[j] = min(min(leftUp + c, dp[j - 1] + a), dp[j] + b);
            }
            leftUp = backup;
        }
    }
    return dp[len2];
}

// 空间压缩,类似于最长公共子序列的空间压缩
int minDistance(char *word1, char *word2) {
    return editDistance(word1, word2, 1, 1, 1);
}

97. 交错字符串

bool isInterleave(char *s1, char *s2, char *s3) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int len3 = strlen(s3);
    if (len1 + len2 != len3) return false;

    // s1[前缀长度为i]和s2[前缀长度为j],能否交错组成s3[前缀长度为i+j]
    bool dp[len1 + 1][len2 + 1];
    for (int i = 0; i <= len1; ++i)
        memset(dp[i], 0, sizeof(bool) * (len2 + 1));
    dp[0][0] = true;
    // 第一行:s3全由s2提供,最多能匹配到哪
    for (int j = 1; j <= len2; ++j) {
        if (s2[j - 1] != s3[j - 1]) break;
        dp[0][j] = true;
    }
    // 第一列:s3全由s1提供,最多能匹配到哪
    for (int i = 1; i <= len1; ++i) {
        if (s1[i - 1] != s3[i - 1]) break;
        dp[i][0] = true;
    }

    // 依赖上方和左侧格子
    for (int i = 1; i <= len1; ++i) {
        for (int j = 1; j <= len2; ++j) {
            // s3最后一个字符来自s1最后一个或者s2最后一个,有一种成立即可
            dp[i][j] = (s1[i - 1] == s3[i + j - 1] && dp[i - 1][j])
                       || (s2[j - 1] == s3[i + j - 1] && dp[i][j - 1]);
        }
    }
    return dp[len1][len2];
}
// 空间压缩
bool isInterleave(char *s1, char *s2, char *s3) {
    int len1 = strlen(s1);
    int len2 = strlen(s2);
    int len3 = strlen(s3);
    if (len1 + len2 != len3) return false;

    bool dp[len2 + 1];
    memset(dp, 0, sizeof(bool) * (len2 + 1));
    dp[0] = true;
    // 第一行:s3全由s2提供,最多能匹配到哪
    for (int j = 1; j <= len2; ++j) {
        if (s2[j - 1] != s3[j - 1]) break;
        dp[j] = true;
    }

    // 依赖上方和左侧格子
    for (int i = 1; i <= len1; ++i) {
        // 第一列:s3全由s1提供,最多能匹配到哪
        dp[0] = s1[i - 1] == s3[i - 1] && dp[0];
        for (int j = 1; j <= len2; ++j) {
            // s3最后一个字符来自s1最后一个或者s2最后一个,有一种成立即可
            dp[j] = (s1[i - 1] == s3[i + j - 1] && dp[j])
                    || (s2[j - 1] == s3[i + j - 1] && dp[j - 1]);
        }
    }
    return dp[len2];
}

有效涂色问题

package class068;

import java.util.Arrays;

// 有效涂色问题
// 给定n、m两个参数
// 一共有n个格子,每个格子可以涂上一种颜色,颜色在m种里选
// 当涂满n个格子,并且m种颜色都使用了,叫一种有效方法
// 求一共有多少种有效的涂色方法
// 1 <= n, m <= 5000
// 结果比较大请 % 1000000007 之后返回
// 对数器验证
public class Code04_FillCellsUseAllColorsWays {

	// 暴力方法
	// 为了验证
	public static int ways1(int n, int m) {
		return f(new int[n], new boolean[m + 1], 0, n, m);
	}

	// 把所有填色的方法暴力枚举
	// 然后一个一个验证是否有效
	// 这是一个带路径的递归
	// 无法改成动态规划
	public static int f(int[] path, boolean[] set, int i, int n, int m) {
		if (i == n) {
			Arrays.fill(set, false);
			int colors = 0;
			for (int c : path) {
				if (!set[c]) {
					set[c] = true;
					colors++;
				}
			}
			return colors == m ? 1 : 0;
		} else {
			int ans = 0;
			for (int j = 1; j <= m; j++) {
				path[i] = j;
				ans += f(path, set, i + 1, n, m);
			}
			return ans;
		}
	}

	// 正式方法
	// 时间复杂度O(n * m)
	// 已经展示太多次从递归到动态规划了
	// 直接写动态规划吧
	// 也不做空间压缩了,因为千篇一律
	// 有兴趣的同学自己试试
	public static int MAXN = 5001;

	public static int[][] dp = new int[MAXN][MAXN];

	public static int mod = 1000000007;

	public static int ways2(int n, int m) {
		// dp[i][j]:
		// 一共有m种颜色
		// 前i个格子涂满j种颜色的方法数
		for (int i = 1; i <= n; i++) {
			dp[i][1] = m;
		}
		for (int i = 2; i <= n; i++) {
			for (int j = 2; j <= m; j++) {
				dp[i][j] = (int) (((long) dp[i - 1][j] * j) % mod);
				dp[i][j] = (int) ((((long) dp[i - 1][j - 1] * (m - j + 1)) + dp[i][j]) % mod);
			}
		}
		return dp[n][m];
	}

	public static void main(String[] args) {
		// 测试的数据量比较小
		// 那是因为数据量大了,暴力方法过不了
		// 但是这个数据量足够说明正式方法是正确的
		int N = 9;
		int M = 9;
		System.out.println("功能测试开始");
		for (int n = 1; n <= N; n++) {
			for (int m = 1; m <= M; m++) {
				int ans1 = ways1(n, m);
				int ans2 = ways2(n, m);
				if (ans1 != ans2) {
					System.out.println("出错了!");
				}
			}
		}
		System.out.println("功能测试结束");

		System.out.println("性能测试开始");
		int n = 5000;
		int m = 4877;
		System.out.println("n : " + n);
		System.out.println("m : " + m);
		long start = System.currentTimeMillis();
		int ans = ways2(n, m);
		long end = System.currentTimeMillis();
		System.out.println("取余之后的结果 : " + ans);
		System.out.println("运行时间 : " + (end - start) + " 毫秒");
		System.out.println("性能测试结束");
	}
}

删除至少几个字符可以变成另一个字符串的子串

package class068;

import java.util.ArrayList;
import java.util.List;

// 删除至少几个字符可以变成另一个字符串的子串
// 给定两个字符串s1和s2
// 返回s1至少删除多少字符可以成为s2的子串
// 对数器验证
public class Code05_MinimumDeleteBecomeSubstring {

	// 暴力方法
	// 为了验证
	public static int minDelete1(String s1, String s2) {
		List<String> list = new ArrayList<>();
		f(s1.toCharArray(), 0, "", list);
		// 排序 : 长度大的子序列先考虑
		// 因为如果长度大的子序列是s2的子串
		// 那么需要删掉的字符数量 = s1的长度 - s1子序列长度
		// 子序列长度越大,需要删掉的字符数量就越少
		// 所以长度大的子序列先考虑
		list.sort((a, b) -> b.length() - a.length());
		for (String str : list) {
			if (s2.indexOf(str) != -1) {
				// 检查s2中,是否包含当前的s1子序列str
				return s1.length() - str.length();
			}
		}
		return s1.length();
	}

	// 生成s1字符串的所有子序列串
	public static void f(char[] s1, int i, String path, List<String> list) {
		if (i == s1.length) {
			list.add(path);
		} else {
			f(s1, i + 1, path, list);
			f(s1, i + 1, path + s1[i], list);
		}
	}

	// 正式方法,动态规划
	// 已经展示太多次从递归到动态规划了
	// 直接写动态规划吧
	// 也不做空间压缩了,因为千篇一律
	// 有兴趣的同学自己试试
	public static int minDelete2(String str1, String str2) {
		char[] s1 = str1.toCharArray();
		char[] s2 = str2.toCharArray();
		int n = s1.length;
		int m = s2.length;
		// dp[len1][len2] :
		// s1[前缀长度为i]至少删除多少字符,可以变成s2[前缀长度为j]的任意后缀串
		int[][] dp = new int[n + 1][m + 1];
		for (int i = 1; i <= n; i++) {
			dp[i][0] = i;
			for (int j = 1; j <= m; j++) {
				if (s1[i - 1] == s2[j - 1]) {
					dp[i][j] = dp[i - 1][j - 1];
				} else {
					dp[i][j] = dp[i - 1][j] + 1;
				}
			}
		}
		int ans = Integer.MAX_VALUE;
		for (int j = 0; j <= m; j++) {
			ans = Math.min(ans, dp[n][j]);
		}
		return ans;
	}

	// 为了验证
	// 生成长度为n,有v种字符的随机字符串
	public static String randomString(int n, int v) {
		char[] ans = new char[n];
		for (int i = 0; i < n; i++) {
			ans[i] = (char) ('a' + (int) (Math.random() * v));
		}
		return String.valueOf(ans);
	}

	// 为了验证
	// 对数器
	public static void main(String[] args) {
		// 测试的数据量比较小
		// 那是因为数据量大了,暴力方法过不了
		// 但是这个数据量足够说明正式方法是正确的
		int n = 12;
		int v = 3;
		int testTime = 20000;
		System.out.println("测试开始");
		for (int i = 0; i < testTime; i++) {
			int len1 = (int) (Math.random() * n) + 1;
			int len2 = (int) (Math.random() * n) + 1;
			String s1 = randomString(len1, v);
			String s2 = randomString(len2, v);
			int ans1 = minDelete1(s1, s2);
			int ans2 = minDelete2(s1, s2);
			if (ans1 != ans2) {
				System.out.println("出错了!");
			}
		}
		System.out.println("测试结束");
	}

}

标签:int,s2,s1,char,二维,strlen,动态,规划,dp
From: https://www.cnblogs.com/sprinining/p/17995365

相关文章

  • onnx导出-多输入+动态维度
    onnx导出-多输入+动态维度目录onnx导出-多输入+动态维度常见问题多参数输入动态输入导出动态输入问题-无法修改维度重新定义onnx输出验证导出和测试多头输入多头输出参考资料常见问题多参数输入importnumpyasnpimportcv2importtorchimporttorch.nnasnnimporttorc......
  • 动态区间特殊值——线段树的简单应用
    目录问题引入简单介绍功能详情碎碎念问题引入很多地方写的是区间最值问题,感觉区间特殊值更好一些,区间gcd、前缀和都可以,问题描述大致就是给出一个数组,要求给出询问区间的特殊值,当然求和之类的可以用树状数组解决,但是最大、最小等等特殊值是不能转化为前缀和用树状数组解决的,因此......
  • 二维数组
    动态初始化内存图练习......
  • SpringBoot实现动态数据源配置
    场景描述:前一阵子接手的新项目中需要使用2个数据源。一个叫行云数据库,一个叫OceanBase数据库。就是说,我有时候查询要查行云的数据,有时候查询要查OceanBase的数据,咋办?废话不多说,下面以mysql为例,开整。一、环境依赖<dependency><groupId>org.springframework.boot</gr......
  • 二维凸包复习笔记
    Graham扫描法向量的叉乘:平行四边形面积,顺负逆正,x1y2-x2y11.确定1个凸包上的点:纵坐标最小(纵坐标相同时横坐标最小)的点2.极角排序3.单调栈维护凸包点击查看代码//二维凸包#include<bits/stdc++.h>usingnamespacestd;structt1{ doublex,y;}t[100005];ints[100......
  • JVS低代码表单引擎:实现下拉框数据来源动态化的解决方案
    下拉选项数据来源调用逻辑引擎的功能在于提供一个可视化的界面,使用户能够方便地配置和管理业务逻辑,实现数据的快速处理、业务模式的自动化和智能化。接下来我详细介绍JVS低代码中如何通过逻辑引擎获取下拉选项的数据来源,以及如何配置下拉框组件以实现这一功能。下拉选项数据来源调......
  • labview静态和动态装载子Vi
    学习《Labview的编程经验》的笔记静态装载子Vi静态装载子Vi:运行一个vi时,将这个vi的子vi全部加载到内存。对于小型程序,影响不大。对于大型程序(子vi过多),会有两个问题,一是占用内存过大问题,二是程序运行时的启动速度过慢问题。动态装载子Vi动态装载子Vi:程序启动时,不载入这些子V......
  • layuiAdmin动态生成菜单并递归显示
    先给出菜单请求回来的json格式:除去外面的code和msg外,data内容是一个[]数组;每个数组里面是一个对象;有4个参数,一个标题(title),一个图标(icon),一个请求地址(url),最后还可能有个子集(list)。{"code":0,"msg":"","data":[{"title":"一级滑块组件","icon&q......
  • 微信小程序:生成二维码
    wxml<view><buttonbindtap='createQrcode'type="primary"style="width:400rpx;margin-top:400rpx;">生成二维码</button><canvasid='qrcode'type="2d"style='width:300rpx;height:30......
  • 动态绑定组件时 v-model:value 的使用
    //requireimportcomponentsconstfiles=require.context("@/components/control",true,/\index.vue$/);//console.log('files:',files.keys())//files:['./cascader/index.vue','./control/cascader/index.vue',......