1. 回文分割
- 給定一個字符串s,將s分割為數個子串,每個子串都是回文。
比如,給定字符串"aab",返回:
[ ["aa","b"], ["a","a","b"] ]
- 思路:遞歸法,子串 s[i]...s[j] 的所有回文分割,都是由 s[1] 單獨構成的一個元素的子串連接上 s[2]...s[j] 的所有回文分割(子問題),以及任何一個回文子串 s[1]...s[k] 連接上上 s[k+1]...s[j] 的回文分割(子問題)。
- 實現:
class Solution { public: vector<vector<string>> partition(string s); private: vector<vector<string>> _partition(const string& s, const int i, const int j); vector<string> addhead(const vector<string>& list, const string& head); bool isPal(const string& s, const int i, const int j); vector<vector<int>> pals; }; vector<vector<string>> Solution::partition(string s) { int n = s.size(); pals = vector<vector<int>>(n, vector<int>(n, 0)); return _partition(s, 0, n-1); } vector<vector<string>> Solution:: _partition(const string& s, const int i, const int j){ vector<vector<string>> list; if(i==j){ string tmp(1, s[i]); list.push_back(vector<string>(1, tmp)); return list; } for(int p=i; p<j; p++){ if(isPal(s,i,p)){ vector<vector<string>> _list = _partition(s, p+1, j); string _head = s.substr(i,p-i+1); for(int k=0; k<=_list.size()-1; k++){ vector<string> tmp = addhead(_list[k],_head); list.push_back(tmp); } } } if(isPal(s,i,j)){ list.push_back(vector<string>(1, s.substr(i,j-i+1))); } return list; } vector<string> Solution::addhead(const vector<string>& list, const string& head){ vector<string> rs; rs.push_back(head); for(int i=0; i<=list.size()-1; i++){ rs.push_back(list[i]); } return rs; } bool Solution::isPal(const string& s, const int i, const int j){ if(i>=j){ return true; } if(pals[i][j]!=0){ return pals[i][j]>0 ? true : false; } if(s[i]==s[j]){ bool _isPal = isPal(s, i+1, j-1); pals[i][j] = _isPal ? 1 : -1; return _isPal; } else{ pals[i][j] = -1; return false; } }
2. 回文分割2
- 給定一個字符串s,將s分割為數個子串,每個子串都是回文。
返回將字符串回文分割最少分割次數。
比如,給定字符串"aab",返回1,因為分割["aa","b"]可由一個分割(逗號)生成。 - 思路:動態規划,維護兩個二維表,分別表示子串 s[i]...s[j] 是否是回文,以及最少回文分割次數(如果子串本身是回文,這個域就是0)。子串的最小回文分割次數就是 s[i]...s[k] 和 s[k+1]...s[j] 的最小回文分割次數之和(兩個子問題的和)加1,在區間[i,j]取一個k值使該結果最小。
- 實現:
class Solution { public: int minCut(string s); private: int _minCut(const string& s, const int i, const int j); bool isPal(const string& s, const int i, const int j); vector<vector<int>> mcTable; vector<vector<int>> isPalTable; // 1 for true, -1 for false, 0 for uncertain }; int Solution::minCut(string s){ int n = s.size(); mcTable = vector<vector<int>>(n, vector<int>(n,-1)); isPalTable = vector<vector<int>>(n, vector<int>(n, 0)); return _minCut(s, 0, n-1); } int Solution::_minCut(const string& s, const int i, const int j){ if(mcTable[i][j] != -1){ return mcTable[i][j]; } else{ if(isPal(s,i,j)){ mcTable[i][j]=0; return 0; } else{ int min = INT_MAX; for(int k=i; k<=j-1; k++){ int cutNums = _minCut(s,i,k) + _minCut(s,k+1,j) + 1; if(cutNums<min){ min = cutNums; } } mcTable[i][j]=min; return min; } } } bool Solution::isPal(const string& s, const int i, const int j){ if(i>=j){ return true; } if(isPalTable[i][j] != 0){ return isPalTable[i][j]>0 ? true : false; } else{ if(s[i]!=s[j]){ isPalTable[i][j]=-1; return false; } else{ if(isPal(s,i+1,j-1)){ isPalTable[i][j]=1; return true; } else{ isPalTable[i][j]=-1; return false; } } } }
3. 圍棋
- 給定一塊二維棋盤,每一個格子要么是'X'要么是'O'。將所有被'X'圍起來(像圍棋一樣圍起來)'O',都轉變為'O'。
比如,給定:
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'是不會被圍起來的,與他們相鄰的'O'也不會被圍起來。該性質可以傳遞下去,類似於“感染”。那么就將四邊上的'O'感染為'A',再遞歸地感染相鄰的'O',完成之后將未被感染的'O'轉為'X',再將'A'轉為'O'。
- 實現:
class Solution { public: void solve(vector<vector<char>> &board); private: void infect(vector<vector<char>>& board, int i, int j); }; void Solution::solve(vector<vector<char>> &board) { int n = board.size(); if(n==0){ return; } int m = board[0].size(); for (int i=0; i<=n-1; i++){ if (board[i][0]=='O'){ infect(board, i, 0); } if (board[i][m-1]=='O'){ infect(board, i, m-1); } } for (int j=0; j<=m-1; j++){ if (board[0][j]=='O'){ infect(board, 0, j); } if (board[n-1][j]=='O'){ infect(board, n-1, j); } } for (int i=0; i<=n-1; i++){ for (int j=0; j<=m-1; j++){ if (board[i][j]=='O'){ board[i][j]='X'; } } } for (int i=0; i<=n-1; i++){ for (int j=0; j<=m-1; j++){ if (board[i][j]=='A'){ board[i][j]='O'; } } } } void Solution::infect(vector<vector<char>>& board, int i, int j){ board[i][j] = 'A'; int n = board.size(); int m = board[0].size(); if(i!=0 && board[i-1][j]=='O'){ infect(board, i-1, j); } if(i!=n-1 && board[i+1][j]=='O'){ infect(board, i+1, j); } if(j!=0 && board[i][j-1]=='O'){ infect(board, i, j-1); } if(j!=m-1 && board[i][j+1]=='O'){ infect(board, i, j+1); } }
4. “根-葉”數之和
- 給定一個二叉樹,每個樹的節點值都為 0~9 的整數。每一個從根節點到葉節點的路徑代表一個整數。
比如,路徑 1-2-3 代表整數 123。找到二叉樹所有從根節點到葉節點的路徑的“根-葉”數之和。
比如,給定:
1 / \ 2 3
1-2路徑表示12,1-3路徑表示13,那么應當返回12+13=25。
- 思路:進入每一棵子樹時,傳入一個從根節點開始累積的值,到達葉節點后將改值加到全局變量sum上,如路徑 1-2-3 : 1-(1)-2-(12)-3(123)-sum+=123。這樣就仍可以自然地進行遞歸迭代,而不用寫二叉樹的迭代遍歷。
- 實現:
/** * 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 sumNumbers(TreeNode *root) { sum = 0; _sumNumbers(root, 0); return sum; } private: void _sumNumbers(TreeNode* root, int rootVal){ if (root==NULL){ return; } int val = rootVal*10+root->val; if (root->left==NULL && root->right==NULL){ sum += val; } _sumNumbers(root->left, val); _sumNumbers(root->right, val); } int sum; };
5. 最長連續序列
- 給定一個未排序的整數數組,找到最長連續序列的長度。
比如,給定[100,4,200,1,3,2],應當返回4,因為最長連續序列為[1,2,3,4],其長度為4。
算法的時間代價應當為O(n)。 - 思路:規定了O(n)的時間復雜度,想到使用哈希表。使用哈希集合st來濾掉重復的元素,哈希表mp中維護着這樣的結構:所有已考慮的元素組成的連續序列的兩端,鍵為兩端的位置,值為另一端的位置。如果序列僅有一個元素(如考慮第一個元素時),則兩端都是它自身,它的值指向它自己。由於st的幫助,所有新考慮的值都不會出現在連續序列中。
- 實現:
class Solution { public: int longestConsecutive(vector<int> &num) { hash_map<int, int> mp; hash_set<int> st; int max = 0; for (int i=0; i<=num.size()-1; i++){ if (st.find(num[i])!=st.end()){ continue; } st.insert(num[i]); int tleft = num[i]; int tright = num[i]; if (mp.find(tleft-1)!=mp.end()){ tleft = mp[tleft-1]; } if (mp.find(tright+1)!=mp.end()){ tright = mp[tright+1]; } int len = tright-tleft+1; max = max<len ? len : max; mp[tleft] = tright; mp[tright] = tleft; } return max; } };