36和37被我搞反了,不过不影响阅读
面试题36:序列化二叉树
#include <iostream>
#include <vector>
#include <string>
#include <queue>
using namespace std;
/*
* 题目:二叉树的序列化和反序列化
* 序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,
* 采取相反方式重构得到原数据。请设计一个算法来实现二叉树的序列化与反序列化。
* 这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
* 你可以将以下二叉树:
1
/ \
2 3
/ \
4 5
序列化为 "[1,2,3,null,null,4,5]"
*/
/*思路:
* 这道题是我做剑指offer所有题里印象最深的一道,第一次写时纠缠了整整一天
* 序列化的过程相对简单,有点类似于按顺序打印二叉树的过程,不同之处在于对空节点也要有所体现
* 观察上述例子,序列化后得到的字符串为"1,2,3,null,null,4,5",而打印出来的二叉树节点为 1 2 3 4 5
* 还有一个问题这里没有体现出来,就是对于序列化后尾部的null,需要进行删除,否则序列化后字符串的长度较长,若进行传输,需要更大的消耗
* 而反序列化则略复杂一些,其难点在于如何实现父子节点的对应,由于null的存在,序列化生成的序列其实是一个完全二叉树的状态,这给了我们启发
* 父子节点的对应可以借助下标来实现,首先对序列进行分割,并保存成字符串数组,
* 随后遍历数组,借助nums数组来实现对null节点的计数,与此同时对于非null值创建节点
* 继续看上述示例,其得到的nums数组值为{0,0,0,1,2,2,2},nums[i]表示到下标i为止空节点的个数
* 与之对应的,有nodes数组,该数组中所有元素均为TreeNode*类型,前面提到过,对于非null的数字,生成了节点,由该数组来控制
* 这里构造nums数组和nodes数组的目的就是为了实现父子节点的准确对应
* 由于节点已经生成完毕,接下来的工作就是遍历nodes数组,跳过空节点,而对于非空节点,实现nodes[i] left和right指针的赋值
* 对于真正的完全二叉树,下标i的左子节点下标为2*i+1,而右子节点下标为2*i+2
* 而nodes数组构成的二叉树明显不是一个完全二叉树,但结合nums数组中保存的到每个下标为止的空节点数,就能实现赋值
* 即nodes[i]->left=nodes[2*(i-nums[i])+1]
* nodes[i]->right=nodes[2*(i-nums[i])+2]
* 别忘了赋值前判断一下是否在范围内,至此完成了反序列化,即通过序列恢复出了二叉树
*/
//leetcode:https://leetcode-cn.com/problems/serialize-and-deserialize-binary-tree/
//牛客:https://www.nowcoder.com/practice/cf7e25aa97c04cc1a68c8f040e71fb84?tpId=13&&tqId=11214&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
// Encodes a tree to a single string.
string serialize(TreeNode* root) {
if(root==nullptr)
return "";
queue<TreeNode*> q;
string s;
q.push(root);
while(!q.empty())
{
TreeNode *pNode=q.front();
q.pop();
if(pNode==nullptr)
s+="null,";
else
{
s+=to_string(pNode->val)+",";
q.push(pNode->left);
q.push(pNode->right);
}
}
int i=s.length()-1;
while(i>4)
{
if(s.substr(i-4,5)=="null,")
i-=5;
else
break;
}
s=s.substr(0,i);
return s;
}
// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
if(data=="")
return nullptr;
vector<string> strs;
string s;
for(int i=0;i<data.length();i++)
{
if(data[i]==',')
{
strs.push_back(s);
s="";
}
else
s.push_back(data[i]);
}
strs.push_back(s);
vector<int> nums(strs.size(),0);
vector<TreeNode*> nodes(strs.size(),nullptr);
for(int i=0;i<strs.size();i++)
{
if(i>0)
nums[i]=nums[i-1];
if(strs[i]=="null")
{
nums[i]++;
nodes[i]=nullptr;
}
else
nodes[i]=new TreeNode(stoi(strs[i]));
}
for(int i=0;i<strs.size();i++)
{
if(nodes[i]==nullptr)
continue;
if(2*(i-nums[i])+1<strs.size())
nodes[i]->left=nodes[2*(i-nums[i])+1];
if(2*(i-nums[i])+2<strs.size())
nodes[i]->right=nodes[2*(i-nums[i])+2];
}
return nodes[0];
}
int main(){
TreeNode* t1=new TreeNode(1);
TreeNode* t2=new TreeNode(2);
TreeNode* t3=new TreeNode(3);
TreeNode* t4=new TreeNode(4);
TreeNode* t5=new TreeNode(5);
t1->left=t2;
t1->right=t3;
t3->left=t4;
t3->right=t5;
string s=serialize(t1);
TreeNode *root=deserialize(s);
return 0;
}
面试题37:二叉搜索树转双向链表
#include <iostream>
#include <vector>
using namespace std;
//题目37 二叉搜索树转双向链表
//输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
/* 思路:
* 二叉搜索树的中序遍历结果是一个递增序列,要将二叉搜索树转换成排序的双向链表,通过更改节点的部分right left指针来实现
* 修改指针指向的操作借助中序遍历
* 此外,考虑是双向链表,所以设置前驱节点也是很有必要的,在转换后,right指针用于指向下一个节点,而left指针用于指向上个节点
* 设置前驱节点为pLastInList,由于存在子函数,且涉及到指针指向的变化,pLastInList参数必须为地址传递
* 大致步骤如下:设置pLastInList初值为nullptr,调用ConvertCore函数来进行中序遍历(实质就是递归),做指针指向的变化,pLastInList节点被传入
* 首先判断当前pNode是否为空,若为空则退出,回到上一层;
* 先调用ConvertCore函数来处理pNode的左子树
* 处理过后,左子树被转化成双向链表,此时pLastInList就是双向链表的最后一个节点,将pNode的left指针指向pLastInList
* 随后判断若pLastInList非空(只有初始时刻为空),则要让pLastInList的right节点指向pNode
* 再更新pLastInList为pNode
* 最后调用ConvertCore函数来处理pNode的右子树
* 处理完毕即得到了完整的双向链表,而此时pLastInList指针指向双向链表的最后,需要不停的找其left节点,回到链表头
* 最终返回链表头
*/
//牛客:https://www.nowcoder.com/practice/947f6eb80d944a84850b0538bf0ec3a5?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x):val(x), left(NULL), right(NULL) {}
};
void ConvertCore(TreeNode *pNode,TreeNode **pLastInList)
{
if(pNode==nullptr)
return;
if(pNode->left!=nullptr)
ConvertCore(pNode->left,pLastInList);
pNode->left=*pLastInList;
if((*pLastInList)!=nullptr)
(*pLastInList)->right=pNode;
*pLastInList=pNode;
if(pNode->right!=nullptr)
ConvertCore(pNode->right,pLastInList);
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
TreeNode *pLastInList=nullptr;
ConvertCore(pRootOfTree,&pLastInList);
TreeNode *pHeadOfList=pLastInList;
while(pHeadOfList!=nullptr&&pHeadOfList->left!=nullptr)
pHeadOfList=pHeadOfList->left;
return pHeadOfList;
}
int main(){
TreeNode* t1=new TreeNode(3);
TreeNode* t2=new TreeNode(1);
TreeNode* t3=new TreeNode(6);
TreeNode* t4=new TreeNode(4);
TreeNode* t5=new TreeNode(7);
t1->left=t2;
t1->right=t3;
t3->left=t4;
t3->right=t5;
TreeNode* head=Convert(t1);
return 0;
}
面试题38:字符串的排列
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
//题目:字符串的排列
//输入一个字符串,打印出该字符串中字符的所有排列。
//你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
/* 思路:
* 这是一个很典型的带重复元素的全排列问题,可以参考leetcode的47题,最简单的方式就是通过交换来实现
* 其中的难点在于如何处理重复元素,因为交换重复元素后得到的字符串是一样的,这使得最终的结果存在重复
* 所以需要排除掉待交换的两个元素重复的情况,即只有当两元素不同时才有交换的意义,否则不进行交换
* 以字符串"abcc"为例描述交换的思路:
* 首先对创建ans数组用于保存结果,进入PermutationHelp函数,该函数为递归函数
* 初始时k值为0,进入循环取i为k到3
* 每次循环交换后字符串分别为abcc bacc cbac cbca
* 随后进入下一次递归,k变为1 分别交换下标1和下标1 2 3
* 对于abcc,再次递归后有 abcc acbc accb
* 对于bacc,再次递归后有 bacc bcac bcca
* 对于cbac,再次递归后有 cbac cabc ccab
* 对于cbca,再次递归后有 cbca ccba cacb
* 再进入最后一次递归,k变为2 分别交换下标2和下标2 3
* 得到 abcc acbc accb bacc bcac bcca cbac cabc ccab cbca ccba cacb 共12个字符串
* 这即是上述问题的全排列
*
* 需要注意的地方就是两点,一是采用交换的方式,二是当交换元素相同时需要跳过
* */
//leetcode:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/
//牛客:https://www.nowcoder.com/practice/fe6b651b66ae47d7acce78ffdd9a96c7?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
void PermutationHelp(vector<string> &ans, string str, int k){
if(k == str.length())
ans.push_back(str);
for(int i = k; i < str.size(); i++) {
if (i != k && str[i] == str[k])
continue;
swap(str[i], str[k]);
PermutationHelp(ans, str, k + 1);
}
}
vector<string> Permutation(string str) {
vector<string> ans;
PermutationHelp(ans,str,0);
return ans;
}
int main()
{
string s="abcc";
vector<string> res=Permutation(s);
for(string s:res)
cout<<s<<endl;
return 0;
}
面试题39:数组中出现次数超过一半的数字
#include <iostream>
#include <vector>
using namespace std;
//题目:数组中出现次数超过一半的数字
/*
* 数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
* 你可以假设数组是非空的,并且给定的数组总是存在多数元素。
* */
/*思路:
* 最容易想到的方法就是利用set和map来进行计数,并选取出现次数最大的数字,看是否出现次数超过了一半
* 但这种方法需要大量的空间
* 这里采用一种新的思路,利用times、num这两变量即可完成数字的查找
* 大致思路如下:
* 初始时设置times为1,num为第一个元素表示从某一下标(上次出现times=0时的下标)到当前下标i(初始时下标为0)为止的子数组中,出现次数超过一半的数字是num
* 随后开始遍历剩下的元素,如果当前元素值numbers[i]等于num,则times++,而如果不等于num,则times--
* 为什么要这么做呢?这可以用"抵消"的思想去理解,举例解释一下,对于数组{1,2,3,2,2,2,5,4,2}
* 初始时times为1,num为1这表示当前这个数组{1}中,1为次数超过一半的数字
* 继续遍历,i=1,此时numbers[1]为2,不等于num值(1) times--后变为0,此时子数组为{1,2}
* 再下一步,由于times变为0 说明子数组{1,2}两元素相互抵消,从i=2开始需要重新设置num和times来继续计数,设置num为numbers[i],times为1
* 继续遍历,i=3,此时numbers[3]为2,子数组为{3,2}......如此下去,如果确保数组中存在出现次数超过一半的数字,最终的num值就是所求数字
* 这里主要是要理解这个"抵消"的概念,每次times为0时就表示前面的子数组中所有的元素都被抵消掉了
* 如果确实存在一个次数超过一半的数字,那么这个数字前面子数组中的出现的概率就是1/2
* 为便于理解,我再给两个极端的情况留给后面的人来分析:数组1:{1,2,3,2,4,2,5,2,2} 数组2:{2,2,2,2,2,1,3,4,5}
* 第一个数组每隔一次就会进行times=0的处理,而第二个数组则不会出现times=0的情况
*
* 最后,如果题目没有明确说明保证有这个数字存在,在遍历完后还要再遍历一次,统计num的个数,做一次验证
* */
//leetcode:https://leetcode-cn.com/problems/shu-zu-zhong-chu-xian-ci-shu-chao-guo-yi-ban-de-shu-zi-lcof/
//牛客:https://www.nowcoder.com/practice/e8a1b01a2df14cb2b228b30ee6a92163?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
int MoreThanHalfNum_Solution(vector<int> numbers) {
if(numbers.size()==0)
return 0;
int times=1;
int num=numbers[0];
int idx=0;
for(int i=1;i<numbers.size();i++){
if(times==0)) {
num = numbers[i];
times = 1;
}
else if(numbers[i]==num)
times++;
else
times--;
}
int n=0;
for(int i=0;i<numbers.size();i++){
if(numbers[i]==num)
n++;
}
if(n>numbers.size()/2)
return num;
else
return 0;
}
int main()
{
int a[]={1,2,3,2,2,2,5,4,2};
vector<int> v(a,a+9);
int res=MoreThanHalfNum_Solution(v);
cout<<res<<endl;
return 0;
}
面试题40:最小的k个数
include <iostream>
#include <vector>
using namespace std;
//题目:最小的k个数
//输入整数数组 arr ,找出其中最小的 k 个数。例如,输入4、5、1、6、2、7、3、8这8个数字,则最小的4个数字是1、2、3、4
/*思路
* 这道题可以采用快排的方式来实现,但不需要全部排序,只需要保证最小的k个数有序即可,因为只需要这k个数
* partition函数不用介绍了,该函数用来实现一次快拍,返回的下标为分界点,即下标左侧元素值小于该下标处的值,而右侧元素大于该下标处的值
* 所以可以多次调用partition来进行排序,直到返回的index值等于k-1,即说明前k个数均小于后面的数字,即是满足条件的最小的k个数
* 大致步骤如下:
* 首先执行一次快排,保存返回值index,比较index和k-1的大小,如果index正好等于k-1,即说明此时数组的前k个数就是结果
* 否则,如果index大于k-1,说明最小的k个数肯定在下标0~index之间,则可以缩小数组范围为从begin到index-1,来继续排序
* 如果index小于k-1,则说明从下标0~index的数肯定包含在结果中已经确定,接下来只需要缩小空间到index+1到end,继续找剩下的k-index-1个数
* 最后,总会出现index=k-1的情况
* 此时将前k个数取出,即为最终结果
* */
//leetcode:https://leetcode-cn.com/problems/zui-xiao-de-kge-shu-lcof/
//牛客:https://www.nowcoder.com/practice/6a296eb82cf844ca8539b57c23e6e9bf?tpId=13&rp=1&ru=%2Fta%2Fcoding-interviews&qru=%2Fta%2Fcoding-interviews%2Fquestion-ranking
void swap(int *a,int *b){
int tmp=*a;
*a=*b;
*b=tmp;
}
int partition(vector<int> &input,int start,int end){
int index=(end-start)/2+start;
swap(&input[index],&input[end]);
int p=start;
for(index=start;index<end;index++){
if(input[index]<input[end]){
if(index!=p)
swap(&input[index],&input[p]);
p++;
}
}
swap(&input[p],&input[end]);
return p;
}
vector<int> GetLeastNumbers_Solution(vector<int> input, int k) {
if(input.size()==0||k>input.size()||k<=0)
return {};
int begin=0;
int end=input.size()-1;
int index=partition(input,begin,end);
while(index!=k-1){
if(index>k-1){
end=index-1;
index=partition(input,begin,end);
}
else{
begin=index+1;
index=partition(input,begin,end);
}
}
vector<int> res(k);
for(int i=0;i<k;i++)
res[i]=input[i];
return res;
}