以下题号均为LeetCode题号,便于查看原题。

10. Regular Expression Matching

题意:实现字符串的正则匹配,包含'.' 和 '*'。'.' 匹配任意一个字符,"*" 匹配 '*' 之前的0个或多个字符。

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]

f[i][j] 表示 s[0..i-1] 和 p[0..j-1] 匹配,我们需要判断 s 和 p 是否匹配,就是求 f[m][n] 的值是否为true,所以要往后更新 f[i][j] 的值。

更新思路如下:

1、if p[j-1]!='*', f[i][j] = f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='.')

2、if p[j-1]=='*', 看 '*' 匹配多少个字符,即匹配多少个p[j-2]。

如果 '*' 匹配0个字符,此时,p[0...j-1]==p[0...j-3],f[i][j]=f[i][j-2];

如果 '*' 匹配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];
}

32. Longest Valid Parentheses

题意: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 处的最长长度

1、如果s[i]=='(',dp[i]=0

2、如果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

1、如果p[j-1]!='*',f[i][j]=f[i-1][j-1] & (s[i-1]==p[j-1] || p[j-1]=='?')

2、如果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]的编辑距离

1、如果word1[i-1]==word2[j-1],则p[i][j]=p[i-1][j-1]

2、如果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个条件

  1. Replace word1[i - 1] by word2[j - 1] (dp[i][j] = dp[i - 1][j - 1] + 1 (for replacement));
  2. Delete word1[i - 1] and word1[0..i - 2] = word2[0..j - 1] (dp[i][j] = dp[i - 1][j] + 1 (for deletion));
  3. Insert 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)

1、left(i,j)记录的是左边界,left(i,j)=max(left(i-1,j), curleft)

2、right(i,j)记录的是右边界,right(i,j)=min(right(i-1,j), curright)

3、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)的取值范围

1、00,30~90     ---> res[i]=0

2、01-09,27~29,31~99    ---> res[i]=res[i-1]

3、10,20       ---> res[i]=res[i-2]

4、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)

 public int numTrees(int n) {
int [] G = new int[n+];
G[] = G[] = ; for(int i=; i<=n; ++i) {
for(int j=; j<=i; ++j) {
G[i] += G[j-] * G[i-j];
}
} return G[n];
}

 95. Unique Binary Search Trees II

这道题是求解所有可行的二叉查找树,从Unique Binary Search Trees中我们已经知道,可行的二叉查找树的数量是相应的卡特兰数,不是一个多项式时间的数量级,所以我们要求解所有的树,自然是不能多项式时间内完成的了。算法上还是用求解NP问题的方法来求解,也就是N-Queens中介绍的在循环中调用递归函数求解子问题。思路是每次一次选取一个结点为根,然后递归求解左右子树的所有结果,最后根据左右子树的返回的所有子树,依次选取然后接上(每个左边的子树跟所有右边的子树匹配,而每个右边的子树也要跟所有的左边子树匹配,总共有左右子树数量的乘积种情况),构造好之后作为当前树的结果返回。

 public List<TreeNode> generateTrees(int n) {
List<TreeNode> res = new ArrayList<>();
if (n < )
return res;
return generateT(, n);
}
private List<TreeNode> generateT(int left, int right){
List<TreeNode> res = new ArrayList<>();
if (left > right){
res.add(null);
return res;
}
if (left == right){
res.add(new TreeNode(right));
return res;
} for (int i=left; i<=right; i++){
List<TreeNode> leftList = generateT(left, i-);
List<TreeNode> rightList = generateT(i+, right); for (TreeNode lnode : leftList){
for (TreeNode rnode : rightList){
TreeNode root = new TreeNode(i);
root.left = lnode;
root.right = rnode;
res.add(root);
}
}
}
return res;
}

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])

 public int maxProfit(int[] prices) {
int len = prices.length;
if (len <= 1)
return 0; int[] min = new int[len];
min[0] = prices[0];
int maxProfit = 0;
for (int i = 1; i < len; i++)
{
min[i] = Math.min(min[i-1], prices[i-1]);
maxProfit = Math.max(maxProfit, prices[i]-min[i]);
}
return maxProfit;
}

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],即为题目所求。

 public int maxProfit(int[] prices) {
int len = prices.length;
if (len <= 1)
return 0; int[] left = new int[len];
left[0] = 0;
// 从前往后
int minPrice = prices[0];
for (int i = 1; i < len; i++)
{
minPrice = Math.min(prices[i-1], minPrice);
left[i] = Math.max(prices[i]-minPrice, left[i-1]);
}
// 从后往前
int max = left[len-1];
int maxPrice = prices[len-1];
int right = 0;
for (int i = len-2; i >= 0; i--)
{
maxPrice = Math.max(maxPrice, prices[i+1]);
right = Math.max(right, maxPrice-prices[i]);
max = Math.max(max, right+left[i]);
}
return max;
}

