劍指offer(三)

前面咱們已經刷過《劍指offer》的一些題目了
劍指offer(一)
劍指offer(二)
今天的題目主要使用一些高級的算法,好比動態規劃、回溯法來解決主要有如下知識點:算法

  • 矩陣中的路徑(回溯法)
  • 剪繩子(動態規劃和貪心算法)
  • 連續子數組的最大和)

矩陣中的路徑(回溯法)

請設計一個函數,用來判斷在一個矩陣中是否存在一條包含某字符串全部字符的路徑。路徑能夠從矩陣中任意一格開始,每一步能夠在矩陣中間向左、右、上、下移動一格。若是一條路徑通過了矩陣的某一格,那麼該路徑不能再次進入該格子。
例如:在下面的3*4的矩陣中包含一條字符串」bcced」的路徑。但矩陣中不包含字符串「abcb」的路徑,由於字符串的第一個字符b佔據了矩陣中的第一行第二格子以後,路徑不能再次進入這個格子。
a b c e
s f c s
a d e e

解題思路

首先,在矩陣中任選一個格子做爲路徑的起點。假設矩陣中某個格子的字符爲c,而且這個格子將對應路徑上的第i個字符。若是路徑上的第i個字符正好是c,那麼往相鄰的格子尋找路徑上的第i+1個字符。除在矩陣邊界上的格子以外,其餘格子都有4個相鄰的格子。
因爲路徑不能重複進入矩陣的格子,還須要定義和字符矩陣大小同樣的布爾值矩陣,用來標識路徑是否已經進入每一個格子。
當矩陣中座標爲(row,col)的格子和路徑字符串中下標爲pathLength的字符同樣時,從4個相鄰的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路徑字符串中下標爲pathLength+1的字符。
若是4個相鄰的格子都沒有匹配字符串中下標爲pathLength+1的字符,代表當前路徑字符串中下標爲pathLength的字符在矩陣中的定位不正確,咱們須要回到前一個字符(pathLength-1),而後從新定位。
一直重複這個過程,直到路徑字符串上全部字符都在矩陣中找到合適的位置。segmentfault

代碼實現

public class Test {

    /**
     * @param matrix 輸入矩陣
     * @param rows   矩陣行數
     * @param cols   矩陣列數
     * @param str    要搜索的字符串
     * @return 是否找到 true是,false否
     */ 

    public static boolean hasPath(char[] matrix, int rows, int cols, char[] str) {

        //輸入判斷
        if (matrix == null || rows < 1 || cols < 1 || str == null)
            return false;

        //visited:訪問標記數組——用來標識路徑是否已經進入過格子 false表示沒有
        boolean[] visited = new boolean[rows * cols];
        for (int i = 0; i < visited.length; i++) {
            visited[i] = false;
        }

        //pathLength:記錄字符串下標
        int pathLength = 0;

        //開始檢索
        for (int row = 0; row < rows; row++) {
            for (int col = 0; col < cols; col++) {
                if (hasPathCore(matrix, rows, cols, row, col, str, pathLength, visited)) {
                    return true;
                }

            }
        }
        return false;
    }
    
    /**
     * 回溯搜索算法
     * @param matrix     輸入矩陣
     * @param rows       矩陣行數
     * @param cols       矩陣列數
     * @param str        要搜索的字符串
     * @param visited    訪問標記數組
     * @param row        當前處理的行號
     * @param col        當前處理的列號
     * @param pathLength 已經處理的str中字符個數
     * @return 是否找到 true是,false否
     */ 
    public static boolean hasPathCore(char[] matrix, int rows, int cols, int row, int col,
                                      char[] str, int pathLength, boolean[] visited) {

        //匹配成功
        if (pathLength == str.length)
            return true;

        boolean hasPath = false;

        //判斷位置是否合法
        if (row >= 0 && row < rows && col >= 0 && col < cols
                && matrix[row * cols + col] == str[pathLength] && !visited[row * cols + col]) {

            pathLength++;
            visited[row * cols + col] = true;

            //按左上右下回溯
            hasPath = hasPathCore(matrix, rows, cols, row, col - 1, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row - 1, col, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row, col + 1, str, pathLength, visited)
                    || hasPathCore(matrix, rows, cols, row + 1, col, str, pathLength, visited);

            //上下左右都沒法匹配到字符則從新回到前一個字符
            if (!hasPath) {
                pathLength--;
                visited[row * cols + col] = false;
            }
        }
        return hasPath;
    }

    public static void main(String[] args) {
        System.out.println(
                hasPath("ABCESFCSADEE".toCharArray(), 3, 4,"ABCCED".toCharArray())
        );
    }
}
輸出:
true

剪繩子(動態規劃和貪心算法)

給你一根長度爲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。

解題思路

  • 動態規劃法
首先定義函數f(n)爲把長度爲n的繩子剪成若干段後各段長度乘積的最大值。在剪第一刀時,咱們有n-1種選擇,也就是說第一段繩子的可能長度分別爲1,2,3.....,n-1。所以 f(n)=max(f(i)*f(n-i)),其中0<i<n。當繩子的長度爲2的時候,只能剪成長度爲1的兩段,因此f(2) = 1,當n = 3時,容易得出f(3) = 2。
  • 貪心算法
