动态规划习题(二)

一、0-1 背包问题

0-1 背包问题 (暴力递归 / 动态规划)

二、数组串 -> 字符串

规定1和A对应、2和B对应、3和C对应…26和Z对应
那么一个数字字符串比如"111”就可以转化为:“AAA”、“KA"和"AK”
给定一个只有数字字符组成的字符串str,返回有多少种转化结果

思路

动态规划习题(二)_第1张图片

代码实现

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"));
    }
}

三、LeetCode 691. 贴纸拼词

动态规划习题(二)_第2张图片

思路

暴力递归
剪枝优化
动态规划
动态规划习题(二)_第3张图片

代码实现

暴力递归

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;
    }
}

四、1143. 最长公共子序列

动态规划习题(二)_第4张图片

思路

暴力递归
动态规划
动态规划习题(二)_第5张图片

代码实现

暴力递归

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];
    }
}

你可能感兴趣的:(算法,动态规划,java,算法,数据结构)