动态规划之数组压缩空间、星号技巧【3】

动态规划之数组压缩空间、星号技巧【3】

1 最小距离累加和

在这里插入图片描述

1.1 正常动规

//正常动态规划
public static int minPathSum1(int[][] m){
    //边界
    if(m == null || m.length == 0 || m[0] == null || m[0].length == 0){
        return 0;
    }
    int row = m.length;
    int col = m[0].length;
    int[][] dp = new int[row][col];
    //初始位置[0, 0] 不依赖于任何位置,只看自己的值
    dp[0][0] = m[0][0];
    //第一行
    for(int i = 1; i < col; i++){
        //对于每一列来说
        dp[0][i] = dp[0][i-1] + m[0][i];
    }
    //第一列
    for(int j = 1; j < row; j++){
        //对于每一行来说
        dp[j][0] = dp[j-1][0] + m[j][0];
    }
    //普遍范围
    for(int i = 1; i < row; i++){
        for(int j = 1; j < col; j++){
            //每一个位置都依赖于它的上左
            //可以选择左边也可以选择上边
            //选择最小的那个
            dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + m[i][j];
        }
    }
    return dp[row-1][col-1];
}

思考:如果题目所给的数组,有太多行怎么办呢?

可以将动态规划表,改为一维数组,如果行太多,则改成一行N列的一维数组

//压缩空间【时间复杂度不变】
public static int minPathSum2(int[][] m){
    if(m == null || m.length == 0 || m[0].length == 0 || m[0] == null){
        return 0;
    } 
    int row = m.length;
    int col = m[0].length;
    //假设行太多
    int[] dp = new int[col];
    dp[0] = m[0][0];
    //初始化数组[填充之前的第一行]
    for(int j = 1; j < col; j++){
        dp[j] = dp[j-1] + m[0][j];
    }
    //压缩空间
    for(int i = 1; i < row; i++){
        //记住最左边位置【之前的第一列】
        dp[0] += m[i][0];
        for(int j = 1; j < col; j++){
            //dp[j]:相当于之前的上方
            //dp[j-1]:相当于之前左方
            dp[j] = Math.min(dp[j-1], dp[j]) + m[i][j];
        }
    }
    return dp[col-1];
}

2 货币问题1【面值相同为两张、张数无限】

在这里插入图片描述

2.1 递归尝试

//arr:钱币数组
//aim:目标值
public static int coinWays(int[] arr, int aim) {
    return process(arr, 0, aim);
}

//arr:钱币数组
//index:当前所在位置
//rest:剩余的目标钱数
public static int process(int[] arr, int index, int rest) {
    if (rest < 0) {
        //剩余钱数小于零
        return 0;
    }
    if (index == arr.length) {
        return rest == 0 ? 1 : 0;
    } else {
        //选当前硬币与不选当前硬币【方法数和】
        return process(arr, index + 1, rest) + process(arr, index + 1, rest - arr[index]);
    }
}

2.2 动态规划

//动态规划
public static int dp(int[] arr, int aim){
    int N = arr.length;
    //0-aim 共有aim+1个数
    //arr.length同理【会取到结尾就+1】
    int[][] dp = new int[N+1][aim+1];
    //base case【index为数组末尾, rest为0时候】
    dp[N][0] = 1;
    for(int index = N - 1; index >= 0; index--){
        for(int rest = aim; rest >= 0; rest--){
            dp[index][rest] = dp[index+1][rest] + (rest - arr[index] >= 0 ? dp[index+1][rest - arr[index]] : 0);
        }
    }
    return dp[0][aim];
}

3 货币问题2【面值相同为一张、张数无限】

在这里插入图片描述

3.1 尝试(zhang)

public static int coinsWay(int[] arr, int aim){
    if(arr == null || arr.length == 0 || aim < 0){
        return 0;
    }
    return process(arr, 0, aim);
}

