😀大家好,我是白晨,一个不是很能熬夜😫,但是也想日更的人?。如果喜欢这篇文章,点个赞👍,关注一下👀白晨吧!你的支持就是我最大的动力!💪💪💪
📗前言
虽然还有很多课,但是也不能忘了写编程题呀🤣。
这次白晨总结了一下大厂笔试时所出的经典题目,题型包括动态规划,数据结构,条件控制,数学归纳,回溯算法 等,难度比上周有很大的提高,有些题目非常巧妙,都是很有代表性的经典题目,适合大家复习和积累经验。
这里是第三周,大家可以自己先试着自己挑战一下,再来看解析哟!
📘笔试经典编程题目(三)
🍕3.1 参数解析
? 原题链接 :参数解析
这道题没有什么思想上的难度,核心就是分割字符串,主要是如何分割,这道题主要考验的是对于边界的控制能力,这里给出2种方法。
? 法一 :
💡 算法思想 :
直接利用输入时的空格来将字符串分割,我们天然的得到了一串串分割的字符串。不过,我们必须单独处理 "" 的字符串,以保证符合题意。
🎃 代码实现 :
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
vector<string> v;
string tmp;
while (cin >> tmp)
{
v.push_back(tmp);
}
int size = v.size();
for (int i = 0; i < v.size(); ++i)
{
if (v[i][0] == '"')
{
int j = i;
for (; j < v.size(); ++j)
{
if (v[j][v[j].size() - 1] == '"')
{
break;
}
}
for (int k = i + 1; k <= j; ++k)
{
size--;
v[i] += " ";
v[i] += v[k];
v[k] = "";
}
v[i].erase(v[i].begin());
v[i].erase(--v[i].end());
}
}
cout << size << endl;
for (auto str : v)
{
if (str != "")
cout << str << endl;
}
return 0;
}
? 法二 :
💡 算法思想 :
先读取一行,再分割字符串。
🎃 代码实现 :
#include <iostream>
#include <string>
#include <vector>
using namespace std;
int main()
{
string s;
getline(cin, s);
int flag = 0;
string tmp;
vector<string> v;
for (int i = 0; i < s.size(); ++i)
{
if (flag)
{
if (s[i] == '"')
{
v.push_back(tmp);
flag = 0;
tmp.clear();
}
else
tmp += s[i];
}
else if (s[i] == ' ')
{
if (!tmp.empty())
v.push_back(tmp);
tmp.clear();
}
else if (s[i] == '"')
{
flag = 1;
}
else
{
tmp += s[i];
}
}
if (!tmp.empty())
{
v.push_back(tmp);
}
cout << v.size() << endl;
for (auto& str : v)
{
cout << str << endl;
}
return 0;
}
🍔3.2 跳石板
? 原题链接 :跳石板
动态规划经典题目,但是很多人可能第一眼会将其当成贪心算法的题目,贪心算法+回溯是可以得出答案的,但是可能不是最优解,这个大家可以自行验证。
💡 算法思想 :
- 状态:
v[i] —— 到达i 最少的跳跃次数。 - 初始化:
v[i]=INT_MAX ,INT_MAX为不可到达 - 状态转移方程:
v
[
n
u
m
+
i
]
=
m
i
n
(
v
[
n
u
m
+
i
]
,
v
[
n
u
m
]
+
1
)
v[num + i] = min(v[num + i], v[num] + 1)
v[num+i]=min(v[num+i],v[num]+1) ,
i 为num 的因数。 - 返回值:
v[m]
🎃 代码实现 :
#include <iostream>
#include <vector>
#include <cmath>
#include <climits>
using namespace std;
int main()
{
int n, m;
cin >> n >> m;
vector<int> v(m + 1, INT_MAX);
v[n] = 0;
int num = n;
while (num < m)
{
if (v[num] == INT_MAX)
{
num++;
continue;
}
for (int i = 2; i <= sqrt(num); ++i)
{
if (num % i == 0 && num + i <= m)
{
v[num + i] = min(v[num + i], v[num] + 1);
}
int repair = num / i;
if (num % repair == 0 && num + repair <= m)
{
v[num + repair] = min(v[num + repair], v[num] + 1);
}
}
num++;
}
if (v[m] != INT_MAX)
cout << v[m] << endl;
else
cout << -1 << endl;
return 0;
}
🌭3.3 计算日期到天数转换
? 原题链接 :计算日期到天数转换
💡 算法思想 :
主要考实现和边界控制,在二月上控制一下就可以。
🎃 代码实现 :
#include <iostream>
using namespace std;
static int Month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int main()
{
int _y, _m, _d;
cin >> _y >> _m >> _d;
int month = 1, day = 0;
while (month < _m)
{
if (month == 2 && (_y % 400 == 0 || _y % 100 != 0 && _y % 4 == 0))
{
day++;
}
day += Month[month++];
}
day += _d;
cout << day << endl;
return 0;
}
🍟3.4 幸运的袋子
? 原题链接 :幸运的袋子
💡 算法思想 :
当两个正整数的和大于此两数的积时,至少一个数是1。 当正整数组的积已经大于正整数组的和,此时向正整数组中加入不为1的数,依然积大于和 ,eg. 1* 2 * 3 * 4 > 1 + 2 + 3 + 4 ,此时加入一个2,1* 2 * 2 * 3 * 4 > 1 + 2 + 3 + 4 + 2 ,无法改变比较结果- 我们给定的数组进行排序,这样的目的是:
- 我们使用深度优先搜索算法,具体实现细节见下面代码。
🎃 代码实现 :
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
int getLuckyPocket(vector<int>& v, int pos, int add, int mul)
{
int cnt = 0;
for (int i = pos; i < v.size(); ++i)
{
add += v[i];
mul *= v[i];
if (add > mul)
{
cnt += 1 + getLuckyPocket(v, i + 1, add, mul);
}
else if (v[i] == 1)
{
cnt += getLuckyPocket(v, i + 1, add, mul);
}
else
break;
add -= v[i];
mul /= v[i];
while (i < v.size() - 1 && v[i] == v[i + 1])
++i;
}
return cnt;
}
int main()
{
int n;
while (cin >> n)
{
vector<int> v(n);
for (int i = 0; i < n; ++i)
cin >> v[i];
sort(v.begin(), v.end());
cout << getLuckyPocket(v, 0, 0, 1) << endl;
}
return 0;
}
🍿3.5 查找输入整数二进制中1的个数
? 原题链接 :查找输入整数二进制中1的个数
这是一道非常经典的题目了,这里我们直接上最优解法。
💡 算法思想 :
- 题目给定一个数
n 。 n &= (n - 1) ,这一步是整个算法的核心,这一步的意义是:每次必定消掉n最右边的1 。- 循环上面的步骤,直到
n == 0 ,循环的次数就是二进制中1的个数
🎃 代码实现 :
int main()
{
int n;
while (cin >> n)
{
int cnt = 0;
while (n)
{
n &= n - 1;
cnt++;
}
cout << cnt << endl;
}
return 0;
}
🧂3.6 手套
? 原题链接 :手套
这道题如果数学不好的话,真的会做的毫无头绪,并且牛客网的这个例子本来就很特殊,这更增加了这道题的难度。
💡 算法思想 :
- 假如有
n 个颜色的小球,每种颜色的小球数量为 $<a_1,a_2,a_3,…,a_n> $ (a != 0) ,要保证每种颜色的小球都有拿到,至少要拿
a
1
+
a
2
+
a
3
+
.
.
.
+
a
n
?
m
i
n
(
<
a
1
,
a
2
,
a
3
,
.
.
.
,
a
n
>
)
+
1
a_1+a_2+a_3+...+a_n -min( <a_1,a_2,a_3,...,a_n> ) + 1
a1?+a2?+a3?+...+an??min(<a1?,a2?,a3?,...,an?>)+1 个小球,这里我们称这个数为 最小覆盖数 ,这也被称为 鸽巢原理 。 - 所以,我们要统计左右两个手套的
最小覆盖数 ,然后选出两个最小覆盖数中更小的最小覆盖数; - 此时,我们只需要在对应的手套中随便拿一个手套就可以匹配。
- 那么如何求手套的
最小覆盖数 呢? -
m
i
n
(
l
e
f
t
S
u
m
?
l
e
f
t
M
i
n
+
1
,
r
i
g
h
t
S
u
m
?
r
i
g
h
t
M
i
n
+
1
)
min(leftSum - leftMin + 1, rightSum - rightMin + 1)
min(leftSum?leftMin+1,rightSum?rightMin+1)
- 最终答案:
-
m
i
n
(
l
e
f
t
S
u
m
?
l
e
f
t
M
i
n
+
1
+
1
,
r
i
g
h
t
S
u
m
?
r
i
g
h
t
M
i
n
+
1
+
1
)
min(leftSum - leftMin + 1 + 1, rightSum - rightMin + 1 + 1)
min(leftSum?leftMin+1+1,rightSum?rightMin+1+1)
- 这里有个特殊情况,就是某个颜色左(右)手套数量为0,也就是说这个颜色的手套不可能匹配,此时我们必须要拿完对应的右(左)手套以保证其余可以匹配的手套不受影响。
🎃 代码实现 :
class Gloves {
public:
int findMinimum(int n, vector<int> left, vector<int> right) {
int left_min = 27, right_min = 27;
int lsum = 0, rsum = 0;
int sum = 0;
for (int i = 0; i < n; ++i)
{
if (left[i] != 0 && right[i] != 0)
{
left_min = min(left[i], left_min);
right_min = min(right[i], right_min);
lsum += left[i];
rsum += right[i];
}
else
{
sum += left[i] + right[i];
}
}
lsum -= left_min;
rsum -= right_min;
return min(lsum, rsum) + 1 + sum + 1;
}
};
🥓3.7 完全数计算
? 原题链接 :完全数计算
💡 算法思想 :
没有什么特别难的,新手入门题目。
🎃 代码实现 :
#include <iostream>
#include <cmath>
using namespace std;
int main()
{
int n;
cin >> n;
int cnt = 0;
for (int i = 2; i <= n; ++i)
{
int sum = 1;
for (int j = 2; j <= sqrt(i); ++j)
{
if (i % j == 0)
{
sum += j + i / j;
}
}
if (sum == i)
cnt++;
}
cout << cnt << endl;
return 0;
}
🥚3.8 扑克牌大小
? 原题链接 :扑克牌大小
这道题同样是实现大于思想的题目,主要考察字符串分割以及条件控制,属于是对于控制考察的比较难的了。
💡 算法思想 :
- 利用哈希表,给每个单牌一个权重,保证单牌之间可以直接比较。
- 这道题主要是实现以及控制,具体见代码。
🎃 代码实现 :
#include <iostream>
#include <string>
#include <vector>
using namespace std;
static const string Hash[] = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2", "joker", "JOKER" };
int getPower(string& s)
{
for (int i = 0; i < 15; ++i)
{
if (Hash[i] == s)
return i;
}
return -1;
}
int main()
{
vector<string> v1, v2;
string s;
getline(cin, s);
int begin = 0;
int i = 0;
for (; i < s.size(); ++i)
{
if (s[i] == ' ')
{
v1.push_back(s.substr(begin, i - begin));
begin = i + 1;
}
if (s[i] == '-')
{
v1.push_back(s.substr(begin, i - begin));
begin = i + 1;
break;
}
}
i++;
for (; i < s.size(); ++i)
{
if (s[i] == ' ')
{
v2.push_back(s.substr(begin, i - begin));
begin = i + 1;
}
if (i == s.size() - 1)
{
v2.push_back(s.substr(begin, i - begin + 1));
}
}
if (v1.size() == v2.size())
{
if (getPower(v1[0]) > getPower(v2[0]))
{
for (auto& str : v1)
{
cout << str << " ";
}
}
else
{
for (auto& str : v2)
{
cout << str << " ";
}
}
}
else if (v1.size() == 2 && ((v1[0] == "joker" && v1[1] == "JOKER") || (v1[1] == "joker" && v1[0] == "JOKER")))
{
for (auto& str : v1)
{
cout << str << " ";
}
}
else if (v2.size() == 2 && ((v2[0] == "joker" && v2[1] == "JOKER") || (v2[1] == "joker" && v2[0] == "JOKER")))
{
for (auto& str : v2)
{
cout << str << " ";
}
}
else if (v1.size() == 4)
{
for (auto& str : v1)
{
cout << str << " ";
}
}
else if (v2.size() == 4)
{
for (auto& str : v2)
{
cout << str << " ";
}
}
else
{
cout << "ERROR" << endl;
}
return 0;
}
🍳3.9 杨辉三角的变形
? 原题链接 :杨辉三角的变形
💡 算法思想 :
白晨的第一想法就是动态规划对杨辉三角变式进行模拟 ,直接莽夫,先保证过了就行。
- 状态转移方程:
v
[
i
]
[
j
]
=
v
[
i
?
1
]
[
j
]
+
v
[
i
?
1
]
[
j
?
1
]
+
v
[
i
?
1
]
[
j
?
2
]
(
2
<
=
j
<
=
2
?
i
)
v[i][j] = v[i-1][j]+v[i-1][j-1]+v[i-1][j-2](2<=j<=2*i)
v[i][j]=v[i?1][j]+v[i?1][j?1]+v[i?1][j?2](2<=j<=2?i)
-
v
[
i
]
[
j
]
=
v
[
i
?
1
]
[
j
]
+
v
[
i
?
1
]
[
j
?
1
]
(
j
<
2
)
v[i][j] = v[i-1][j]+v[i-1][j-1](j<2)
v[i][j]=v[i?1][j]+v[i?1][j?1](j<2)
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> v(n);
for (int i = 0; i < n; ++i)
{
v[i].resize(2 * i + 3, 0);
v[i][0] = v[i][2 * i] = 1;
}
for (int i = 1; i < n; ++i)
{
for (int j = 1; j < 2 * i + 1; ++j)
{
if (j - 2 >= 0)
{
v[i][j] = v[i - 1][j - 2] + v[i - 1][j - 1] + v[i - 1][j];
}
else
{
v[i][j] = v[i - 1][j - 1] + v[i - 1][j];
}
}
}
for (int i = 1; i < 2 * n - 1; ++i)
{
if (v[n - 1][i] % 2 == 0)
{
cout << i + 1 << endl;
return 0;
}
}
cout << -1 << endl;
return 0;
}
上面就是第一次白晨动态模拟实现的杨辉三角,但是我忽略了n过大的情况,比如n=10000,就已经超出了可申请空间的范围,所以我又做了以下优化:
- 由于状态转移方程只需要用到上一行的数据,所以我们可以创建两个数组换着保存数据即可(一个数组也可以,但是要从后向前遍历),这样就能将空间复杂度降到
O
(
n
)
O(n)
O(n) 。
- 并且由于变式三角是对称的,我们只用保存一半的数据即可。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
int n;
cin >> n;
vector<vector<int>> v(2, vector<int> (n + 1));
v[0][0] = v[1][0] = 1;
for (int i = 1; i < n; ++i)
{
if (i % 2 == 1)
{
for (int j = 1; j < i + 1; ++j)
{
if (j - 2 < 0)
{
v[0][j] = v[1][j - 1] + v[1][j];
}
else if (j == i)
{
v[0][j] = 2 * v[1][j - 2] + v[1][j - 1];
}
else
{
v[0][j] = v[1][j - 2] + v[1][j - 1] + v[1][j];
}
}
}
else
{
for (int j = 1; j < i + 1; ++j)
{
if (j - 2 < 0)
{
v[1][j] = v[0][j - 1] + v[0][j];
}
else if (j == i)
{
v[1][j] = 2 * v[0][j - 2] + v[0][j - 1];
}
else
{
v[1][j] = v[0][j - 2] + v[0][j - 1] + v[0][j];
}
}
}
}
if (n % 2 == 0)
{
for (int i = 1; i < n; ++i)
{
if (v[0][i] % 2 == 0)
{
cout << i + 1 << endl;
return 0;
}
}
}
else
{
for (int i = 1; i < n; ++i)
{
if (v[1][i] % 2 == 0)
{
cout << i + 1 << endl;
return 0;
}
}
}
cout << -1 << endl;
return 0;
}
但是还是哒咩,这一次是n=100000的时候超时了。所以,模拟的方法是彻底行不通了,我们现在要试着找规律了。
前十行的数据:
- 前两行没有偶数,返回1。
- 奇数行:
- 偶数行:
- 可以被4整除时,第一个偶数在
3 个位置; - 不能被4整除时,第一个偶数在第
4 个位置。
🎃 代码实现 :
#include <iostream>
using namespace std;
int main()
{
int n;
cin >> n;
if (n <= 2)
cout << -1 << endl;
else if (n % 2 == 1)
cout << 2 << endl;
else
{
if (n % 4 == 0)
cout << 3 << endl;
else
cout << 4 << endl;
}
return 0;
}
🧇3.10 二叉树的镜像
? 原题链接 :二叉树的镜像
这道题其实就是反转二叉树的套皮题目,只用理解如何反转一棵二叉树,就能理解这道题。
💡 算法思想 :
这里我们使用使用分治的思想:
- 先判断根结点是否为空,如果为空,返回
NULL ;不为空,执行以下操作。 - 反转左子树,再拿到左子树的根结点。
- 反转右子树,再拿到右子树的根结点。
- 然后让反转后的右子树成为根结点的左孩子,再让反转后的左子树成为根结点的右孩子。
上面的过程可以类似的视为前序遍历的过程。
🎃 代码实现 :
class Solution {
public:
TreeNode* mirrorTree(TreeNode* root) {
if(root == nullptr)
return nullptr;
TreeNode* left = mirrorTree(root->left);
TreeNode* right = mirrorTree(root->right);
root->left = right;
root->right = left;
return root;
}
};
🥞3.11 统计每个月兔子的总数
? 原题链接 :统计每个月兔子的总数
💡 算法思想 :
- 这个题其实就是一道找规律+动态规划的题目,我们可以现写出前7个月兔子的变化情况。
- 观察图表,我们可以得到这样一个动态转移方程:
f
(
x
)
=
f
(
x
?
1
)
+
f
(
x
?
2
)
f(x) = f(x-1)+f(x-2)
f(x)=f(x?1)+f(x?2) 。
- 这就是斐波那契数列的变式题。
🎃代码实现 :
#include <iostream>
using namespace std;
int main()
{
int f1 = 1, f2 = 1;
int n;
cin >> n;
for(int i = 3; i <= n; ++i)
{
int tmp = f1 + f2;
f1 = f2;
f2 = tmp;
}
cout << f2 << endl;
return 0;
}
🧈3.12 字符串通配符
? 原题链接 :字符串通配符
?法一 :递归+记忆化搜索
💡 算法思想 :
-
s1 为带有通配符的串,s2 为待匹配的串。 -
具体思路见代码
🎃代码实现 :
#include <iostream>
#include <string>
#include <ctype.h>
using namespace std;
bool is_match(const char* s1, const char* s2)
{
if (*s2 == '\0')
{
if (*s1 == '\0')
{
return true;
}
else
{
int i = 0;
while (s1[i] == '*')
i++;
if (s1[i] == '\0')
return true;
else
return false;
}
}
else if (*s1 == '\0')
{
return false;
}
if (*s1 == '?' && isalnum(*s2))
{
return is_match(s1 + 1, s2 + 1);
}
else if (*s1 == '*' && isalnum(*s2))
{
int i = 0;
while (s1[i] == '*')
i++;
return is_match(s1 + i - 1, s2 + 1) || is_match(s1 + i, s2) || is_match(s1 + i, s2 + 1);
}
else if (*s1 == *s2 || *s1 + 32 == *s2 || *s1 == *s2 + 32)
{
return is_match(s1 + 1, s2 + 1);
}
return false;
}
int main()
{
string s1, s2;
cin >> s1 >> s2;
bool match = is_match(s1.c_str(), s2.c_str());
if (match == true)
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
?法二 :动态规划
💡 算法思想 :
带通配符的字符串匹配:动态规划
- 状态:
v[i][j] —— s1的前i个字符是否匹配s2的前j个字符 - 初始化:
v[0][0]=true ,对于s1一开始为* 的情况,v[i][0]=0 ,其余都设为false - 状态转移方程:见代码实现
- 返回值:
v[s1.size()][s2.size()]
🎃代码实现 :
#include <iostream>
#include <string>
#include <ctype.h>
#include <vector>
using namespace std;
int main()
{
string s1, s2;
cin >> s1 >> s2;
vector<vector<bool>> v(s1.size() + 1, vector<bool>(s2.size() + 1));
v[0][0] = true;
for (int i = 0; s1[i] == '*'; ++i)
{
v[i + 1][0] = true;
}
for (int i = 1; i < v.size(); ++i)
{
for (int j = 1; j < v[0].size(); ++j)
{
if (s1[i - 1] == '?' && isalnum(s2[j - 1]))
v[i][j] = v[i - 1][j - 1];
else if (s1[i - 1] == '*' && isalnum(s2[j - 1]))
v[i][j] = v[i - 1][j - 1] || v[i][j - 1] || v[i - 1][j];
else
v[i][j] = v[i - 1][j - 1] && (s1[i - 1] == s2[j - 1] || s1[i - 1] + 32 == s2[j - 1] || s1[i - 1] == s2[j - 1] + 32);
}
}
if (v[s1.size()][s2.size()])
cout << "true" << endl;
else
cout << "false" << endl;
return 0;
}
📙后记
本次题目难度有了很大提高,特别是动态规划以及数学归纳 题目的难度,本次增加了不同动态规划的题目,比如:一维数组,二维矩阵,有助于大家提高应对动态规划题目的能力。
对于数学归纳的能力也有很强的考察,相信大家做完会有所收获。
这个是一个新的系列——《笔试经典编程题目》,隶属于【刷题日记】系列,白晨开这个系列的目的是向大家分享经典的笔试编程题,以便于大家参考,查漏补缺以及提高代码能力。如果你喜欢这个系列的话,不如关注白晨,更快看到更新呀😜。
本文是笔试经典编程题目的第三篇,如果喜欢这个系列的话,不如订阅【刷题日记】系列专栏,更快看到更新哟
如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。
如果大家喜欢这个系列,还请大家多多支持啦😋!
如果这篇文章有帮到你,还请给我一个大拇指 👍和小星星 ??支持一下白晨吧!喜欢白晨【算法】系列的话,不如关注 👀白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。
|