- 三角形最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
采用动态规划求解,可以自顶向下也可以自底向上
方法一 递归&&备忘录递归 超时
递归
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
return helper(triangle,0,0);
}
int helper(vector<vector<int>> triangle,int i,int j){
if(i==triangle.size()-1) return triangle[i][j];
int lt = helper(triangle,i+1,j);
int rt = helper(triangle,i+1,j+1);
return min(lt, rt) + triangle[i][j];
}
};
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<vector<int>> dp(triangle);
for(int i=triangle.size()-2; i>=0; i--)
for(int j=0; j<triangle[i].size(); j++) dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + dp[i][j];
return dp[0][0];
}
};
方法二 动态规划
自底而上,第i行的最小路径和 = 第i+1的最小路径和 + 第i行路径值
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<vector<int>> dp(triangle);
for(int i=triangle.size()-2; i>=0; i--)
for(int j=0; j<triangle[i].size(); j++) dp[i][j] = min(dp[i+1][j],dp[i+1][j+1]) + dp[i][j];
return dp[0][0];
}
};
优化,在原来数组基础上动作
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
for(int i=triangle.size()-2; i>=0; i--)
for(int j=0; j<triangle[i].size(); j++)
triangle[i][j] = min(triangle[i+1][j],triangle[i+1][j+1]) + triangle[i][j];
return triangle[0][0];
}
空间压缩
class Solution {
public:
int minimumTotal(vector<vector<int>>& triangle) {
vector<int> dp(triangle.size()+1,0);
for(int i=triangle.size()-1; i>=0; i--)
for(int j=0; j<triangle[i].size(); j++) dp[j] = min(dp[j],dp[j+1]) + triangle[i][j];
return dp[0];
}
};
- 买股票的最佳时机
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。
因为只能买入后再卖,因此遍历一次即可,先更新最大收益,再更新最低价格,遍历完成即可
class Solution {
public:
int maxProfit(vector<int>& prices)
{
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices)
{
maxprofit = max(maxprofit, price - minprice);
minprice = min(price, minprice);
}
return maxprofit;
}
};
- 买卖股票的最佳时机2
本题和上题的区别在于可以多次买入,但是一次只能有一笔交易
做法其实大同小异,增加一个判断:如果价格会降就卖出再买入就好了
class Solution {
public:
int maxProfit(vector<int>& prices) {
int ret = 0;
int inf = 1e9;
int minprice = inf, maxprofit = 0;
for (int price: prices)
{
if (price - minprice > maxprofit)
{
maxprofit = price - minprice;
}
else
{
minprice = price;
ret += maxprofit;
maxprofit = 0;
}
}
return ret + maxprofit;
}
};
但是就本题而言,其实还有更简单的方法:直接把每次递增的收益加起来就OK了
class Solution {
public:
int maxProfit(vector<int>& prices) {
int maxprofit = 0;
for (int i = 1; i < prices.size(); i++)
{
if (prices[i] > prices[i - 1])
maxprofit += prices[i] - prices[i - 1];
}
return maxprofit;
}
};
- 买股票3
和上题区别在于只可以买卖两次,所以需要考虑最大收益
状态定义
f[i][j][k]表示第i天,进行了j次交易,当前状态为k时的最大利润
该题中j<=2j<=2
k=0表示没有持有股票,k=1表示持有股票
状态转移方程
f[i][j][0] = max(f[i - 1][j][1] + prices[i - 1], f[i - 1][j][0])
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - prices[i - 1])
初始状态定义
f[i][0][0] = 0
f[i][0][1] = -inf
f[0][i][0] = -inf
f[0][i][1] = -inf
class Solution {
public:
int maxProfit(vector<int> &prices)
{
const int inf = 1 << 30;
const int n = prices.size();
int f[30000 + 5][3][2];
for (int i = 0; i <= n; i++)
{
f[i][0][0] = 0;
f[i][0][1] = -inf;
}
for (int i = 1; i <= 2; i++)
{
f[0][i][0] = -inf;
f[0][i][1] = -inf;
}
for (int i = 1; i <= n; i++)
{
for (int j = 0; j <= 2; j++)
{
f[i][j][0] = max(f[i - 1][j][1] + prices[i - 1], f[i - 1][j][0]);
if (j)
{
f[i][j][1] = max(f[i - 1][j][1], f[i - 1][j - 1][0] - prices[i - 1]);
}
}
}
return max(max(f[n][0][0], f[n][1][0]), f[n][2][0]);
}
};
- 二叉树的最大路径和
给定一个非空二叉树,返回其最大路径和。
b + a + c。
b + a + a 的父结点。
c + a + a 的父结点。
其中情况 1,表示如果不联络父结点的情况,或本身是根结点的情况。
这种情况是没法递归的,但是结果有可能是全局最大路径和。
情况 2 和 3,递归时计算 a+b 和 a+c,选择一个更优的方案返回,也就是上面说的递归后的最优解啦。
另外结点有可能是负值,最大和肯定就要想办法舍弃负值(max(0, x))(max(0,x))。
但是上面 3 种情况,无论哪种,a 作为联络点,都不能够舍弃。
代码中使用 val 来记录全局最大路径和。
ret 是情况 2 和 3。
lmr 是情况 1。
所要做的就是递归,递归时记录好全局最大和,返回联络最大和。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution
{
public:
int maxPathSum(TreeNode* root, int &val)
{
if (root == nullptr) return 0;
int left = maxPathSum(root->left, val);
int right = maxPathSum(root->right, val);
int lmr = root->val + max(0, left) + max(0, right);
int ret = root->val + max(0, max(left, right));
val = max(val, lmr);
return ret;
}
int maxPathSum(TreeNode* root)
{
int val = INT_MIN;
maxPathSum(root, val);
return val;
}
};
- 验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
本题难点在于需要排除空格等其他因素,外加忽略大小写,其他很容易
class Solution {
public:
bool isPalindrome(string s) {
int i = 0, j = s.size() - 1;
while (i < j)
{
while(!isalnum(s[i]) && i < j)
i++; //直到s[left]为字母或数字
while(!isalnum(s[j]) && i < j)
j--; //直到s[right]为字母或数字
if (toupper(s[i]) != toupper(s[j]))
return false;
else
{
i++;
j--;
}
}
return true;
}
};
- 单词接龙2
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典中的单词。
本题本质上是一个回溯法的过程,但是由于需要找到所有变换的序列,会较为复杂。主要重点在于如何优化回溯法的表现,过程较长,可以参考这篇文章
class Solution {
public:
bool sim(const string& s1, const string& s2) {
int diff = 0;
for (int i = 0; i < s1.size(); ++i) {
diff += s1[i] != s2[i];
}
return diff <= 1;
}
void dfs(const vector<vector<int> >& g, const vector<int>& dfn, const vector<string>& wordList,
int i, vector<string>& path, vector<vector<string> >& paths) {
if (dfn[i] == 0) {
vector<string> v(path);
reverse(v.begin(), v.end());
paths.push_back(v);
return;
}
for (auto j : g[i]) {
if (dfn[j] == dfn[i] - 1) {
path.push_back(wordList[j]);
dfs(g, dfn, wordList, j, path, paths);
path.pop_back();
}
}
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
wordList.push_back(beginWord);
int N = wordList.size();
vector<vector<int> > g(N);
int endi = -1;
// 构图
for (int i = 0; i < N; ++i) {
if (wordList[i] == endWord) endi = i;
for (int j = i + 1; j < N; ++j) {
if (sim(wordList[i], wordList[j])) {
g[i].push_back(j);
g[j].push_back(i);
}
}
}
if (endi == -1) return {};
// 层级编号
vector<int> dfn(N, -1);
queue<int> q;
q.push(N - 1);
dfn[N - 1] = 0;
while (!q.empty()) {
auto i = q.front();
q.pop();
for (auto j : g[i]) {
if (dfn[j] == -1) {
dfn[j] = dfn[i] + 1;
q.push(j);
}
}
}
if (dfn[endi] == -1) return {};
// 回溯路径
vector<string> path{endWord};
vector<vector<string> > paths;
dfs(g, dfn, wordList, endi, path, paths);
return paths;
}
};