从记忆化搜索入门动态规划
引导题目:Triangle
给定一个数字三角形,找到从顶部到底部的最小路径和。每一步可以移动到下面一行的相邻数字上。
[1]
[23]
[456]
这种结构的特点是中间的数字可能从不同的路径访问到。因此可以保存到该点的最小路径和。
四种解决方案:1、Traverse 2、Divide Conquer 3、Traditional Dynamic Programming
class Solution {
public:
/**
* @param triangle: a list of lists of integers.
* @return: An integer, minimum path sum.
*/
int minimumTotal(vector<vector<int> > &triangle) {
// write your code here
if (triangle.size() == 0)
return 0;
vector<int> f(triangle[triangle.size()-1].size());
//f[] 代表每个点所在位置的最小的总和,下一层直接调用就可以。
f[0] = triangle[0][0];
for(int i = 1; i < triangle.size(); i++)
for(int j = triangle[i].size() - 1; j >= 0; j--)
// 下一行的最左边,只有一条路径能访问,直接上一层最小值加上该点值
if (j == 0)
f[j] = f[j] + triangle[i][j];
// 下一行的最右边,只有一条路径能访问,直接上一层最小值加上该点值
else if (j == triangle[i].size() - 1)
f[j] = f[j-1] + triangle[i][j];
// 下一行中间的某个点,判断它上面连接的两个点哪个和小,选它并加上该点值。
else
f[j] = min(f[j-1], f[j]) + triangle[i][j];
int ret = INT_MAX;
for(int i = 0; i < f.size(); i++)
ret = min(ret, f[i]);
return ret;
}
};
记忆化搜索可以将指数级别时间复杂度降低到多项式级别,记忆化搜索本质就是动态规划。
不适用DP的三个场景:求所有的具体方案,输入序列无序,暴力算法时间复杂度已经是多项式级别。
例题:Palindrome Partitioning 求出具体方案
给定字符串 s, 需要将它分割成一些子串, 使得每个子串都是回文串.返回所有可能的分割方案.
思路:用DFS就行
class Solution {
public:
vector<vector<string>> partition(string s) {
vector<vector<string>> res;
vector<string> cur;
dfs(s,cur,res);
return res;
}
bool isPalindrome(string s){
return s==string(s.rbegin(),s.rend());
}
void dfs(string s,vector<string> &cur,vector<vector<string>> &res){
if (s==""){
res.push_back(cur);
return;
}
for (int i = 1; i <= s.length(); ++i) {
string sub=s.substr(0,i);
if (isPalindrome(sub)){
cur.push_back(sub);
dfs(s.substr(i,s.length()-i),cur,res);
cur.pop_back();
}
}
}
};
如果只求出一个具体方案,可以加入DP
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。返回符合要求的最少分割次数。
class Solution {
public:
int minCut(string s) {
int len=s.length();
// 状态定义:dp[i]:前缀子串 s[0:i] (包括索引 i 处的字符)符合要求的最少分割次数
int dp[len];
// 1 个字符最多分割 0 次;
// 2 个字符最多分割 1 次;
// 3 个字符最多分割 2 次
// 初始化的时候,设置成为这个最多分割次数
for(int i=0;i<len;i++){
dp[i]=i;
}
//创建二维数组用于记录子串s[a:b]是否为回文串,且一开始全部初始化为false(可以发现a<=b)
vector<vector<bool>> checkPalindrome(len, vector<bool>(len, false));
//根据所给的字符串s,遍历,记录子串s[a:b]是否为回文串
for(int right=0;right<len;right++){
for(int left=0;left<=right;left++){
if(s[left]==s[right] && (right-left<=2 || checkPalindrome[left+1][right-1])){ // "right-left<=2" 和 "checkPalindrome[left+1][right-1]"位置不可换
checkPalindrome[left][right]=true;
}
}
}
// 状态转移方程:
// dp[i] = min(dp[j] + 1 if s[j + 1: i] 是回文 for j in range(i))
for(int i=0;i<len;i++){
if(checkPalindrome[0][i]){
dp[i]=0;
continue;
}
for(int j=0;j<i;j++){
if(checkPalindrome[j+1][i]){
dp[i]=min(dp[i],dp[j]+1);
}
}
}
return dp[len-1];
}
};
输入数据是无序的:最长连续序列
给定一个未排序的整数数组,找出最长连续序列的长度。
时间复杂度:for循环n次,while整个也只会运行n次,O(n+n) = O(n)
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
if(nums.empty()){
return 0;
}
unordered_set<int> myset(nums.begin(), nums.end());
int res = 0;
for(auto num : nums){
// 如果集合内 num-1 个数为0,表示num是连续数组的开头,则运行
if(myset.count(num-1)==0){
int x = num + 1;
// 当后面有数字,x++
while(myset.count(x)){
x ++;
}
// 保存最长的个数
res = max(res, x-num);
}
}
return res;
}
};
暴力算法的复杂度已经是多项式级别:柱状图中最大的矩形(难题)
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。求在该柱状图中,能够勾勒出来的矩形的最大面积。
常规思路要O(n^3),下面栈的思路为O(n)
class Solution {
public:
int largestRectangleArea(vector<int>& heights) {
stack<int> s;
s.push(-1);
int max_area = 0, height, width;
for(int i = 0; i < heights.size(); ++i) {
while(s.top() != -1 && heights[i] <= heights[s.top()]) {
height = heights[s.top()];
s.pop();
width = i-s.top()-1;
//cout<<"height:"<<height<<" width:" <<width<<endl;
max_area = max(max_area, width*height);
//cout<<max_area<<endl;
}
s.push(i);
}
while(s.top() != -1) {
height = heights[s.top()];
s.pop();
width = heights.size() - s.top() - 1;
max_area = max(max_area, width*height);
//cout<<max_area<<endl;
}
return max_area;
}
};
作者:wallcwr
链接:https://leetcode-cn.com/problems/largest-rectangle-in-histogram/solution/dan-diao-di-zeng-zhan-by-wallcwr/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
适用DP的三个场景:求最值,求方案总数,求可行性。(都是很宽泛的一个结果)
题目:Wildcard Matching 通配符匹配 (双序列型动态规划)
判断两个可能包含通配符“?”和“”的字符串是否匹配。匹配规则如下:
‘?’ 可以匹配任何单个字符。
'’ 可以匹配任意字符串(包括空字符串)。
两个串完全匹配才算匹配成功。
class Solution {
public:
/**
* @param s: A string
* @param p: A string includes "?" and "*"
* @return: A boolean
*/
bool isMatch(const char *s, const char *p) {
if (s == NULL || p == NULL) {
return false;
}
// 字符串转bool
int n = strlen(s);
int m = strlen(p);
int f[n + 1][m + 1];
// f 填充 false
memset(f, false, sizeof(f));
f[0][0] = true;
// f[][0] 全部填充false
for (int i = 1; i <= n; i++)
f[i][0] = false;
// f[0][] 填充false
for (int i = 1; i <= m; i++)
f[0][i] = f[0][i - 1] && p[i - 1] == '*';
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= m; j++) {
if (p[j - 1] == '*') {
f[i][j] = f[i - 1][j] || f[i][j - 1];
} else if (p[j - 1] == '?') {
f[i][j] = f[i - 1][j - 1];
} else {
f[i][j] = f[i - 1][j - 1] && (s[i - 1] == p[j - 1]);
}
}
} // for
return f[n][m];
}
};