#　面试题

## 1

### 题目

H2O生成

现在有两种线程，氧 oxygen 和氢 hydrogen，你的目标是组织这两种线程来产生水分子。

存在一个屏障（barrier）使得每个线程必须等候直到一个完整水分子能够被产生出来。

氢和氧线程会被分别给予 releaseHydrogen 和 releaseOxygen 方法来允许它们突破屏障。

这些线程应该三三成组突破屏障并能立即组合产生一个水分子。

你必须保证产生一个水分子所需线程的结合必须发生在下一个水分子产生之前。

换句话说:

如果一个氧线程到达屏障时没有氢线程到达，它必须等候直到两个氢线程到达。
如果一个氢线程到达屏障时没有其它线程到达，它必须等候直到一个氧线程和另一个氢线程到达。
书写满足这些限制条件的氢、氧线程同步代码。

### 答案

```c++
class H2O {
public:
    int cntO;// 氧气的计数
    int cntH;// 氢气的计数
    mutex m;
    condition_variable cv;

    H2O() {
        cntO = 0;
        cntH = 0;
    }

    void hydrogen(function<void()> releaseHydrogen) {
        unique_lock<mutex> l(m);
        cv.wait(l, [this]()
        {
            // 氢气最大是2
            return this->cntH < 2;
        });
        // releaseHydrogen() outputs "H". Do not change or remove this line.
        releaseHydrogen();
        ++cntH;
        // 已经构成 H2O ，重置计数器
        if (cntH + cntO == 3)
        {
            cntH = 0;
            cntO = 0;
        }
        cv.notify_one();
    }

    void oxygen(function<void()> releaseOxygen) {
        unique_lock<mutex> l(m);
        cv.wait(l, [this]()
        {
            // 氧气最大是1
            return this->cntO < 1;
        });
        // releaseOxygen() outputs "O". Do not change or remove this line.
        releaseOxygen();
        ++cntO;
        // 已经构成 H2O ，重置计数器
        if (cntH + cntO == 3)
        {
            cntH = 0;
            cntO = 0;
        }
        cv.notify_one();
    }
};
```

## 2

### 题目

用图画出无人机姿态控制的串级反馈控制系统

### 答案

