題目來源:力扣
題目介紹
給你一根長度爲 n 的繩子,請把繩子剪成整數長度的 m 段(m、n都是整數,n>1並且m>1),每段繩子的長度記爲 k[0],k[1]…k[m] 。請問 k[0]k[1]…*k[m] 可能的最大乘積是多少?例如,當繩子的長度是8時,我們把它剪成長度分別爲2、3、3的三段,此時得到的最大乘積是18。
========================================================
示例 1:
輸入: 2
輸出: 1
解釋: 2 = 1 + 1, 1 × 1 = 1
示例 2:
輸入: 10
輸出: 36
解釋: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36
提示:
2 <= n <= 58
=========================================================
審題:
對於這樣一道最優化問題,我們考慮使用動態規劃方法解決.
我們使用兩個狀態變量,分別爲當前繩子長度,以及裁剪段數.我們使用二維數組S[i][j]表示長度爲i的繩子剪裁爲j段時,每段長度乘積的最大值.對於每一個子問題,例如求解狀態(i, j)下的最優值,我們有多少種選擇?我們首先需要選擇的是一個切分點,將當前繩子切分爲左右兩段,其後我們需要確定左側需要切分爲多少段.如果我們選擇將繩子切分爲長度爲k和i-k的兩段,並且左側切分r段,則當前選擇下的長度爲i的繩子切分爲j段的最大乘積等於S[k][r]*S[i-k][j-r].我們的選擇規模爲, 判斷每一種選擇,我們可以做出最優選擇.
class Solution {
public int cuttingRope(int n) {
int[][] S = new int[n+1][n+1];
for(int i = 1; i <= n; i++)
S[i][1] = i;
for(int m = 2; m <= n; m++){//裁剪m段
for(int l = m; l <=n; l++){//長度爲l
S[l][m] = Integer.MIN_VALUE;
for(int left = 1; left < l; left++){//裁剪後,左側的長度
//左側裁剪r段, 右側裁剪m-r段
for(int r = 1; r < m; r++){
S[l][m] = Math.max(S[l][m], S[left][r] * S[l-left][m-r]);
}
}
}
}
int max = S[n][2];
for(int i = 2; i <= n; i++)
max = Math.max(max, S[n][i]);
return max;
}
}
複雜度分析
不難發現,上述算法的時間複雜度爲, 空間複雜度爲.
說實話,上面我的設計一點也不優雅,而且算法時間複雜度很高.沒辦法,簡潔的辦法似乎總是很難第一時間想到.藍瘦香菇.
我們可以僅維護一個狀態變量即當前繩子長度,我們使用S[i]表示長度爲i是的最優切分方案下最大乘積.在每一步,我們選擇不會被繼續分割的位置對繩子進行切分.而進行切分後,我們可以終止切分.因爲我們已經將繩子分成了兩段,符合題目要求,也可以進一步選擇將右段繼續切分.
對於長度爲i的繩子,我們我們在k處進行切割,則如果不繼續進行切割,當前乘積爲k * (i-k), 如果我們繼續進行切分,則乘積爲k * S[i-k], 我們可以選擇最優方案.
class Solution {
public int cuttingRope(int n) {
int[] S = new int[n+1];
//S[i]表示長度爲i最佳方案
S[1] = 1;
S[2] = 1;
for(int i = 3; i <= n; i++){
//對於每一步,我們可以選擇切分2段或更多段
int max = Integer.MIN_VALUE;
for(int j = 1; j < i; j++){
max = Math.max(max, j * S[i-j]); //繼續切分
max = Math.max(max, j * (i-j)); //不切分
}
S[i] = max;
}
return S[n];
}
}
複雜度分析
上述算法的時間複雜度爲,空間複雜度爲.