问题描述
设I是一个n位十进制整数。如果将I划分为k段,则可得到k个整数。这k个整数的乘积称为I的一个k乘积。试设计一个算法,对于给定的I和k,求出I的最大k乘积。
例如十进制整数 1234 划分为 3 段可有如下情形:
1 × 2 × 34 = 68
1 × 23 × 4 = 92
12 × 3 × 4 = 144
编程任务
对于给定的I 和k,编程计算I 的最大k 乘积。
数据输入
输入的第1 行中有2个正整数n和k。
正整数n是序列的长度;正整数k是分割的段数。接下来的一行中是一个n位十进制整数。(n<=10)
结果输出
input.txt | output.txt |
---|---|
4 3 | 144 |
1234 |
思路分析
1. 引导
假如按照上述input.txt文件所示,我要求出4位整数1234划分成3段的最大乘积。
那么根据划分子问题的原则:
上述最大乘积是不是等价于:
max {
3位整数123划分成2段的最大乘积 x 4 ,
2位整数12划分成2段的最大乘积 x 34,
1位整数1划分成2段的最大乘积 x 234
}
此时,我们再取第一种情况 3位整数123划分成2段的最大乘积 来进行子问题的划分:
3位整数123划分成2段的最大乘积 等价于:
max {
2位整数12划分成1段的最大乘积 x 3,
1位整数1划分成1段的最大乘积 x 23
}
划分到这里我们就已经能够直接求解了,因为划分成1段就是最基本的子问题,此时的max = 36
2. 选择正确的数据结构
从上面可以看出,我们需要把整数1234的任意连续子段的数值给求出来,例如12,23,34,234
这首先要求我们把每位数字分隔到一个数组中。
因此有了下面的两个函数:
// 把数值分隔放入数组中,num表示数字,n表示位数
public static void putNumberIntoArray(int number[], int num, int n ) {
int temp = 0;
for(int i = n; i > 0; i--) {
temp = num % 10;
num = num / 10;
number[i] = temp;
}
}
// 求一个数中,从 第m位 到 第n位的数值大小 (1 <= m <= n <= length)
public static int getSubsequenceNumber(int m, int n, int number[]) {
int subsequenceNumber = 0;
for(int i = m ; i <= n ; i++) {
subsequenceNumber = subsequenceNumber * 10 + number[i];
}
return subsequenceNumber;
}
3. 定义dp数组
定义 dp[ k ] [ i ]
表示将文件中给定的整数num的前 i 位划分为 k 段的最大k乘积。
k \ i | 1 | 2 | 3 | 4 |
---|---|---|---|---|
1 | 1 | 12 | 123 | 1234 |
2 | 0 | 2 | 36 | 492 |
3 | 0 | 0 | 6 | 144 |
4. 状态转移
- 首先用一个二重循环遍历整个dp数组,外层是 i,内层是k
- 如果内层大于外层,说明分段数比位数还大,这是不可能的,直接dp[ j ][ i ] = 0即可
- 如果内层小于外层,说明符合条件,此时再来一层循环,从当前的列 i 开始,一直到1,具体的过程见 1. 引导 部分
- 值得注意的是,这里的dp数组的下标要格外注意,分别是j - 1 和 w - 1,其中 j -1代表去掉后面的数字之后,前面的自然是分为k-1段;w - 1代表后面数字的前面数字部分的位数。
完整代码
package algorithm;
import java.io.*;
// 最大k乘积问题 动态规划
public class experiment02 {
public static void main(String[] args) {
int []number = new int[10]; // 存放数字的数组
int [][]dp = new int[10][10]; // 存放计算结果的dp二维数组
String [][]arr = new String[2][2]; // 保存输入文件中数据的二维数组
String srcPath = "Input.txt";
String targetPath = "Output.txt";
try {
FileReader fr = new FileReader(srcPath);
BufferedReader bfr = new BufferedReader(fr);
String c = null;
int cnt = 0;
while ((c = bfr.readLine()) != null) {
arr[cnt++] = c.split(" ");
}
fr.close();
bfr.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
int num = Integer.parseInt(arr[1][0]); // 文件中的数字
int n = Integer.parseInt((arr[0][0])); // 数字的位数
int k = Integer.parseInt(arr[0][1]); // 分割成k段
// 把文件中的数字分开 放入数组中
putNumberIntoArray(number, num, n);
// base case
if(k == 1) {
System.out.println(num);
}
// 初始化
for(int i = 1 ; i <= n ; i++) {
dp[1][i] = getSubsequenceNumber(1,i,number);
}
// for(int i = 1 ; i <= n ; i++) System.out.println(dp[1][i]);
// 状态转移
for(int i = 1 ; i <= n ; i++) { // i表示列 意思是 n位整数的前i位表示的数字 比如123 ,i = 2.那就是12
for(int j = 2 ; j <= k ; j++) { // j表示行
if(j > i) { // 把n位数分成 >n 的片段,是不可能的情况,break
dp[j][i] = 0;
break;
} else {
int max = 0;
int temp = 0;
for(int w = i ; w > 0 ; w--) {
temp = dp[j-1][w-1] * getSubsequenceNumber(w , i, number);
max = Math.max(temp,max);
// System.out.println(temp);
}
dp[j][i] = max;
}
}
}
try {
FileWriter fw = new FileWriter(targetPath);
BufferedWriter bfw = new BufferedWriter(fw);
bfw.write(Double.toString(dp[k][n])); // int 写不进去 double不适用 因此只能转成字符串才能写进去
bfw.flush();
bfw.close();
fw.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
for(int i = 1 ; i <= k ; i++) {
for( int j = 1 ; j <= n ; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
System.out.println("----------------");
System.out.println("将" + num + "分成" + k + "段,最大k乘积为:" + dp[k][n]);
}
// 把数值分隔放入数组中,num表示数字,n表示位数
public static void putNumberIntoArray(int number[], int num, int n ) {
int temp = 0;
for(int i = n; i > 0; i--) {
temp = num % 10;
num = num / 10;
number[i] = temp;
}
}
// 求一个数中,从 第m位 到 第n位的数值大小 (1 <= m <= n <= length)
public static int getSubsequenceNumber(int m, int n, int number[]) {
int subsequenceNumber = 0;
for(int i = m ; i <= n ; i++) {
subsequenceNumber = subsequenceNumber * 10 + number[i];
}
return subsequenceNumber;
}
}