本文为2020年07月09日 本文为覃超关于模拟面试的线上直播课的听课笔记。
正确对待面试
- 作为和未来同事的一次合作
- 并肩作战,解决问题
- 减少压力(一定要积极的沟通和表达)
所以一定要积极的沟通和表达
注意:题目的熟练程度
技术面试
- 项目经验:要点,深度
- 基础知识:语言、数据库、并发、框架知识等
- 算法和数据结构。(重点)60%~70%
面试题目
线程和进程:
线程:没有独立的地址空间,与进程以及其他线程共享一个地址空间,它是系统任务调度的最小单元(轻量级进程);进程:有独立的地址空间。
多进程场景:监控进程系统(启动服务,然后监控服务是否退出,把服务重新启动);多线程的场景:高并发(nginx、tars框架)。
并发和并行:
并发:两个或者两个以上的事件在同一时间段内发生(是指微观的时间段,非常短,几乎感觉不出时间发生的时间差。)
并行:指两个或者两个以上的事件在同一时刻发生。并行意味着应用程序拆分成更小的子任务,这些任务可以并行处理,例如在多个CPU在同一时间。
同步和异步:
同步:下一个任务依赖上一个任务结束之后才能进行。
异步:等待某件事情的过程中继续做自己的事,不需要等待这一事件完成后再工作。
线程就是实现异步的一个方式。异步是让调用方法的主线程不需要同步等 待另一线程的完成,从而可以让主线程干其它的事情。
并发和并行其实是异步线程实现的两种形式。并行其实是真正的异步,多核 CPU可以同时开启多条线程供多个任务同时执行,互不干扰。但是并发就不一 样了,是一个伪异步。在单核CUP中只能有一条线程,但是又想执行多个任务。 这个时候,只能在一条线程上不停的切换任务。
算法题目
class Solution {
public:
bool isValid(string s) {
if (s.empty()) {
return true;
}
std::stack<char> stack;
for (char i : s) {
if (i == '}' || i == ']' || i == ')') {
// 如果栈已经空了,放入反括号,一定不合法。
// 另外,如果这里不做判断,下面的stack.top()将访问未知地址,导致异常
if (stack.empty()) {
return false;
}
if ( (i == '}' && stack.top() == '{')
|| (i == ')' && stack.top() == '(')
|| (i == ']' && stack.top() == '[')) {
stack.pop();
} else {
return false;
}
} else {
stack.push(i);
}
}
return stack.empty();
}
};
AddressSanitizer: SEGV on unknown address 0x000000000000 (pc 0x000000383e02 bp 0x7ffe462fb990 sp 0x7ffe462fb880 T0)
特别注意:如果stack为空,则stack.top()将访问未知的地址,会引发异常(it is meaningless to have references to the top element when the stack is empty)。因此,进行stack.top()操作之前,必须确保stack不为空。
class Solution {
public:
int climbStairs(int n) {
if (n < 3) {
return n;
}
int f1 = 1;
int f2 = 2;
int f3 = 3;
for (int i = 3; i <= n; i++) {
f3 = f1 + f2;
f1 = f2;
f2 = f3;
}
return f3;
}
};
这道题特别经典,变形很多。一定要重视。
- 如果n种走法?
- 如果相邻走法不能重复?
- 如何打印出所有走法?
题目三:50.Pow(x, n)
解法1:暴力求解
class Solution1 {
public:
double myPow(double x, int n) {
long long N = n; // 变成长整形,处理负数越界的常用方法
if (N < 0) {
N = -N;
x = 1 / x;
}
double res = 1.0;
for (int i = 0; i < N; i++) {
res *= x;
}
return res;
}
};
Time Limit Exceeded,无法AC
Line 21: Char 35: runtime error: negation of -2147483648 cannot be represented in type ‘int’; cast to an unsigned type to negate this value to itself (solution.cpp)
注意:int类型的范围
n∈[−2147483648,2147483647]
,如果n=−2147483648
,执行-n
就会出现越界,所以转为long来操作就安全了。将 x 存入 long 变量 y ,后面用 y 操作即可。
解法2:快速幂-递归+分治法(自顶向下)
- 关键字:比O(n)更好的解法
- 模式识别:分治法实现计算量减半
- 时间复杂度:O(logn)
- 空间复杂度:O(logn) (利用递归,存在递归栈)
class Solution2 {
public:
double myPowHelper(double x, long long n) {
if (n == 0) {
return 1;
}
double half = myPowHelper(x, n/2);
if (n % 2 != 0) {
return half * half * x;
} else {
return half * half;
}
}
double myPow(double x, int n) {
if (n == 0 || x == 1) {
return 1;
}
long long N = n;
if (n < 0) {
return 1 / myPowHelper(x, -N);
} else {
return myPowHelper(x, N);
}
}
};
改写为位运算
class Solution2 {
public:
double myPowHelper(double x, long long n) {
if (n == 0) {
return 1;
}
double half = myPowHelper(x, n >> 1);
if (n & 1 != 0) {
return half * half * x;
} else {
return half * half;
}
}
double myPow(double x, int n) {
if (n == 0 || x == 1) {
return 1;
}
long long N = n;
if (n < 0) {
return 1 / myPowHelper(x, -N);
} else {
return myPowHelper(x, N);
}
}
};
解法3:快速幂-循环递推(自底向上)
class Solution {
public:
double myPow(double x, int n) {
long N = n;
if (N < 0) {
N = -N;
}
double res = 1.0;
for (long i = N; i != 0; i >>= 1) {
if (i & 1) {
res *= x; // 不同二进制位的权重进行累加(当且仅当1才可累加)
}
x *= x; // 对应二进制位的权重(对于二进制而言,高位是相邻低位的2倍)
}
return n > 0 ? res : 1/res;
}
};
改写为while循环
class Solution {
public:
double myPow(double x, int n) {
long N = n;
if (N < 0) {
N = -N;
}
double res = 1.0;
while (N) {
if (N & 1) {
res *= x;
}
x *= x;
N >>= 1;
}
return n > 0 ? res : 1/res;
}
};
题目四:112. Path Sum
class Solution {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (!root) {
return false;
}
// recursion terminator
if (!root->left && !root->right) {
return sum == root->val;
}
return hasPathSum(root->left, sum - root->val) ||
hasPathSum(root->right, sum - root->val);
}
};
变形一:不需要到叶子节点,可以在任意节点结束。
class SolutionVariation1 {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (!root) {
return false;
}
// recursion terminator
if (sum == root->val) {
return true;
}
// return sum == root->val;
return hasPathSum(root->left, sum - root->val) ||
hasPathSum(root->right, sum - root->val);
}
};
变形二:不需要到叶子节点,可以在任意节点结束。并且也不需要从根节点开始,可以从任意节点开始。
// 思路:把所有的节点都看做根节点进行遍历
// 看不懂,不理解
class SolutionVariation2 {
public:
bool hasPathSum(TreeNode* root, int sum) {
if (!root) {
return false;
}
// recursion terminator
return hasSubPathSum(root, sum)
|| hasPathSum(root->left, sum)
|| hasPathSum(root->right, sum);
}
bool hasSubPathSum(TreeNode* root, int sum) {
if (!root) {
return false;
}
if (root->val == sum) {
return true;
}
return hasSubPathSum(root->left, sum - root->val) ||
hasSubPathSum(root->right, sum - root->val);
}
};