Leetcode 力扣动态规划笔记 部分中等题算法总结CPP

5. Longest Palindromic Substring
Given a string s, find the longest palindromic substring in s. You may assume that the maximum length of s is 1000.

给定字符串s,找出它的最长回文字符串。
DP解法思路:如果有s的最长回文字符串t,那么把t的头尾去掉依旧是一个回文字符串。建立二维数组table,保存s中长度1到s长度的回文字符串长度:

class Solution {
public:
    string longestPalindrome(string s) {
        
        vector<vector<int>> b(s.length()+1,vector<int> (s.length(),0));
        
        int i,j,l,recordl=1,recordi=0;
        
        if(s.length()==0)return "";
        
        for(i=0;i<s.length();i++) {
            b[1][i]=1;
            if(i<s.length()-1 && s[i+1]==s[i]) {
                b[2][i]=2;
                recordl=2;
                recordi=i;
            }
        }
        
        for(l=3;l<=s.length();l++) {
            for(i=0;i<=s.length()-l;i++) {
                j=i+l-1;
            if(b[l-2][i+1]==l-2 && s[i]==s[j]) {
                    b[l][i]=l;
                    recordl=l;
                    recordi=i;
                }  
            }
            if(!l%2 && recordl!=l) return s.substr(recordi,recordl);
        }
        return s.substr(recordi,recordl);        
    }
};

二维数组可以用一维数组或单独的字符串代替,只需要在不同长度上找到最长的回文字符串并保存就可以了。这种方法时间 O ( n 2 ) O(n^2) On2)

对回文字符串的查找有Manacher算法,更快,速度 O ( n ) O(n) On)

62. Unique Paths
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
How many possible unique paths are there?

Leetcode 力扣动态规划笔记 部分中等题算法总结CPP_第1张图片
mn的棋盘,如果只能向右和向下走,从左上角走到右下角,有多少种走法?
可以发现到一个格子的前一格必定是左边的格子或上边的格子,因此如果到一个i行j列的格子的走法有f(i,j)种,那么f(i,j)=f(i-1,j)+f(i,j-1),这就是DP解法的递归公式。可以建立一个同样m
n大小的表格grid,保存f(i,j),这种解法的时间复杂度和空间复杂度都是O(mn):

class Solution {
public:
    int uniquePaths(int m, int n) {
        
        vector<vector<int>> grid(m,vector<int>(n,0));
        
        int i,j;
        
        for(i=0;i<m;i++)
            for(j=0;j<n;j++) {
                if(i==0 || j==0)
                    grid[i][j]=1;
                else 
                    grid[i][j]=grid[i-1][j]+grid[i][j-1];
            }
        
        return grid[m-1][n-1];
    }
};

二维数组也可以用一维数组代替。

63. Unique Paths II
A robot is located at the top-left corner of a m x n grid (marked ‘Start’ in the diagram below).
The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked ‘Finish’ in the diagram below).
Now consider if some obstacles are added to the grids. How many unique paths would there be?

与上题类似,但是部分格子有障碍,不能通过。解法相同,设置不能通过的地方f(i,j)=0即可。

class Solution {
public:
    int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {
        int m=obstacleGrid.size();
        int n=obstacleGrid[0].size();
        
        if(obstacleGrid.size()==0)return 0;
        
        int i,j;
        vector<vector<long long int>> Grid(m,vector<long long int>(n,0));
        
        for(i=0;i<n;i++) {
            if(obstacleGrid[0][i]==1)break;
            Grid[0][i]=1;
        }
        
        for(i=0;i<m;i++) {
            if(obstacleGrid[i][0]==1)break;
            Grid[i][0]=1;
        }
        
        for(i=1;i<m;i++)
            for(j=1;j<n;j++)
                if(obstacleGrid[i][j]!=1)Grid[i][j]=Grid[i-1][j]+Grid[i][j-1];
    
        return Grid[m-1][n-1];  
    }
};

64. Minimum Path Sum
Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.
Note: You can only move either down or right at any point in time.

与上面两题类似,只不过每个格子有了一个值,需要找到到达右下角的路线最小值。这道题很像加权有向图的最短路径问题,可以将给出的二维数组转化成图,用Dijkstra算法。这里给出DP算法:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        
        int m=grid.size();
        
        if(!m)return 0;
        
        int n=grid[0].size();
        vector<vector<int>> table(m,vector<int>(n));
        int i,j;
        
        table[0][0]=grid[0][0];
        for(i=1;i<n;i++) 
            table[0][i]=table[0][i-1]+grid[0][i];
        for(i=1;i<m;i++) {
            table[i][0]=table[i-1][0]+grid[i][0];
            for(j=1;j<n;j++)
            table[i][j]=min(table[i-1][j],table[i][j-1])+grid[i][j];
        }    
        
        return table[m-1][n-1];
    }
};