188. Best Time to Buy and Sell Stock IV

题意:在最多进行k次交易的前提下,求最大收益。

我们还是使用“局部最优和全局最优解法”。我们维护两种量,一个是当前到达第i天可以最多进行j次交易,最好的利润是多少(global[i][j]),另一个是当前到达第i天,最多可进行j次交易,并且最后一次交易在当天卖出的最好的利润是多少(local[i][j])。下面我们来看递推式,全局的比较简单,

global[i][j]=max(local[i][j],global[i-1][j]),

也就是去当前局部最好的,和过往全局最好的中大的那个(因为最后一次交易如果包含当前天一定在局部最好的里面,否则一定在过往全局最优的里面)。

全局(到达第i天进行j次交易的最大收益) = max{局部(在第i天交易后,恰好满足j次交易),全局(到达第i-1天时已经满足j次交易)}

对于局部变量的维护,递推式是

local[i][j]=max(global[i-1][j-1]+max(diff,0),local[i-1][j]+diff),

也就是看两个量,第一个是全局到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)。

 public int maxProfit(int k, int[] prices) {
int len = prices.length;
if (len == 0) return 0;
if (k >= len / 2) return quickSolve(prices);
int [][] local = new int[len][k+1];
int [][] global = new int[len][k+1]; for (int i = 1; i < len; i++)
{
int diff = prices[i]-prices[i-1];
for (int j = 1; j <= k; j++)
{
local[i][j] = Math.max(global[i-1][j-1]+Math.max(0,diff), local[i-1][j]+diff);
global[i][j] = Math.max(global[i-1][j], local[i][j]);
}
}
return global[len-1][k];
}
private int quickSolve(int[] prices) {
int len = prices.length, profit = 0;
for (int i = 1; i < len; i++)
// as long as there is a price gap, we gain a profit.
if (prices[i] > prices[i - 1]) profit += prices[i] - prices[i - 1];
return profit;
}

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

 public int nthUglyNumber(int n)
{
int[] k = new int[n];
k[0] = 1;
int t1 = 0, t2 = 0, t3 = 0;
for (int i = 1; i < n; i++)
{
k[i] = min(k[t1]*2, k[t2]*3, k[t3]*5);
if (k[i]==k[t1]*2) t1 += 1;
if (k[i]==k[t2]*3) t2 += 1;
if (k[i]==k[t3]*5) t3 += 1;
}
return k[n-1];
}
private int min(int a, int b, int c)
{
a = a < b ? a : b;
return a < c ? a : c;
}

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

 public int numSquares(int n)
{
int[] p = new int[n+1];
p[0] = 0;
for (int i = 1; i <= n; i++)
{
p[i] = n; for (int j = 1; j*j <= i; j++)
p[i] = Math.min(p[i], p[i-j*j]+1);
}
return p[n];
}

 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

 public int lengthOfLIS(int[] nums)
{
if (nums == null || nums.length == 0)
return 0;
int max = 0;
int[] p = new int[nums.length];
for (int i = 0; i < nums.length; i++)
{
p[i] = 1;
for (int j = 0; j < i; j++)
{
if (nums[i] > nums[j] && p[j] + 1 > p[i])
{
p[i] = p[j] + 1;
}
}
max = Math.max(max, p[i]);
}
return max;
}

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])
* */
 /*
* buy[i] = max(sell[i-2]-price, buy[i-1])
* sell[i] = max(buy[i-1]+price, sell[i-1])
* */
public int maxProfit(int[] prices)
{
int sell = 0, prev_sell = 0, buy = Integer.MIN_VALUE, prev_buy;
for (int i = 0; i < prices.length; ++i)
{
prev_buy = buy;
buy = Math.max(prev_sell-prices[i], prev_buy);
prev_sell =sell;
sell = Math.max(prev_buy+prices[i], prev_sell);
}
return sell;
}

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 也是这种规律。所以,二进制每多一位就把结果数组中所有的结果都加一,再放回结果数组中。

 public int[] countBits(int num)
{
if (num == 0) return new int[]{0};
int[] res = new int[num+1];
int count = 0;
while (true)
{
int len = count+1;
for (int j = 0; j < len; j++)
{
res[++count] = res[j]+1;
if (count >= num) return res;
}
}
}

