状态和状态转移方程。状态可以根据子问题定义,状态方程就是描述状态是怎么转移的,它的的本质不在于是递推或是递归,也不需要纠结是不是内存换时间。
(1)最大和子数组O(n):
假设A(0, i)区间存在k,使得[k, i]区间是以i结尾区间的最大值, 定义为dp[i], 在这里,当求取dp[i+1]时,有两种情况:
dp[i+1] = dp[i] + A[i+1], if (dp[i] + A[i+1] >0)
= 0, if(dp[i]+A[i+1] <0),如果和小于零,A[i+1]必为负数,舍弃重新开始
然后从左往右扫描,求取dp数字的最大值即为所求。
int maxSubArr(vector<int> nums) { if (nums.empty()) return 0; int sum = nums[0]; // vector<int> dp(nums.size()); //O(n) int dp = nums[0]; //O(1) for (int i = 1; i < nums.size(); i++) { dp = max((dp + nums[i]), nums[i]); sum = max(sum, dp); } return sum; }如果时间复杂度低一些O(n)可以采用分治的方法,采用二分。即最大和子数组区间要么在左半边,要么在右半边,要么横跨两部分。前两种情况可以递归的进行求解。第三种情况则需以以mid为中心,向两侧扫描求最大值。
//O(n^2)解法 int maxArr(vector<int> nums, int begin, int end, int &maxV) { if (begin > end) return INT_MIN; int mid = begin + (end - begin) /2; int lmax = maxArr(nums, begin, mid-1, maxV); int rmax = maxArr(nums, mid+1, end, maxV); maxV = max(max(maxV, lmax), rmax); int sum = 0, mlmax = 0; for (int i = mid-1; i>= begin; i--) { sum += nums[i]; mlmax = max(sum, mlmax); } sum = 0; int mrmax = 0; for (int i = mid+1; i <= end; i++) { sum += nums[i]; mrmax = max(sum, mrmax); } maxV = max(maxV, mlmax + mrmax + nums[mid]); } int maxSubArr(vector<int> nums) { if (nums.empty()) return 0; int maxV = INT_MIN; return maxArr(nums, 0, nums.size()-1, maxV); }
(2)最长升序子序列LIS
定义dp[i],表示前i个数中以A[i]结尾(前i个元素)的最长非降子序列的长度.那么dp[i+1]可以利用如下状态转移方程得到
d[i+1] = max{1, d[j]+1},其中j<i,A[j]<=A[i].即如果A[i+1]>A[j],那么第i+1个元素可以接在dp[i]长的子序列的后面构成一个更长的子序列。与此同时,A[i+1]本身就可以构成一个长度为1的子序列。
//O(n^2)的解法 class Solution { public: int lengthOfLIS(vector<int>& nums) { if (nums.empty()) return 0; vector<int> dp(nums.size(), 1); for (int i = 0; i < nums.size(); i++) {//对于第i+1个元素,不考虑前i个元素的情况 for (int j = 0; j < i; j++) { if (nums[i] > nums[j] && (dp[j] + 1 > dp[i])) dp[i] = dp[j] + 1; } } return *(max_element(dp.begin(), dp.end())); } }; <pre name="code" class="cpp">/*O(nlogn)的解法 利用一个数组纪录递增子序列的尾元素,即数组下标为i对应的元素是长度为i的子序列的尾元素 */ Class Solution() { public: int lengthOfLIS(vector<int> &nums) { vector<int> ret; for (int i = 0; i < nums.size(); i++) { auto it = upper_bound(ret.begin(), ret.end(), nums[i]);//返回的是大于某个数的第一个数的位置 if(it == ret.end()) ret.push_back(nums[i]); else *it = nums[i]; } return ret.size(); } };
1. Minimum Path Sum:给一个矩阵,从上至下从左至右的走,求路径最小和。
注意初始条件。设dp[i][j]表示走到A[i][j]时,路径中所包含的最小和。状态转移方程dp[i][j]=min(dp[i-1][j], dp[i][i-1])+A[i][j-1]
int minPathSum(vector<vector<int> > &mat) { if(mat.empty()) return 0; int rows = mat.size(); int cols = mat[0].size(); vector<vector <int>> dp(rows, vector<int>(cols, 0)); dp[0][0] = mat[0][0]; for (int i = 1; i < rows; i++) dp[i][0] = dp[i-1][0] + mat[i][0]; for (int j = 1; j < cols; j++) dp[0][j] = dp[0][j-1] + mat[0][j]; for (int i = 1; i < rows; i++) { for (int j = 1; j < cols; j++) { dp[i][j] = (dp[i-1][j], dp[i][j-1]) + mat[i][j]; } } return dp[rows-1][cols-1]; }2. Interleaving String:给定三个字符串s1, s2, s3,判断s3是不是由前两个字符串交替组成的。
设dp[i][j]表示s1取i长度的字符,最后一个字符是s1[i-1],s2取j个长度的字符串,最后一个字符是s2[j-1],能否与s3(i+j)长度的字符串匹配上。
边界条件是,其中一个字符串长度为0,就用另一个字符串去匹配s3
bool table[s1.length()+1][s2.length()+1]; for(int i=0; i<s1.length()+1; i++) for(int j=0; j< s2.length()+1; j++){ if(i==0 && j==0) table[i][j] = true; else if(i == 0) table[i][j] = ( table[i][j-1] && s2[j-1] == s3[i+j-1]); else if(j == 0) table[i][j] = ( table[i-1][j] && s1[i-1] == s3[i+j-1]); else table[i][j] = (table[i-1][j] && s1[i-1] == s3[i+j-1] ) || (table[i][j-1] && s2[j-1] == s3[i+j-1] ); } return table[s1.length()][s2.length()]; }