终于到了最后的7题!
61.请实现两个函数,分别用来序列化和反序列化二叉树 二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。 二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
这题的测试样例真是太秀了。无力吐槽。。。
//O(1)写法【误 :)】
typedef TreeNode* pnode;
class Solution {
pnode hehe;
public:
char* Serialize(TreeNode *root) {
hehe = root;
return "(*^_^*)";
}
TreeNode* Deserialize(char *str) {
return hehe;
}
};
//O(n)写法
typedef TreeNode node;
typedef TreeNode* pnode;
typedef int* pint;
class Solution {
vector<int> buf;
void dfs(pnode p){
if(!p) buf.push_back(0x23333);
else{
buf.push_back(p -> val);
dfs(p -> left);
dfs(p -> right);
}
}
pnode dfs2(pint& p){
if(*p == 0x23333){
++p;
return NULL;
}
pnode res = new node(*p);
++p;
res -> left = dfs2(p);
res -> right = dfs2(p);
return res;
}
public:
char* Serialize(TreeNode *p) {
buf.clear();
dfs(p);
int *res = new int[buf.size()];
for(unsigned int i = 0; i < buf.size(); ++i) res[i] = buf[i];
return (char*)res;
}
TreeNode* Deserialize(char *str) {
int *p = (int*)str;
return dfs2(p);
}
};
62.给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8)中,按结点数值大小顺序第三小结点的值为4。
/*
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}
};
*/
class Solution {
public:
//中序遍历的结果就是有序序列,第K个元素就是vec[K-1]存储的节点指针;
TreeNode* KthNode(TreeNode* pRoot, unsigned int k)
{
if(pRoot==nullptr||k<=0) return nullptr;
vector<TreeNode*> vec;
Inorder(pRoot,vec);
if(k>vec.size())
return nullptr;
return vec[k-1];
}
//中序遍历,将节点依次压入vector中
void Inorder(TreeNode* pRoot,vector<TreeNode*>& vec)
{
if(pRoot==nullptr) return;
Inorder(pRoot->left,vec);
vec.push_back(pRoot);
Inorder(pRoot->right,vec);
}
};
63.如何得到一个数据流中的中位数?如果从数据流中读出奇数个数值,那么中位数就是所有数值排序之后位于中间的数值。如果从数据流中读出偶数个数值,那么中位数就是所有数值排序之后中间两个数的平均值。我们使用Insert()方法读取数据流,使用GetMedian()方法获取当前读取数据的中位数。
参考别人的代码:
class Solution {
private:
vector<int> min;
vector<int> max;
public:
void Insert(int num)
{
int size=min.size()+max.size();
if((size&1)==0)
{
if(max.size()>0 && num<max[0])
{
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
num=max[0];
pop_heap(max.begin(),max.end(),less<int>());
max.pop_back();
}
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
}
else
{
if(min.size()>0 && num>min[0])
{
min.push_back(num);
push_heap(min.begin(),min.end(),greater<int>());
num=min[0];
pop_heap(min.begin(),min.end(),greater<int>());
min.pop_back();
}
max.push_back(num);
push_heap(max.begin(),max.end(),less<int>());
}
}
double GetMedian()
{
int size=min.size()+max.size();
if(size<=0)
return 0;
if((size&1)==0)
return (max[0]+min[0])/2.0;
else
return min[0];
}
};
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]}。
class Solution {
public:
vector<int> maxInWindows(const vector<int>& num, unsigned int size)
{
vector<int>max;
if(num.empty()||size>num.size()||size<1)
return max;
int m;
for(int i=0;i<num.size()-size+1;i++)
{
m=num[i];
for(int j=i+1;j<i+size;j++)
{
if(num[j]>m)
{
m=num[j];
}
}
max.push_back(m);
}
return max;
}
};
总环一下不同的思路:
一、平衡树
直接用map来对每个出现的值计数,map可以直接加入,删除,求最大。
时间复杂度O(NlogW)
空间复杂度O(N)
二、Sparse Table
利用Sparse_Table算法思想,将区间[A, A + W) 分解成[A, A+R) 和[A+W-R, A+W) 其中R是满足 2*R >= W的最小的2的次方。分解成的两个小区间有重叠,但由于是求最值,重叠部分不会影响答案。预处理部分中需要得到从每个下标A开始长度为1,2,4,8,…,R的区间的最值。相比Sparse_Table算法,这里的空间可以优化成O(N),因为只需要保留长度为R的数组。
时间复杂度O(NlogW)
空间复杂度O(N)
三、单调队列
利用这条性质:当A < B且num[A] < num[B]时,num[A]对右端点在B或之后的区间来说可以忽略,因为num[B]是个更优解且num[A]先过期。
于是维护一个单调队列,队列中的元素由高到低排列(队列中存下标,方便判断过期)。
从小到大扫描num数组,当考虑到下标B时,根据上面的性质,可以安全地从队尾删除所有比num[B]小的值。
然后将B加入队尾。
然后从队首删除所有过期的值。
做完以上3点之后,队首的值即为以B为右端点的区间的最大值。
时间和空间复杂度都是O(N),因为每个元素只进出一次队列。
时间复杂度O(N)
空间复杂度O(N)
class Solution {
public:
//平衡树
vector<int> maxInWindows1(const vector<int>& num,int W)
{
int N = num.size();
vector<int> ret;
map<int, int> Count;
for(int i = 0 ; i < N ; ++i){
//加入当前值
++Count[num[i]];
//删除过期元素
if(i - W >= 0 && 0 == --Count[num[i - W]])
Count.erase(num[i - W]);
//计算答案
if(i >= W - 1)
ret.push_back(Count.rbegin()->first);
}
return ret;
}
//Sparse Table
vector<int> maxInWindows2(const vector<int>& num,int W)
{
int N = num.size();
vector<int> Max(num.begin(), num.end());
int MaxRange = 1;
//Max[i]为区间[i, i + MaxRange)中的最大值
//每次循环将MaxRange翻倍并保持此性质
while(MaxRange * 2 < W){
for(int i = 0 ; i + 2 * MaxRange <= N ; ++i)
Max[i] = max(Max[i], Max[i+MaxRange]);
MaxRange *= 2;
}
//此时 MaxRange * 2 >= W,即MaxRange至少覆盖半个窗口
vector<int> ret;
for(int i = 0 ; i + W <= N; ++i){
// [i, i + W)被分成[i, i + MaxRange)
// 和 [i + W - MaxRange, i + W)这两个区间。
ret.push_back(max(Max[i], Max[i+W-MaxRange]));
}
return ret;
}
//单调队列
vector<int> maxInWindows3(const vector<int>& num, int W)
{
int N = num.size();
vector<int> ret;
list<int> L;
for(int i = 0 ; i < N ; ++i){
//从队尾删除比num[i]小的数
while(!L.empty() && num[*L.rbegin()] < num[i])
L.pop_back();
//将i加入队尾
L.push_back(i);
//从队首删除过期的数
while(!L.empty() && (*L.begin() <= i - W))
L.pop_front();
//将以i结尾的区间最值加入答案
if(i >= W - 1)
ret.push_back(num[*L.begin()]);
}
return ret;
}
};
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占据了矩阵中的第一行第二个格子之后,路径不能再次进入该格子。
分析:回溯算法
这是一个可以用回朔法解决的典型题。首先,在矩阵中任选一个格子作为路径的起点。如果路径上的第i个字符不是ch,那么这个格子不可能处在路径上的
第i个位置。如果路径上的第i个字符正好是ch,那么往相邻的格子寻找路径上的第i+1个字符。除在矩阵边界上的格子之外,其他格子都有4个相邻的格子。
重复这个过程直到路径上的所有字符都在矩阵中找到相应的位置。
由于回朔法的递归特性,路径可以被开成一个栈。当在矩阵中定位了路径中前n个字符的位置之后,在与第n个字符对应的格子的周围都没有找到第n+1个
字符,这个时候只要在路径上回到第n-1个字符,重新定位第n个字符。
由于路径不能重复进入矩阵的格子,还需要定义和字符矩阵大小一样的布尔值矩阵,用来标识路径是否已经进入每个格子。 当矩阵中坐标为(row,col)的
格子和路径字符串中相应的字符一样时,从4个相邻的格子(row,col-1),(row-1,col),(row,col+1)以及(row+1,col)中去定位路径字符串中下一个字符
如果4个相邻的格子都没有匹配字符串中下一个的字符,表明当前路径字符串中字符在矩阵中的定位不正确,我们需要回到前一个,然后重新定位。
一直重复这个过程,直到路径字符串上所有字符都在矩阵中找到合适的位置。
多说一句,八皇后问题也是经典的回溯法例题,大家可以参考;在《剑指offer》书中也给出了八皇后问题的思路;
不过,那个是在全排列问题中引出来的。其实回溯法也是全排列的一种方案,在本题中,也就是尝试了 matrix矩阵中所有点作为起点的方法,然后依据这个点进行向四个方向的递归;在递归中,不满足题目的会自动出栈回到上一个状态;
class Solution {
public:
bool hasPath(char* matrix, int rows, int cols, char* str) {
bool result = 0;
bool *flag=new bool[rows*cols];
memset(flag,0,rows*cols);
for (int i = 0;i<rows;++i) {
for (int j = 0;j<cols;++j) {
//bool *flag = (bool *)calloc(rows*cols, 1);
result = dfs(matrix, rows, cols, i, j, flag, str);//1
if (result == true)
return result;
}
}
delete[] flag;
return result;
}
bool dfs(char* matrix, int rows, int cols, int i, int j, bool* flag, char* str) {
if (*str == '\0')
return true;
if(i<0||i>=rows||j<0||j>=cols)
return false;
if (*(flag+i*cols + j) == 1 || (*(flag+i*cols + j) == 0 && *(matrix + i*cols + j) != *str))
return false;
else {
*(flag+i*cols + j) = 1;
bool result=dfs(matrix, rows, cols, i, j - 1, flag, str + 1)//左
||dfs(matrix, rows, cols, i, j + 1, flag, str+1)//右
||dfs(matrix, rows, cols, i - 1, j, flag, str+1)//上
||dfs(matrix, rows, cols, i + 1, j, flag, str+1);//下
if(result==0)
*(flag+i*cols + j)=0;//这样从1处开始进入的DFS即使没找到路径,但是flag最后全部置为0
return result;
}
}
};
66.地上有一个m行和n列的方格。一个机器人从坐标0,0的格子开始移动,每一次只能向左,右,上,下四个方向移动一格,但是不能进入行坐标和列坐标的数位之和大于k的格子。 例如,当k为18时,机器人能够进入方格(35,37),因为3+5+3+7 = 18。但是,它不能进入方格(35,38),因为3+5+3+8 = 19。请问该机器人能够达到多少个格子?
DFS/BFS的问题。
此题用回溯法:DFS的思路,递归求解。
(1)首先创建一个大小为rows*cols的一维bool数组,来表示二维数组,并初始化为全false
(2)将bool数组上,满足“机器人不能去”要求的位置都设为true
(3)利用countCanPos函数进行递归,回溯地寻找所有可以到达的位置:
- 用i,j表示当前的数组位置,若i,j未越界且bool数组的这个位置不为true,则令计数器count+1;
- 对当前位置上下左右位置递归求解。
class Solution {
public:
int movingCount(int threshold, int rows, int cols){
bool *isBanPos = new bool[rows*cols](); // 加上小括号,进行默认初始化,默认元素全为false
// 将bool数组上,满足“机器人不能去”要求的位置都设为true
for(int i = 0; i < rows; ++i) {
for(int j = 0; j < cols; ++j) {
if(threshold < addPart(i) + addPart(j)) {
isBanPos[cols*i+j] = true;
}
}
}
// 设置计数器、机器人起始位置
int count = 0;
int i = 0, j = 0;
countCanPos(isBanPos, rows, cols, i, j, count);
delete[] isBanPos;
return count;
}
// 计算数位加和的小函数
int addPart(int num) {
if(num >= 0 && num < 10)
return num;
int sum = 0;
while(num > 0) {
sum += num % 10;
num /= 10;
}
return sum;
}
// 回溯法递归函数
void countCanPos(bool* &isBanPos, int rows, int cols, int i, int j, int &count) {
if(i < 0 || i >= rows || j < 0 || j >= cols || isBanPos[cols*i+j] == true) {
return;
}
++count;
isBanPos[cols*i+j] = true;
countCanPos(isBanPos, rows, cols, i-1, j, count);
countCanPos(isBanPos, rows, cols, i+1, j, count);
countCanPos(isBanPos, rows, cols, i, j-1, count);
countCanPos(isBanPos, rows, cols, i, j+1, count);
}
};
67.给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],…,k[m]。请问k[0]xk[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
动态规划问题,贴一个大佬的思路:
#include <iostream>
#include <cmath>
using namespace std;
/**
* 题目分析:
* 先举几个例子,可以看出规律来。
* 4 : 2*2
* 5 : 2*3
* 6 : 3*3
* 7 : 2*2*3 或者4*3
* 8 : 2*3*3
* 9 : 3*3*3
* 10:2*2*3*3 或者4*3*3
* 11:2*3*3*3
* 12:3*3*3*3
* 13:2*2*3*3*3 或者4*3*3*3
*
* 下面是分析:
* 首先判断k[0]到k[m]可能有哪些数字,实际上只可能是2或者3。
* 当然也可能有4,但是4=2*2,我们就简单些不考虑了。
* 5<2*3,6<3*3,比6更大的数字我们就更不用考虑了,肯定要继续分。
* 其次看2和3的数量,2的数量肯定小于3个,为什么呢?因为2*2*2<3*3,那么题目就简单了。
* 直接用n除以3,根据得到的余数判断是一个2还是两个2还是没有2就行了。
* 由于题目规定m>1,所以2只能是1*1,3只能是2*1,这两个特殊情况直接返回就行了。
*
* 乘方运算的复杂度为:O(log n),用动态规划来做会耗时比较多。
*/
long long n_max_3(long long n) {
if (n == 2) {
return 1;
}
if (n == 3) {
return 2;
}
long long x = n % 3;
long long y = n / 3;
if (x == 0) {
return pow(3, y);
} else if (x == 1) {
return 2 * 2 * (long long) pow(3, y - 1);
} else {
return 2 * (long long) pow(3, y);
}
}
//给你一根长度为n的绳子,请把绳子剪成m段(m、n都是整数,n>1并且m>1),每段绳子的长度记为k[0],k[1],...,k[m]。请问k[0]xk[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
//
//输入描述:
//输入一个数n,意义见题面。(2 <= n <= 100)
//
//
//输出描述:
//输出答案。
//示例1
//输入
//8
//输出
//18
int main() {
long long n = 0;
cin >> n;
cout << n_max_3(n) << endl;
return 0;
}
整理一下代码为:
class Solution {
public:
int cutRope(int number) {
if(number==2)
return 1;
if(number==3)
return 2;
int x=number%3;
int y=number/3;
if(x==0)
return pow(3,y);
else if(x==1)
return 2*2*(int)pow(3,y-1);
else
return 2*pow(3,y);
}
};
总结:
总算是全“刷"完了,然而这第一遍还是以借鉴别人的修改为主。以后还会经常自己回来看看,记录蒟蒻的成长!