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; } };