算法学习动态规划-1

前言

动态规划,按个人理解,一句话就是:在暴力递归的工程中,使用缓存,减少重复计算,就是所谓的动态规划。
紧跟大佬学习的脚步~~~
动态规划的套路就是:先写出优秀的递归函数,然后将递归函数改写成动态规划

下面就以实际题目进行学习。

一、背包问题

需求:

给定两个长度都为N的数组weights和values,
weights[i]和values[i]分别代表 i号物品的重量和价值。
给定一个正数bag,表示一个载重bag的袋子,
你装的物品不能超过这个重量。
返回你能装下最多的价值是多少? 

思路:

1、先想如何尝试,对于每个i位置下的weight,我们可以要,也可以不要
2、基于第一步实现的递归函数改写动态规划

代码如下:

纯递归版本:

    /**
     * 返回不超出bag重量限制下,最大的价值数
     *
     * @param W   重量
     * @param V   价值
     * @param bag 背包最多的重量
     * @return
     */
    public static int maxValue(int[] W, int[] V, int bag) {
        if (W.length == 0 || V.length == 0 || V.length != W.length || bag < 0) {
            return 0;
        }

        return process(W, V, 0, bag);
    }

    /**
     * 返回,从index后最大价值
     *
     * @param W
     * @param V
     * @param index
     * @param restBag
     * @return
     */
    public static int process(int[] W, int[] V, int index, int restBag) {
        //base case
        if (index == W.length || restBag < 0) {
            return 0;
        }

        //index位置不选择
        int p1 = process(W, V, index + 1, restBag);

        //index位置选择
        int rest = restBag - W[index];
        int p2 = Integer.MIN_VALUE;
        if (rest >= 0) {
            p2 = V[index] + process(W, V, index + 1, rest);
        }

        return Math.max(p1, p2);
    }
    

思考是否需要改动态规划

考虑是否需要改动态规划,我们举个例子
比如w[1,1,3] bag=6
假设第一次1选择,则后续需要算bag=5情况下价值最大
然后第一次不选,我们选第二个1,此时仍需要算bag=5情况下价值最大。
由此我们知道动态规划有存在的必要。

动态规划代码如下:

    public static int dp(int[] W, int[] V, int bag) {
        if (W.length == 0 || V.length == 0 || V.length != W.length || bag < 0) {
            return 0;
        }

        int N = W.length;
        int[][] dp = new int[N + 1][bag + 1];

        //index位置不选择
        for (int i = N - 1; i >= 0; i--) {
            for (int j = 0; j <= bag; j++) {
                //index位置不选择
                int p1 = dp[i + 1][j];

                //index位置选择
                int rest = j - W[i];
                int p2 = Integer.MIN_VALUE;
                if (rest >= 0) {
                    p2 = V[i] + process(W, V, i + 1, rest);
                }
                dp[i][j] = Math.max(p1, p2);
            }
        }
        return dp[0][bag];
    }

二、数字字符串转字母字符串

需求:

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

思路

1、单个数字可以直接转成字母,
2、两个数字需要判断小于27也可转成字母
3、先写递归函数,再改写动态规划

递归代码:

 public static int number(String s) {
        if (s == null || s.length() == 0) {
            return 0;
        }

        char[] str = s.toCharArray();
        return process(str, 0);
    }

    /**
     * 返回str[i...]可以转化的结果
     *
     * @param str
     * @param i
     * @return
     */
    public static int process(char[] str, int i) {
        if (i == str.length) {
            return 1;
        }

        if (str[i] == '0') {
            return 0;
        }

        int ways = process(str, i + 1);
        if (i + 1 < str.length && ((str[i] - '0') * 10 + (str[i + 1] - '0')) < 27) {
            ways += process(str, i + 2);
        }

        return ways;
    }

思考是否需要改动态规划

对于递归函数,假设str[]={"1","2","3"}
假设有一个递归分支跳到“3”算了一遍
然后有一个从“2”正常到“3”,这个时候,又要算一遍,这个时候存在重复计算了过来
所以动态规划就有必要了