//大体思路和货币问题1一样【只不过多了一层for循环,需要做暴力尝试】
public static int process(int[] arr, int index, int rest){
    if(index == arr.length){
        return rest == 0 ? 1 : 0;
    }
    int ways = 0;
    //张数可以能为0,可能不为0【包含了选该货币和不选该货币的情况】
    for(int zhang = 0; zhang * arr[index] <= rest; zhang++){
        ways += process(arr, index+1, rest - zhang * arr[index]);
    }
    return ways;
}

3.2 动态规划1(三层for)

//动态规划1:三层for循环
public static int dp1(int[] arr, int aim) {
    if (arr == null || arr.length == 0) {
        return 0;
    }
    int N = arr.length;
    int[][] dp = new int[N + 1][aim + 1];
    dp[N][0] = 1;
    //index+1 -> 上面的依赖下面的
    for (int index = N - 1; index >= 0; index--) {
        for (int rest = aim; rest >= 0; rest--) {
            //穷举:张数
            int ways = 0;
            for (int zhang = 0; zhang * arr[index] <= rest; zhang++) {
                ways += dp[index + 1][rest - zhang * arr[index]];
            }
            dp[index][rest] = ways;
        }
    }
    return dp[0][aim];
}

3.2 动态规划2

//建立空间感:优化【√ = ※ + a】
//  ※   √
//      a
public static int dp2(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0;
    }
    int N = arr.length;
    int[][] dp = new int[N + 1][aim + 1];
    dp[N][0] = 1;
    for (int index = N - 1; index >= 0; index--) {
        for (int rest = aim; rest >= 0; rest--) {
            //画格子:找规律    8      11      14  (建立空间感)
            //           3张3元    2张3元    1张3元
            //  ※   √
            //①获得我下面格子的值a
            dp[index][rest] = dp[index + 1][rest];
            //②如果没有越界,我直接获得我※号的位置
            if (rest - arr[index] >= 0) {
                dp[index][rest] += dp[index][rest - arr[index]];
            }
        }
    }
    return dp[0][aim];
}

4 货币问题3【每个值都是一张货币,张数有限】

在这里插入图片描述

>>前置类及方法

//收集纸币信息
public static class Info {
    //有哪些纸币
    public int[] coins;
    //每种纸币对应的张数
    public int[] zhangs;

    public Info(int[] coins, int[] zhangs) {
        this.coins = coins;
        this.zhangs = zhangs;
    }
}

//收集纸币信息[不同纸币对应数量]
public static Info getInfo(int[] arr) {
    Map<Integer, Integer> map = new Hashtable<>();
    for (int i = 0; i < arr.length; i++) {
        if (map.containsKey(arr[i])) {
            map.put(arr[i], map.get(arr[i]) + 1);
        } else {
            map.put(arr[i], 1);
        }
    }
    int N = map.size();
    int[] coins = new int[N];
    int[] zhangs = new int[N];
    int index = 0;
    for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
        coins[index] = entry.getKey();
        zhangs[index++] = entry.getValue();
    }
    return new Info(coins, zhangs);
}

4.1 尝试(收集纸币信息)


//方法入口
public static int coinsWay(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0;
    }
    Info info = getInfo(arr);
    int[] coins = info.coins;
    int[] zhangs = info.zhangs;
    return process(coins, zhangs, 0, aim);
}

//递归调用方法
public static int process(int[] coins, int[] zhangs, int index, int rest) {
    int N = coins.length;
    if (index == N) {
        return rest == 0 ? 1 : 0;
    }
    int ways = 0;
    for(int zhang = 0; zhang * coins[index] <= rest && zhang <= zhangs[index]; zhang++){
        ways += process(coins, zhangs, index+1, rest - zhang*coins[index]);
    }
    return ways;
}

4.2 动态规划第一个版本(三个for)

