Java详解剑指offer面试题47--礼物的最大价值

Java详解剑指offer面试题47–礼物的最大价值

在一个mxn的棋盘的每一格斗放油一个礼物,每个礼物都有一定的价值(大于0)从棋盘的左上角开始,每次可以往右边或者下边移动一格,知道到达棋盘的右下角。给定一个棋盘和上面的礼物,计算我们最多可以拿到多少价值的礼物

递归–两个方向的深度优先搜索

我第一想到的是递归,然后被书上打脸。不管,还是实现一下吧。

package Chap5;

public class MaxGiftVal {
    /**
     * 方法一:递归,两个方向的深度优先搜索,用一个对象数组保存最大值(只需一个长度)
     */
    public int getMax(int[] gifts, int rows, int cols) {
        if (gifts == null || gifts.length == 0) return 0;
        int[] max = {0};
        select(gifts, 0, 0, rows, cols, 0, max);
        return max[0];
    }

    private void select(int[] gifts, int row, int col, int rows, int cols, int val, int[] max) {
        if (row >= rows || col >= cols) return;
        // 一维数组表示,对应着二维数组中的array[row][col]
        val += gifts[row * cols + col];
        // 到达右下角,和max比较
        if (row == rows - 1 && col == cols - 1) {
            if (val > max[0]) max[0] = val;
        }
        select(gifts, row + 1, col, rows, cols, val, max);
        select(gifts, row, col + 1, rows, cols, val, max);
    }
}

每进入一个格子,累加礼物价值。当到达右下角时,将累加和与全局的max变量比较,如果某条路径的累加和比max大,就更新max。边界控制很重要,在超出行或者超出列的范围后,直接返回。然后就不断在两个方向递归——右边或者下边。全局max由于是int型,作为参数并不能在递归调用后被改变,所以需要一个对象,由于只需要存放一个值,一个长度为1的对象数组即可。

动态规划–要到达当前格子有两个方向

设当前格子能获得的最大礼物价值为 f ( i , j ) f(i, j) f(i,j), 要到达该位置,只有两种情况:

  • 从该位置的左边来,即 f ( i , j − 1 ) f(i, j-1) f(i,j1)
  • 从该位置的上边来,即 f ( i − 1 , j ) f(i-1, j) f(i1,j)

f ( i , j ) f(i, j) f(i,j)处的礼物价值设为 g i f t ( i , j ) gift(i, j) gift(i,j)

那么到达 f ( i , j ) f(i, j) f(i,j)处能收集到的最大礼物价值为

m a x [ f ( i , j − 1 ) , f ( i − 1 , j ) ] + g i f t [ i , j ] max[f(i, j- 1), f(i-1, j)]+gift[i, j] max[f(i,j1),f(i1,j)]+gift[i,j]

可以发现,要知道当前格子能获得最大礼物价值,需要用到当前格子左边一个和上面一个格子的最大礼物价值和。所以从左上角开始,计算到达每一个格子能获得最大礼物价值是多少,并保存下这些结果。在后面求其他格子的最大礼物价值时会用到前面的结果。基于这个思路可写出如下代码。

package Chap5;

    /**
     * 方法2:动态规划,到达f(i,j)处拥有的礼物价值和有两种情况:
     * 1、从左边来,即f(i, j) = f(i, j -1) + gift(i, j)
     * 2、从上边来,即f(i, j) = f(i -1, j) + gift(i, j)
     *
     * 保证到达每一个格子得到的礼物价值之和都是最大的,也就是取max[f(i, j-1), f(i-1, j)] +gift(i, j)
     * 可以发现,要知道当前格子能获得最大礼物价值,需要用到当前格子左边一个和上面一个格子的最大礼物价值和
     */

    public int getMaxVal(int[] gifts, int rows, int cols) {
        if (gifts == null || gifts.length == 0) return 0;
        int[][] maxVal = new int[rows][cols];
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                int left = 0;
                int up = 0;
                if (row > 0) up = maxVal[row -1][col];
                if (col > 0) left = maxVal[row][col -1];
                maxVal[row][col] = Math.max(up, left) + gifts[row *cols+col];
            }
        }
        return maxVal[rows-1][cols-1];
    }
}

用到一个二维数组保存到达每一个格子时能获得的最大礼物价值。up和left分别是上面说的 f ( i − 1 , j ) f(i-1, j) f(i1,j) f ( i , j − 1 ) f(i, j -1) f(i,j1),循环完毕后,返回到达右下角处能获得最大礼物价值即可。

上面方法的优化——用一维数组代替二维数组

当前礼物的最大价值只依赖 f ( i − 1 , j ) f(i-1, j) f(i1,j) f ( i , j − 1 ) f(i, j -1) f(i,j1)这两个格子,因此只需要当前行i,第j列的前面几个格子,也就是 f ( i , 0 ) f(i, 0) f(i,0)~ f ( i , j − 1 ) f(i, j-1) f(i,j1);以及i -1行的,第j列及其之后的几个格子,也就是 f ( i − 1 , j ) f(i-1, j) f(i1,j)~ f ( i − 1 , c o l s − 1 ) f(i-1, cols-1) f(i1,cols1)

两部分加起来的个数刚好是棋盘的列数cols。所以只需要一个长度为cols的一维数组即可,优化如下。

package Chap5;

    public int betterGetMaxVal(int[] gifts, int rows, int cols) {
        if (gifts == null || gifts.length == 0) return 0;
        int[] maxVal = new int[cols];
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                int left = 0;
                int up = 0;
                if (row > 0) up = maxVal[col];
                if (col > 0) left = maxVal[col -1];
                maxVal[col] = Math.max(up, left) + gifts[row *cols+col];
            }
        }
        return maxVal[cols-1];
    }
}

int[] maxVal = new int[cols]; 中,索引为0~j-1的就是 f ( i , 0 ) f(i, 0) f(i,0)~ f ( i , j − 1 ) f(i, j-1) f(i,j1),索引j~cols-1的就是 f ( i − 1 , j ) f(i-1, j) f(i1,j)~ f ( i − 1 , c o l s − 1 ) f(i-1, cols-1) f(i1,cols1)


本文参考文献:
[1]github.com/haiyusun/data-structures

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐李同学(李俊德-大连理工大学)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值