根據數學計算,當n>=5時,2(n-2)>n,3(n-3)>n,這就是說, 將繩子剪成2和(n-2)或者剪成3和(n-3)時,乘積大於不剪的乘積,所以須要把繩子剪成2或者3。而且3(n-3)>=2(n-2),也就是說,當n>=5時,應該剪儘可能多的3,可使最後的乘積最大。
對於長度是n的繩子,咱們能夠剪出n/3個3,剩餘長度是1或者2,若是餘數是1,就能夠把1和最後一個3合併成4,那麼4剪出兩個2獲得的乘積是4,比1*3大,所以這種狀況下,須要將3的個數減小1,變成兩個2;若是餘數是2,那麼無需作修改。
能夠獲得最大的乘積是: 3^timesOf3 * 2^timesOf

代碼實現

  • 動態規劃法
public class Test14 {

   /**
    * 動態規劃法
    * @param length 繩子長度
    * @return   乘積最大值
    */
    public static int maxAfterCutRope1(int length) {

        if (length < 2) return 0;
        if (length == 2) return 1;
        if (length == 3) return 2;

        //products[i]表示長度爲i的繩子的乘積最大值
        //products[一、二、3]是特例
        int[] products = new int[length + 1];
        products[0] = 0;
        products[1] = 1;
        products[2] = 2;
        products[3] = 3;

        int max = 0;
        // 爲何動態規劃從4開始?
        // 由於當length<=3時,繩子不剪的長度>=剪後長度乘積最大值
        // 當length>=4時,繩子不剪的長度<=剪後長度乘積最大值
        for (int i = 4; i <= length; i++) {
            max = 0;
            //求得全部的f(j)*f(i-j)並比較它們獲得乘積最大值
            for(int j=1;j<=i/2;j++){
                int product=products[j]*products[i-j];
                if (product>max){
                    max=product;
                }
                products[i]=max;
            }
        }
        max=products[length];
        return max;
    }

我我的以爲動態規劃在筆試中常常遇到,Leetcode上有一道求子數組的最大乘積和這道很相似,對這道題來講最重要的是理解爲何動態規劃要從4開始,註釋已經寫得很清楚了就很少說了!數組

  • 貪心算法
/**
    * 貪心算法
    * @param length 繩子長度
    * @return   乘積最大值
    */
    public static int maxProductAfterCutRope2(int length){

        if (length < 2) return 0;
        if (length == 2) return 1;
        if (length == 3) return 2;

        //長度爲3的繩子段
        int timesOf3=length/3;

        //當剩下繩子長度爲4時,再也不去剪長度爲3的繩子段
        //更好的方法是把繩子剪成長度爲2的兩段,由於2*2>1*3
        if (length-timesOf3*3==1)
            timesOf3-=1;

        int timesOf2=(length-timesOf3*3)/2;

        return (int) (Math.pow(3, timesOf3)*Math.pow(2, timesOf2));
    }

    public static void main(String[] args) {

        System.out.println(Test14.maxAfterCutRope1(11));
        System.out.println(Test14.maxProductAfterCutRope2(11));
    }
}

連續子數組的最大和

輸入一個整型數組,數組裏有正數也有負數。數組中一個或連續的多個整數組成一個子數組。
求全部子數組的和的最大值。要求時間複雜度爲O(n)。
例如輸入的數組爲{1, -2, 3, 10, -4, 7, 2, -5},和最大的子數組爲{3, 10, -4, 7, 2},所以輸出爲該子數組的和18 。

解題思路

解法一:分析數組的規律
初始化和爲0。第一步加上第一個數字1, 此時和爲1。接下來第二步加上數字-2,和就變成了-1。第三步刷上數字3。
咱們注意到因爲此前累計的和是-1,小於0,那若是用-1加上3 ,獲得的和是2 ,比3自己還小。
也就是說從第一個數字開始的子數組的和會小於從第三個數字開始的子數組的和。
所以咱們不用考慮從第一個數字開始的子數組,以前累計的和也被拋棄。函數

解法二: 應用動態歸劃法
若是用函數f(i)表示以第i個數字結尾的子數組的最大和
當i = 0或f(i-1) <= 0時,f(i) = a[i];
當i > 0且f(i-1) > 0時,f(i) = f(i-1) + a[i];
這個公式的意義:當以第 i-1 個數字結尾的子數組中全部數字的和小於0時,若是把這個負數與第 i 個數累加,獲得的結果比第 i 個數字自己還要小,因此這種狀況下以第i個數字結尾的子數組就是第 i 個數字自己。若是以第 i-1 個數字結尾的子數組中全部數字的和大於0 ,
與第 i 個數字累加就獲得以第 i 個數字結尾的子數組中全部數字的和。設計

代碼實現

解法一:分析數組的規律code

public static int findGreatestSumOfSubArray1(int[] a) {

    if (a == null || a.length <= 0)
        throw new RuntimeException("不合法的輸入");
        
     int curSum = 0;
    int greatestSum = 0x80000000;//int最小值
    for (int i = 0; i < a.length; i++) {
        //若是前面累加和<0,拋棄累加和從當前數字從新累加
        if (curSum <= 0) {
            curSum = a[i];
        } else {
            curSum += a[i];
        }
        //找出最大值
        if (curSum > greatestSum) {
            greatestSum = curSum;
        }
    }
    return greatestSum;
}

解法二: 應用動態歸劃法字符串

public static int findGreatestSumOfSubArray2(int[] a) {

    if (a == null || a.length == 0) {
        throw new RuntimeException("不合法的輸入");
    }

    int sum = a[0];//局部最大值
    int max = a[0];//全局最大值
    
    for (int i = 1; i < a.length; i++) {
        sum = Math.max(sum + a[i], a[i]);
        if (sum >= max) {
            max = sum;
        }
    }
    return max;
}
相關文章
相關標籤/搜索