//动态规划:第一个版本
public static int dp1(int[] arr, int aim){
    if(arr == null || arr.length == 0 || aim < 0){
        return 0;
    }
    Info info = getInfo(arr);
    //纸币数组
    int[] coins = info.coins;
    //每种纸币对应的张数
    int[] zhangs = info.zhangs;
    int N = coins.length;
    int[][] dp = new int[N+1][aim+1];
    dp[N][0] = 1;
    for(int i = N - 1; i >= 0; i--){
        for(int rest = aim; rest >= 0; rest--){
            int ways = 0;
            for(int zhang = 0; zhang * coins[i] <= rest && zhang <= zhangs[i]; zhang++){
                ways += dp[i+1][rest - zhang*coins[i]];
            }
            dp[i][rest] = ways;
        }
    }
    return dp[0][aim];
}

4.3 动态规划第二个版本(√、※)

//动态规划2:画表格,建立空间感,找规律
public static int dp2(int[] arr, int aim) {
    if (arr == null || arr.length == 0 || aim < 0) {
        return 0;
    }
    Info info = getInfo(arr);
    int[] coins = info.coins;
    int[] zhangs = info.zhangs;
    //纸币的种类
    int N = coins.length;
    int[][] dp = new int[N + 1][aim + 1];
    dp[N][0] = 1;
    for (int index = N - 1; index >= 0; index--) {
        //一行中的元素之间如果没有依赖,所以无论从左往右还是从右往左都可以【此处只能从左往右:因为我们在技巧上要依赖它的左边※】
        for (int rest = 0; rest <= aim; rest++) {
            //画表格,建立空间感,找规律
            //      ※    √
            // x          a(多算了一个x)
            dp[index][rest] = dp[index + 1][rest];
            if (rest - coins[index] >= 0) {
                dp[index][rest] += dp[index][rest - coins[index]];
            }
            //判断是否多算
            if (rest - coins[index] * (zhangs[index] + 1) >= 0) {
                dp[index][rest] -= dp[index + 1][rest - coins[index] * (zhangs[index] + 1)];
            }
        }
    }
    return dp[0][aim];
}

5 圈问题【Bob Die】

在这里插入图片描述

5.1 递归尝试

//row * col 范围
//N * M 存活区域,超过该区域,bob掉沟里,死亡
//k bob一共走k步
public static double livePossibility1(int row, int col, int k, int N, int M) {
    return (double) process(row, col, k, N, M) / Math.pow(4, k);
}

//目前在row、col位置,还有rest步要走,走完了如果还在范围内,就获得一个生存点,返回总的生存点数
public static long process(int row, int col, int rest, int N, int M) {
    if (row < 0 || row == N || col < 0 || col == M) {
        return 0;
    }
    //还在棋盘中
    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;
}

5.2 动态规划(三维)

//三维动态规划【三个变量】
public static double livePossibility2(int row, int col, int k, int N, int M){
	//根据N * M范围建立矩阵即可【取不到N、M】
    long[][][] dp = new long[N][M][k+1];
    // 根据尝试:rest == 0 -> 返回1
    for(int i = 0; i < N; i++){
        for(int j = 0; j < M; j++){
            dp[i][j][0] = 1;
        }
    }
    for(int rest = 1; rest <= k; rest++){
        for(int r = 0; r < N; r++){
            for(int c = 0; c < M; c++){
                dp[r][c][rest] = pick(dp, N, M, r-1, c, rest-1);
                dp[r][c][rest] += pick(dp, N, M, r+1, c, rest-1);
                dp[r][c][rest] += pick(dp, N, M, r, c-1, rest-1);
                dp[r][c][rest] += pick(dp, N, M, r, c+1, rest-1);
            }
        }
    }
    return (double) dp[row][col][k] / Math.pow(4, k);
}

//控制范围【防止越界还计算】
public static long pick(long[][][] dp, int N, int M, int r, int c, int rest){
    if(r < 0 || r == N || c < 0 || c == M){
        return 0;
    }
    return dp[r][c][rest];
}
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值