动态规划的代码

    public static int dp(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];
    }

三、贴纸拼接问题

需求
leetcode原题:stickers-to-spell-word

给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文
arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来
返回需要至少多少张贴纸可以完成这个任务。
例子:str= "babac",arr = {"ba","c","abcd"}
ba + ba + c  3  abcd + abcd 2  abcd+ba 2
所以返回2

思路:

1、字符为顺序要求,只要能剪出对应数量的字符就行
2、统计出目标的26个字母词频,分别统计出所有贴纸的26个字母的词频
3、递归尝试,假设每种贴纸都是第一张,分别去尝试
4、后续根据情况再改写动态规划

递归代码

 public static int minStickers(String[] stickers, String target) {
        int N = stickers.length;

        //词频统计
        int[][] counts = new int[N][26];
        for (int i = 0; i < N; i++) {
            char[] str = stickers[i].toCharArray();
            for (char c : str) {
                counts[i][c - 'a']++;
            }
        }

        int ans = process(counts, target);
        return ans == Integer.MAX_VALUE ? -1 : ans;

    }


    public static int process(int[][] stickers, String t) {
        //base case
        if (t.length() == 0) {
            return 0;
        }

        //target做出词频统计
        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 builder = new StringBuilder();
                for (int j = 0; j < 26; j++) {
                    if (tcounts[j] > 0) {
                        int nums = tcounts[j] - sticker[j];
                        for (int k = 0; k < nums; k++) {
                            builder.append((char) (j + 'a'));
                        }
                    }
                }
                String rest = builder.toString();
                min = Math.min(min, process(stickers, rest));
            }
        }
        return min + (min == Integer.MAX_VALUE ? 0 : 1);

    }

思考是否需要改动态规划

根据递归函数方法的定义,我们知道可变参数是String字符串,这种情况下就没有办法做出严格意义上数组格式的动态规划了。因为无法穷举所有的字符串,加张缓存表就足够了

加缓存的代码

public static int minStickers(String[] stickers, String target) {
        int N = stickers.length;
        //所有贴纸的词频
        int[][] count = new int[N][26];
        for (int i = 0; i < N; i++) {
            String sticker = stickers[i];
            char[] str = sticker.toCharArray();
            for (char c : str) {
                count[i][c - 'a']++;
            }
        }
        HashMap<String, Integer> map = new HashMap<>();

        int ans = process(count, target, map);
        return ans == Integer.MAX_VALUE ? -1 : ans;

    }

    /**
     * 返回,需要最少几张贴纸完成目标
     *
     * @param stickers 贴纸的词频
     * @param target   目标字符串
     * @param map      缓存表
     * @return
     */
    public static int process(int[][] stickers, String target, HashMap<String, Integer> map) {

        if (map.containsKey(target)) {
            return map.get(target);
        }
        //base case
        if (target.length() == 0) {
            map.put("", 0);
            return 0;
        }

        //目标的词频
        char[] tStr = target.toCharArray();
        int[] tCount = new int[26];
        for (char c : tStr) {
            tCount[c - 'a']++;
        }

        int N = stickers.length;
        int min = Integer.MAX_VALUE;

        for (int i = 0; i < N; i++) {
            int[] sticker = stickers[i];
            //剪枝
            if (sticker[tStr[0] - 'a'] > 0) {
                //词频相减,求出剩余的目标
                StringBuilder builder = new StringBuilder();
                for (int j = 0; j < 26; j++) {
                    if (tCount[j] > 0) {
                        int restNum = tCount[j] - sticker[j];
                        for (int k = 0; k < restNum; k++) {
                            builder.append((char) (j + 'a'));
                        }
                    }
                }
                String rest = builder.toString();
                min = Integer.min(min, process(stickers, rest, map));
            }
        }

        int ans = min + (min == Integer.MAX_VALUE ? 0 : 1);
        map.put(target, ans);
        return ans;
    }

总结

动态规划其实就是在递归函数的基础上面根据可变参数改写的。动态规划就是为了减少递归函数的重复计算

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值