【算法】解题总结:剑指Offer 67 剪绳子(递归、记忆化递归、动态规划) 、剑指Offer 29 顺时针打印矩阵

JZ67 剪绳子

(中等)

题目

描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。

示例
输入:
8
返回值:
18

思路一:递归

暴力递归在此题中会运行超时,但却算是一种思路,因此也记录下来。

本题总绳长为2、3、4时情况特殊,2、3单独判断,而4则直接在下面递归终止条件中判断,因为4分为2、2乘积最大,也正好与下面递归终止条件的返回值相同。

递归的终止条件为此段绳长 <= 4,则返回当前绳长,终止条件意味着此段绳子不可再分,或是再分之后,在此段的局部范围内,最大乘积会变小(小于其本身),例如当绳长为1时,不可以再分,应当返回,当绳长为2时,可再分,可分为1、1,但是乘积会变小,绳长为3同理,而绳长为4则可理解为两个绳长为2的情况,因此也不需要再分,而绳长为5以及之后就不一样了,例如5可分为2、3,乘积为6,因此递归终止条件是到4的。

递归过程为各段乘积 = Math.max(上次各段乘积, 当前绳长 * 递归(此段总长 - 当前绳长))(当前绳长是自变量,应在 1 ~ len - 1 之间变动),这样,每次递归都只算两段绳长的乘积,并在最深的递归中逐步向外,每次都可以选出局部最大的乘积,最终结果也自然是最大的乘积了。

实现

public class JZ67剪绳子 {
    /**
     * 解法一:递归
     */

    public int searchMax(int target) {
        if (target <= 4) return target;

        int res = 0;
        for (int i = 1; i < target; i++) {
            res = Math.max(res, i * searchMax(target - i));
        }

        return res;
    }

    public int cutRope1(int target) {
        if (target == 2) return 1;
        if (target == 3) return 2;
        return searchMax(target);
    }

    @Test
    public void test1() {
        System.out.println("cutRope1(8) = " + cutRope1(8));
        System.out.println("cutRope1(9) = " + cutRope1(9));
        System.out.println("cutRope1(12) = " + cutRope1(12));
        System.out.println("cutRope1(16) = " + cutRope1(16));
        System.out.println("cutRope1(2) = " + cutRope1(2));
        System.out.println("cutRope1(3) = " + cutRope1(3));
        System.out.println("cutRope1(4) = " + cutRope1(4));
    }
}

在这里插入图片描述

思路二:记忆化搜索

此处我的思路是加一个成员变量 int 数组 temp,用来存储 for 中寻找出的最大局部绳长乘积,从而在之后的递归中,如果已经有之前的递归计算过次段绳长的最大切割后的乘积,那么直接返回之前存储的结果即可,不必再重复递归计算,以此达到记忆化递归的目的,从而一定程度上提高了思路一的效率。

实现

/**
     * 解法二:记忆化搜索
     */
    int[] temp = null;

    public int searchMax2(int target) {
        if (target <= 4) return target;
        if (temp[target] != 0) return temp[target];

        int res = 0;
        for (int i = 1; i < target; i++) {
            res = Math.max(res, i * searchMax2(target - i));
        }
        temp[target] = res;
        return res;
    }

    public int cutRope2(int target) {
        if (target == 2) return 1;
        if (target == 3) return 2;
        temp = new int[target + 1];
        return searchMax2(target);
    }

    @Test
    public void test2() {
        System.out.println("cutRope2(8) = " + cutRope2(8));
        System.out.println("cutRope2(9) = " + cutRope2(9));
        System.out.println("cutRope2(12) = " + cutRope2(12));
        System.out.println("cutRope2(16) = " + cutRope2(16));
        System.out.println("cutRope2(2) = " + cutRope2(2));
        System.out.println("cutRope2(3) = " + cutRope2(3));
        System.out.println("cutRope2(4) = " + cutRope2(4));
    }

在这里插入图片描述
改为记忆化递归后,则可以提交成功了。
在这里插入图片描述

思路三:动态规划

如果说前两种递归方法是从外层逐步到内层,再从内层返回局部最优解到外层,那么动态规划则是一开始就从内层计算最优解,逐步向逼近结果的外层来进行,我们前面已经总结出绳长为1、2、3、4时的特殊性了,因此这里我们也是单独处理,而在5以及之后的绳长中,我们可以两层循环来解决,外层循环用于逐步增加总绳长,直至达到传入参数要求的绳长,而内存循环则进行当前绳长划分出最大乘积的计算。

实现

/**
     * 解法三:动态规划
     */
public int cutRope3(int target) {
        if (target == 2) return 1;
        if (target == 3) return 2;

        int[] f = new int[target + 1];
        for (int i = 1; i <= 4; i++) {
            f[i] = i;
        }
        for (int i = 5; i <= target; i++) {
            for (int j = 1; j < i; j++) {
                f[i] = Math.max(f[i], j * f[i - j]);
            }
        }
        return f[target];
    }

    @Test
    public void test3() {
        System.out.println("cutRope3(8) = " + cutRope3(8));
        System.out.println("cutRope3(9) = " + cutRope3(9));
        System.out.println("cutRope3(12) = " + cutRope3(12));
        System.out.println("cutRope3(16) = " + cutRope3(16));
        System.out.println("cutRope3(2) = " + cutRope3(2));
        System.out.println("cutRope3(3) = " + cutRope3(3));
        System.out.println("cutRope3(4) = " + cutRope2(4));
    }

在这里插入图片描述

在这里插入图片描述

JZ29 顺时针打印矩阵

(简单)

题目

描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵:
[[1,2,3,4],
[5,6,7,8],
[9,10,11,12],
[13,14,15,16]]
则依次打印出数字
[1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10]
数据范围:
0 <= matrix.length <= 100
0 <= matrix[i].length <= 100

示例1
输入:
[[1,2,3,4],[5,6,7,8],[9,10,11,12],[13,14,15,16]]
返回值:
[1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10]
示例2
输入:
[[1,2,3,1],[4,5,6,1],[4,5,6,1]]
返回值:
[1,2,3,1,1,1,6,5,4,4,5,6]

思路

这里引用一张官网题解的图(链接),由下图可知,我们需要逐步遍历的同时,每转一圈就缩小一次矩阵的上、右、下、左边界,循环这个过程即可,重要的是控制四个边界的动态变化,直接看代码比较容易理解,不再进行过多的文字赘述。
在这里插入图片描述

实现

public class JZ29顺时针打印矩阵 {
    public ArrayList<Integer> printMatrix(int[][] matrix) {
        ArrayList<Integer> list = new ArrayList<>();
        if (matrix == null || matrix.length == 0) return list;

        int n = matrix.length * matrix[0].length;
        int top = 0;
        int bottom = matrix.length - 1;
        int left = 0;
        int right = matrix[0].length - 1;

        while (top <= bottom && left <= right && list.size() < n) {
            for (int i = left; i <= right && list.size() < n; i++) {
                list.add(matrix[top][i]);
            }
            for (int j = top + 1; j < bottom && list.size() < n; j++) {
                list.add(matrix[j][right]);
            }
            for (int i = right; i >= left && list.size() < n; i--) {
                list.add(matrix[bottom][i]);
            }
            for (int j = bottom - 1; j > top && list.size() < n; j--) {
                list.add(matrix[j][left]);
            }
            top++;
            left++;
            right--;
            bottom--;
        }
        return list;
    }
}

在这里插入图片描述

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

超周到的程序员

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

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

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

打赏作者

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

抵扣说明:

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

余额充值