😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人✈。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪
文章目录
📕前言
虽然还有很多课,但是也不能忘了写编程题呀🤣。
这次白晨总结了一下大厂笔试时所出的经典题目,题型包括贪心算法,动态规划,数据结构等,难度并不是很难,但是都是很有代表性的经典题目,适合大家复习和积累经验。
这里是第二周,大家可以自己先试着自己挑战一下,再来看解析哟!🎉
📗笔试编程题(二)
⚽2.1 Fibonacci数列
⚔ 原题链接
:Fibonacci数列
💡 具体思路
:
- 从0,1开始计算斐波那契数列,记录本次结果
cur
和上一次的结果prev
。 - 当本次结果大于等于题目所给数
n
时,停止计算。 - 此时 p r e v < n < = c u r prev<n<=cur prev<n<=cur ,输出 m i n ( n − p r e v , c u r − n ) min(n - prev,cur-n) min(n−prev,cur−n) 即可。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int prev = 0, cur = 1, tmp;
int n;
cin >> n;
while (cur < n)
{
tmp = cur;
cur += prev;
prev = tmp;
}
cout << min(n - prev, cur - n) << endl;
return 0;
}
⚾2.2 合法括号序列判断
⚔ 原题链接
:合法括号序列判断
如果对于栈结构有了解的同学一定见过这个经典题目,所以这个题目的考点非常明显——栈。
没有了解过栈结构的同学可以先看一下这个文章——【数据结构】栈结构全解析
💡 具体思路
:
- 遍历字符串,遇到
(
就入栈; - 遇到
)
要出栈,但是这里必须要保证栈不为空才能出栈,不然可能会出现)(
这样的违法情况; - 不满足上面两种情况直接返回假。
- 遍历完字符串,判断栈是否为空,为空返回真,反之为假。
class Parenthesis {
public:
bool chkParenthesis(string A, int n) {
stack<int> st;
for (int i = 0; i < n; i++)
{
if (A[i] == '(')
st.push(A[i]);
else if (A[i] == ')' && !st.empty())
st.pop();
else
return false;
}
return st.empty();
}
};
🥎2.3 两种排序方法
⚔ 原题链接
:两种排序方法
这道题思路没有什么难度,主要是代码实现。
🎁 代码实现
:
#include <iostream>
#include <string>
#include <vector>
#include <string.h>
using namespace std;
// 判断是否按长度排序
bool is_len(vector<string>& v)
{
for (int i = 1; i < v.size(); ++i)
{
if (v[i - 1].size() > v[i].size())
return false;
}
return true;
}
// 判断是否按字符顺序排序
bool is_lex(vector<string>& v)
{
for (int i = 1; i < v.size(); ++i)
{
if (strcmp(v[i - 1].c_str(), v[i].c_str()) > 0)
return false;
}
return true;
}
int main()
{
vector<string> v;
int n;
string tmp;
cin >> n;
for (int i = 0; i < n; ++i)
{
cin >> tmp;
v.push_back(tmp);
}
bool b1 = is_lex(v);
bool b2 = is_len(v);
if (b1 && b2)
cout << "both" << endl;
else if (b1 && !b2)
cout << "lexicographically" << endl;
else if (!b1 && b2)
cout << "lengths" << endl;
else
cout << "none" << endl;
return 0;
}
🏀2.4 求最小公倍数
⚔ 原题链接
:求最小公倍数
💡 算法思想
:
- 使用辗转相除法求出最大公约数
c
。
辗转相除法
:
具体实例
:
ans = a * b / c
最小公倍数流程图
:
🎃 代码实现
:
#include <iostream>
using namespace std;
int main()
{
int a, b, c;
cin >> a >> b;
int tmp1 = a, tmp2 = b;
if (tmp1 < tmp2)
swap(tmp1, tmp2);
// 辗转相除法求出最大公约数,这里要注意的是:最后最大公约数存放在tmp2中
while (c = tmp1 % tmp2)
{
tmp1 = tmp2;
tmp2 = c;
}
// ans = a * b / tmp2
int ans = a * b / tmp2;
cout << ans << endl;
return 0;
}
🏐2.5 另类加法
⚔ 原题链接
:另类加法
一般来说,这种题目越短,给的条件越少的的题越让人摸不着头脑,这道题也不例外。
💡 具体思路
:
- 异或操作:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
- 异或操作本身可视为将两个数加在一起,但是没有进位,比如:
3 ^ 5 --> 011 ^ 101 = 110
,这里没有算上第一位的进位。 - 那么进位如何处理呢🧐?
- 二进制同位置上如果出现两个1就相当于进位了,所以进位我们可以用
&
来获得,但是获取之后为了使进位进到上一位,必须将结果左移一位,比如:3 & 5 --> 011 & 101 --> 001 ,左移一位,010。
- 接下来,相当于要获取 110 + 001 的和,怎么获取呢?
- 很简单,重复前两步,直到进位为0时,此时异或以后的值就是最后的和。
🎉 具体实例
:
- 我们以
5 + 13
举例,先将其化为2进制5-->0101 , 13-->1101
。 5 ^ 13 = 1000 , (5 & 13) << 1 = 1010
1000 ^ 1010 = 0010 , (1000 & 1010) << 1 = 10000
0010 ^ 10000 = 10010 , (0010 & 10000) << 1 = 0
- 此时
10010-->18
就为最终答案。
🎃 代码实现
:
class UnusualAdd {
public:
int addAB(int A, int B) {
while (B != 0)
{
int tmp = A ^ B;
B = (A & B) << 1;
A = tmp;
}
return A;
}
};
🏈2.6 求路径
⚔ 原题链接
:求路径
法一:递归
class Solution {
public:
int uniquePaths(int m, int n) {
if (m == 1 || n == 1)
return 1;
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
}
};
法二:动态规划
class Solution {
public:
int uniquePaths(int m, int n) {
if (m == 1 || n == 1)
return 1;
// 初始化为1
vector<vector<int> > a(m, vector<int>(n, 1));
for (int i = 1; i < m; i++)
{
// 到达 [i][j] 的路径数 等于 到达[i - 1][j]和到达[i][j - 1]的路径数之和
for (int j = 1; j < n; j++)
a[i][j] = a[i][j - 1] + a[i - 1][j];
}
return a[m - 1][n - 1];
}
};
法三:公式法
class Solution {
public:
int uniquePaths(int m, int n) {
long long ret = 1;
for (int i = n, j = 1; j < m; i++, j++)
ret = ret * i / j;
return ret;
}
};
🏉2.7 井字棋
⚔ 原题链接
:井字棋
没什么思想上的难度,主要是考虑全部情况即可。
- 井字棋的胜利条件是:有三个棋子在横线、直线或斜线连成一线。
🎃 代码实现
:
class Board {
public:
bool checkWon(vector<vector<int> > board) {
// 横三或者竖三胜利
for (int i = 0; i < 3; ++i)
{
if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] == 1)
return true;
if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] == 1)
return true;
}
// 斜三胜利
if (board[1][1] == board[2][2] && board[1][1] == board[0][0] && board[1][1] == 1)
return true;
if (board[1][1] == board[2][0] && board[1][1] == board[0][2] && board[1][1] == 1)
return true;
return false;
}
};
🎱2.8 密码强度等级
⚔ 原题链接
:密码强度等级
同样是思路很简单,就是照着题目实现代码即可。
🎃 代码实现
:
#include <iostream>
#include <string>
#include <cctype>
using namespace std;
int main()
{
string s;
cin >> s;
int grade = 0;
// 密码长度
if (s.size() <= 4)
grade += 5;
else if (s.size() > 4 && s.size() <= 7)
grade += 10;
else
grade += 25;
// 字母&数字&符号
int NUM = 0, num = 0, cnt = 0, punc = 0;
for (auto ch : s)
{
if (isalpha(ch))
{
if (islower(ch))
num++;
else
NUM++;
}
else if (isdigit(ch))
{
cnt++;
}
else if (ispunct(ch))
{
punc++;
}
}
if ((!num && NUM) || (!NUM && num))
grade += 10;
else if (num && NUM)
grade += 20;
if (cnt == 1)
grade += 10;
else if (cnt > 1)
grade += 20;
if (punc == 1)
grade += 10;
else if (punc > 1)
grade += 25;
// 奖励
if (num && NUM && cnt && punc)
grade += 5;
else if (((!num && NUM) || (!NUM && num)) && cnt && punc)
grade += 3;
else if (((!num && NUM) || (!NUM && num)) && cnt)
grade += 2;
if (grade >= 90)
cout << "VERY_SECURE" << endl;
else if (grade >= 80)
cout << "SECURE" << endl;
else if (grade >= 70)
cout << "VERY_STRONG" << endl;
else if (grade >= 60)
cout << "STRONG" << endl;
else if (grade >= 50)
cout << "AVERAGE" << endl;
else if (grade >= 25)
cout << "WEAK" << endl;
else
cout << "VERY_WEAK" << endl;
return 0;
}
🎳2.9 求最大连续bit数
⚔ 原题链接
:求最大连续bit数
🎡 法一:二进制转换法
💡 算法思想
:
- 利用十进制向二进制转化的方式直接算出每一位,然后统计出连续出现的1的个数即可。
🎃 代码实现
:
#include <iostream>
using namespace std;
int main()
{
// cnt--当前连续1的个数,Max--最多连续1的个数
int cnt = 0, Max = 0;
int num;
cin >> num;
while (num)
{
// 二进制位为1
if (num % 2 == 1)
{
cnt++;
}
else
{
Max = max(Max, cnt);
cnt = 0;
}
num /= 2;
}
Max = max(Max, cnt);
cout << Max << endl;
return 0;
}
🎡 法二:逻辑与
💡 算法思想
:
- 1110 这个数据有三个连续的1,所以我们可以将其左移1位与原数相与,此时结果应该还有两个连续的1,
1110 & (1110 << 1) = 1100
,重复上面的操作,继续左移一位相与。 1100 & (1100 << 1) == 1000
,1000 & (1000 << 1) = 0
。- 经过三次左移相与结果为0,说明数中有3个连续的1。
🎃 代码实现
:
#include<iostream>
using namespace std;
int main()
{
int num;
cin >> num;
int cnt = 0;
while (num)
{
num = num & (num << 1);
++cnt;
}
cout << cnt << endl;
return 0;
}
🥌2.10 最近公共祖先
⚔ 原题链接
:最近公共祖先
🎡 法一:暴力求解
💡 算法思想
:
所以,我们可以直接算出a,b的全部祖先结点,最后找到序号最大的,二者共同出现的序号即可。
🎃 代码实现
:
#include <vector>
#include <iostream>
using namespace std;
class LCA {
public:
int getLCA(int a, int b) {
// 保证a大于b
if (a < b)
swap(a, b);
int tmp = b;
// 哈希表映射
vector<int> v1(a + 1, 0);
// 计算a,b的祖先结点,每遇到一个祖先结点,在对应映射位置++
while (a)
{
v1[a]++;
a /= 2;
}
while (b)
{
v1[b]++;
b /= 2;
}
// 反着找第一个两者共同出现的祖先
for (int i = b; i > 0; --i)
{
if (v1[i] == 2)
return i;
}
return 0;
}
};
🎡 法二:交替求解
💡 算法思想
:
- 上一个方法太麻烦,效率不高而且空间复杂度比较高,这里我们可以进行一个优化。
🎃 代码实现
:
class LCA {
public:
int getLCA(int a, int b) {
while(a && b)
{
// a > b 时,a 寻求父节点
if(a > b)
a /= 2;
// a < b 时,b 寻求父节点
else if(a < b)
b /= 2;
// a = b 时,返回
else
return a;
}
return a;
}
};
⚒ 进阶题目
:二叉树的最近公共祖先
🎡 法一:递归暴力求解
class Solution {
public:
bool postorder(TreeNode* root, int target, vector<TreeNode*>& st)
{
if (root == nullptr)
return false;
st.push_back(root);
if (root->val == target)
return true;
bool i = postorder(root->left, target, st);
if (i == true)
return true;
bool j = postorder(root->right, target, st);
if (j == true)
return true;
st.pop_back();
return false;
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
vector<TreeNode*> st1, st2;
postorder(root, p->val, st1);
postorder(root, q->val, st2);
while (!st1.empty())
{
TreeNode* tmp = st1.back();
auto ans = find(st2.begin(), st2.end(), tmp);
if (ans != st2.end())
return tmp;
st1.pop_back();
}
return nullptr;
}
};
🎡 法二:递归优化
class Solution {
public:
TreeNode* ans;
bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == nullptr) return false;
bool lson = dfs(root->left, p, q);
bool rson = dfs(root->right, p, q);
if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
ans = root;
}
return lson || rson || (root->val == p->val || root->val == q->val);
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
dfs(root, p, q);
return ans;
}
};
⛳2.11 二进制插入
⚔ 原题链接
:二进制插入
这道题其实非常简单,但是牛客网这个描述很搞人心态,把明明很简单的题目描述的很复杂,这里用一个例子来解释这道题:
🎃 代码实现
:
class BinInsert {
public:
int binInsert(int n, int m, int j, int i) {
return n | (m << j);
}
};
⛸2.12 查找组成一个偶数最接近的两个素数
⚔ 原题链接
:查找组成一个偶数最接近的两个素数
💡 算法思想
:
- 设题目给的数为
n
,我们可以从n / 2
到1
找离n / 2
最近的素数,用n - 据n / 2最近的素数
可以得到另一个解。 - 由于结果必须为两个素数,所以另一个解必须验证为素数,如果不为素数,继续查找距离
n / 2
次近的素数。 - 重复上述过程,直到找到两个素数解。
🎃 代码实现
:
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;
cin >> n;
int a1, a2;
// 从 n / 2 开始查找素数
for (int i = n / 2; i >= 1; --i)
{
// flag为1代表为素数,0代表不为素数
int flag = 1;
// 判断i是否为素数
for (int j = 2; j <= sqrt(i); ++j)
{
if (i % j == 0)
{
flag = 0;
break;
}
}
// 如果i为素数
if (flag)
{
a1 = i;
a2 = n - a1;
// 判断另一个解是否为素数
for (int j = 2; j <= sqrt(a2); ++j)
{
if (a2 % j == 0)
{
flag = 0;
break;
}
}
// 如果另一个解为素数,跳出
if (flag)
break;
}
}
cout << a1 << endl << a2 << endl;
return 0;
}
📘后记
本次加入了数据结构的考察,题目难度有一个小小的提升,并且出现了许多类型的经典题目,希望大家可以从中学习🧐。
这个是一个新的系列——《经典笔试编程题》,隶属于【刷题日记】系列,白晨开这个系列的目的是向大家分享经典的笔试编程题,以便于大家参考,查漏补缺以及提高代码能力。如果你喜欢这个系列的话,不如关注白晨,更快看到更新呀😜。
本文是经典笔试编程题的第二篇,如果喜欢这个系列的话,不如订阅【刷题日记】系列专栏,更快看到更新哟!✈。
如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。
如果大家喜欢这个系列,还请大家多多支持啦😋!
如果这篇文章有帮到你,还请给我一个大拇指
👍和小星星
⭐️支持一下白晨吧!喜欢白晨【算法】系列的话,不如关注
👀白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。