程序员面试题(C++ 实现) - Day4

说明


  • 以下题目均来自于牛客网
  • 以下代码用 C++11 编写
  • 以下代码均已编译通过(Compile by MINGW)
  • 以下代码均有测试案例(Main function)
  • 以下代码均已进行优化或部分优化(Optimize)
  • 以下代码均有注释(Comment)
  • 部分题目附有解析(Analysis)
  • 如有错误或侵权,请联系博主

今日题目一览


  1. 树的子结构
  2. 二叉树的镜像
  3. 顺时针打印矩阵
  4. 包含min函数的栈
  5. 栈的压入、弹出序列

1. 树的子结构


问题描述

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

解析

首先要明确,B 树是 A 树的子结构的话,从 A 树找到和 B 的根节点的值相等的结点,由此开始,两者的左右子树必须要一模一样才算是子结构,这个一模一样有两层含义:

  1. 同为非空结点(空结点)
  2. 结点不为空的情况下二者的值必须相等

实现

/*
输入两棵二叉树A,B,判断B是不是A的子结构。
(ps:我们约定空树不是任意一个树的子结构)
 */

#include <iostream>

using namespace std;

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
        val(x), left(NULL), right(NULL) {
    }
};

class Solution {
public:

    bool HasSubtree(TreeNode *pRoot1, TreeNode *pRoot2) {
        if(!pRoot1)
            return false; // A 树不能为空

        if(!pRoot2)
            return false; // 空树不是任何树的子树

        // 对于每一个结点,必须判断当前结点以及当前结点的左右子树
        return ( dfs(pRoot1, pRoot2)) ||
               HasSubtree(pRoot1->left, pRoot2) ||
               HasSubtree(pRoot1->right, pRoot2);
    }

private:
    bool dfs(TreeNode *r1, TreeNode *r2) {
        if(!r2)
            return true;  // 如果 r2 是空的话,不管左子树是不是空,那么 r2 一定是 r1 的子树 (r1 >= r2)

        if(!r1)
            return false;  // 这种情况属于 r2 为空,r1 为空,那么肯定不是子树

        if(r1->val != r2->val) // 结点都不为空的话,就比较值是否相等
            return false;

        return dfs(r1->left, r2->left) && dfs(r1->right, r2->right); // 迭代
    }
};

int main(int argc, char const *argv[]) {
    //测试用例略
    //
    return 0;
}

输出结果

更多有关树的内容请参考我的另一篇博客:树结构大全

2. 二叉树的镜像


问题描述

操作给定的二叉树,将其变换为源二叉树的镜像。

解析

二叉树的镜像定义:

源二叉树

              ~~~~~~~~~~~~~               8
           ~~~~~~~~~~            /       ~~~~~       \
          ~~~~~~~~~           6     ~~~     10     ~~~    
          ~~~~~~~~~          /   ~   \      ~~~~      /    ~~   \
       ~~~~~~        5   ~   7   ~   9   ~   11

镜像二叉树

              ~~~~~~~~~~~~~               8
           ~~~~~~~~~~            /        ~~~~~~        \
          ~~~~~~~~~           10      ~~~~      6     ~~~    
          ~~~~~~~~~          /   ~   \       ~~~~~       /    ~~   \
       ~~~~~~        11   ~   9   ~   7   ~   5

所以问题就简单化了,就是简单的左右子树交换地址变量,然后迭代

实现

/*
操作给定的二叉树,将其变换为源二叉树的镜像。

二叉树的镜像定义:
源二叉树
            8
           /  \
          6   10
         / \  / \
        5  7 9 11

镜像二叉树
            8
           /  \
          10   6
         / \  / \
        11 9 7  5

*/

#include <iostream>

using namespace std;

struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
        val(x), left(NULL), right(NULL) {
    }
};

void Mirror1(TreeNode *pRoot) { // 简洁高效
    if (pRoot == NULL)
        return;

    TreeNode *pTmp;
    pTmp = pRoot->left;
    pRoot->left = pRoot->right;
    pRoot->right = pTmp;
    Mirror1(pRoot->left);
    Mirror1(pRoot->right);
}

