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