91. Decode Ways
A message containing letters from A-Z is being encoded to numbers using the following mapping:
‘A’ -> 1
‘B’ -> 2

‘Z’ -> 26
Given a non-empty string containing only digits, determine the total number of ways to decode it.

给出一个只含数字的字符串,按照A=1,B=2,…,Z=26这种加密方式进行解码,找出给出字符串的解码方法总数。
一个字符串有不同的解码方法是因为当存在1-26之间的数,比如25,可以按照25=Y的方式解码,或者按照2=B或5=E,25=BE的方式解码。这道题的陷阱是‘0’的存在。因为编码是从1开始的,如果第一位是0便无法解码,10,20只有一种解码方法,70无法解码。

@fastcode的简洁明了的代码,稍作改动:

int numDecodings(string s) {
      int n = s.size();
      if(!n || s[0] == '0')
          return 0;
      int f0 = 1, f1 = 1, f2, i;
      for(i = 2; i <= n; ++i)
      {
           f2 = (s[i-1] != '0')*f1 + ((s[i-2] == '1') || (s[i-2] == '2' && s[i-1] < '7'))*f0;
           f0 = f1;
           f1 = f2;
      }
    return f1;
}

三个变量f0,f1,f2保存当前位置去掉最后两位的解码方法数,去掉最后一位的解码方法数和当前位置的解码方法数。递推公式是关键:
如果当前值是0,前一位不是1或2,那么无法解码,比如311708,0只能和前一位共同存在,而70无法解码。
如果当前值是0,前一位是1或2,那么f(i)=f(i-2)。比如31110,因为10不能分开,所以和311的解码总数一样多。

如果当前值不是0,前一位不是1或2,第i位可以单独分离出来,有f(i-1)种解码方法。
如果当前值不是0,前一位是1或2,第i位和第i-1位可以放在一起,有f(i-1)+f(i-2)种解码方法。
将上述四种情况综合,有f(i) = (s[i-1] != ‘0’)*f(i-1) + ((s[i-2] == ‘1’) || (s[i-2] == ‘2’ && s[i-1] < ‘7’))*f(i-2)。

120. Triangle
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.

给一个三角形,只能向下走相邻位置,找到从顶到底的和最小的路径,返回最小值。
例如
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]

从顶到底的最小值是 11 (i.e., 2 + 3 + 5 + 1 = 11)。
DP解法的递推公式:如果i=0,f(i)=triangle[0][0];如果i>0,f[i][j]=min(f[i-1][j-1],f[i-1][j])+triangle[i][j]。可以建立一个表格储存对每一个i,j的f(i),也可以只用一个一维数组和一个临时数组,分别保存f(i-1)和f(i),最后可以用min_element函数找出数组中的最小值。给出O(n)空间的代码:

int minimumTotal(vector<vector<int>>& triangle) {
		int n = triangle.back().size();
        vector<int> dp(n, INT_MAX);
        dp[0] = triangle[0][0];
        vector<int> prev;
        
        for (int level = 1; level < n; ++level) {
            prev = dp;
            for (int i = 0; i < triangle[level].size(); ++i) {
                dp[i] = (i >= 1 ? std::min(prev[i-1], prev[i]) : prev[i]) + triangle[level][i];
            }
        }   
        return *std::min_element(dp.begin(), dp.end());
    }

95. Unique Binary Search Trees II
Given an integer n, generate all structurally unique BST’s (binary search trees) that store values 1 … n.

给出一个数n,输出所有用1,2,3,…,n这n个数能够成的不同二叉搜索树。
例如n=3:
输出:
[
[1,null,3,2],
[3,2,null,1],
[3,1,null,null,2],
[2,1,3],
[1,null,2,null,3]
]
得到的树如下:
Leetcode 力扣动态规划笔记 部分中等题算法总结CPP_第2张图片
根据二叉搜索树的性质,根节点的左子树全部小于根节点,根节点的右子树全部大于根节点。根据这一性质可以让1,2,3,…,n分别作为根节点,用递归或者动态规对一任意节点找出左子树和右子树的全部排列。可以作n*n的表格,第m行保存1,2,3,…,m的全排列树。第m+1行,对于树根为k,将第k-1行的全排列树作为左子树,第m-k行的全排列树所有节点加k,再分别作为k的右子树。