void Mirror2(TreeNode *pRoot) {
    if (pRoot->left) {
        Mirror2(pRoot->left);

        TreeNode *pTmp;
        pTmp = pRoot->left;
        pRoot->left = pRoot->right;
        pRoot->right = pTmp;
    }

    if (pRoot->right) {
        Mirror2(pRoot->right);

        TreeNode *pTmp;
        pTmp = pRoot->right;
        pRoot->right = pRoot->left;
        pRoot->left = pTmp;
    }
}

int main(int argc, char const *argv[]) {
    /* code */
    return 0;
}

输出结果

更多有关树的内容请参考我的另一篇博客:树结构大全

3. 顺时针打印矩阵


问题描述

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字,

例如,如果输入如下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 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.

 */

#include <iostream>
#include <vector>

using namespace std;

vector<int> printMatrix(vector<vector<int> > matrix) {
    int row = matrix.size();
    int col = matrix[0].size();
    vector<int> result;
    if(row == 0 || col == 0)
        return result;

    int left = 0, right = col - 1, top = 0, btm = row - 1;
    while(left <= right && top <= btm) {
        for(int i = left; i <= right; i++)
            result.push_back(matrix[top][i]);  // 1 2 3 4       6 7

        if(top < btm)
            for(int i = top + 1; i <= btm; i++)
                result.push_back(matrix[i][right]); // 8 12 16   11

        if(top < btm && left < right)
            for(int i = right - 1; i >= left; i--)
                result.push_back(matrix[btm][i]);  // 15 14 13   10

        if(top + 1 < btm && left < right)
            for(int i = btm - 1; i >= top + 1; i--)
                result.push_back(matrix[i][left]);  // 3 5

        left++; right--; top++; btm--;
    }
    return result;
}

vector<vector<int> > createMatrix() {
    vector<vector<int> > vec;
    for(int i = 0; i < 4; ++i) {
        vector<int> vec_son;
        for(int j = 1; j <= 4; ++j) {
            vec_son.push_back(4 * i + j);
        }
        vec.push_back(vec_son);
    }
    return vec;
}

void Print(vector<vector<int> > &vec) {
    for(int i = 0; i < vec.size(); ++i) {
        for(int j = 0; j < vec[i].size(); j++)
            cout << vec[i][j] << "  ";
        cout << endl;
    }

}

int main(int argc, char const *argv[]) {
    //Test
    vector<vector<int> > vec = createMatrix();
    Print(vec);

    vector<int> v = printMatrix(vec);
    for(int i = 0; i < v.size(); i++)
        cout << v[i] << " ";

    return 0;
}

输出结果

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. 包含min函数的栈


问题描述

定义栈的数据结构,请在该类型中实现一个能够得到栈中所含最小元素的min函数
(时间复杂度应为O(1))。

解析

建立两个 stack1、stack2,其中,stack1 用来存取新 push 进来的元素,stack2 用来存储“stack1 对应位置”的最小值。

每当stack1 push 进来新元素后就和 stack2.top() 的元素进行比较,如果小于 stack2.top() 的值,就将新值添加到 stack2,否则将 stack2.back() 的元素再次添加到 stack2。

上面的解释可以用下面的这个图来说明:

push ============ >
StackInt 11 9 6 20 18 2
StackMin 11 9 6 6 6 2
< ============= pop

这样做的目的是为了保证 stack1.pop() 出元素后,stack2 中仍能正确对应 stack1 中所有值的最小值

实现

/*
定义栈的数据结构,
请在该类型中实现一个能够得到栈中所含最小元素的min函数
(时间复杂度应为O(1))。
 */

#include <iostream>
#include <stack>

using namespace std;

class MyStack {
public:
    void push(int value) {
        StackInt.push(value);
        if(StackMin.empty())  // 注意逻辑
            StackMin.push(value);
        else if(StackMin.top() < value)
            StackMin.push(StackMin.top());
        else
            StackMin.push(value);
    }

    void pop() {
        if(!StackInt.empty()) {
            StackInt.pop();
            StackMin.pop();
        }
    }

    int top() {
        return StackInt.top();
    }
    int min() {
        return StackMin.top();
    }
private:
    stack<int> StackInt;
    stack<int> StackMin;
};

int main(int argc, char const *argv[]) {
    //Test
    /*
    eg:
    push ====================== >
    StackInt  11  9  6  20  18  2
    StackMin  11  9  6   6   6  2
    < ======================= pop
     */

    MyStack ms;
    int array[6] = {11, 9, 6, 20, 18, 2};
    for(int i = 0; i < sizeof(array) / sizeof(int); i++)
        ms.push(array[i]);


    for(int i = sizeof(array) / sizeof(int) - 1; i >= 0; i--) {
        cout << ms.min() << " ";
        ms.pop();
    }

    return 0;
}

