LeetCode作題筆記之動態規劃

LeetCode之動態規劃

時間有限只作了下面這幾道:70、33八、87七、9六、120、9五、647,後續會繼續更新java

70:爬樓梯

先來道簡單的練練手,一道經典的動態規劃題目算法

能夠採用動態規劃的備忘錄法,第n節樓梯的數目等於第n-1節和n-2節的和,由於第n節必定由n-1或n-2上去的。能夠不使用遞歸而使用簡單的for循環實現:數組

public int climbStairs(int n) {
    int[] ints = new int[n + 1];
    ints[1] = 1;
    if (n > 1) ints[2] = 2;
    for (int i = 3; i <= n; i++) {
        ints[i] = ints[i - 1] + ints[i - 2];
    }
    return ints[n];
}

這道題的難點在於想到第n節的數目等於n-1和n-2節的數目,對於沒有接觸過動態規劃的菜鳥來講仍是有點意思的。
PS:若是數目過大超出了int類型的最大值,可使用BigInteger類app

338:比特位計數

這道題也還行,對二進制數來講,一個偶數的最後一位必定是0,基於這個原理能夠得出下面的代碼:ui

for (int i = 0; i < arr.length; i++) {
    if (i % 2 == 0) {
        arr[i] = arr[i / 2];
    }else {
        arr[i] = arr[i /2] + 1;
    }
}

然而~~大佬們老是有強大的方法,我那if else用一行代碼就搞定了:arr[i] = arr[i >> 1] + (i & 1),右移倒也還好,這個i & 1用的就簡直了,佩服佩服。url

話說這和動態規劃沒什麼關係吧?可我是專門挑的動態規劃的題作的欸spa

877:石子游戲

感受這題出的不太好,由於我想看到的是你的算法比個人算法厲害,而不是你直接給我說先手必贏,由於我徹底沒有往這方面考慮過(估計大佬能想出來吧)。先手必贏的緣由是先手能夠決定拿全部的偶數堆仍是奇數堆,而對偶數堆和奇數堆兩個必定有一個是數目比較多的(總數爲奇數)
最開始我以爲比較一下先後的的大小直接挑大的就行,可是對於[3,2,10,4]這樣的排列先手會得7分然後手得12分,這顯然是錯誤的。
這一題的思路是以局部最優解得出總體最優解,典型的動態規劃.net

public static boolean stoneGame(int[] piles) {
    int length = piles.length;
    //results[i][j]存儲的是piles中第i個數到第j個數組成序列的最佳拿取方式下的得分
    int[][] results = new int[length][length];
    //當集合中只有一個堆的時候,拿的那我的直接得分
    for(int i=0;i<length;i++){
        results[i][i]=piles[i];
    }
    //當集合中有兩個數的時候,先選的人確定是拿較大數,分數爲max-min
    for(int i=0;i<length-1;i++){
        results[i][i+1]=Math.abs(piles[i]-piles[i+1]);
    }

    for(int i=length-3;i>=0;i--){
        for(int j=i+2;j<length;j++){
            results[i][j]=Math.max(piles[i]-results[i+1][j],piles[j]-results[i][j-1]);
        }
    }
    return results[0][length-1]>0;
}

對於二維數組results[i][j]
第一個for循環計算的是綠色的方塊,第二個for循環計算藍色的方塊,第三個填充右上的白色的方塊
877code

最後只要看右上角的值是否大於0便可。遞歸

96:不一樣的二叉搜索樹

這一道題的難點在於找出節點增長時二叉搜索樹種類如何增長。
首先,新增長的節點默認是最大的(否則沒什麼意義),對於n+1個節點的樹來講,有C(n+1) = C0 * Cn + C1 * C(n-1) + ... + C(n-1) * C1 + Cn * C0其中C0 = 1, C1 = 1解釋:對於第n+1個節點的樹來講,C0 * Cn對應以最小節點爲根節點,左節點爲0個,右節點爲n個;C1 * C(n-1)對應以倒數第二小的爲根節點,左邊1個節點,右邊n-1個節點......直到第n+1個節點爲子節點。這樣的話代碼就很容易寫出來了。

public static int numTrees(int n) {
    if(n<=1)return 1;
    int[] dp=new int[n+1];
    dp[0]=1;
    dp[1]=1;
    for(int i=2;i<=n;i++){
        for(int j=1;j<=i;j++){
            dp[i]+=dp[j-1]*dp[i-j];
        }
    }
    return dp[n];
}