class Solution {
public:
    vector<TreeNode*> generateTrees(int n) {
        
        vector<vector<TreeNode*>> table(n+1);
        
        if(n==0)return table[0];
        table[0].push_back(NULL);
        table[1].push_back(new TreeNode(1));
        
        int k,l,li,ri;
        
        for(l=2;l<=n;l++)
            for(k=1;k<=l;k++) 
                for(li=0;li<table[k-1].size();li++)
                    for(ri=0;ri<table[l-k].size();ri++) {
                        cout<<k<<" "<<ri<<" "<<li<<endl;
                        table[l].push_back(new TreeNode(k));
                        table[l].back()->left=table[k-1][li];
                        table[l].back()->right=copy_and_add(table[l].back()->right, table[l-k][ri], k);
                        }
        return table[n];
}
        
TreeNode* copy_and_add(TreeNode* origin, TreeNode* copytree, int n) {
    if(copytree==NULL) return NULL;
    origin=new TreeNode(copytree->val+n);
    origin->left=copy_and_add(origin->left,copytree->left,n);
    origin->right=copy_and_add(origin->right,copytree->right,n);  
    return origin;
}

另外附上不用数组的递归方法@IgorLeet:

vector<TreeNode*> genTreesUtil(int beg, int end) {
	if (end < beg) return { nullptr };
	if (end == beg) return { new TreeNode(beg) };

	vector<TreeNode*> trees;
	for (int i = beg; i <= end; ++i) {
		auto leftTrees = genTreesUtil(beg, i - 1);
		auto rightTrees = genTreesUtil(i + 1, end);
		for (auto& l : leftTrees)
			for (auto& r : rightTrees) {
				auto t = new TreeNode(i);
				t->left = l;
				t->right = r;
				trees.push_back(t);
			}
	}

	return trees;
}

vector<TreeNode*> generateTrees(int n) {
	if (n == 0) return {};
	return genTreesUtil(1, n);
}

96. Unique Binary Search Trees
Given n, how many structurally unique BST’s (binary search trees) that store values 1 … n?

给出一个数n,输出所有用1,2,3,…,n这n个数能够成的不同二叉搜索树的数量。

如果理解了上一题,这道题就非常简单了,不需返回数组,只需返回二叉树的数量,对于每个在1和n之间的数i,有两个子问题:i-1个节点能组成的不同二叉树和n-i个节点能组成的不同二叉树。i个节点不同二叉树的数量f(i)=f(i-1)*f(n-i)。

class Solution {
public:
    int numTrees(int n) {
        
        vector<int> dp(n+1,0);
          
        int i,k;
        
        dp[0]=1;
        if(n) dp[1]=1;
        
        for(i=2;i<=n;i++) 
            for(k=1;k<=i;k++)
                dp[i]+=dp[k-1]*dp[i-k];    
        
        return dp[n];
    }
};

139. Word Break
Given a non-empty string s and a dictionary wordDict containing a list of non-empty words, determine if s can be segmented into a space-separated sequence of one or more dictionary words.
Note:
The same word in the dictionary may be reused multiple times in the segmentation.
You may assume the dictionary does not contain duplicate words.

给出一个字符串,一个“词典”数组,判断字符串是否可以正好分隔成词典中的单词。词典中没有重复单词,但是词典中的单词可以重复使用。

先找到子结构:如果一个字符串能刚好分割成不同的单词,那么拿掉一侧的一个单词,剩下的字符串也能分割成不同单词。动态规划的常规方法就可以解决了:建立表格DP(bool),每一行DP[L]记录所有位置长度为L的子串能否分割。然后继续分割长度为L的字符串,如果a. 长度为L的子串存在词典里。b.L再分割成两个长度为k和L-k的子串,对应位置的DP[k]和DP[L-k]都为真。a或b任一成立则DP[L]为真。时间 O ( n 2 ) O(n^2) O(n2),空间 O ( n 2 ) O(n^2) O(n2)

class Solution {
public:
        bool wordBreak(string s, vector<string>& wordDict) {
            
            int n=s.length();

            if(n==0)return false;
            
            vector<vector<bool>> dp(n+1,vector<bool>(n,false));
            int i,j,k,l; 
            
            for(i=0;i<n;i++) {
                string temp(1,s[i]);
                if(find(wordDict.begin(),wordDict.end(),temp)!=wordDict.end()) 
                    dp[1][i]=true;
            }
                
            for(l=2;l<=n;l++)
                for(i=0;i<n-l+1;i++) {
                    if(find(wordDict.begin(),wordDict.end(),s.substr(i,l))!=wordDict.end()) 
                        dp[l][i]=true;
                    else for(k=1;k<l;k++) 
                            if(dp[k][i] && dp[l-k][i+k])
                                dp[l][i]=true;          
                }
            return dp[l-1][0];
    }
};

152. Maximum Product Subarray
Given an integer array nums, find the contiguous subarray within an array (containing at least one number) which has the largest product.

对给出的正数数组,找出其中乘积最大的连续部分,返回乘积。

有一道类似的题目,找出和最大的连续部分,返回和,是一道简单题 (Leetcode 力扣动态规划笔记 部分简单题算法总结CPP)。这道题比较麻烦的地方就在于0和负数的存在。仔细考虑可以发现,如果不考虑0,最长的部分要么是prefix(前面一段一直到最后或者最后一个负数之前)的乘积,要么是suffix(后面一段一直往前到最前或者第一个负数之后)的乘积。如果考虑0,可以把整个数组分成小段分别计算,用一个变量保存最大值。时间O(n)。@hujie:

class Solution {
public:
    int maxProduct(vector<int>& nums) {
        if(nums.empty()) return 0;
        int maxProduct=INT_MIN;
        int product=1;
        for(int i=0;i<nums.size();++i){
            product*=nums[i];
            maxProduct=max(maxProduct,product);
            if(product==0) product=1;
        }
        product=1;
        for(int i=nums.size()-1;i>=0;--i){
            product*=nums[i];
            maxProduct=max(maxProduct,product);
            if(product==0) product=1;
        }
        return maxProduct;
    }
};

213. House Robber II
You are a professional robber planning to rob houses along a street. Each house has a certain amount of money stashed. All houses at this place are arranged in a circle. That means the first house is the neighbor of the last one. Meanwhile, adjacent houses have security system connected and it will automatically contact the police if two adjacent houses were broken into on the same night.
Given a list of non-negative integers representing the amount of money of each house, determine the maximum amount of money you can rob tonight without alerting the police.

简单题里面也有一道House Robber I,在给出的数组里取数字,不能相邻,找出和最大的取法,返回和。这道题把数组收尾相接,也就是取了第一个数就不能取最后一个数,取了最后一个数就不能取第一个数。

这里其实已经看出来,该问题可以简化为House Robber I,只要找到nums[0]到nums[n-2]的最优解,再找到nums[1]到nums[n-1]的最优解,再比较大小就可以了。时间O(n)。

class Solution {
public:
    int rob(vector<int>& nums) {
        
        if(nums.size()==0) return 0;
        if(nums.size()<3) return max(nums[0],nums.back());
        
        vector<int> dp(nums.size());
        int i,val1,val2;
        
        dp[0]=nums[0];
        dp[1]=max(dp[0],nums[1]);
        
        for(i=2;i<nums.size()-1;i++) {
            
            dp[i]=max(dp[i-1],nums[i]+dp[i-2]);
        }
        val1=dp[i-1];
        
        dp[1]=nums[1];
        dp[2]=max(dp[1],nums[2]);
        
        for(i=3;i<nums.size();i++) {
            
            dp[i]=max(dp[i-1],nums[i]+dp[i-2]);
        }
        val2=dp[i-1];
        
        return max(val1, val2);
    }
};

改进:使用O(n)的空间,将House Robber I的计算作为函数调用(高赞回答@jianchao-li):

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size(); 
        if (n < 2) return n ? nums[0] : 0;
        return max(robber(nums, 0, n - 2), robber(nums, 1, n - 1));
    }
