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