0-1 背包问题 (暴力递归 / 动态规划)
规定1和A对应、2和B对应、3和C对应…26和Z对应
那么一个数字字符串比如"111”就可以转化为:“AAA”、“KA"和"AK”
给定一个只有数字字符组成的字符串str,返回有多少种转化结果
public class Code02_ConvertToLetterString {
// 暴力递归
public static int number(String str) {
if (str == null || str.length() == 0) return 0;
return process(str.toCharArray(), 0);
}
// 返回 str[i...] i之后的可能性数量
public static int process(char[] chars, int i) {
// 如果i == chars.length 说明到达字符串尾部,而且前面的路径是合法的,结果加1,所以返回1
if (i == chars.length) {
return 1;
}
// 如果i位置为0,0不能够单独转换,所以说明前面的判断是错误的,这种路径有问题,不可取,返回0
if (chars[i] == '0') {
return 0;
}
// 情况一、让i位置单独转换,直接跳转到下一位
int ways = process(chars, i + 1);
// 情况二、让i 和 i + 1 位置一起转换,要判断,是否可以一起转换
// 组合后不能超出 '26'
if ((i + 1 < chars.length) && (((chars[i] - '0') * 10 + chars[i + 1] - '0') < 27)) {
ways += process(chars, i + 2);
}
return ways;
}
// 动态规划
public static int dp(String str) {
if (str == null || str.length() == 0) return 0;
char[] chars = str.toCharArray();
int N = str.length();
int[] dp = new int[N + 1];
dp[N] = 1;
for (int i = N - 1; i >= 0; i--) {
if (chars[i] != '0') {
int ways = dp[i + 1];
if ((i + 1 < chars.length) && (((chars[i] - '0') * 10 + chars[i + 1] - '0') < 27)) {
ways += dp[i + 2];
}
dp[i] = ways;
}
}
return dp[0];
}
public static void main(String[] args) {
System.out.println(number("7210231231232031203123"));
System.out.println(dp("7210231231232031203123"));
}
}
暴力递归
public class Code03_StickersToSpellWord {
public static int minStickers1(String[] stickers, String target) {
int ans = process1(stickers, target);
return ans == Integer.MAX_VALUE ? 0 : ans;
}
// 所有贴纸 stickers,每一种贴纸都有无穷张
// 当前目标 target
// 返回最少张数
public static int process1(String[] stickers, String target) {
if (target.length() == 0) { // 如果target为空,说明不需要贴纸了,已经组合完毕,返回0
return 0;
}
int min = Integer.MAX_VALUE;
for (String sticker : stickers) {
// 在目标字符串target中减去 sticker中的字符,返回剩余字符串
String rest = minus(target, sticker);
// 只有返回的这个rest大小不等于target大小时,才有必要将sticker加入当前组合,否则说明sticker中没有target中的任何字符
if (rest.length() != target.length()) {
min = Math.min(min, process1(stickers, rest)); // 再剩余字符中,找能够组成的贴纸
}
}
// 如果min==Integer.MAX_VALUE,证明无法组成target,返回系统最大值,最终返回给主函数,方法判断返回0
// 如果min!=Integer.MAX_VALUE,证明当前组合组成了target,min代表后续最小贴纸数,再加上当前层所用贴纸sticker,一起返回给上一层,即 min + 1
return min + (min == Integer.MAX_VALUE ? 0 : 1);
}
private static String minus(String s1, String s2) {
char[] c1 = s1.toCharArray();
char[] c2 = s2.toCharArray();
int[] count = new int[26];
for (char c : c1) {
count[c - 'a']++;
}
for (char c : c2) {
count[c - 'a']--;
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 26; i++) {
if (count[i] > 0) {
for (int j = 0; j < count[i]; j++) {
sb.append((char) i + 'a');
}
}
}
return sb.toString();
}
}
剪枝优化
public class Code03_StickersToSpellWord {
public static int minStickers2(String[] stickers, String target) {
int N = stickers.length;
// 关键优化(用词频表替代贴纸数组)
int[][] count = new int[N][26];
for (int i = 0; i < N; i++) {
char[] chars = stickers[i].toCharArray();
for (char aChar : chars) {
count[i][aChar - 'a']++;
}
}
return process2(count, target);
}
// stickers[i] 数组,当初i号贴纸的字符统计 int[][] stickers -> 所有的贴纸
// 每一种贴纸都有无穷张
// 返回搞定target的最少张数
// 最少张数
public static int process2(int[][] stickers, String t) {
if (t == null) {
return 0;
}
// target 统计词频
int[] tcounts = new int[26];
char[] target = t.toCharArray();
for (char c : target) {
tcounts[c - 'a']++;
}
int N = stickers.length;
int min = Integer.MAX_VALUE;
for (int i = 0; i < N; i++) {
int[] sticker = stickers[i];
// 剪枝,判断目标字符的首位,在贴纸中是否存在,如果存在才会进行下面的流程
// 剪枝掉那些不存在首位字符的贴纸
if (sticker[target[0] - 'a'] > 0) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < 26; j++) {
int nums = tcounts[j] - sticker[j];
for (int k = 0; k < nums; k++) {
sb.append((char) (j + 'a'));
}
}
String rest = sb.toString();
min = Math.min(min, process2(stickers, rest));
}
}
return min + (min == Integer.MAX_VALUE ? 0 : 1);
}
}
动态规划
import java.util.HashMap;
public class Code03_StickersToSpellWord {
public static int minStickers3(String[] stickers, String target) {
int N = stickers.length;
int[][] count = new int[N][26];
for (int i = 0; i < N; i++) {
char[] chars = stickers[i].toCharArray();
for (char aChar : chars) {
count[N][aChar - 'a']++;
}
}
HashMap<String, Integer> dp = new HashMap<>();
int ans = process3(count, target, dp);
return ans == Integer.MAX_VALUE ? -1 : ans;
}
public static int process3(int[][] stickers, String t, HashMap<String, Integer> dp) {
if (dp.containsKey(t)) {
return dp.get(t);
}
if (t.length() == 0) {
return 0;
}
char[] target = t.toCharArray();
int[] tCounts= new int[26];
for (char c : target) {
tCounts[c - 'a']++;
}
int N = stickers.length;
int min = Integer.MAX_VALUE;
for (int i = 0; i < N; i++) {
int[] sticker = stickers[i];
if (sticker[target[0] - 'a'] > 0) {
StringBuilder sb = new StringBuilder();
for (int j = 0; j < 26; j++) {
int nums = tCounts[j] - sticker[j];
for (int k = 0; k < nums; k++) {
sb.append((char) (j + 'a'));
}
}
String rest = sb.toString();
min = Math.min(min, process3(stickers, rest, dp));
}
}
int ans = min + (min == Integer.MAX_VALUE ? 0 : 1);
dp.put(t, ans);
return ans;
}
}
暴力递归
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 process(str1, str2, str1.length, str2.length);
}
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) {
if (str1[i] == str2[j]) {
return 1;
} else {
return process(str1, str2, i, j - 1);
}
} else if (j == 0) {
if (str1[i] == str2[j]) {
return 1;
} else {
return process(str1, str2, i - 1, j);
}
} else {
// 不考虑j位置的值
int p1 = process(str1, str2, i, j - 1);
// 不考虑i位置的值
int p2 = process(str1, str2, i - 1, j);
// 同时考虑 i 和 j 位置的值,所以要判断str1[i] 和 str2[j]的值是否相同
int p3 = process(str1, str2, i - 1, j - 1);
return Math.max(p1, Math.max(p2, p3));
}
}
}
动态规划
public class Code04_LongestCommonSubsequence {
public static int longestCommonSubsequence(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];
// base case: i==0 和 j==0
dp[0][0] = str1[0] == str2[0] ? 1 : 0;
for (int i = 0; i < N; i++) {
dp[i][0] = str1[1] == str2[0] ? 1 : dp[i - 1][0];
}
for (int j = 0; j < M; j++) {
dp[0][j] = str1[0] == str2[j] ? 1 : dp[0][j - 1];
}
for (int i = 1; i < N; i++) {
for (int j = 1; j < M; j++) {
int p1 = dp[i - 1][0];
int p2 = dp[0][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];
}
}