输出结果

2 6 6 6 9 11

5. 栈的压入、弹出序列


问题描述

输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否可能为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如序列1,2,3,4,5是某栈的压入顺序,序列4,5,3,2,1是该压栈序列对应的一个弹出序列,但4,3,5,1,2就不可能是该压栈序列的弹出序列。(注意:这两个序列的长度是相等的)

解析

首先要理解为什么入栈顺序是 1,2,3,4,5,会出现 4,5,3,2,1 的出栈顺序(示意图中 1 代表出栈):

1 - 2 - 3 - 4 - 4 - 5 - 5 - 3 - 2 - 1

所以造成这样的原因是因为有些元素提前出栈(出栈的元素不会再次入栈)

再看看为什么 4,3,5,1,2 为什么不可能是出栈顺序:

1 - 2 - 3 - 4 - 4 - 3 - 5 - 5 ,再往下走肯定是 2 先出栈,1 再出栈,所以错就错在 1 和 2 的顺序

懂得上面的道理后,我们来看怎么实现这个功能:

我们可以建立一个 vector 类型的辅助栈来实现这个过程,代码比较好理解,这里不做太多的解释,大概用图描述一下辅助 vector 的变化:

pushV 1 2 3 4 5

popV 4 5 3 2 1

v 1
v 1 2
v 1 2 3
v 1 2 3 4
v 1 2 3
v 1 2 3 5
v 1 2 3
v 1 2
v 1
v

判断标志就是 v 最后为不为空,空的话就是正确的出栈顺序!!!

实现

/*
输入两个整数序列,第一个序列表示栈的压入顺序,
请判断第二个序列是否可能为该栈的弹出顺序。
假设压入栈的所有数字均不相等。
例如序列1,2,3,4,5是某栈的压入顺序,
序列4,5,3,2,1是该压栈序列对应的一个弹出序列,
但4,3,5,1,2就不可能是该压栈序列的弹出序列。
(注意:这两个序列的长度是相等的)
 */

#include <iostream>
#include <stack>
#include <vector>

using namespace std;

// 方法一
bool IsPopOrder1(vector<int> pushV, vector<int> popV) {
    if(pushV.size() == 0) return false;
    vector<int> stack;
    for(int i = 0, j = 0 ; i < pushV.size();) {
        stack.push_back(pushV[i++]);
        while(j < popV.size() && stack.back() == popV[j]) {
            stack.pop_back();
            j++;
        }
    }
    return stack.empty();
}

// 方法二
bool IsPopOrder2(vector<int> pushV, vector<int> popV) {
    stack<int> st;//辅助栈
    int id = 0;
    for(int i = 0; i < popV.size(); ++i) {
        while(st.empty() || st.top() != popV[i]) {
            st.push(pushV[id++]);
            if(id > pushV.size()) {
                return false;
            }
        }
        st.pop();
    }

    if(st.empty()) // 判断条件就是最后有没有剩下的
        return true;
    else
        return false;
}

int main(int argc, char const *argv[]) {
    //Test
    vector<int> pushV;
    pushV.push_back(1); pushV.push_back(2); pushV.push_back(3);
    pushV.push_back(4); pushV.push_back(5);

    vector<int> popV;
    popV.push_back(4); popV.push_back(5); popV.push_back(1);
    popV.push_back(2); popV.push_back(3);

    cout << boolalpha << ((IsPopOrder1(pushV, popV) == 1) ? true : false) << endl;

    return 0;
}

输出结果

false

推荐一篇博客,作者提出了一种比较巧妙的想法,有喜欢者可以自行实现一下:栈 - 关于出栈序列

该文章的想法很独特,提出了后出先入逆序原则

亦即:对于入栈顺序为 1 2 3 4,出栈顺序为 3 1 2 4 的情况:

  • 选择任意元素 e ,这里选择 3
  • 比 3 后出栈的有三个元素 1 2 4
  • 1 2 4 中比 3 先入栈的有两个元素 1 2
  • 但是 1 2 是正序的,而不是逆序的

===> 所以这个序列不是合法出栈序列

有木有很惊艳~~~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值