![image-20210513201926139](https://yewuyadeimagewall.oss-cn-hangzhou.aliyuncs.com/image-20210513201926139.png)

进程间通信消息队列



## 3

### 题目

请你说一下多线程，线程同步的几种方式

### 答案

进程是对运行时程序的封装，是系统进行资源调度和分配的的基本单位，实现了操作系统的并发；

线程是进程的子任务，是CPU调度和分派的基本单位，用于保证程序的实时性，实现进程内部的并发；线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器：独自的寄存器组，指令计数器和处理器状态。每个线程完成不同的任务，但是共享同一地址空间（也就是同样的动态内存，映射文件，目标代码等等），打开的文件队列和其他内核资源。

线程间通信的方式:

1、临界区：

通过多线程的串行化来访问公共资源或一段代码，速度快，适合控制数据访问；

2、互斥量 Synchronized/Lock：

采用互斥对象机制，只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个，所以可以保证公共资源不会被多个线程同时访问

3、信号量 Semphare：

为控制具有有限数量的用户资源而设计的，它允许多个线程在同一时刻去访问同一个资源，但一般需要限制同一时刻访问此资源的最大线程数目。

4、事件(信号)，Wait/Notify：

通过通知操作的方式来保持多线程同步，还可以方便的实现多线程优先级的比较操作

## 4

### 题目

编写一个函数，作用是把一个char组成的字符串循环右移n个。比如原来是“abcdefghi”如果n=2，移位后应该是“hiabcdefg” 函数头是这样的：
//pStr是指向以'\0'结尾的字符串的指针
//steps是要求移动的n

```c++
void LoopMove ( char * pStr, int steps )
{
 //请填充...
}
```

### 答案

```c++
void LoopMove ( char *pStr, int steps )
{
 int n = strlen( pStr ) - steps;
 char tmp[MAX_LEN]; 
 strcpy ( tmp, pStr + n ); 
 strcpy ( tmp + steps, pStr); 
 *( tmp + strlen ( pStr ) ) = '\0';
 strcpy( pStr, tmp );
}
```

## 5

### 题目

把M个同样的苹果放在N个同样的盘子里，允许有的盘子空着不放，问共有多少种不同的分法？（用K表示）5，1，1和1，5，1 是同一种分法。

输入描述

第一行是测试数据

 输出描述

对输入的每组数据M和N，用一行输出相应的K。

 输入例子

```
1
7 3
```

 输出例子

```
8
```

### 答案

```c
#include<stdio.h>
 
int fun(int m,int n)  //m个苹果放在n个盘子***有几种方法
{
    if(m==0||n==1)  //因为我们总是让m>=n来求解的，所以m-n>=0,所以让m=0时候结束，如果改为m=1，
        return 1;    //则可能出现m-n=0的情况从而不能得到正确解   
    if(n>m)
        return fun(m,m);
    else
        return fun(m,n-1)+fun(m-n,n);
}
 
int main()
{
    int T,m,n;
    scanf("%d",&T);
    while(T--)
    {
        scanf("%d%d",&m,&n);
        printf("%d\n",fun(m,n));
    }
}
```

## 6

### 题目

给定一个经过编码的字符串，返回它解码后的字符串。

编码规则为: k[encoded_string]，表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的；输入字符串中没有额外的空格，且输入的方括号总是符合格式要求的。

此外，你可以认为原始数据不包含数字，所有的数字只表示重复的次数 k ，例如不会出现像 3a 或 2[4] 的输入。

示例 1：

输入：

```
s = "3[a]2[bc]"
```

输出：

```
aaabcbc
```

输入：

```
s = "3[a2[c]]"
```
输出：

```
accaccacc
```

初始代码

```c++
class Solution {
public:
    string decodeString(string s) {

    }
};
```

### 答案

```c++
class Solution {
public:
    string getDigits(string &s, size_t &ptr) {
        string ret = "";
        while (isdigit(s[ptr])) {
            ret.push_back(s[ptr++]);
        }
        return ret;
    }

    string getString(vector <string> &v) {
        string ret;
        for (const auto &s: v) {
            ret += s;
        }
        return ret;
    }

    string decodeString(string s) {
        vector <string> stk;
        size_t ptr = 0;

        while (ptr < s.size()) {
            char cur = s[ptr];
            if (isdigit(cur)) {
                // 获取一个数字并进栈
                string digits = getDigits(s, ptr);
                stk.push_back(digits);
            } else if (isalpha(cur) || cur == '[') {
                // 获取一个字母并进栈
                stk.push_back(string(1, s[ptr++])); 
            } else {
                ++ptr;
                vector <string> sub;
                while (stk.back() != "[") {
                    sub.push_back(stk.back());
                    stk.pop_back();
                }
                reverse(sub.begin(), sub.end());
                // 左括号出栈
                stk.pop_back();
                // 此时栈顶为当前 sub 对应的字符串应该出现的次数
                int repTime = stoi(stk.back()); 
                stk.pop_back();
                string t, o = getString(sub);
                // 构造字符串
                while (repTime--) t += o; 
                // 将构造好的字符串入栈
                stk.push_back(t);
            }
        }

        return getString(stk);
    }
};
```

## 7

### 题目

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

输入：

```
matrix = [[1,2,3],[4,5,6],[7,8,9]]
```


输出：

```
[1,2,3,6,9,8,7,4,5]
```

输入：

```
matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
```

输出：

```
[1,2,3,4,8,12,11,10,9,5,6,7]
```

初始代码

```c++
class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {

    }
};
```

### 答案

```c++
class Solution {
private:
    static constexpr int directions[4][2] = {{0, 1}, {1, 0}, {0, -1}, {-1, 0}};
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if (matrix.size() == 0 || matrix[0].size() == 0) {
            return {};
        }
        
        int rows = matrix.size(), columns = matrix[0].size();
        vector<vector<bool>> visited(rows, vector<bool>(columns));
        int total = rows * columns;
        vector<int> order(total);

        int row = 0, column = 0;
        int directionIndex = 0;
        for (int i = 0; i < total; i++) {
            order[i] = matrix[row][column];
            visited[row][column] = true;
            int nextRow = row + directions[directionIndex][0], nextColumn = column + directions[directionIndex][1];
            if (nextRow < 0 || nextRow >= rows || nextColumn < 0 || nextColumn >= columns || visited[nextRow][nextColumn]) {
                directionIndex = (directionIndex + 1) % 4;
            }
            row += directions[directionIndex][0];
            column += directions[directionIndex][1];
        }
        return order;
    }
};
```

## 8

### 题目

static的用法（定义和用途）

### 答案

1）用static修饰局部变量：使其变为静态存储方式(静态数据区)，那么这个局部变量在函数执行完成之后不会被释放，而是继续保留在内存中。

2）用static修饰全局变量：使其只在本文件内部有效，而其他文件不可连接或引用该变量。

3）用static修饰函数：对函数的连接方式产生影响，使得函数只在本文件内部有效，对其他文件是不可见的（这一点在大工程中很重要很重要，避免很多麻烦，很常见）。这样的函数又叫作静态函数。使用静态函数的好处是，不用担心与其他文件的同名函数产生干扰，另外也是对函数本身的一种保护机制。

## 9

### 题目

const的用法（定义和用途）

### 答案

const主要用来修饰变量、函数形参和类成员函数：

1）用const修饰常量：定义时就初始化，以后不能更改。

2）用const修饰形参：func(const int a){};该形参在函数里不能改变。

3）用const修饰类成员函数：该函数对成员变量只能进行只读操作，就是const类成员函数是不能修改成员变量的数值的。

被const修饰的东西都受到强制保护，可以预防意外的变动，能提高程序的健壮性。

## 10

### 题目

一个机器人位于一个 m x n 网格的左上角 （起始点在下图中标记为 “Start” ）。

机器人每次只能向下或者向右移动一步。机器人试图达到网格的右下角（在下图中标记为 “Finish” ）。

问总共有多少条不同的路径？

![img](https://yewuyadeimagewall.oss-cn-hangzhou.aliyuncs.com/robot_maze.png)

```
输入：m = 3, n = 7
输出：28
```

初始代码

```c++
class Solution {
public:
    int uniquePaths(int m, int n) {

    }
};
```

### 答案

```c++
class Solution {
public:
    int uniquePaths(int m, int n) {
        vector<vector<int>> f(m, vector<int>(n));
        for (int i = 0; i < m; ++i) {
            f[i][0] = 1;
        }
        for (int j = 0; j < n; ++j) {
            f[0][j] = 1;
        }
        for (int i = 1; i < m; ++i) {
            for (int j = 1; j < n; ++j) {
                f[i][j] = f[i - 1][j] + f[i][j - 1];
            }
        }
        return f[m - 1][n - 1];
    }
};
```





