數組合並問題(阿里筆試題,動態規劃)
咋一看到本題,首先想到的是窮舉法,後來跑測試資料時,程式執行效率很慢,無法滿足本題要求。後來想到本題是屬於典型的動態規劃問題,找出計算公式,將大問題轉化為小問題求解,本題的邊界情況比較多,找出全部的邊界情況是難點。
問題分析:
(1)求出公式:
當陣列長度n=1時,f(a1,b1) = a1*b1,通過對n=2,n=3舉例分析,發現存在以下求兩個數組合並後相鄰兩項的乘積累計最小值公式:i和j分別表示兩個陣列的下標,a1和它相鄰的a2結合,a1和它對應的b1結合,或者b1和b2結合,其他情況計算的結果包含在這三種情況中,因此可得以下公式。
(2)分析邊界情況:
i和j表示陣列的下標,從大到小開始變化。
1.當i=0並且j=0的時候,就是說陣列長度為1時,
2. 當i=0時,此時對應的情況是a陣列只有一個元素,b陣列有多個元素,此時對應的公式中只有2種情況
3. 當j=0時,此時對應的情況是b陣列只有一個元素,a陣列有多個元素,此時對應的公式中只有2種情況
4. 當i<0時,說明a陣列已經結束,只剩下b陣列需要合併,只需累計求出b數字下標從0到j的元素接入整合後的陣列。
5. 當j<0時,說明b陣列已經結束,只剩下a陣列需要合併,只需累計求出a數字下標從0到i的元素接入整合後的陣列。
分析到這裡,遞迴法求解的思路已經出來了。java程式碼實現如下:
import org.apache.commons.lang3.ArrayUtils; import java.util.Scanner; /** * Created by *** on 2018/3/29. * 遞迴求解 */ public class aliTest2 { public int FmaxArray(int[] array1 , int[] array2, int i,int j){ int min = 0; if(i==0&&j==0){ min +=array1[0]*array2[0]; return min; } if(i<0){ for(int k=0;k<=j;k+=2){ min+=array2[k]*array2[k+1]; } return min; } if(j<0){ for(int k=0;k<=i;k+=2){ min+=array1[k]*array1[k+1]; } return min; } if(i==0){ int f2 = array1[i]*array2[j]+FmaxArray(array1,array2,i-1,j-1); int f3 = array2[j-1]*array2[j]+FmaxArray(array1,array2,i,j-2); min = Math.min(f2,f3); return min; } if(j==0){ int f1 = array1[i-1]*array1[i]+FmaxArray(array1,array2,i-2,j); int f2 = array1[i]*array2[j]+FmaxArray(array1,array2,i-1,j-1); min = Math.min(f1,f2); return min; } int f1 = array1[i-1]*array1[i]+FmaxArray(array1,array2,i-2,j); int f2 = array1[i]*array2[j]+FmaxArray(array1,array2,i-1,j-1); int f3 = array2[j-1]*array2[j]+FmaxArray(array1,array2,i,j-2); min =Math.min(f3,Math.min(f1,f2)); return min; } public static void main(String args[]){ aliTest2 test2 = new aliTest2(); Scanner scanner = new Scanner(System.in); int i=0; int j=0; System.out.println("請輸入陣列的長度(兩個陣列的長度相同):"); int length = scanner.nextInt(); int[] array1=new int[length]; int[] array2=new int[length]; System.out.println("請輸入2個數組元素:"); while(scanner.hasNext()){ array1[i++] = scanner.nextInt(); if(i==length) break; } while (scanner.hasNext()){ array2[j++] = scanner.nextInt(); if(j==length) break; } int min = test2.FmaxArray(array1,array2,array1.length-1,array2.length-1); System.out.println(min); return; } }
當然遞迴法求解存在中間計算結果重複計算問題,那麼我們能不能採用正向推導法求解,將中間的計算結果儲存在二維陣列中,因為這裡有兩個變數i和j,因此需要設定一個二維陣列。遞推法java實現如下:
/** * Created by *** on 2018/3/29. * 遞推求解 */ import java.io.*; import java.util.*; import java.text.*; import java.math.*; import java.util.regex.*; public class aliTest3 { static long min(int[] from, int[] to) { int fl = from.length; int tl = to.length; long[][] f = new long[fl + 1][tl + 1]; f[1][1] = from[0] * to[0]; f[0][0] = 0; //i表示from陣列的下標,j表示to陣列的下標。 //當只有from陣列有元素的時候,因為是相鄰兩項的乘積累加,所以i從2開始變化,每次加2。 for (int i = 2; i < fl + 1; i += 2) { f[i][0] = from[i - 1] * from[i - 2] + f[i - 2][0]; } //當只有to陣列有元素的時候,與只有from陣列類似。 for (int j = 2; j < tl + 1; j += 2) { f[0][j] = to[j - 1] * to[j - 2] + f[0][j - 2]; } //注意這裡的i 和j 代表的是不是下標,是陣列的長度,減一之後才是下標。 for (int i = 1; i <= fl; i++) { for (int j = 1; j <= tl; j++) { long v1 = Long.MAX_VALUE; if (i - 2 >= 0) v1 = from[i - 1] * from[i - 2] + f[i - 2][j]; long v2 = from[i - 1] * to[j - 1] + f[i - 1][j - 1]; long v3 = Long.MAX_VALUE; if (j - 2 >= 0) v3 = to[j - 1] * to[j - 2] + f[i][j - 2]; f[i][j] = Math.min(Math.min(v1, v2), v3); } } return f[fl][tl]; } public static void main(String[] args) { randomTest(); } private static void randomTest() { Random random = new Random(); for (int i = 0; i < 10; i++) { int n = random.nextInt(1000) + 1; int[] array1 = new int[n]; int[] array2 = new int[n]; for (int j = 0; j < n; j++) { array1[j] = random.nextInt(10000) + 1; array2[j] = random.nextInt(10000) + 1; } System.out.println("array1 = " + Arrays.toString(array1)); System.out.println("array2 = " + Arrays.toString(array2)); System.out.println( min(array1, array2)); } } }
窮舉法雖然不高效,但也是最易寫出來的解法,附上程式碼:
import org.apache.commons.lang3.ArrayUtils;
/**
* Created by *** on 2018/3/28.
*/
public class aliTest {
int i=0;
int j=0;
static int[] array1 = {1,2,3};
static int[] array2 = {4,5,6};
static int length1 = array1.length;
static int length2 = array2.length;
int[] res2;
int[] res3;
int[] result = null;
int max = Integer.MAX_VALUE;
public int SumArray(int[] a,int length){
int sum =0;
for(int k=0;k<length;k+=2){
sum+=a[k]*a[k+1];
}
return sum;
}
public int linkArray(int i, int j, int[] res){
if(i==length1){
result = ArrayUtils.addAll(res,ArrayUtils.subarray(array2,j,length2));
for(int k =0;k<result.length;k++){
System.out.print(result[k]+" ");
}
System.out.println(" ");
int sum = SumArray(result,result.length);
if(max>sum){
max = sum;
}
return max;
}
if (j==length2){
result = ArrayUtils.addAll(res,ArrayUtils.subarray(array1,i,length1));
for(int k =0;k<result.length;k++){
System.out.print(result[k]+" ");
}
System.out.println(" ");
int sum = SumArray(result,result.length);
if(max>sum){
max = sum;
}
return max;
}
res2 = ArrayUtils.addAll(res, array1[i]);
linkArray(i+1,j,res2);
res3 = ArrayUtils.addAll(res, array2[j]);
linkArray(i,j+1,res3);
return max;
}
public static void main(String[] args){
int[] array=null;
int m =0;
aliTest a = new aliTest();
System.out.println("合併後的陣列形式有以下幾種:");
m = a.linkArray(0,0,array);
System.out.println("數組合並後相鄰兩項的乘積和最小為:"+m);
return;
}
}
本題求解可以用窮舉法,遞迴法,遞推法,其中,窮舉法效率最低,運算時間長,不能滿足本題要求,不建議採用;遞迴法思路簡單,公式逆推,執行效率高於窮舉法,但存在重複計算問題,適合本題解答;最後要說的是正向遞推法,可以儲存中間計算結果,避免重複計算,執行效率最高,但編碼不太好寫。