4 解决面试题的思路
2020/03/25 上午09点17分
面试官都喜欢在动手之前先将清楚思路,可以通过画图、举例子等方式,讲清楚思路以后再开始写代码。
面试题27 二叉树的镜像
题目描述
操作给定的二叉树,将其变换为源二叉树的镜像。
解题思路
递归进行,先镜像左子树,然后镜像右子树,然后在当前节点把左右子树交换。
Code
class Solution {
public:
void Mirror(TreeNode *pRoot) {
if(pRoot == NULL)
return;
Mirror(pRoot -> left);
Mirror(pRoot -> right);
TreeNode* t = pRoot -> left;
pRoot -> left = pRoot -> right;
pRoot -> right = t;
}
};
面试题28 对称的二叉树
题目描述
请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。
解题思路
左子树和右子树同时递归,递归时要保证访问的顺序,访问到目前的这两个节点的时候,判断他们节点的值是否相等。
Code
class Solution {
public:
bool isSym(TreeNode* left, TreeNode* right){
if(left == NULL && right == NULL)
return true;
if(left == NULL || right == NULL)
return false;
if(left -> val != right -> val)
return false;
return isSym(left -> left, right -> right) && isSym(left -> right, right -> left);
}
bool isSymmetrical(TreeNode* pRoot)
{
if (pRoot == NULL) return true;
return isSym(pRoot -> left, pRoot -> right);
}
};
面试题29 顺时针打印矩阵
题目描述
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,例如,如果输入如下4 X 4矩阵: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.
解题思路
定义 4 个边界,每次轮流打印这四个边界上的值。
Code
class Solution {
public:
vector<int> printMatrix(vector<vector<int> > matrix) {
int left = 0, right = matrix[0].size() - 1, top = 0, bottom = matrix.size() - 1;
vector<int> result;
while(left <= right && top <= bottom){
for (int i = left; i <=right; i ++){
result.push_back(matrix[top][i]);
}
for (int i = top + 1; i <= bottom; i ++){
result.push_back(matrix[i][right]);
}
if (top != bottom){
// 偶数行
for (int i = right - 1; i >= left; i --){
result.push_back(matrix[bottom][i]);
}
}
if (left != right){
// 偶数列,而且这里不可以取等号,不然会多输出一个
for (int i = bottom - 1; i > top; i --){
result.push_back(matrix[i][left]);
}
}
left ++; top ++;
right --; bottom --;
}
return result;
}
};
面试题30 包含 min 函数的栈
题目描述
定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数(时间复杂度应为O(1))。
注意:保证测试中不会当栈为空的时候,对栈调用pop()或者min()或者top()方法。
解题思路
分析的思路:需要添加一个变量来存放最小的元素,每次入栈时更新这个最小的元素。但是如果这个最小元素出栈,这时我们就不知道下一个最小的元素应该是多少了。所以应该想到,也用一个栈来保存上一个最小的元素。
Code
class Solution {
public:
void push(int value) {
s.push(value);
if(min_s.empty() || min_s.top() >= value){
min_s.push(value);
}
}
void pop() {
int v = s.top();
s.pop();
if (v == min_s.top()){
min_s.pop();
}
}
int top() {
return s.top();
}
int min() {
return min_s.top();
}
private:
stack<int> s;
stack<int> min_s;
};
面试题31 栈的压入、弹出序列
题目描述
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)
解题思路
用一个辅助栈,如果当前栈顶不是需要 pop 的元素,那么就继续 push 元素进栈,直到已经没有元素进栈或者当前栈顶的元素是需要 pop 的元素,那么就让当前元素 pop 出来。
Code
class Solution {
public:
bool IsPopOrder(vector<int> pushV,vector<int> popV) {
stack<int> s;
int j = 0;
for(int i = 0; i < popV.size(); i ++){
while(s.empty() || s.top() != popV[i]){
if (j == pushV.size())
break;
s.push(pushV[j ++]);
}
if (s.empty() || s.top() != popV[i])
return false;
s.pop();
}
return true;
}
};
面试题32.1 从上往下打印二叉树
题目描述
从上往下打印出二叉树的每个节点,同层节点从左至右打印。
解题思路
树的层次遍历,利用队列实现。
Code
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
std::vector<int> result;
if (root == NULL) return result;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
TreeNode* t = q.front();
q.pop();
if(t == NULL) continue;
result.push_back(t -> val);
q.push(t -> left);
q.push(t -> right);
}
return result;
}
};
面试题32.2 把二叉树打印成多行
题目描述
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
解题思路
- 方法1:同样也是使用队列,不过这里需要记录当前节点是否是这一层的末尾,如果是末尾的话,把当前打印的节点放到总的结果里,然后把这个 vector 清空,同时更换到下一行的最末尾的节点。因为这一层已经处理完了,下一层最末尾的节点就是现在队列的最末尾,要考虑到要提取列表最末尾,需要使用双端队列 或者 list。
- 方法2:在打印的过程中统计孩子节点的个数,有了当前层节点个数,就能知道什么时候打印完这一层。
Code
解法1:
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
std::vector<std::vector<int> > result;
if (pRoot == NULL) return result;
list<TreeNode*> q;
TreeNode* last = pRoot;
q.push_back(pRoot);
vector<int> v;
while(!q.empty()){
TreeNode* t = q.front();
q.pop_front();
v.push_back(t -> val);
if (t -> left){
q.push_back(t -> left);
}
if (t -> right){
q.push_back(t -> right);
}
if (t == last){
last = q.back();
result.push_back(v);
v.clear();
}
}
return result;
}
};
解法2:
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
td::vector<std::vector<int> > result;
if (pRoot == NULL) return result;
list<TreeNode*> q;
q.push_back(pRoot);
int current_layer = 1, next_layer = 0, current_processed = 0;
std::vector<int> v;
while(!q.empty()){
TreeNode* t = q.front(); q.pop_front();
v.push_back(t -> val);
if (t -> left){
q.push_back(t -> left);
next_layer ++;
}
if (t -> right){
q.push_back(t -> right);
next_layer ++;
}
current_processed ++;
if (current_processed == current_layer){
result.push_back(v);
v.clear();
current_layer = next_layer;
next_layer = 0;
current_processed = 0;
}
}
return result;
}
};
面试题32.3 之字形打印二叉树
题目描述
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。
解题思路
- 使用一个队列和一个栈来实现,设置一个变量指示当前层是否应该逆向打印,对于每一个节点,正常按照层次遍历,如果当前层不需要逆向打印,直接打印就好,如果需要逆向打印,就把该元素入栈,等到这一层便利完成以后再逆向打印出来该层的元素。
- 使用两个栈实现,打印奇数层的时候,让孩子节点入栈1,此时需要先入右孩子,再入左孩子;打印偶数层的时候,让孩子节点入栈2,此时需要先入左孩子,再入右孩子。
Code
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
if (pRoot == NULL) return {};
std::vector<vector<int>> result;
list<TreeNode*> q;
q.push_back(pRoot);
TreeNode* last = pRoot;
stack<int> s;
std::vector<int> v;
bool reverse = false;
while(!q.empty()){
TreeNode* t = q.front(); q.pop_front();
if (reverse){
s.push(t -> val);
}else{
v.push_back(t -> val);
}
if (t -> left)
q.push_back(t -> left);
if (t -> right)
q.push_back(t -> right);
if (t == last){
if (reverse){
while(!s.empty()){
int a = s.top();
s.pop();
v.push_back(a);
}
}
result.push_back(v);
last = q.back();
v.clear();
reverse ^= 1;
}
}
return result;
}
};
面试题33 二叉搜索树的后序遍历序列
题目描述
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
解题思路
二叉搜索树的后序遍历需要满足以下的条件:最后一个节点是根节点,前面一部分是小于根节点,后面一部分是大于根节点,根据这个性质,不断的去递归判断。
Code
class Solution {
public:
bool isBST(std::vector<int> sequence, int start, int end){
if (start >= end || start < 0 || end > sequence.size() - 1)
return true;
int root_val = sequence[end];
int i = start;
while(i < end && sequence[i] < root_val)
i ++;
int j = i; //first larger value
while(i < end && sequence[i] > root_val)
i ++;
if (i != end)
return false;
return isBST(sequence, start, j - 1) && isBST(sequence, j, end - 1);
}
bool VerifySquenceOfBST(vector<int> sequence) {
if (sequence.empty()) return false;
return isBST(sequence, 0, sequence.size() - 1);
}
};
面试题34 二叉树中和为某一值的路径
题目描述
输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)
解题思路
先序遍历二叉树,在遍历过程中保留住前缀,并且不断更新需要寻找的 target 值,当遍历到叶节点时,如果叶结点的 val 和 target 值相等,说明找到,加入到结果中。
Code
class Solution {
public:
void run(TreeNode* root, int target, vector<int> prefix){
if (root == NULL) return;
prefix.push_back(root -> val);
if (root -> left == NULL && root -> right == NULL){
if (root -> val == target){
result.push_back(prefix);
}
return;
}
run(root -> left, target - root -> val, prefix);
run(root -> right, target - root -> val, prefix);
}
vector<vector<int> > FindPath(TreeNode* root,int expectNumber) {
if (root == NULL) return {};
std::vector<int> current;
run(root, expectNumber, current);
return result;
}
private:
std::vector<vector<int>> result;
};
面试题35 复杂链表的复制
题目描述
输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针指向任意一个节点),返回结果为复制后复杂链表的head。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。
解题思路
- 暴力解法,先按照 next 的值不断去建立链表,然后再在新的链表中去寻找对应的 random 指针,时间复杂度 为 n方,空间复杂度为 O(1)。
- 使用一个字典,建立新节点和旧节点之间的映射关系。然后递归的去改变节点指针的指向。时间复杂度为 O(n),空间复杂度为 O(1)。
- 但这题还有更巧妙的解法,空间复杂度为 O(1),时间复杂度为 O(n)。可以将该问题拆分成三个子问题
4. 在旧链表中创建新链表。每个旧的节点后面建立一个新的节点
5. 根据旧链表的 random 节点,改变新链表的 random 节点
6. 将两个链表分离开来
Code
解法2:
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if (pHead == NULL) return NULL;
if (m_.count(pHead))
return m_[pHead];
RandomListNode* t = new RandomListNode(pHead -> label);
m_[pHead] = t;
t -> next = Clone(pHead -> next);
t -> random = Clone(pHead -> random);
return t;
}
private:
unordered_map<RandomListNode*, RandomListNode*> m_;
};
解法3:(本地可以通过,但是牛客提交上去无法通过,不知道为什么)
class Solution {
public:
RandomListNode* Clone(RandomListNode* pHead)
{
if (pHead == NULL) return NULL;
RandomListNode* p = pHead;
while(p){
RandomListNode* t = p -> next;
p -> next = new RandomListNode(p -> label);
p -> next -> next = t;
p = t;
}
p = pHead;
while(p){
RandomListNode* t = p -> next;
if (p -> random){
t -> random = p -> random -> next;
}
p = t -> next;
}
RandomListNode* newHead = pHead -> next;
p = newHead;
while(p && p -> next){
RandomListNode* tobedeleted = p -> next;
p -> next = p -> next -> next;
p = p -> next;
}
return newHead;
}
}
面试题36 二叉搜索树与双向链表
题目描述
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
解题思路
二叉树的中序遍历,需要注意,遍历的过程中保留住上一个节点,还有最后需要返回双向链表的头。
Code
递归版本:
class Solution {
public:
void core(TreeNode* cur, TreeNode*& pre){
if(cur == NULL)
return;
core(cur -> left, pre);
cur -> left = pre;
if(pre) pre -> right = cur;
pre = cur;
core(cur -> right, pre);
}
TreeNode* Convert(TreeNode* pRootOfTree)
{
if(pRootOfTree == NULL) return NULL;
TreeNode* pre = NULL;
core(pRootOfTree, pre);
while(pRootOfTree -> left){
pRootOfTree = pRootOfTree -> left;
}
return pRootOfTree;
}
};
非递归版本:
class Solution {
public:
TreeNode* Convert(TreeNode* pRootOfTree)
{
if (pRootOfTree == NULL) return NULL;
stack<TreeNode*> s;
TreeNode* pre = NULL;
TreeNode* t = pRootOfTree, *head;
while(!s.empty() || t != NULL){
while(t != NULL){
s.push(t);
t = t -> left;
}
if(!s.empty()){
t = s.top(); s.pop();
if (pre != NULL){
pre -> right = t;
t -> left = pre;
}
else{ // 第一次出栈,说明是链表的头节点
head = t;
}
pre = t;
t = t -> right;
}
}
return head;
}
};
面试题37 序列化二叉树
题目描述
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
解题思路
主要是设计到字符串的操作。序列化的结果是,除了空节点之外,每一个节点之后都跟一个逗号。用到的字符串操作函数:
strcpy(char* dest, string source)
将字符串复制到char*
表示的字符串strcat(char* dest, char* sourse)
将后面的字符串补到前面(前提是前面的字符串有足够的位置)string
可以直接使用push_back(char c)
来往后面补字符
Code
class Solution{
private:
TreeNode* decode(char*& str){
if(*str == '#'){
++ str;
return NULL;
}
int num = 0;
while(*str != ','){
num = num * 10 + (*(str ++) - '0');
}
str ++;
TreeNode* node = new TreeNode(num);
node -> left = decode(str);
node -> right = decode(str);
return node;
}
public:
char* Serialize(TreeNode* root){
if(!root){
char *res = new char[2];
*res = '#';
res[1] = '\0';
return res;
}
string r = to_string(root -> val);
r.push_back(',');
char *left = Serialize(root -> left);
char *right = Serialize(root -> right);
char *ret = new char[strlen(left) + strlen(right) + r.size() + 1];
strcpy(ret, r.c_str());
strcat(ret, left);
strcat(ret, right);
delete left;
delete right;
return ret;
}
TreeNode* Deserialize(char *str){
return decode(str);
}
};
面试题38 字符串的排列
题目描述
输入一个字符串,按字典序打印出该字符串中字符的所有排列。例如输入字符串abc,则打印出由字符a,b,c所能排列出来的所有字符串abc,acb,bac,bca,cab 和 cba。
解题思路
直接考虑全排列有点困难,需要从以下的角度考虑:第一个字符,可以取哪些值,把这些值都取到了以后,开始考虑第二个字符,可以取哪些值,所以可以递归进行求解。
递归的终止条件是需要考虑的位置已经到了最后一个,没有元素可以交换,这时把结果记录下来。
在递归的每一步,计算 start 位置可以取的字符的值,为它之后所有的字符,所以不断让他们交换,然后去递归进行处理。
还有一点需要注意,因为结果中可能会有重复的,所以需要用集合来避免统计两次。
Code
class Solution {
public:
void main(string str, int start){
int n = str.size();
if (start == n - 1){
s.insert(str);
return;
}
for(int i = start; i < n; i ++){
swap(str[i], str[start]);
main(str, start + 1);
}
}
vector<string> Permutation(string str) {
main(str, 0);
std::vector<string> v(s.begin(), s.end());
return v;
}
private:
std::set<string> s;
};