如下题号均为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 }