1. 程式人生 > >數組合並問題(阿里筆試題,動態規劃)

數組合並問題(阿里筆試題,動態規劃)

咋一看到本題,首先想到的是窮舉法,後來跑測試資料時,程式執行效率很慢,無法滿足本題要求。後來想到本題是屬於典型的動態規劃問題,找出計算公式,將大問題轉化為小問題求解,本題的邊界情況比較多,找出全部的邊界情況是難點。

問題分析:

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

本題求解可以用窮舉法,遞迴法,遞推法,其中,窮舉法效率最低,運算時間長,不能滿足本題要求,不建議採用;遞迴法思路簡單,公式逆推,執行效率高於窮舉法,但存在重複計算問題,適合本題解答;最後要說的是正向遞推法,可以儲存中間計算結果,避免重複計算,執行效率最高,但編碼不太好寫。