如下題號均爲LeetCode題號,便於查看原題。node
10. Regular Expression Matching算法
題意:實現字符串的正則匹配,包含'.' 和 '*'。'.' 匹配任意一個字符,"*" 匹配 '*' 以前的0個或多個字符。express
example:數組
isMatch("aa","a") → false isMatch("aa","aa") → true isMatch("aaa","aa") → false isMatch("aa", "a*") → true isMatch("aa", ".*") → true isMatch("ab", ".*") → true isMatch("aab", "c*a*b") → true
思路:輸入字符串 s[0...m] 和 p[0...n]curl
f[i][j] 表示 s[0..i-1] 和 p[0..j-1] 匹配,咱們須要判斷 s 和 p 是否匹配,就是求 f[m][n] 的值是否爲true,因此要日後更新 f[i][j] 的值。ide
更新思路以下:函數
一、if p[j-1]!='*', f[i][j] = f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='.')優化
二、if p[j-1]=='*', 看 '*' 匹配多少個字符,即匹配多少個p[j-2]。ui
若是 '*' 匹配0個字符,此時,p[0...j-1]==p[0...j-3],f[i][j]=f[i][j-2];url
若是 '*' 匹配1個字符,此時,p[0...j-1]==p[0...j-2],f[i][j]=f[i][j-1];
若是 '*' 匹配多個字符,此時,p[0...j-1]=={ p[0: j-2], p[j-2], ... , p[j-2] },f[i][j]=(s[i-1]==p[j-2] || p[j-2]=='.') & f[i-1][j]
public boolean isMatch(String s, String p) { int m = s.length(); int n = p.length(); boolean[][] f = new boolean[m+1][n+1]; f[0][0] = true; for (int i = 0; i <= m; i++) { for (int j = 1; j <= n; j++) { if(p.charAt(j-1)!='*') { f[i][j] = i>0 && f[i-1][j-1] && (s.charAt(i-1)==p.charAt(j-1) || p.charAt(j-1)=='.'); } else { f[i][j] = (j>1&&f[i][j-2]) || (i>0&&(s.charAt(i-1)==p.charAt(j-2)||p.charAt(j-2)=='.')&&f[i-1][j]); } } } return f[m][n]; }
題意:Given a string containing just the characters '('
and ')'
, find the length of the longest valid (well-formed) parentheses substring.
example:
"(()" --> 2
")()())" --> 4
思路:dp[i] 表示在 i 處的匹配的最長子串的長度,維護一個全局max變量,記錄到 i 處的最長長度
一、若是s[i]=='(',dp[i]=0
二、若是s[i]==')',若是s[i-1]=='(',dp[i]=dp[i-2]+2;若是s[i-1]==')' 而且 s[i-dp[i-1]-1]=='(',dp[i]=dp[i-1]+2+dp[i-dp[i-1]-2]
def longestValidParentheses(self, s): """ :type s: str :rtype: int """ cmax = 0 dp = [0 for x in range(len(s))] for i in range(1, len(s)): if s[i] == ")": if s[i - 1] == "(": if i - 2 >= 0: dp[i] = dp[i - 2] + 2 else: dp[i] = 2 cmax = max(cmax, dp[i]) else: if i - dp[i - 1] - 1 >= 0 and s[i - dp[i - 1] - 1] == "(": if i - dp[i - 1] - 2 >= 0: dp[i] = dp[i - 1] + 2 + dp[i - dp[i - 1] - 2] else: dp[i] = dp[i - 1] + 2 cmax = max(cmax, dp[i]) return cmax
44. Wildcard Matching
思路:解法和10.很是像,輸入字符串 s[0...m] 和 p[0...n]
f[i][j] 表示 s[0..i-1] 和 p[0..j-1] 匹配,咱們須要判斷 s 和 p 是否匹配,就是求 f[m][n] 的值是否爲true,因此要日後更新 f[i][j] 的值。
example:
isMatch("aa","a") → false isMatch("aa","aa") → true isMatch("aaa","aa") → false isMatch("aa", "*") → true isMatch("aa", "a*") → true isMatch("ab", "?*") → true isMatch("aab", "c*a*b") → false
一、若是p[j-1]!='*',f[i][j]=f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='?')
二、若是p[j-1]=='*',f[i][j]=f[i][j-1] || f[i-1][j]
Equation 1). means that if p[j-1] is not *, f(i,j) is determined by if s[0:i-2] matches p[0:j-2] and if (s[i-1]==p[j-1] or p[j-1]=='?').
Equation 2). means that if p[j-1] is *, f(i,j) is true if either f(i,j-1) is true: s[0:i-1] matches p[0:j-2] and * is not used here; or f(i-1,j) is true: s[0:i-2] matches p[0:j-1] and * is used to match s[i-1].
public boolean isMatch(String s, String p) { int sl = s.length(); int pl = p.length(); boolean[][] a = new boolean[sl+1][pl+1]; a[0][0] = true; for (int i = 1; i <= pl; i++) { a[0][i] = p.charAt(i-1) == '*' ? a[0][i-1] : false; } for (int i = 1; i <= sl; i++) { for (int j = 1; j <= pl; j++) { if (p.charAt(j-1) != '*') { a[i][j] = a[i-1][j-1] & (s.charAt(i-1) == p.charAt(j-1) || p.charAt(j-1) == '?'); } else { a[i][j] = a[i][j-1] || a[i-1][j]; } } } return a[sl][pl]; }
53. Maximum Subarray
題意:求最大子串和
最優化問題,能夠用DP解決,而後考慮構造子問題,能夠考慮maxSubArray(int A[], int i, int j),可是遞歸方程不太好想到,因此考慮
maxSubArray(int A[], int i),表示以A[i]結尾的子串的最大和。
因此maxSubArray(A, i) = (maxSubArray(A, i - 1) > 0 ? maxSubArray(A, i - 1) : 0 ) + A[i];
public int maxSubArray(int[] nums) { int length = nums.length; if (length < 1) return 0; // int[] dp = new int[length]; // dp[0] = nums[0]; // int max = dp[0]; int curMax = nums[0]; int max = curMax; for (int i = 1; i < length; i++) { // dp[i] = Math.max(dp[i-1]+nums[i], nums[i]); // max = Math.max(max, dp[i]); curMax = Math.max(curMax+nums[i], nums[i]); max = Math.max(max, curMax); } return max; }
62. Unique Paths
題意:求從左上到右下有多少不一樣的路徑
p[i][j]表示到(i,j)的路徑數,能夠觀察到,機器人到達(i,j),要麼從左邊,要麼從上邊,因此遞歸方程爲:
p[i][j]=p[i-1][j]+p[i][j-1]
public int uniquePaths(int m, int n) { int[][] p = new int[m][n]; for(int i = 0; i<m;i++){ p[i][0] = 1; } for(int j= 0;j<n;j++){ p[0][j]=1; } for(int i = 1;i<m;i++){ for(int j = 1;j<n;j++){ p[i][j] = p[i-1][j]+p[i][j-1]; } } return p[m-1][n-1]; }
63. Unique Paths II
題意:在62. 基礎上加入障礙,求如今有多少路徑。
相似p[i][j]表示到(i,j)的路徑數,若是(i,j)=1,則p[i][j]=0,若是(i,j)=0,p[i][j]=p[i-1][j]+p[i][j-1]
須要注意的是邊緣狀況,即p[0][j]和p[i][0]的值
public int uniquePathsWithObstacles(int[][] obstacleGrid) { int n = obstacleGrid.length; int m = obstacleGrid[0].length; int[][] p = new int[n][m]; for (int i = 0; i < m; i++){ p[0][i] = 1 - obstacleGrid[0][i]; if (p[0][i] == 0) break; } for (int j = 0; j < n; j++){ p[j][0] = 1 - obstacleGrid[j][0]; if (p[j][0] == 0) break; } for ( int i = 1; i < n; i++){ for (int j = 1; j < m; j++) { if(obstacleGrid[i][j] == 1) p[i][j] = 0; else p[i][j] = p[i-1][j] + p[i][j-1]; } } return p[n-1][m-1]; }
64. Minimum Path Sum
題意:找到一條從左上到右下的路徑,要使其和最小。
p[i][j]表示到(i,j)的路徑和,遞歸方程爲:p[i][j]=min(p[i-1][j],p[i][j-1])+(i,j)
注意邊界條件便可
public int minPathSum(int[][] grid) { int m = grid.length; int n = grid[0].length; return minPathSum(grid, m, n); } public int minPathSum(int[][] grid, int m, int n) { int[][] p = new int[m][n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { if (i == 0 && j == 0) p[i][j] = grid[i][j]; else if (i == 0 && j > 0) p[i][j] = p[i][j-1] + grid[i][j]; else if (i > 0 && j == 0) p[i][j] = p[i-1][j] + grid[i][j]; else p[i][j] = Math.min(p[i-1][j], p[i][j-1]) + grid[i][j]; } } return p[m-1][n-1]; }
70. Climbing Stairs
題意:爬n節樓梯,每步能爬1或2步,求有多少種不一樣的方法爬到頂端。
p[i]表示到i的路徑數,則遞歸方程爲:p[i]=p[i-1]+p[i-2]
public int climbStairs(int n) { int[] p = new int[n+1]; p[0] = 1; p[1] = 1; for (int i = 2; i <= n; i++) { p[i] = p[i-1] + p[i-2]; } return p[n]; }
72. Edit Distance
題意:這題就是求編輯距離。
p[i][j]表示word1[0...i-1]和word2[0...j-1]的編輯距離
一、若是word1[i-1]==word2[j-1],則p[i][j]=p[i-1][j-1]
二、若是word1[i-1]!=word2[j-1],則p[i][j]=min(p[i-1][j-1]+1,p[i-1][j]+1,p[i][j-1]+1)
第1個條件好理解,主要說說第2個條件
word1[i - 1]
by word2[j - 1]
(dp[i][j] = dp[i - 1][j - 1] + 1 (for replacement)
);word1[i - 1]
and word1[0..i - 2] = word2[0..j - 1]
(dp[i][j] = dp[i - 1][j] + 1 (for deletion)
);word2[j - 1]
to word1[0..i - 1]
and word1[0..i - 1] + word2[j - 1] = word2[0..j - 1]
(dp[i][j] = dp[i][j - 1] + 1 (for insertion)
).好比p[i][j]=p[i-1][j]+1,將word1[i-1]刪掉(一次操做),而後就是看word1[0...i-2]和word2[0...j-1]的編輯距離,即p[i-1][j]
public int minDistance(String word1, String word2) { int len1 = word1.length(); int len2 = word2.length(); int[][] p = new int[len1+1][len2+1]; for (int i = 0; i <= len1; i++) { p[i][0] = i; } for (int i = 0; i <= len2; i++) { p[0][i] = i; } for (int i = 1; i <= len1; i++) { for (int j = 1; j <= len2; j++) { if (word1.charAt(i-1) == word2.charAt(j-1)) { p[i][j] = p[i-1][j-1]; }else { p[i][j] = min(p[i-1][j]+1, p[i][j-1]+1, p[i-1][j-1]+1); } } } return editDst[len1][len2]; }
85. Maximal Rectangle
題意:在0,1填充的矩形中找出全是1的最大的矩形。
以(i,j)爲右下頂點的矩形的最大的面積爲[right(i,j)-left(i,j)]*height(i,j)
因此咱們維護3個變量,left(i,j),right(i,j)和height(i,j)
一、left(i,j)記錄的是左邊界,left(i,j)=max(left(i-1,j), curleft)
二、right(i,j)記錄的是右邊界,right(i,j)=min(right(i-1,j), curright)
三、height(i,j)記錄的是上邊界,若是matrix[i][j]=='1',則height(i,j)=height(i-1,j)+1,不然 height(i,j)=0
public int maximalRectangleUsingDP(char[][] matrix) { int m = matrix.length; int n = matrix[0].length; int maxA = 0; int[] left = new int[n]; int[] right = new int[n]; for (int i = 0; i < n; i++) { right[i]=n; } int[] height = new int[n]; for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) height[j] = matrix[i][j]=='1'?height[j]+1:0; int preleft = 0; for (int j = 0; j < n; j++) { if (matrix[i][j] == '1') left[j] = Math.max(left[j], preleft); else{left[j]=0;preleft=j+1;} } int preright = n; for (int j = n-1; j >= 0; j--) { if (matrix[i][j] == '1') right[j] = Math.min(right[j], preright); else{right[j]=n;preright=j;} } for (int j = 0; j < n; j++) maxA = Math.max((right[j]-left[j])*height[j], maxA); } return maxA; }
87. Scramble String
題意:一個字符串有不少種二叉表示法,判斷兩個字符串s1,s2是否能夠作這樣的交換。
使用一個3維數組res[len][len][len+1],其中第一維爲s1的起始索引,第二維爲s2的起始索引,第三維爲子串的長度。
res[i][j][k]表示的是以i和j分別爲s1和s2起點的長度爲k的字符串是否是互爲scramble。
咱們首先是把當前s1[i...i+len-1]字符串劈一刀分紅兩部分,而後分兩種狀況:第一種是左邊和s2[j...j+len-1]左邊部分是否是scramble,以及右邊和s2[j...j+len-1]右邊部分是否是scramble;第二種狀況是左邊和s2[j...j+len-1]右邊部分是否是scramble,以及右邊和s2[j...j+len-1]左邊部分是否是scramble。若是以上兩種狀況有一種成立,說明s1[i...i+len-1]和s2[j...j+len-1]是scramble的。
上面說的是劈一刀的狀況,對於s1[i...i+len-1]咱們有len-1種劈法,在這些劈法中只要有一種成立,那麼兩個串就是scramble的。
總結起來遞推式是res[i][j][len] = || (res[i][j][k]&&res[i+k][j+k][len-k] || res[i][j+len-k][k]&&res[i+k][j][len-k]) 對於全部1<=k<len,也就是對於全部len-1種劈法的結果求或運算。由於信息都是計算過的,對於每種劈法只須要常量操做便可完成,所以求解遞推式是須要O(len)(由於len-1種劈法)。
如此總時間複雜度由於是三維動態規劃,須要三層循環,加上每一步須要線行時間求解遞推式,因此是O(n^4)。雖然已經比較高了,可是至少不是指數量級的,動態規劃仍是有很大有事的,空間複雜度是O(n^3)。
public boolean isScramble(String s1, String s2) { int n1 = s1.length(); int n2 = s2.length(); if(n1 != n2) return false; if(n1 == 0) return true; boolean[][][] res = new boolean[n1][n2][n1+1]; for(int i = 0; i<n1; i++){ for(int j=0;j<n2;j++){ res[i][j][1] = (s1.charAt(i)==s2.charAt(j)); } } for(int len=2; len <= n1; len++){ for(int i=0; i<= n1-len; i++){ for(int j=0; j<=n2-len; j++){ for(int k = 1; k<len; k++){ res[i][j][len] |= res[i][j][k]&res[i+k][j+k][len-k] || res[i][j+len-k][k]&res[i+k][j][len-k]; } } } } return res[0][0][n1]; }
91. Decode Ways
題意:求解有多少種不一樣的解碼方式。
res[i]表示s[0...i-1]的不一樣的解碼方式,如今要更新到res[n]。
考慮s(i-2,i-1)的取值範圍
一、00,30~90 ---> res[i]=0
二、01-09,27~29,31~99 ---> res[i]=res[i-1]
三、10,20 ---> res[i]=res[i-2]
四、11~19,21~26 ---> res[i]=res[i-1]+res[i-2]
public int numDecodings(String s) { if (s==null || s.length()==0 || s.charAt(0)=='0'){ return 0; } int n = s.length(); int[] res = new int[n+1]; res[0]=1; res[1]=1; for(int i=1; i<n; i++){ char ch = s.charAt(i); if(ch == '0'){ if(s.charAt(i-1)=='1' || s.charAt(i-1)=='2'){ //10, 20 res[i+1]=res[i-1]; }else{ // 00, 30,40,50,60,70,80,90 return 0; } } else{ if(s.charAt(i-1)=='0' || s.charAt(i-1)>'2'){ // 01-09, 31-99 res[i+1]=res[i]; }else if(s.charAt(i-1)=='2' && ch<='6' || s.charAt(i-1)=='1'){ //21-26, 11-19 res[i+1]=res[i]+res[i-1]; }else{ // 27-29 res[i+1]=res[i]; } } } return res[n]; }
96. Unique Binary Search Trees
題意:判斷有多少種不一樣的二叉搜索樹結構
能夠用動態規劃解決。維護兩個變量:
G(n):長度爲n的不一樣的二叉搜索樹的數量
F(i,n):以i爲跟結點的長度爲n的不一樣的二叉搜索樹的數量
則G(n) = F(1, n) + F(2, n) + ... + F(n, n),其中G(0)=1,G(1)=1
考慮[1, 2, 3, 4, 5, 6, 7],跟結點爲3,則左子樹[1,2],右子樹[4,5,6,7]
F(3,7)=G(2)*G(4),因此有F(i, n) = G(i-1) * G(n-i)
得出:G(n) = G(0) * G(n-1) + G(1) * G(n-2) + … + G(n-1) * G(0)
1 public int numTrees(int n) { 2 int [] G = new int[n+1]; 3 G[0] = G[1] = 1; 4 5 for(int i=2; i<=n; ++i) { 6 for(int j=1; j<=i; ++j) { 7 G[i] += G[j-1] * G[i-j]; 8 } 9 } 10 11 return G[n]; 12 }
95. Unique Binary Search Trees II
這道題是求解全部可行的二叉查找樹,從Unique Binary Search Trees中咱們已經知道,可行的二叉查找樹的數量是相應的卡特蘭數,不是一個多項式時間的數量級,因此咱們要求解全部的樹,天然是不能多項式時間內完成的了。算法上仍是用求解NP問題的方法來求解,也就是N-Queens中介紹的在循環中調用遞歸函數求解子問題。思路是每次一次選取一個結點爲根,而後遞歸求解左右子樹的全部結果,最後根據左右子樹的返回的全部子樹,依次選取而後接上(每一個左邊的子樹跟全部右邊的子樹匹配,而每一個右邊的子樹也要跟全部的左邊子樹匹配,總共有左右子樹數量的乘積種狀況),構造好以後做爲當前樹的結果返回。
1 public List<TreeNode> generateTrees(int n) { 2 List<TreeNode> res = new ArrayList<>(); 3 if (n < 1) 4 return res; 5 return generateT(1, n); 6 } 7 private List<TreeNode> generateT(int left, int right){ 8 List<TreeNode> res = new ArrayList<>(); 9 if (left > right){ 10 res.add(null); 11 return res; 12 } 13 if (left == right){ 14 res.add(new TreeNode(right)); 15 return res; 16 } 17 18 for (int i=left; i<=right; i++){ 19 List<TreeNode> leftList = generateT(left, i-1); 20 List<TreeNode> rightList = generateT(i+1, right); 21 22 for (TreeNode lnode : leftList){ 23 for (TreeNode rnode : rightList){ 24 TreeNode root = new TreeNode(i); 25 root.left = lnode; 26 root.right = rnode; 27 res.add(root); 28 } 29 } 30 } 31 return res; 32 }
121. Best Time to Buy and Sell Stock
題意:給一數組,表示天天的股票的價格,求最多進行一次交易,最大化收益。
最大化第 i 天的最大化收益就是第 i 天的價格 - 之前的最低價格。因此咱們維護一個變量min[i],表示前 i-1 天的最低價格。則:
min[i] = min(min[i-1], prices[i-1])
maxProfit = max(maxProfit, prices[i]-min[i])
1 public int maxProfit(int[] prices) { 2 int len = prices.length; 3 if (len <= 1) 4 return 0; 5 6 int[] min = new int[len]; 7 min[0] = prices[0]; 8 int maxProfit = 0; 9 for (int i = 1; i < len; i++) 10 { 11 min[i] = Math.min(min[i-1], prices[i-1]); 12 maxProfit = Math.max(maxProfit, prices[i]-min[i]); 13 } 14 return maxProfit; 15 }
123. Best Time to Buy and Sell Stock III
題意:給一數組,表示天天的股票的價格,求最多進行兩次交易,最大化收益。
能夠在整個區間的每一點切開,而後分別計算左子區間和右子區間的最大值,而後再用O(n)時間找到整個區間的最大值。
O(n^2)的算法很容易想到:
找尋一個點j,將原來的price[0..n-1]分割爲price[0..j]和price[j..n-1],分別求兩段的最大收益。
進行優化:
對於點j+1,求price[0..j+1]的最大收益時,不少工做是重複的,在求price[0..j]的最大收益中已經作過了。
相似於上題,能夠在O(1)的時間從price[0..j]推出price[0..j+1]的最大收益。
可是如何從price[j..n-1]推出price[j+1..n-1]?反過來思考,咱們能夠用O(1)的時間由price[j+1..n-1]推出price[j..n-1]。
最終算法:
數組l[i]記錄了price[0..i]的最大收益,
數組r[i]記錄了price[i..n]的最大收益。
已知l[i],求l[i+1]是簡單的,一樣已知r[i],求r[i-1]也很容易。
最後,咱們再用O(n)的時間找出最大的l[i]+r[i],即爲題目所求。
1 public int maxProfit(int[] prices) { 2 int len = prices.length; 3 if (len <= 1) 4 return 0; 5 6 int[] left = new int[len]; 7 left[0] = 0; 8 // 從前日後 9 int minPrice = prices[0]; 10 for (int i = 1; i < len; i++) 11 { 12 minPrice = Math.min(prices[i-1], minPrice); 13 left[i] = Math.max(prices[i]-minPrice, left[i-1]); 14 } 15 // 從後往前 16 int max = left[len-1]; 17 int maxPrice = prices[len-1]; 18 int right = 0; 19 for (int i = len-2; i >= 0; i--) 20 { 21 maxPrice = Math.max(maxPrice, prices[i+1]); 22 right = Math.max(right, maxPrice-prices[i]); 23 max = Math.max(max, right+left[i]); 24 } 25 return max; 26 }
188. Best Time to Buy and Sell Stock IV
題意:在最多進行k次交易的前提下,求最大收益。
咱們仍是使用「局部最優和全局最優解法」。咱們維護兩種量,一個是當前到達第i天能夠最多進行j次交易,最好的利潤是多少(global[i][j]),另外一個是當前到達第i天,最多可進行j次交易,而且最後一次交易在當天賣出的最好的利潤是多少(local[i][j])。下面咱們來看遞推式,全局的比較簡單,
也就是去當前局部最好的,和過往全局最好的中大的那個(由於最後一次交易若是包含當前天必定在局部最好的裏面,不然必定在過往全局最優的裏面)。
全局(到達第i天進行j次交易的最大收益) = max{局部(在第i天交易後,剛好知足j次交易),全局(到達第i-1天時已經知足j次交易)}
對於局部變量的維護,遞推式是
也就是看兩個量,第一個是全局到i-1天進行j-1次交易,而後加上今天的交易,若是今天是賺錢的話(也就是前面只要j-1次交易,最後一次交易取當前天),第二個量則是取local第i-1天j次交易,而後加上今天的差值(這裏由於local[i-1][j]好比包含第i-1天賣出的交易,因此如今變成第i天賣出,並不會增長交易次數,並且這裏不管diff是否是大於0都必定要加上,由於不然就不知足local[i][j]必須在最後一天賣出的條件了)。
局部(在第i天交易後,總共交易了j次) = max{狀況2,狀況1}
狀況1:在第i-1天時,剛好已經交易了j次(local[i-1][j]),那麼若是i-1天到i天再交易一次:即在第i-1天買入,第i天賣出(diff),則這不併不會增長交易次數!【例如我在第一天買入,次日賣出;而後次日又買入,第三天再賣出的行爲 和 第一天買入,第三天賣出 的效果是同樣的,其實只進行了一次交易!由於有連續性】
狀況2:第i-1天后,共交易了j-1次(global[i-1][j-1]),所以爲了知足「第i天事後共進行了j次交易,且第i天必須進行交易」的條件:咱們能夠選擇1:在第i-1天買入,而後再第i天賣出(diff),或者選擇在第i天買入,而後一樣在第i天賣出(收益爲0)。
1 public int maxProfit(int k, int[] prices) { 2 int len = prices.length; 3 if (len == 0) return 0; 4 if (k >= len / 2) return quickSolve(prices); 5 int [][] local = new int[len][k+1]; 6 int [][] global = new int[len][k+1]; 7 8 for (int i = 1; i < len; i++) 9 { 10 int diff = prices[i]-prices[i-1]; 11 for (int j = 1; j <= k; j++) 12 { 13 local[i][j] = Math.max(global[i-1][j-1]+Math.max(0,diff), local[i-1][j]+diff); 14 global[i][j] = Math.max(global[i-1][j], local[i][j]); 15 } 16 } 17 return global[len-1][k]; 18 } 19 private int quickSolve(int[] prices) { 20 int len = prices.length, profit = 0; 21 for (int i = 1; i < len; i++) 22 // as long as there is a price gap, we gain a profit. 23 if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1]; 24 return profit; 25 }
264. Ugly Number II
題意:求第n個ugly數。ugly數定義爲因子只有2,3,5的數,如:1,2,3,4,5,6,8,9,10,12等,定義1爲第一個ugly數。
k[n-1]爲第n個ugly數,則k[0]=1
k[1]=min(k[0]*2,k[0]*3,k[0]*5) -> k[0]*2
k[2]=min(k[1]*2,k[0]*3,k[0]*5) -> k[0]*3
k[3]=min(k[1]*2,k[1]*3,k[0]*5) -> k[1]*2
1 public int nthUglyNumber(int n) 2 { 3 int[] k = new int[n]; 4 k[0] = 1; 5 int t1 = 0, t2 = 0, t3 = 0; 6 for (int i = 1; i < n; i++) 7 { 8 k[i] = min(k[t1]*2, k[t2]*3, k[t3]*5); 9 if (k[i]==k[t1]*2) t1 += 1; 10 if (k[i]==k[t2]*3) t2 += 1; 11 if (k[i]==k[t3]*5) t3 += 1; 12 } 13 return k[n-1]; 14 } 15 private int min(int a, int b, int c) 16 { 17 a = a < b ? a : b; 18 return a < c ? a : c; 19 }
279. Perfect Squares
題意:求數n是最少多少個平方數的和。
p[i]表示由p[i]個平方數構成數i,其中p[i]是最小的值,則p[i]=min(p[k]+p[i-k]),k>=1且k<=i.
咱們能夠再優化下k的取值,將k取值爲平方數,則p[i]=min(1+p[i-k]),k=j*j且k<=i
1 public int numSquares(int n) 2 { 3 int[] p = new int[n+1]; 4 p[0] = 0; 5 for (int i = 1; i <= n; i++) 6 { 7 p[i] = n; 8 9 for (int j = 1; j*j <= i; j++) 10 p[i] = Math.min(p[i], p[i-j*j]+1); 11 } 12 return p[n]; 13 }
300. Longest Increasing Subsequence
題意:找到數組nums最長遞增子序列,求出長度。
p[i]爲包含索引 i 的最長遞增子序列的長度,則更新p[i]=max(p[j]+1, p[i]),nums[i]>nums[j],j from 0 to i-1
1 public int lengthOfLIS(int[] nums) 2 { 3 if (nums == null || nums.length == 0) 4 return 0; 5 int max = 0; 6 int[] p = new int[nums.length]; 7 for (int i = 0; i < nums.length; i++) 8 { 9 p[i] = 1; 10 for (int j = 0; j < i; j++) 11 { 12 if (nums[i] > nums[j] && p[j] + 1 > p[i]) 13 { 14 p[i] = p[j] + 1; 15 } 16 } 17 max = Math.max(max, p[i]); 18 } 19 return max; 20 }
309. Best Time to Buy and Sell Stock with Cooldown
題意:
給定一個數組,第i個元素表明某隻股票在第i天的價格。
設計一個算法計算最大收益。你能夠完成屢次交易(亦即,屢次買入、賣出同一只股票),須要知足下列限制:
這道題比較麻煩的是有個cooldown的限制,其實本質也就是買與賣之間的限制。對於某一天,股票有三種狀態: buy, sell, cooldown, sell與cooldown咱們能夠合併成一種狀態,由於手裏最終都沒股票,最終須要的結果是sell,即手裏股票賣了得到最大利潤。因此咱們能夠用兩個DP數組分別記錄當前持股跟未持股的狀態。
對於當天最終未持股的狀態,最終最大利潤有兩種可能,一是今天沒動做跟昨天未持股狀態同樣,二是昨天持股了,今天賣了。因此咱們只要取這二者之間最大值便可,表達式:sells[i] = max(sells[i-1], buys[i-1]+price[i])
對於當天最終持股的狀態,最終最大利潤有兩種可能,一是今天沒動做跟昨天持股狀態同樣,二是前天還沒持股,今天買了股票,這裏是由於cooldown的緣由,因此今天買股要追溯到前天的狀態。咱們只要取這二者之間最大值便可,表達式:buys[i] = max(buys[i-1], sells[i-2]-price[i])
第二種思路:
/* * 每一天有3種狀態,buy,sell,rest
* buy[i]表示第i天買入後的最大收益,sell[i]表示第i天賣出後的最大收益,rest[i]表示第i天不作操做的最大收益 * buy[i] = max(buy[i-1]-price[i], sell[i-1]-price[i], rest[i-1]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]+price[i], rest[i-1]+price[i], sell[i-1]) * rest[i] = max(buy[i-1], sell[i-1], rest[i-1]) * 而後去除上述不合理的狀況 * buy[i]<=sell[i],rest[i]<=sell[i],因此rest[i] = sell[i-1] * buy[i] = max(buy[i-1]-price[i], sell[i-1]-price[i], sell[i-2]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]+price[i], sell[i-2]+price[i], sell[i-1]) * 由於 buy[i-1]-price[i] <= buy[i-1], 昨天賣了,今天不可能再買 * 因此 buy[i] = max(sell[i-2]-price[i], buy[i-1]) * 由於 昨天和前天賣了,今天不可能再賣 * 因此 sell[i] = max(buy[i-1]+price[i], sell[i-1]) * * 因此有遞推式 * buy[i] = max(sell[i-2]-price[i], buy[i-1]) * sell[i] = max(buy[i-1]+price[i], sell[i-1]) * */
1 /* 2 * buy[i] = max(sell[i-2]-price, buy[i-1]) 3 * sell[i] = max(buy[i-1]+price, sell[i-1]) 4 * */ 5 public int maxProfit(int[] prices) 6 { 7 int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy; 8 for (int i = 0; i < prices.length; ++i) 9 { 10 prev_buy = buy; 11 buy = Math.max(prev_sell-prices[i], prev_buy); 12 prev_sell =sell; 13 sell = Math.max(prev_buy+prices[i], prev_sell); 14 } 15 return sell; 16 }
338. Counting Bits
題意:計算二進制表示中有多少個1.
先寫寫看,找找規律。0,1,1,2,1,2,2,3,1,2,2,3,2,3,3,4,發現 0,1,1,2,1,2,2,3, | 1,2,2,3,2,3,3,4 後半部分正好是前半部分加1。相似,0,1,1,2,| 1,2,2,3 也是這種規律。因此,二進制每多一位就把結果數組中全部的結果都加一,再放回結果數組中。
1 public int[] countBits(int num) 2 { 3 if (num == 0) return new int[]{0}; 4 int[] res = new int[num+1]; 5 int count = 0; 6 while (true) 7 { 8 int len = count+1; 9 for (int j = 0; j < len; j++) 10 { 11 res[++count] = res[j]+1; 12 if (count >= num) return res; 13 } 14 } 15 }