LeetCode动态规划题总结【持续更新】的更多相关文章

  1. LeetCode刷题(持续更新ing……)

    准备刷题了!已经预见未来的日子是苦并快乐的了!虽然 N 年前刷过题,但现在感觉数据结构与算法的基本功快忘光了

  2. 知道创宇爬虫题--代码持续更新中 - littlethunder的专栏 - 博客频道 - CSDN.NET

    知道创宇爬虫题--代码持续更新中 - littlethunder的专栏 - 博客频道 - CSDN.NET undefined 公司介绍 - 数人科技 undefined

  3. 快速上手leetcode动态规划题

    快速上手leetcode动态规划题 我现在是初学的状态,在此来记录我的刷题过程,便于以后复习巩固. 我leetcode从动态规划开始刷,语言用的java. 一.了解动态规划 我上网查了一下动态规划,了 ...

  4. LeetCode python实现题解(持续更新)

    目录 LeetCode Python实现算法简介 0001 两数之和 0002 两数相加 0003 无重复字符的最长子串 0004 寻找两个有序数组的中位数 0005 最长回文子串 0006 Z字型变 ...

  5. leetcode腾讯精选练习(50 题)(持续更新)

    1.除自身以外数组的乘积 给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘 ...

  6. Codeforces/TopCoder/ProjectEuler/CodeChef 散题笔记 (持续更新)

    最近做到了一些有趣的散题,于是开个Blog记录一下吧… (如果有人想做这些题的话还是不要看题解吧…) 2017-03-16 PE 202 Laserbeam 题意:有一个正三角形的镜子屋,光线从$C$ ...

  7. hzwer分块九题(暂时持续更新)

    hzwer分块9题 分块1:区间加法,单点查询 Code #include<bits/stdc++.h> #define in(i) (i=read()) using namespace ...

  8. DP刷题记录(持续更新)

    DP刷题记录 (本文例题目前大多数都选自算法竞赛进阶指南) TYVJ1071 求两个序列的最长公共上升子序列 设\(f_{i,j}\)表示a中的\(1-i\)与b中色\(1-j\)匹配时所能构成的以\ ...

  9. 用python做oj上的简单题(持续更新中.......)

    本人刚開始接触python,在oj上解一些简单的题,欢迎交流,不喜勿喷. OJ地址链接:acm.sdut.edu.cn http://acm.sdut.edu.cn/sdutoj/showproble ...

随机推荐

  1. PostgreSQL——启动脚本

    <仅供参考,执行結果受环境影响> 如下: pgpath='/usr/local/pgsql/bin' pgdata='/usr/local/pgsql/data' #以 postgres ...

  2. flex 布局,flex-grow 宽度未等比放大问题解决办法

    本文转载自:https://blog.csdn.net/sinat_41695090/article/details/79215893 先粘贴上一段代码,flex总体布局 <body> & ...

  3. VC++ 2010 创建高级Ribbon界面详解(2)

    Ribbon 控件的使用 1.命令按钮 命令按钮可以说是我们最常用的Ribbon控件了,我们通常都是通过命令按钮来发送某个命令,执行某个动作.它代替了过去的菜单命令,成为使用最频繁的Ribbon控件. ...

  4. ubuntu14.04标题栏显示上下网速

    首先当然是用 wget 下载 indicator-sysmonitor,终端执行命令: wget -c https://launchpad.net/indicator-sysmonitor/trunk ...

  5. scip 练习2.20

    (define (same-parity x . z) (define (q? y) (= (remainder y ) )) (define (o? y) (= (remainder y ) )) ...

  6. Python学习笔记(九)——字符串

    # 5.1 字符串的拼接 str1 = '我今天一共走了' num = 1280 str2 = '步' print(str1+str(num)+str2) # 计算字符串长度 print(len(st ...

  7. Echart中X轴数据过多时横向拉动展示

    chart.setOption( { tooltip: { trigger: 'axis' }, toolbox: { feature: { saveAsImage: {} } }, grid: { ...

  8. c++内存相关函数

    memset void *memset(void *s, int ch, size_t n); 函数解释:将s中当前位置后面的n个字节 (typedef unsigned int size_t )用 ...

  9. 用Cygwin实现在window环境下使用Linux命令-nohup 来后台运行程序

    1.安装Cygwin 下载 cygdrive-选择64或32位   http://www.cygwin.com/ 注:可以百度搜索安装步骤 2.配置它的环境变量 添加到path路径中 3.cmd  执 ...

  10. WPF 模仿 UltraEdit 文件查看器系列一 用户控件

    WPF 模仿 UltraEdit 文件查看器系列一 用户控件 运行环境:Win10 x64, NetFrameWork 4.8, 作者:乌龙哈里,日期:2019-05-10 章节: 起步 添加用户控件 ...