話說這道題根本和動態規劃沒多大關係啊,徹底是在計算CatAlan數。。。

120:三角形最小路徑和

這一題和第877題比較像,能夠把三角形斜向左上,就像這樣:
120
解法也和877比較像:

public static int minimumTotal(List<List<Integer>> triangle) {
    int[][] arr = new int[triangle.size()][triangle.size()];
    List<Integer> lastList = triangle.get(triangle.size() - 1);
    for (int i = 0; i < lastList.size(); i++) {
        arr[arr.length - 1 - i][i] = lastList.get(i);
    }

    for (int n = triangle.size() -2; n >= 0; n--) {
        List<Integer> list = triangle.get(n);
        for (int i = 0; i <= n; i++) {
            int j = n-i;
            arr[i][j] = list.get(j) + Math.min(arr[i + 1][j], arr[i][j + 1]);
        }
    }
    return arr[0][0];
}

而後再新建個同樣的二維數組記錄由該點到底部所需的最小和。

95:不一樣的二叉搜索樹2

這一題是第96題的加強題,不只要求求出多少個,還要求出每一個什麼樣子。這一題其實最開始沒解出來,看了答案以後才動了怎麼作,原來是經過暴力遞歸(貌似也只能這麼作了),題目作多了以後下意識地以爲確定不是暴力解,總想着找一種更省力的方法,然而有時候就是會陷入其中。

private static List<TreeNode> helper(int start, int end) {
    List<TreeNode> res = new ArrayList<>();
    if (start > end) {
        res.add(null);
        return res;
    }
    for (int val = start; val <= end; val++) {
        List<TreeNode> left = helper(start, val - 1);
        List<TreeNode> right = helper(val + 1, end);
        for (TreeNode l : left) {
            for (TreeNode r : right) {
                TreeNode root = new TreeNode(val);
                root.left = l;
                root.right = r;
                res.add(root);
            }
        }
    }
    return res;
}

647:迴文子串

最開始看到這一題時想到了第五題:最長迴文子串,而後想到了Manacher(馬拉車)算法,馬拉車的數組P[]既然能夠求出最長迴文子串,那麼就能求出迴文子串的個數:

//此方法對字符串進行加工
public static String preProcess(String s) {
    StringBuilder sb = new StringBuilder(s.length()*2+3);
    sb.append("^");
    for (int i = 0; i < s.length(); i++) {
        sb.append("#").append(s.charAt(i));
    }
    sb.append("#$");
    return sb.toString();
}

// 馬拉車算法
public static int countSubstrings(String s) {
    String T = preProcess(s);
//        System.out.println(Arrays.toString(T.toCharArray()));
    int n = T.length();
    int[] P = new int[n];
    int C = 0, R = 0;
    for (int i = 1; i < n - 1; i++) {
        int i_mirror = 2 * C - i;
        if (R > i) {
            P[i] = Math.min(R - i, P[i_mirror]);// 防止超出 R
        } else {
            P[i] = 0;// 等於 R 的狀況
        }

        // 碰到以前講的三種狀況時候,須要繼續擴展
        while (T.charAt(i + 1 + P[i]) == T.charAt(i - 1 - P[i])) {
            P[i]++;
        }

        // 判斷是否須要更新 R
        if (i + P[i] > R) {
            C = i;
            R = i + P[i];
        }
    }
    //此時P[]數組已經求出,根據該數組能夠得到迴文子串的個數
    int sum = 0;
    for (int i = 1; i < P.length - 1; i++) {
        if (i % 2 == 0) {
            sum += (P[i] + 1) / 2;
        } else {
            sum += P[i] / 2;
        }
    }
//        System.out.println(Arrays.toString(P));
    return sum;
}

結果一看評論全都是用的兩邊掃描……我就納悶了,作第五題的時候都在馬拉車,就我兩邊掃描,結果到這題了都是兩邊掃描就我馬拉車,難道我思惟比較奇葩? 貼個中心掃描的答案:

public int countSubstrings2(String s) {
    for (int i=0; i < s.length(); i++){
        count(s, i, i);//迴文串長度爲奇數
        count(s, i, i+1);//迴文串長度爲偶數
    }
    return num;
}

public void count(String s, int start, int end){
    while(start >= 0 && end < s.length() && s.charAt(start) == s.charAt(end)){
        num++;
        start--;
        end++;
    }
}

┓(;´_`)┏

相關文章
相關標籤/搜索