买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
示例 1:
输入:[7,1,5,3,6,4]
输出:5
解释:在第 2 天(股票价格 = 1)的时候买入,在第 5 天(股票价格 = 6)的时候卖出,最大利润 = 6-1 = 5 。
注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格;同时,你不能在买入前卖出股票。
示例 2:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 104
解题思路,顺序遍历数组,记录最大利润以及最小股票价格。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
int minPrice = prices[0];
for(int i = 1; i < prices.size(); ++i) {
res = max(prices[i]-minPrice, res);
minPrice = min(prices[i], minPrice);
}
return res;
}
};
买卖股票的最佳时机 II
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入: [7,1,5,3,6,4]
输出: 7
解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。
示例 2:
输入: [1,2,3,4,5]
输出: 4
解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入: [7,6,4,3,1]
输出: 0
解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。
提示:
1 <= prices.length <= 3 * 10 ^ 4
0 <= prices[i] <= 10 ^ 4
解题思路,可以顺序遍历数组,寻找每一个升序,将每一个升序的值累加起来得到总利润值。
class Solution {
public:
int maxProfit(vector<int>& prices) {
int res = 0;
int minPrice = prices[0];
int resTmp = 0;
for(int i = 1; i < prices.size(); ++i) {
if(prices[i] <= prices[i-1]) {
minPrice = prices[i];
res += resTmp;
resTmp = 0;
}else {
resTmp = prices[i] - minPrice;
}
}
res += resTmp;
return res;
}
};
买卖股票的最佳时机 III
给定一个数组,它的第 i 个元素是一支给定的股票在第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成 两笔 交易。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例 1:
输入:prices = [3,3,5,0,0,3,1,4]
输出:6
解释:在第 4 天(股票价格 = 0)的时候买入,在第 6 天(股票价格 = 3)的时候卖出,这笔交易所能获得利润 = 3-0 = 3 。
随后,在第 7 天(股票价格 = 1)的时候买入,在第 8 天 (股票价格 = 4)的时候卖出,这笔交易所能获得利润 = 4-1 = 3 。
示例 2:
输入:prices = [1,2,3,4,5]
输出:4
解释:在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。
注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。
因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。
示例 3:
输入:prices = [7,6,4,3,1]
输出:0
解释:在这个情况下, 没有交易完成, 所以最大利润为 0。
示例 4:
输入:prices = [1]
输出:0
提示:
1 <= prices.length <= 105
0 <= prices[i] <= 105
解题思路,可以顺序遍历数组,寻找每一个升序,保存两个较大的升序的值,然后返回结果。错误,eg. [1,2,4,2,5,7,2,4,9,0],相隔的两个升序数组未必是最大利润。
后续思考并尝试一段时间,放弃了寻找升序的方法。尝试使用动态规划的方法去解决问题。
动态规划,状态定义,每天的状态包含不买也不卖,第一次买入,第一次卖出,第二次买入和第二次卖出。
这五种状态可以简化成四种,因为第一种不买也不卖的状态利润永远都是0。剩余的四种状态利润分别用 firBuy,firSell,secBuy,secSell。
首先定义状态的初始值,firBuy = -prices[0], firSell = 0, secBuy = -prices[0], secSell = 0;
然后写出状态转移方程:(假设当天的价格为 p)
firBuy = max(firBuy, -p); //第一次购买的利润实际上是找之前的最低价格买入。
firSell = max(firSell, p+firBuy); //第一次卖出的利润是当天卖出或者之前利润的最大值。
secBuy = max(secBuy, firSell-p); //第二次购买的利润是之前利润或者当天第一次卖出然后再买入的利润最大值。
secSell = max(secSell, p+secBuy); //第二次卖出的利润是之前利润或者当天第二次卖出的利润最大值
class Solution {
public:
int maxProfit(vector<int>& prices) {
int firBuy = -prices[0], firSell = 0;
int secBuy = -prices[0], secSell = 0;
for(auto p : prices) {
firBuy = max(firBuy, -p);
firSell = max(firSell, p+firBuy);
secBuy = max(secBuy, firSell-p);
secSell = max(secSell, p+secBuy);
}
return secSell;
}
};
二叉树中的最大路径和
路径:被定义为一条从树中任意节点出发,沿父节点-子节点连接,达到任意节点的序列。同一个节点在一条路径序列中 至多出现一次 。该路径 至少包含一个 节点,且不一定经过根节点。
路径和:是路径中各节点值的总和。
给你一个二叉树的根节点 root ,返回其 最大路径和 。
示例 1:
输入:root = [1,2,3]
输出:6
解释:最优路径是 2 -> 1 -> 3 ,路径和为 2 + 1 + 3 = 6
示例 2:
输入:root = [-10,9,20,null,null,15,7]
输出:42
解释:最优路径是 15 -> 20 -> 7 ,路径和为 15 + 20 + 7 = 42
提示:
树中节点数目范围是 [1, 3 * 104]
-1000 <= Node.val <= 1000
解题思路
利用中序遍历,找出最大的升序数组,失败,原因是因为中序的时候右节点和其父节点的父节点相连。
然后想到可以用递归的方式找到左子树和右子树的最大值,然后比较 父节点,子左节点,子右节点 三个节点组合的最大值。
需要注意的点是:
1 当开始递归的时候,需要考虑左节点和右节点只能考虑其中之一;
2 而题目需要找到的最大值也有可能是递归的时候找到的;
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int resMax = INT_MIN;
int getMaxSum(TreeNode* root) {
if(root == nullptr) return 0;
int ltmp = max(0, getMaxSum(root->left));
int rtmp = max(0, getMaxSum(root->right));
resMax = max(resMax, root->val + ltmp + rtmp);
return max(ltmp, rtmp) + root->val;
}
int maxPathSum(TreeNode* root) {
getMaxSum(root);
return resMax;
}
};
验证回文串
给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。
说明:本题中,我们将空字符串定义为有效的回文串。
示例 1:
输入: "A man, a plan, a canal: Panama"
输出: true
示例 2:
输入: "race a car"
输出: false
解题思路
判断回文串首先想到的就是双指针,一个首一个尾,判断首尾是否相等。
所以重点就在于如何过滤,因为只考虑字母和数字,忽略大小写。
做法:
首尾都需要满足符合数字和字母才进行判断;
判断的时候,先判断是否相等,然后再都转成大写再判断是否相等;
class Solution {
public:
bool isPalindrome(std::string s) {
auto start = s.begin(), end = s.end()-1;
while(start < end) {
if((isdigit(*start) || isalpha(*start)) && (isdigit(*end) || isalpha(*end))) {
if((*start != *end) && (toupper(*start) != toupper(*end))) {
return false;
}
start++;
end--;
}else if (!(isdigit(*start) || isalpha(*start))){
start++;
}else {
end--;
}
}
return true;
}
};
单词接龙 II
给定两个单词(beginWord 和 endWord)和一个字典 wordList,找出所有从 beginWord 到 endWord 的最短转换序列。转换需遵循如下规则:
- 每次转换只能改变一个字母。
- 转换后得到的单词必须是字典中的单词。
说明:
- 如果不存在这样的转换序列,返回一个空列表。
- 所有单词具有相同的长度。
- 所有单词只由小写字母组成。
- 字典中不存在重复的单词。
- 你可以假设 beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
输入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
输出:
[
["hit","hot","dot","dog","cog"],
["hit","hot","lot","log","cog"]
]
示例 2:
输入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
输出: []
解释: endWord "cog" 不在字典中,所以不存在符合要求的转换序列。
解题思路
这道题可以说是搜索剪枝的典型了,需要找到最短路径长度(利用bfs),需要打印出全部的最短路径(利用dfs+回溯),回溯的时候需要记忆化,否则有可能会陷入死胡同~
再接再厉!
class Solution {
public:
vector<vector<string>> res;
string endW;
map<string, unordered_set<string> > transMap;
unordered_set<string> memo;
//利用广度搜索建立索引并且找到最短路径
void buildTree(vector<string> path, unordered_set<string> wordList) {
vector<string> vs;
for(auto iter = path.begin(); iter != path.end(); ++iter) {
for(int i = 0; i < (*iter).size(); ++i) {
char tmp = 'a';
string str = *iter;
while(tmp <= 'z') {
swap(tmp, str[i]);
if(wordList.count(str)) {
transMap[*iter].insert(str);
if(find(vs.begin(), vs.end(), str) == vs.end()) vs.push_back(str);
}
swap(tmp, str[i]);
tmp++;
}
}
}
if(vs.size() > 0) {
for(auto s : vs) {
wordList.erase(s);
}
if((find(vs.begin(), vs.end(), endW) == vs.end()))buildTree(vs, wordList);
}
}
//利用回溯找到路径,并且需要添加 memo 记录路径是否成功
bool findPath(vector<string> path) {
string end = *path.rbegin();
if(memo.count(end)) return false;
int level = path.size() - 1;
int flag = false;
if(end == endW) {
res.push_back(path);
return true;
}else {
if(transMap.find(end) != transMap.end()) {
for(auto str : transMap[end]) {
path.push_back(str);
if(findPath(path)) flag = true;
path.pop_back();
}
}
}
if(!flag) memo.insert(end);
return flag;
}
vector<vector<string>> findLadders(string beginWord, string endWord, vector<string>& wordList) {
auto iter = find(wordList.begin(), wordList.end(), endWord);
if(iter != wordList.end()) {
unordered_set<string> uns(wordList.begin(), wordList.end());
if(uns.count(beginWord)) uns.erase(beginWord);
endW = endWord;
vector<string> path = {beginWord};
buildTree(path, uns);
findPath(path);
}
return res;
}
};
单词接龙
字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:
- 序列中第一个单词是 beginWord 。
- 序列中最后一个单词是 endWord 。
- 每次转换只能改变一个字母。
- 转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。
解题思路
和上一道题一样,甚至不需要回溯找路径,直接利用 bfs 即可。
class Solution {
public:
int res;
bool isFound = false;
string endW;
map<string, unordered_set<string> > transMap;
//利用广度搜索建立索引并且找到最短路径
void buildTree(vector<string> path, unordered_set<string> wordList) {
vector<string> vs;
for(auto iter = path.begin(); iter != path.end(); ++iter) {
for(int i = 0; i < (*iter).size(); ++i) {
char tmp = 'a';
string str = *iter;
while(tmp <= 'z') {
swap(tmp, str[i]);
if(wordList.count(str)) {
transMap[*iter].insert(str);
if(find(vs.begin(), vs.end(), str) == vs.end()) vs.push_back(str);
}
swap(tmp, str[i]);
tmp++;
}
}
}
if(vs.size() > 0) {
res++;
for(auto s : vs) {
wordList.erase(s);
}
if((find(vs.begin(), vs.end(), endW) == vs.end()))buildTree(vs, wordList);
else isFound = true;
}
}
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
auto iter = find(wordList.begin(), wordList.end(), endWord);
if(iter != wordList.end()) {
unordered_set<string> uns(wordList.begin(), wordList.end());
if(uns.count(beginWord)) uns.erase(beginWord);
endW = endWord;
vector<string> path = {beginWord};
buildTree(path, uns);
}
return isFound ? res + 1 : 0;
}
};
最长连续序列
给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。
进阶:你可以设计并实现时间复杂度为 O(n) 的解决方案吗?
示例 1:
输入:nums = [100,4,200,1,3,2]
输出:4
解释:最长数字连续序列是 [1, 2, 3, 4]。它的长度为 4。
示例 2:
输入:nums = [0,3,7,2,5,8,4,6,0,1]
输出:9
解题思路,看到题目很容易的想到用排序可以解决问题,但是看到进阶提示可以得出这道题的目的并不是要求用排序去解决,因为排序的时间复杂度都不满足;
那么既然排序算法不能满足时间复杂度,那么剩下的策略就是利用更多的空间去减少时间;
思路就是利用 unordered_set 来判断数组内的数字是否存在相邻数字;如果判断上下两边都判断的话,可能会做重复的工作,所以最好的办法就是只从一个连续数组的端点去开始搜索;
class Solution {
public:
int longestConsecutive(vector<int>& nums) {
unordered_set<int> nums_set;
for(auto n : nums) nums_set.insert(n);
int res = 0;
for(auto num : nums) {
if(!nums_set.count(num - 1)) {
int temp = num;
int resTmp = 1;
while(nums_set.count(temp + 1)) {
++temp;
++resTmp;
}
res = max(res, resTmp);
}
}
return res;
}
};
求根节点到叶节点数字之和
给你一个二叉树的根节点 root ,树中每个节点都存放有一个 0 到 9 之间的数字。
每条从根节点到叶节点的路径都代表一个数字:
例如,从根节点到叶节点的路径 1 -> 2 -> 3 表示数字 123 。
计算从根节点到叶节点生成的 所有数字之和 。
叶节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3]
输出:25
解释:
从根到叶子节点路径 1->2 代表数字 12
从根到叶子节点路径 1->3 代表数字 13
因此,数字总和 = 12 + 13 = 25
示例 2:
输入:root = [4,9,0,5,1]
输出:1026
解释:
从根到叶子节点路径 4->9->5 代表数字 495
从根到叶子节点路径 4->9->1 代表数字 491
从根到叶子节点路径 4->0 代表数字 40
因此,数字总和 = 495 + 491 + 40 = 1026
提示:
树中节点的数目在范围 [1, 1000] 内
0 <= Node.val <= 9
树的深度不超过 10
解题思路,利用 bfs 并且修改原节点的 val;
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int sumNumbers(TreeNode* root) {
int sum = 0;
queue<TreeNode *> qtn;
qtn.push(root);
while(!qtn.empty()) {
int size = qtn.size();
for(int i = 0; i < size; ++i) {
TreeNode* tmp = qtn.front();
qtn.pop();
if(!tmp->left && !tmp->right) {
sum += tmp->val;
}
if(tmp->left) {
tmp->left->val += tmp->val * 10;
qtn.push(tmp->left);
}
if(tmp->right) {
tmp->right->val += tmp->val * 10;
qtn.push(tmp->right);
}
}
}
return sum;
}
};
被围绕的区域
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
示例 1:
输入:board = [["X","X","X","X"],["X","O","O","X"],["X","X","O","X"],["X","O","X","X"]]
输出:[["X","X","X","X"],["X","X","X","X"],["X","X","X","X"],["X","O","X","X"]]
解释:被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
示例 2:
输入:board = [["X"]]
输出:[["X"]]
提示:
m == board.length
n == board[i].length
1 <= m, n <= 200
board[i][j] 为 'X' 或 'O'
解题思路,题目要求的是把被 ‘X’ 包围的 ‘O’ 改成 ‘X’;实例 1 的解释“被围绕的区间不会存在于边界上,换句话说,任何边界上的 ‘O’ 都不会被填充为 ‘X’。”也表明了这道题可以围绕着边界去解决,即从边界出发,把能够走到的 ‘O’ 打上标记。最后处理没有标记的 ‘O’;
class Solution {
public:
vector<vector<int>> flag;
vector<vector<char>> Board;
int m, n;
void dfs(int x, int y) {
if (x < 0 || x >= m || y < 0 || y >= n || Board[x][y] == 'X' || flag[x][y]) {
return;
}
flag[x][y] = 1;
dfs(x + 1, y);
dfs(x - 1, y);
dfs(x, y + 1);
dfs(x, y - 1);
}
void solve(vector<vector<char>>& board) {
m = board.size();
n = (*board.begin()).size();
if(m <= 2 || n <= 2) return;
vector<vector<int>> flagTmp(m, vector(n, 0));
flag = flagTmp;
Board = board;
for(int i = 1; i < m - 1; ++i) {
dfs(i, 0);
dfs(i, n - 1);
}
for(int j = 1; j < n - 1; ++j) {
dfs(0, j);
dfs(m - 1, j);
}
for(int i = 1; i < m - 1; i++) {
for(int j = 1; j < n - 1; ++j) {
if(board[i][j] == 'O' && !flag[i][j]) {
board[i][j] = 'X';
}
}
}
}
};