6.字梯游戏
- 给定两个单词start和end,以及一本字典,找到由start到end的最短变换路径,每一次变换只允许改变一个字母,且变换后的单词必须出现在字典中。比如给出start为"hit"而end为"cog",字典为["hot","dot","dog","lot","log"],那么应当返回5,因为最短路径是:"hit" -> "hot" -> "dot" -> "dog" -> "cog"
- 思路:函数参数中unordered_set类型参数说明字典是用散列表维护的。题目就是图论中最短路径算法的简单应用,同样使用散列表为词典中的每一个单词维护一个域m_ladderLens,记录与start顶点的距离(即变换的次数):具体的,对某个顶点,遍历所有存在于字典中的变换(邻接顶点),挑选出m_ladderLens值小于顶点自己的m_ladderLens值+1的,对这些顶点重复该过程。
- 实现:
class Solution { public: int ladderLength(string start, string end, unordered_set<string> &dict) { m_wordLen = start.length(); for (unordered_set<string>::iterator i=dict.begin(); i!=dict.end(); i++){ m_ladderLens[*i]=INT_MAX; } m_ladderLens[start]=1; _ladderLength(start, end, dict); if (m_ladderLens[end] == INT_MAX){ m_ladderLens[end] = 0; } return m_ladderLens[end]; } private: void _ladderLength(string start, string end, unordered_set<string> &dict){ for (int i=0; i<=m_wordLen-1; i++){ for (int j=0; j<=26-1; j++){ string s = start; s[i] = 'a'+j; if (s!=start && dict.find(s)!=dict.end() && m_ladderLens[s]>m_ladderLens[start]+1) { m_ladderLens[s] = m_ladderLens[start]+1; if (s != end){ _ladderLength(s, end, dict); } } } } } int m_wordLen; unordered_map<string, int> m_ladderLens; };
7.字梯游戏2
- 给定两个单词start和end,以及一本字典,找到由start到end的最短变换路径,每一次变换只允许改变一个字母,且变换后的单词必须出现在字典中。但是需要输出所有最短的路径(即使有多条,也要一起输出)。比如给出start为"hit"而end为"cog",字典为["hot","dot","dog","lot","log"],那么需要输出:
[ ["hit","hot","dot","dog","cog"], ["hit","hot","lot","log","cog"] ]
- 思路:与6几乎一致,但是输出不一样,需要在迭代过程中添加一个量currentLadders,对某个顶点,记录从start点到该点的最短路径。(如果有多条最短的路径,此时currentLadders中只有一条,它表示迭代到该函数的过程中产生的那一条。)
- 实现:
class Solution { public: vector<vector<string>> findLadders(string start, string end, unordered_set<string> &dict) { m_ladders = vector<vector<string>>(); m_wordLen = start.size(); for (unordered_set<string>::iterator i=dict.begin(); i!=dict.end();i++){ m_ladderLens[*i]=INT_MAX; } m_ladderLens[start] = 1; _ladderLength(start, end, dict); shortestLen = m_ladderLens[end]==INT_MAX ? 0 : m_ladderLens[end]; _findLadders(start, end, vector<string>(), dict); return m_ladders; } private: void _findLadders(const string& start, const string& end, vector<string> currentLadders, unordered_set<string>& dict){ currentLadders.push_back(start); if (start==end && currentLadders.size()==shortestLen){ m_ladders.push_back(currentLadders); return; } for (int i=0; i<=m_wordLen-1; i++){ for (int j=0; j<=26-1; j++){ string s = start; s[i] = 'a'+j; if (s!=start && dict.find(s)!=dict.end() && m_ladderLens[s]>=m_ladderLens[start]+1){ m_ladderLens[s]=m_ladderLens[start]+1; _findLadders(s, end, currentLadders, dict); } } } } void _ladderLength(string start, string end, unordered_set<string> &dict){ for (int i=0; i<=m_wordLen-1; i++){ for (int j=0; j<=26-1; j++){ string s = start; s[i] = 'a'+j; if (s!=start && dict.find(s)!=dict.end() && m_ladderLens[s]>m_ladderLens[start]+1){ m_ladderLens[s] = m_ladderLens[start]+1; if (s != end){ _ladderLength(s, end, dict); } } } } } int m_wordLen; unordered_map<string, int> m_ladderLens; int shortestLen; vector<vector<string>> m_ladders; };
8.回文验证
- 给定一个字符串,判断其是否是回文,只考虑数字和字母部分,标点符号略去。同样,大小写一致的字母也认为是一样的。比如字符串
"A man, a plan, a canal: Panama"就是回文,而
"race a car"则不是回文。
- 思路:很简单。
- 实现:
class Solution { public: bool isPalindrome(string s){ for (int i=0, j=s.size()-1; i<=j; ){ if (!isAlphanumeric(s[i])){ i++; continue; } if (!isAlphanumeric(s[j])){ j--; continue; } if (tolower(s[i])==tolower(s[j])){ i++; j--; continue; } return false; } return true; } private: bool isAlphanumeric(char c){ if (c>='a' && c<='z'){return true;} if (c>='A' && c<='Z'){return true;} if (c>='1' && c<='9'){return true;} if (c=='0'){return true;} return false; } };
9.二叉树最大路径和
- 给出一棵最大二叉树,找到其“最大路径和”。一条路径,可以从二叉树的任何一个节点开始,并到任何一个节点结束。路径和就是这一条路径上所有节点的值的和。需要考虑负数节点。比如,给出这样一棵二叉树:
1 / \ 2 3
需要返回6,因为路径2-3-1的路径和为6(2+3+1)。
- 思路:这个题的陷阱是,可能出现负数值的节点,我已开始就漏考虑了,而认为最大路径至少有一个端点是叶子节点,另一个端点是根节点或叶子节点。可能有负数值的节点的情况下,这样考虑:
- 为每个节点考虑两个属性,最大路径和(即要求的,记为S)和有一个端点为根节点的最大路径和(记为P)。
- 某个节点的S是以下这几个值当中最大的:
- 左节点的S(如果左节点存在的话)
- 右节点的S(如果右节点存在的话)
- 左节点的P(如果左节点存在且P>0,否则算0,就当没取它,以根节点为一个端点)+右节点的P(同左节点,若两者都不去,就是根节点一个端点组成的路径)+节点自身的值
- 某个节点的P是以下这几个值中最大的:
- 左节点的P(如果左节点存在且P>0,否则算0,就当没取它,仅有根节点一个节点组成的路径)+节点自身的值
- 右节点的P(同上)+节点自身值
- 实现:
/** * Definition for binary tree * 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 sum = INT_MIN; if (root->left != NULL){ int sum_left = maxPathSum(root->left); sum = max(sum, sum_left); } if (root->right != NULL){ int sum_right = maxPathSum(root->right); sum = max(sum, sum_right); } int sum_root = (root->left==NULL?0:max(0,maxPathSumToRoot(root->left))) + root->val + (root->right==NULL?0:max(0,maxPathSumToRoot(root->right))); sum = max(sum, sum_root); return sum; } private: int maxPathSumToRoot(TreeNode *root){ int sum = INT_MIN; if (root->left != NULL){ int sum_left = root->val + max(0, maxPathSumToRoot(root->left)); sum = max(sum, sum_left); } if (root->right != NULL){ int sum_right = root->val + max(0, maxPathSumToRoot(root->right)); sum = max(sum, sum_right); } sum = max(sum, root->val); return sum; } };
10.股价
- 给定一个数组prices,表示第 i 天的股价,你总共只能买入并卖出1股,请确定买入和卖出的时机,使收益最大。
- 思路:简单,从前向后遍历数组,维护一个域记录已遍历的天数中股价最低的那一天,然后用那一天的股价减去股价最低的那天的股价,获得一个收益。取最大的收益。
- 实现:
class Solution { public: int maxProfit(vector<int> &prices) { int minIndex = 0; int maxProfitValue = 0; for (int i=0; i<=int(prices.size())-1; i++) { if (prices[i]<prices[minIndex]) { minIndex = i; } int profit = prices[i]-prices[minIndex]; maxProfitValue = maxProfitValue<profit ? profit : maxProfitValue; } return maxProfitValue; } };
11.股价2
- 给定一个数组prices,表示第 i 天的股价,你每天都可以进行一次交易(买入或卖出一股),请确定买入和卖出的时机,使收益最大。
- 思路:简单,直观地看,如果第 i 天的股价低于第 i+1 天,那么就在第 i 天买进,第 i+1天卖出。
- 实现:
class Solution { public: int maxProfit(vector<int> &prices) { int maxProfitValue = 0; for (int i=0; i<=int(prices.size())-2; i++){ if (prices[i]<prices[i+1]){ maxProfitValue+=prices[i+1]-prices[i]; } } return maxProfitValue; } };
12.股价3
- 给定一个数组prices,表示第 i 天的股价,你只能依次进行两次交易(买入-卖出-买入-卖出),请确定买入和卖出的时机,使收益最大。
- 思路:一开始的想法是,把10中的代码拿过来稍作修改,然后将数组分割成[0...i]和[i...n-1]两个部分,最大收益就是两个部分最大收益之和(每个部分进行一次交易)。遍历 i 找出最大的。但是这样太耗时间了,后来看到uniEagle提供的思路,才恍然大悟。其实10中找出一段时间[i...j]内最大收益的过程就已经解决了[i...k]的子问题,只要维护两个一维数组,一个记录[0...i]的最大收益,一个记录[i...n-1]的最大收益即可。这两个数组都是可以在一次遍历中得出的。然后再考虑两个部分收益之和。
- 实现:
class Solution { public: int maxProfit(vector<int> &prices) { vector<int> profitToEnd(prices.size(), 0); { int minIndex = 0; int maxPft = 0; for (int i=0; i<prices.size(); i++){ int tmpPft = prices[i]-prices[minIndex]; if (tmpPft > maxPft){ maxPft = tmpPft; } if (prices[i]<prices[minIndex]){ minIndex = i; } profitToEnd[i]=maxPft; } } vector<int> profitFromStart(prices.size(), 0); { int maxIndex = prices.size()-1; int maxPft = 0; for(int i=prices.size()-1; i>=0; i--){ int tmpPft = prices[maxIndex]-prices[i]; if (tmpPft > maxPft){ maxPft = tmpPft; } if (prices[maxIndex] < prices[i]){ maxIndex = i; } profitFromStart[i]=maxPft; } } int maxPft = 0; for (int i=0; i<prices.size(); i++) { int tmpPft = profitFromStart[i]+profitToEnd[i]; if (maxPft<tmpPft){ maxPft = tmpPft; } } return maxPft; } };