private:
    int robber(vector<int>& nums, int l, int r) {
        int pre = 0, cur = 0;
        for (int i = l; i <= r; i++) {
            int temp = max(pre + nums[i], cur);
            pre = cur;
            cur = temp;
        }
        return cur;
    }
};

221. Maximal Square
Given a 2D binary matrix filled with 0’s and 1’s, find the largest square containing only 1’s and return its area.

给出只含0和1的二维数组,找到其中由1构成的最大正方形面积。
如果用Area(i,j)表现用matrix[i][j]作为右下角的最大正方形面积,这道题的关键点在于发现:如果matrix[i][j]=0,Area(i,j)=0;如果matrix[i][j]=1,Area(i,j)取决于Area(i-1,j),Area(i-1,j-1)和Area(i,j-1)的最小值(高赞回答@jianchao-li)。

class Solution {
public:
    int maximalSquare(vector<vector<char>>& matrix) {
        if (matrix.empty()) {
            return 0;
        }
        int m = matrix.size(), n = matrix[0].size(), sz = 0;
        vector<vector<int>> dp(m, vector<int>(n, 0));
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (!i || !j || matrix[i][j] == '0') {
                    dp[i][j] = matrix[i][j] - '0';
                } else {
                    dp[i][j] = min(dp[i - 1][j - 1], min(dp[i - 1][j], dp[i][j - 1])) + 1;
                }
                sz = max(dp[i][j], sz);
            }
        }
        return sz * sz;
    }
};

你可能感兴趣的:(Leetcode题解)