58 对称二叉树
题目描述
请实现一个函数,用来判断一棵二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
示例1
输入:
{8,6,6,5,7,7,5}
返回值:
true
示例2
输入:
{8,6,9,5,7,7,5}
返回值:
false
思路:
判断左子树和右子树是否对称,相当对判断左子树的左孩子和右子树的右孩子是否相等,左子树的右孩子和右子树的左孩子是否相等。
采用队列存取每个结点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
bool isSymmetrical(TreeNode* pRoot) {
queue<TreeNode*> l;
queue<TreeNode*> r;
if(!pRoot)return true;
l.push(pRoot->left);
r.push(pRoot->right);
while(!l.empty()&&!r.empty()){
TreeNode* tl=l.front();
TreeNode* tr=r.front();
if(!tl&&!tr);
else if(tl&&tr){
if(tl->val!=tr->val)return false;
l.push(tl->left);
l.push(tl->right);
r.push(tr->right);
r.push(tr->left);
}
else return false;
l.pop();r.pop();
}
return true;
}
};
59 按之字形顺序打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
示例1
输入:
{8,6,10,5,7,9,11}
返回值:
[[8],[10,6],[5,7,9,11]]
思路:
维护两个栈,一个保存从前向后的结点,一个保存从后向前的结点。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
stack<TreeNode*> st;
stack<TreeNode*> st2;
int flag=1;
vector<vector<int>> res;
if(!pRoot)return res;
st.push(pRoot);
while(!st.empty()||!st2.empty()){
int len=st.empty()?st2.size():st.size();
vector<int> rol;
while(len>0){
TreeNode* t;
if(flag==1){
t=st.top();
if(t->left)st2.push(t->left);
if(t->right)st2.push(t->right);
st.pop();
}
else{
t=st2.top();
if(t->right)st.push(t->right);
if(t->left)st.push(t->left);
st2.pop();
}
rol.push_back(t->val);
len--;
}
flag*=-1;
res.push_back(rol);
}
return res;
}
};
60 把二叉树打印成多行
题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
示例1
输入:
{8,6,10,5,7,9,11}
返回值:
[[8],[6,10],[5,7,9,11]]
思路:
维护一个队列,广度遍历二叉树,遍历的时候要记住每层的结点数
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
queue<TreeNode*> qu;
vector<vector<int>> res;
if(!pRoot)return res;
qu.push(pRoot);
while(!qu.empty()){
TreeNode* t;
int len=qu.size();
vector<int> rol;
while(len>0){
t=qu.front();
qu.pop();
if(t->left)qu.push(t->left);
if(t->right)qu.push(t->right);
rol.push_back(t->val);
len--;
}
res.push_back(rol);
}
return res;
}
};
61 序列化二叉树
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
示例1
输入:
{8,6,10,5,7,9,11}
返回值:
{8,6,10,5,7,9,11}
思路:
先序遍历二叉树,将二叉树的结点转换成字符串,‘#’表示空结点,’!'分割每个结点。
反序列化时以’!'为界将字符串中的数字转化成int类型,并为其创建新结点,以先序遍历的顺序创建树
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
char* Serialize(TreeNode *root) {
string s=ser(root);
char* ch=new char[s.size()];
strcpy(ch, s.c_str());
return ch;
}
string ser(TreeNode* root){
if(!root)return "#";
string s=to_string(root->val)+"!";
s+=ser(root->left);
s+=ser(root->right);
return s;
}
TreeNode* Deserialize(char *str) {
int i=0;
return des(i,str);
}
TreeNode* des(int& i,char* s){
if(s[i]=='#'){
i++;
return nullptr;
}
int num=0;
while(s[i]!='!'){
num=num*10+(s[i]-'0');
i++;
}
i++;
TreeNode* root=new TreeNode(num);
root->left=des(i,s);
root->right=des(i,s);
return root;
}
};
62 二叉搜索树的第k个结点
题目描述
给定一棵二叉搜索树,请找出其中的第k小的TreeNode结点
输入:
{5,3,7,2,4,6,8},3
返回值:
{4}
思路:
中序遍历二叉搜索树,返回遇到的第k个结点
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k) {
if (!pRoot)return nullptr;
int i = k;
TreeNode* res=nullptr;
dfs(pRoot, i, res);
return res;
}
void dfs(TreeNode* root, int& i, TreeNode*& res) {
if (root->left)dfs(root->left, i, res);
if (i <= 0)return;
if (i == 1)res = root;
i--;
if (root->right)dfs(root->right, i, res);
}
};
63 数据流中的中位数
题目描述
如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
思路:
创建一个大顶堆一个小顶堆,
第单数个数放入大顶堆,第偶数个数放入小顶堆
放入大顶堆前先放入小顶堆排序,再将小顶堆的堆顶放入大顶堆,确保大顶堆的数全都小于小顶堆
放入小顶堆前先放入大顶堆排序,再将大顶堆的堆顶放入小顶堆,确保小顶堆的数全都大于大顶堆
数据流有单数个时大顶堆的堆顶就是中位数,偶数时取两个堆顶的和*0.5
c++中priority_queue意为优先队列,有三个参数,第一个是数据类型,第二个是存储容器,第三个是排序方法,默认用vector存储,大顶堆。设置greater参数就是小顶堆
class Solution {
public:
priority_queue<int> maxHeap;
priority_queue<int,vector<int>,greater<int>> minHeap;
void Insert(int num) {
if(maxHeap.size()==minHeap.size()){
minHeap.push(num);
maxHeap.push(minHeap.top());
minHeap.pop();
}
else{
maxHeap.push(num);
minHeap.push(maxHeap.top());
maxHeap.pop();
}
}
double GetMedian() {
int lenMax=maxHeap.size(),lenMin=minHeap.size();
return (lenMax==lenMin)?(maxHeap.top()+minHeap.top())*0.5:maxHeap.top();
}
};
64 滑动窗口的最大值
题目描述
给定一个数组和滑动窗口的大小,找出所有滑动窗口里数值的最大值。例如,如果输入数组{2,3,4,2,6,2,5,1}及滑动窗口的大小3,那么一共存在6个滑动窗口,他们的最大值分别为{4,4,6,6,6,5}; 针对数组{2,3,4,2,6,2,5,1}的滑动窗口有以下6个: {[2,3,4],2,6,2,5,1}, {2,[3,4,2],6,2,5,1}, {2,3,[4,2,6],2,5,1}, {2,3,4,[2,6,2],5,1}, {2,3,4,2,[6,2,5],1}, {2,3,4,2,6,[2,5,1]}。
窗口大于数组长度的时候,返回空
示例1
输入:
[2,3,4,2,6,2,5,1],3
返回值:
[4,4,6,6,6,5]
思路:
创建一个双端队列,保存数组的下标,当队首不在窗口内出队,每加入一个新元素就和队尾比较,确保队列是一个降序队列,队首元素是当前滑动窗口最大值
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size) {
deque<int> dqu;
vector<int> res;
if(num.size()==0||size>num.size()||size==0)return res;
for(int i=0;i<num.size();i++){
while(!dqu.empty()&&i-dqu.front()+1>size)dqu.pop_front();
while(!dqu.empty()&&num[dqu.back()]<num[i])dqu.pop_back();
dqu.push_back(i);
if(i+1>=size)res.push_back(num[dqu.front()]);
}
return res;
}
};
65 矩阵中的路径
题目描述
请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串所有字符的路径。路径可以从矩阵中的任意一个格子开始,每一步可以在矩阵中向左,向右,向上,向下移动一个格子。如果一条路径经过了矩阵中的某一个格子,则该路径不能再进入该格子。 例如
a
b
c
e
s
f
c
s
a
d
e
e
\begin{matrix} a&b&c&e\\ s&f&c&s\\ a&d&e&e\\ \end{matrix}
asabfdcceese
矩阵中包含一条字符串"bcced"的路径,但是矩阵中不包含"abcb"路径,因为字符串的第一个字符b占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
示例1
输入:
"ABCESFCSADEE",3,4,"ABCCED"
返回值:
true
思路:
用回溯法解决。在搜索中,遇到这条路不可能和目标字符串匹配成功的情况(例如:此矩阵元素和目标字符不同、此元素已被访问,超出矩阵范围),则应立即返回,称之为可行性剪枝 。
每一个元素都可能是起点,所以要遍历矩阵找出符合条件的字符。
深度搜索当前字符的前后左右,搜索过的元素标为空,防止重复搜索,若不匹配返回false
class Solution {
public:
bool res=false;
bool hasPath(string matrix, int rows, int cols, string str) {
for(int i=0;i<rows;i++){
for(int j=0;j<cols;j++){
if(dfs(matrix,str,rows,cols,0,i,j))return true;;
}
}
return false;
}
bool dfs(string matrix,string str,int rows, int cols,int pre,int i,int j){
if(i>=rows||i<0||j>=cols||j<0||matrix[j+i*cols]!=str[pre])return false;
if(pre==(str.size()-1))return true;
matrix[j+i*cols]='\0';
bool res=dfs(matrix,str,rows,cols,pre+1,i+1,j)||dfs(matrix,str,rows,cols,pre+1,i-1,j)
||dfs(matrix,str,rows,cols,pre+1,i,j+1)||dfs(matrix,str,rows,cols,pre+1,i,j-1);
return res;
}
};
66 机器人的运动范围
题目描述
地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
示例1
输入:
5,10,10
返回值:
21
思路:
从第一个位置开始,深度搜索上下左右四个位置,判断当前格子是否符合条件,不符合返回0,且不往这个格子后搜索,相当于路被堵住了。符合则返回值+1
设置flag数组记录走过的格子,避免重复
class Solution {
public:
int row,col;
int movingCount(int threshold, int rows, int cols) {
int res=0;
row=rows;col=cols;
vector<vector<bool>> flag(rows,vector<bool>(cols,true));
res=Compare(threshold, 0, 0, flag);
return res;
}
int Compare(int k,int i,int j,vector<vector<bool>>& flag){
if(i>=row||i<0||j>col||j<0||!flag[i][j])return 0;
string s1=to_string(i);
string s2=to_string(j);
int num=0;
for(auto c:s1)num+=(c-'0');
for(auto c:s2)num+=(c-'0');
flag[i][j]=false;
if(num>k)return 0;
int res=Compare(k, i+1, j, flag)+Compare(k, i-1, j, flag)
+Compare(k, i, j+1, flag)+Compare(k, i+1, j-1, flag)+1;
return res;
}
};
67 剪绳子
题目描述
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
输入:
8
返回值:
18
思路:
利用动态规划的思想
我们想要求长度为n的绳子剪掉后的最大乘积,可以从前面比n小的绳子转移而来
用一个dp数组记录从0到n长度的绳子剪掉后的最大乘积,也就是dp[i]表示长度为i的绳子剪成m段后的最大乘积,初始化dp[2] = 1
我们先把绳子剪掉第一段(长度为j),如果只剪掉长度为1,对最后的乘积无任何增益,所以从长度为2开始剪
剪了第一段后,剩下(i - j)长度可以剪也可以不剪。如果不剪的话长度乘积即为j * (i - j);如果剪的话长度乘积即为j * dp[i - j]。取两者最大值max(j * (i - j), j * dp[i - j])
第一段长度j可以取的区间为[2,i/2+1],大于i/2+1会剪重复,对所有j不同的情况取最大值,因此最终dp[i]的转移方程为
dp[i] = max(dp[i], max(j * (i - j), j * dp[i - j]))
最后返回dp[n]即可
class Solution {
public:
int cutRope(int number) {
vector<int> dp(number+1);
dp[2]=1;
for(int i=3;i<=number;i++){
for(int j=2;j<=(i/2)+1;j++){
dp[i]=max(dp[i],max(j*(i-j),j*dp[i-j]));
}
}
return dp[number];
}
};