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循環計算藍色的方塊,第三個填充右上的白色的方塊
code
最後只要看右上角的值是否大於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題比較像,能夠把三角形斜向左上,就像這樣:
解法也和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++; } }
┓(;´_`)┏