1、序列化二叉树
思路:这里把一颗二叉树转换成一个序列,再将序列转换成树。那这里只有一个序列,不存在先+中确定唯一二叉树的情况,因此这里要借助层序的关系,从上到下将节点保存,并保存空节点。这里比较麻烦的是,对字符串的处理,因为要考虑“,”。可以将得到的字符串,先去掉“,”,那么此时留下的都是节点。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Codec {
public:
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(root==NULL)
return "[]";
string data="[";
queue<TreeNode*> q;
q.push(root);
while(q.size()!=0)
{
TreeNode* node = q.front();
q.pop();
if(node!=NULL)
{
data+=to_string(node->val);
data+=",";
q.push(node->left);
q.push(node->right);
}else{
data+="null,";
}
}
// data.erase(data.length()-1,1); 本来想去掉末尾的“,”,但是有后面的函数,不去也可以
return data+"]";
}
vector<string> split(string& s, string delim) // 去掉“,”
{
vector<string> ret;
int p = s.find(delim);
while (p != -1)
{
ret.push_back(s.substr(0,p));
s.erase(0, p+1);
p = s.find(delim);
}
ret.push_back(s);
return ret;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
data.erase(0, 1); // 去掉【
data.erase(data.length()-1, 1); // 去掉 】
if(data.length()==0) // "[]"的情况
return NULL;
vector<string> vec = split(data, ","); // 此时节点都保存在vec中
int i=0;
int value = atoi(vec[i].c_str());
i++;
TreeNode* root = new TreeNode(value);
queue<TreeNode*> q;
q.push(root);
while(q.size()!=0)
{
TreeNode* node = q.front();
q.pop();
string tmp_left = vec[i];
if(tmp_left=="null")
{
node->left=NULL;
}else{
int value = atoi(tmp_left.c_str());
TreeNode* le = new TreeNode(value);
node->left = le;
q.push(node->left);
}
i++;
string tmp_right = vec[i];
if(tmp_right=="null")
{
node->right=NULL;
}else{
int value = atoi(tmp_right.c_str());
TreeNode* ri = new TreeNode(value);
node->right = ri;
q.push(node->right);
}
i++;
}
return root;
}
};
// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
2、字符串的排列
思路:这题考的是全排列,如果直接暴力肯定超时。其实就是设置一个visited数组,用来标志当前字符是否被访问,如果被访问过了那就跳过下一个,逐渐罗列出所有情况。
class Solution {
public:
void dfs(string s, string tmp, vector<int> visited, set<string>& ans)
{
if(tmp.length()==s.length())
{
ans.insert(tmp);
return;
}
for(int i=0;i<s.length();i++)
{
if(visited[i]==1)
continue;
visited[i]=1;
dfs(s, tmp+s[i], visited, ans);
visited[i]=0;
}
}
vector<string> permutation(string s) {
set<string> ans; // 这里用set是由于有些情况会出现重复,用set去重
vector<int> visited(s.length(), 0);
dfs(s, "", visited, ans);
vector<string> final;
set<string>::iterator iter;
iter = ans.begin();
while(iter!=ans.end())
{
final.push_back(*iter);
iter++;
}
return final;
}
};
还有一种回溯写法,可以不借助set,因为太慢了。
class Solution {
public:
vector<string> rec;
vector<int> vis;
void backtrack(const string& s, int i, int n, string& perm) {
if (i == n) {
rec.push_back(perm);
return;
}
for (int j = 0; j < n; j++) {
if (vis[j] || (j > 0 && !vis[j - 1] && s[j - 1] == s[j])) { // 这里判断的意思是:1)如果被访问过,那就不要;2)如果这个字符不是第一个重复字符,就不要,意思是每次只拿重复字符中第一个,这样就避免重复
continue;
}
vis[j] = true;
perm.push_back(s[j]);
backtrack(s, i + 1, n, perm);
perm.pop_back();
vis[j] = false;
}
}
vector<string> permutation(string s) {
int n = s.size();
vis.resize(n);
sort(s.begin(), s.end()); // 先把字符串排序,让重复的字符放在一起
string perm;
backtrack(s, 0, n, perm);
return rec;
}
};
3、格雷码(不会)
思路:这里就记住格雷码的公式
class Solution {
public:
vector<int> grayCode(int n) {
vector<int> ret(1 << n); // 初始化2^n长度的vector
for (int i = 0; i < ret.size(); i++) {
ret[i] = (i >> 1) ^ i;
}
return ret;
}
};
4、丑数(超时)
思路:根据题目意思,其实可以想到,在已有n个丑数的情况下,要找第n+1个丑数的话:
但不能直接用循环遍历做,会超时。为了保证每个丑数都有乘2,乘3, 乘5,且保持有序,就要记录这个丑数是否乘过。设置3个索引a, b, c,分别记录前几个数已经被乘2, 乘3, 乘5了,比如a表示前(a-1)个数都已经乘过一次2了,下次应该乘2的是第a个数。
对于某个状态下的丑数序列,我们知道此时第a个数还没有乘2(有没有乘3或者乘5不知道), 第b个数还没有乘3(有没有乘2或者乘5不知道),第c个数还没有乘5(有没有乘2或者乘3不知道), 下一个丑数一定是从第a丑数乘2, 第b个数乘3, 第c个数乘5中获得,他们三者最小的那个就是下个丑数。
class Solution {
public:
int nthUglyNumber(int n) {
if(n < 7) return n;
vector<int> res(n);
//初始化
res[0] = 1;
int a = 0,b = 0,c = 0; // 用a,b,c分别作为2、3、5因子倍数的索引
for(int i = 1;i < n;i++)
{
// 取dp[a]*2,dp[b]*3,dp[c]*5的最小值
res[i]=min({res[a]*2,res[b]*3,res[c]*5});
if(res[i] == res[a] * 2) a++; // 如果是2的倍数,那么2对应的索引++
if(res[i] == res[b] * 3) b++;
if(res[i] == res[c] * 5) c++;
}
return res[n - 1];
}
};
5、 n个骰子的点数(不会)
思路:暴力法肯定超时。对于n个骰子,最小值为n,最大是6n,总共可能出现的和的数量是5n+1种。
假设n-1个骰子,所有和出现概率的情况记为f(n-1),现在要求f(n)。f(n-1,x)表示n-1个骰子投出和为x的概率。
这相当于在n-1个骰子已经求完和后,再扔第n个骰子,此时第n个骰子的值只能是1-6,且概率都是1/6,独立于前n-1个骰子。
现在假设要求f(n,x),那么n个骰子和为x的概率是:
f ( n , x ) = ∑ 1 < = i < = 6 f ( n − 1 , x − i ) ∗ 1 6 f(n, x) = {\sum_{1<=i<=6}}f(n-1,x-i)*{\frac{1}{6}} f(n,x)=1<=i<=6∑f(n−1,x−i)∗61
但在遍历1-6的过程中,x可能小于i,导致出现负数,需要增加判断条件。也可以改为正向思路:
f ( n , x + i ) + = f ( n − 1 , x ) ∗ 1 6 f(n, x+i) += f(n-1,x)*{\frac{1}{6}} f(n,x+i)+=f(n−1,x)∗61
当n个骰子和为x+i时,是在n-1个骰子和为x的基础上再乘1/6。
class Solution {
public:
vector<double> dicesProbability(int n) {
vector<double> dp(6, 1.0 / 6.0);
for (int i = 2; i <= n; i++) {
vector<double> tmp(5 * i + 1, 0); // 当i个骰子的时候,总共可能出现的情况数量
for (int j = 0; j < dp.size(); j++) {
for (int k = 0; k < 6; k++) {
tmp[j + k] += dp[j] / 6.0;
}
}
dp = tmp;
}
return dp;
}
};
6、正则表达式匹配(不会)
思路:
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size() + 1, n = p.size() + 1;
vector<vector<bool>> dp(m, vector<bool>(n, false));
dp[0][0] = true;
// 初始化首行
for(int j = 2; j < n; j += 2)
dp[0][j] = dp[0][j - 2] && p[j - 1] == '*';
// 状态转移
for(int i = 1; i < m; i++) {
for(int j = 1; j < n; j++) {
if(p[j - 1] == '*') {
if(dp[i][j - 2]) dp[i][j] = true; // 1.
else if(dp[i - 1][j] && s[i - 1] == p[j - 2]) dp[i][j] = true; // 2.
else if(dp[i - 1][j] && p[j - 2] == '.') dp[i][j] = true; // 3.
} else {
if(dp[i - 1][j - 1] && s[i - 1] == p[j - 1]) dp[i][j] = true; // 1. 在前i-1和j-1匹配的基础上,若新添加s[i-1]和p[j-1]两者相等,那么dp[i][j]也匹配
else if(dp[i - 1][j - 1] && p[j - 1] == '.') dp[i][j] = true; // 2. 取‘.’代表任意字母,可以匹配
}
}
}
return dp[m - 1][n - 1];
}
};
7、数组中的逆序对(不会)
思路:这里是借助了归并排序,在合并的过程中,需要比较前后两个数字大小来判断先放谁,后放谁,这就包含了计算逆序对的过程。
class Solution {
public:
int reversePairs(vector<int>& nums) {
vector<int> tmp(nums.size());
return mergeSort(0, nums.size() - 1, nums, tmp);
}
private:
int mergeSort(int l, int r, vector<int>& nums, vector<int>& tmp) {
// 终止条件
if (l >= r) return 0;
// 递归划分
int m = (l + r) / 2;
int res = mergeSort(l, m, nums, tmp) + mergeSort(m + 1, r, nums, tmp);
// 合并阶段
int i = l, j = m + 1;
for (int k = l; k <= r; k++)
tmp[k] = nums[k];
for (int k = l; k <= r; k++) {
if (i == m + 1)
nums[k] = tmp[j++];
else if (j == r + 1 || tmp[i] <= tmp[j])
nums[k] = tmp[i++];
else {
nums[k] = tmp[j++];
res += m - i + 1; // 统计逆序对
}
}
return res;
}
};
8、1-n整数中1 出现的次数(不会)
思路:设n是x位数,可表示为 n x n x − 1 . . . n 2 n 1 n_xn_{x-1}...n_2n_1 nxnx−1...n