大家好,我是白晨,一个不是很能熬夜,但是也想日更的人✈。如果喜欢这篇文章,点个赞,关注一下白晨吧!你的支持就是我最大的动力!
虽然还有很多课,但是也不能忘了写编程题呀。
白晨总结了大厂笔试时所出的经典题目,本周题型包括动态规划,条件控制,数学归纳,算法
等,其中数据结构和条件控制
题目难度比上周有很大增长。这次的题目平均难度比以前的题都难,特别是最后还出现了英文题目,如果一时间没有想到,不用怀疑自己,试着看完解析再试试。
都是很有代表性的经典题目,适合大家复习和积累经验。
这里是第六周,大家可以自己先试着自己挑战一下,再来看解析哟!
原题链接
:分解因数
算法思想
:
代码实现
:
#include
#include
#include
using namespace std;
int main()
{
int n;
while (cin >> n)
{
vector<int> factor;// 存放因子
int tmp = n;
// 求因子
for (int i = 2; i <= sqrt(n); ++i)
{
if (n % i == 0)
{
// 一次去掉重复的因子
while (n % i == 0)
{
factor.push_back(i);
n /= i;
}
}
}
// 当n不为1时,说明这是一个大于sqrt(n)的质数
// eg.26 = 2*13,sqrt(26)<13,所以无法在上面求得。
if (n != 1)
factor.push_back(n);
cout << tmp << " = ";
for (int i = 0; i < factor.size() - 1; ++i)
cout << factor[i] << " * ";
cout << factor.back() << endl;
}
return 0;
}
原题链接
:美国节日
这道题虽然看着平平无奇,但是实现起来是相当复杂。我这里提供两种思路:
算法思想
:
蔡勒(Zeller)公式
,是一个计算星期的公式,随便给一个日期,就能用这个公式推算出是星期几。
- D = [ c / 4 ] − 2 c + y + [ y / 4 ] + [ 13 ( m + 1 ) / 5 ] + d − 1 D=[c/4]−2c+y+[y/4]+[13(m+1)/5]+d−1 D=[c/4]−2c+y+[y/4]+[13(m+1)/5]+d−1
- W = D % 7 W=D \% 7 W=D%7
其中:
- W W W 是星期数。注意: W W W为0时,这一天是周日。
- c c c 是世纪数减一,也就是年份的前两位。
- y y y 是年份的后两位。
- m m m 是月份。 m m m 的取值范围是 3 至 14,所以某年的 1、2 月要看作上一年的 13、14月,比如 2019 年的 1 月 1 日要看作 2018 年的 13 月 1 日来计算。
- d d d 是日数。
- [ ] [] [] 是取整运算。
- % \% % 是求余运算。
第x月的第y个星期n
这种形式。所以,我们可以先计算出第x月的1号是星期几。代码实现
:
#include
using namespace std;
// 蔡勒公式,求某一天的星期数
int zeller(int year, int month, int day = 1)
{
// m 的取值范围是 3 至 14,所以某年的 1、2 月要看作上一年的 13、14月
if (month == 1 || month == 2)
{
month += 12;
year--;
}
// 按照公式求解
int c = year / 100;
int y = year % 100;
int D = c / 4 - 2 * c + y + y / 4 + 13 * (month + 1) / 5 + day - 1;
int W = D % 7;
// w==0,说明这一天是周日
return W == 0 ? 7 : W;
}
// 求xxxx年y月的第x个星期n 是 几号
int theDayOfDemand(int year, int month, int cnt, int weekDay)
{
int first_day = zeller(year, month);
int day = 1 + (cnt - 1) * 7 + (weekDay + 7 - first_day) % 7;
return day;
}
void newYear(int year)
{
printf("%d-01-01\n", year);
}
void martinDay(int year)
{
printf("%d-01-%02d\n", year, theDayOfDemand(year, 1, 3, 1));
}
void presidentDay(int year)
{
printf("%d-02-%02d\n", year, theDayOfDemand(year, 2, 3, 1));
}
void soldiersDay(int year)
{
// 因为一个月最多有5个周一,所以直接求第五个周一的日期,如大于31减7。
int day = theDayOfDemand(year, 5, 5, 1);
while (day > 31)
day -= 7;
printf("%d-05-%02d\n", year, day);
}
void nationalDay(int year)
{
printf("%d-07-04\n", year);
}
void workerDay(int year)
{
printf("%d-09-%02d\n", year, theDayOfDemand(year, 9, 1, 1));
}
void thanksgiving(int year)
{
printf("%d-11-%02d\n", year, theDayOfDemand(year, 11, 4, 4));
}
void Christmas(int year)
{
printf("%d-12-25\n", year);
}
int main()
{
int n;
while (cin >> n)
{
newYear(n);
martinDay(n);
presidentDay(n);
soldiersDay(n);
nationalDay(n);
workerDay(n);
thanksgiving(n);
Christmas(n);
cout << endl;
}
return 0;
}
原题链接
:淘宝网店
算法思想
:
逐步逼近
的策略,让起始日期不断逼近结束日期,随着起始日期的逼近,总收益也不断随着起始日期的增加而增加,直到最后起始日期等于结束日期。代码实现
:
#include
using namespace std;
// 获取year年month月的天数
int getMonthDay(int year, int month)
{
static int monthDay[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
int ret = monthDay[month];
if (month == 2 && (year % 400 == 0 || (year % 100 != 0 && year % 4 == 0)))
ret++;
return ret;
}
// 判断是否为素月
bool is_poor_month(int month)
{
if (month == 2 || month == 3 || month == 5 || month == 7 || month == 11)
return true;
return false;
}
int main()
{
int sy, sm, sd, ey, em, ed;// 初始年,月,日,结束年,月,日
while (cin >> sy >> sm >> sd >> ey >> em >> ed)
{
int w = 0;// 总利润
int y1 = sy + 1, y2 = ey - 1;
// 将初始年和结束年中间的年份的收益加起来
while (y2 - y1 >= 0)
{
// 闰年每年的收益为580,素年为579,闰年比平常年2月多一天,2月为素月
if (y1 % 400 == 0 || (y1 % 100 != 0 && y1 % 4 == 0))
w += 580;
else
w += 579;
y1++;
}
// 通过上一个函数的逼近,初始年和结束年就是前后两年了,现在逼近月
while (sm != em || sy != ey)
{
// sm等于13代表已经到了结束年的一月
if (sm == 13)
{
sy = ey;
sm = 1;
}
// 按照月份加总收益
if (is_poor_month(sm))
w += getMonthDay(sy, sm);
else
w += 2 * getMonthDay(sy, sm);
sm++;
}
// 天数逼近
if (sd <= ed)
{
if (is_poor_month(sm))
w += ed - sd + 1;
else
w += 2 * (ed - sd + 1);
}
else
{
if (is_poor_month(sm))
w -= ed - sd + 1;
else
w -= 2 * (ed - sd + 1);
}
cout << w << endl;
}
return 0;
}
原题链接
:斐波那契凤尾
算法思想
:
n > 24
时,斐波那契数会大于等于6位,输出时,必须输出6位。代码实现
:
#include
#include
using namespace std;
int main()
{
vector<int> v(100001);
v[0] = 1;
v[1] = 1;
for (int i = 2; i <= 100000; ++i)
{
v[i] = (v[i - 1] + v[i - 2]) % 1000000;
}
int n;
while (cin >> n)
{
if (n > 24)
printf("%06d\n", v[n]);
else
printf("%d\n", v[n]);
}
return 0;
}
原题链接
:剪花布条
- 法一:strstr
算法思想
:
strstr
函数进行搜索,将返回的字符指针 + 短串(待匹配串)的长度,也就是下一个搜索的位置。cnt
++。代码实现
:
#include
#include
using namespace std;
int main()
{
string s, t;
while (cin >> s >> t)
{
// 进行搜索
char* tmp = strstr(s.c_str(), t.c_str());
int cnt = 0;
// 当搜索成功时,进入循环
while (tmp)
{
cnt++;
// 字符指针 + 短串(待匹配串)的长度,也就是下一个搜索的位置
tmp += t.size();
// 匹配串搜索结束
if (tmp >= s.c_str() + s.size())
break;
// 搜索
tmp = strstr(tmp, t.c_str());
}
cout << cnt << endl;
}
return 0;
}
- 法二:BF算法
算法思想
:
BF算法
,即暴力(Brute Force)算法,是普通的模式匹配算法,BF算法的思想就是将目标串S的第一个字符与模式串T的第一个字符进行匹配,若相等,则继续比较S的第二个字符和 T的第二个字符;若不相等,则比较S的第二个字符和T的第一个字符,依次比较下去,直到得出最后的匹配结果。BF算法是一种蛮力算法。
# define _CRT_SECURE_NO_WARNINGS
#include
#include
#include
// str是主串,也就是要被查找的字符串
// tar是子串,也就是目标字符串
int BF(const char* str, const char* tar)
{
assert(str && tar);
if (str == NULL || tar == NULL)
return -1;
int lenStr = strlen(str);
int lenTar = strlen(tar);
int i = 0, j = 0;
while (i < lenStr && j < lenTar)
{
// 两个字符相等,一起向后走,比较下一个字符
if (str[i] == tar[j])
{
++i;
++j;
}
// 不相等,i 跳到上一次开始的后一个位置,i - j + 1中 j 相当于向后走的步数,因为j每次从0开始
// i - j就为回到str上一次的起始位置,+1就是向后跳一个位置,也即回到上一次开始的后一个位置
// 不相等,j回到0位置继续匹配
else
{
i = i - j + 1;
j = 0;
}
}
// 当子串走完时,说明找到了,返回str上一次的起始位置
if (j == lenTar)
return i - j;
// 子串没走完,主串走完了,就说明没找到,返回-1
return -1;
}
代码实现
:
#include
#include
using namespace std;
int bf(string& s, string& t)
{
int n1 = 0, n2 = 0;
int cnt = 0;
// 将传统BF算法进行调整,此时只有当匹配串走完才算走完
while (n1 < s.size())
{
// 当匹配串走完时,说明成功找到一个,归零继续找
if (n2 == t.size())
{
n2 = 0;
cnt++;
}
if (s[n1] == t[n2])
{
n1++;
n2++;
}
else
{
n1 = n1 - n2 + 1;
n2 = 0;
}
}
if (n2 == t.size())
cnt++;
return cnt;
}
int main()
{
string s, t;
while (cin >> s >> t)
{
cout << bf(s, t) << endl;
}
return 0;
}
- 法三:KMP算法
算法思想
:
代码实现
:
#include
#include
using namespace std;
void SupGetNext(const char* sub, int* next, int lenSub)
{
if (lenSub == 1)
{
next[0] = -1;
return;
}
// next[i]是sub[i]匹配不到以后跳到的位置
// sub[0 , next[i] - 1] 和 [i - k,i - 1]是相等的
// 但是sub[i]和sub[next[i]]不一定相等,只是它们前面有一段相等
next[0] = -1;
next[1] = 0;
if (sub[0] == sub[1])
{
next[1] = -1;
}
int i = 2;// 从2开始遍历
int k = 0;// 上一个数的next值,当前也就是next[1],也就是0
while (i < lenSub)
{
// 当sub[i-1]和sub[k]相同时,说明正好可以和前面相同的续上
// 已知next[i-1] = k,则next[i] = k + 1
if (k == -1 || sub[i - 1] == sub[k])
{
next[i] = k + 1;
// 如果回退的字符和当前的字符相等,说明一旦这个字符不等于主串的字符,回退的字符也不等于主串的字符
// 还要回退,这样是很浪费时间的,所以我们直接让这个字符回退到回退字符的回退位置
if (sub[k + 1] == sub[i])
{
next[i] = next[k + 1];
}
++i;
++k;
}
else
{
k = next[k];
}
}
}
int KMP(const char* str, const char* sub, int pos = 0)
{
int cnt = 0;
int lenStr = strlen(str);
int lenSub = strlen(sub);
if (lenStr == 0 || lenSub == 0)
return -1;
if (pos < 0 || pos >= lenStr)
return -1;
int i = pos;// 记录主串遍历下标
int j = 0;// 记录模式串的下标
// 获取next数组
int* next = (int*)malloc(sizeof(int) * lenSub);
if (next == NULL)
{
perror("malloc fail");
exit(-1);
}
SupGetNext(sub, next, lenSub);
while (i < lenStr)
{
if (j == lenSub)
{
j = 0;
cnt++;
}
if (j == -1 || str[i] == sub[j])
{
++i;
++j;
}
else
{
j = next[j];
}
}
free(next);
if (j == lenSub)
cnt++;
return cnt;
}
int main()
{
string s, t;
while (cin >> s >> t)
{
cout << KMP(s.c_str(), t.c_str()) << endl;
}
return 0;
}
原题链接
:客似云来
算法思想
:
代码实现
:
#include
#include
using namespace std;
int main()
{
int from, to;
vector<long long> v(81);
v[1] = 1;
v[2] = 1;
for (int i = 3; i <= 80; ++i)
{
v[i] = v[i - 1] + v[i - 2];
}
while (cin >> from >> to)
{
long long sum = 0;
for (int i = from; i <= to; ++i)
sum += v[i];
cout << sum << endl;
}
return 0;
}
原题链接
:收件人列表
算法思想
:
代码实现
:
#include
#include
#include
using namespace std;
int main()
{
int n;
while (cin >> n)
{
vector<string> s(n);
getchar();// 这一步是避免geiline读到空串
for (int i = 0; i < n; ++i)
getline(cin, s[i]);
for (int i = 0; i < n; ++i)
{
int flag = 0;
for (auto& ch : s[i])
{
// 串中有空格和,,进行标记
if (ch == ' ' || ch == ',')
flag = 1;
}
// 当串中有空格和, ,并且不为最后一个串时
if (flag && i != n - 1)
cout << '"' << s[i] << "\", ";
// 当串中有空格和, ,并且为最后一个串时
else if (flag && i == n - 1)
cout << '"' << s[i] << '"' << endl;
// 最后一个普通串时
else if (!flag && i == n - 1)
cout << s[i] << endl;
// 普通串
else
cout << s[i] << ", ";
}
}
return 0;
}
原题链接
:养兔子
算法思想
:
时间/月份 | 1 | 2 | 3 | 4 | 5 | 6 | 7 |
---|---|---|---|---|---|---|---|
兔子数量 | 1 | 2 | 3 | 5 | 8 | 13 | 21 |
代码实现
:
#include
#include
using namespace std;
int main()
{
int n;
vector<long long> v(91);
v[0] = 1;
v[1] = 1;
for (int i = 2; i <= 90; ++i)
v[i] = v[i - 1] + v[i - 2];
while (cin >> n)
{
cout << v[n] << endl;
}
return 0;
}
原题链接
:年会抽奖
算法思想
:
这是一道考验排列组合的题目,这里我们可以使用元素法:无人获奖的概率 = 无人获奖的全部情况数 / 抽奖的所有情况数
。
设一共有n
个人参与抽奖。
抽 奖 的 所 有 情 况 数 = A n n 抽奖的所有情况数 = A_n^n 抽奖的所有情况数=Ann ,也就是排列数。
n个人中无人获奖的情况数我们设为f(n)
,可以分为以下情况讨论:
m
的名字没有被m
拿到,其他n - 1
个人都有可能拿到,即有n - 1
种情况。k
拿到了m
的名字,那么对于k
的名字有两种情况:case1 :是k
的名字被m
拿到了,也就是m
、k
互相拿到了对方的名字,那么对于其他n - 2
个人互相拿错又是一个子问题f(n - 2)
。
case2 :是k
的名字没有被m
拿到,这时要保证在除了k
的n - 1
个人中m
不能拿到k
的名字,其他人也不能拿到自己的名字,则剩下的问题是子问题f(n - 1)
。(此时,在n - 1
个人中,相当于m
的名字变成了k
,而规定自己不能拿自己的名字,所以问题又变成了f(n - 1)
。)
因此可得递推公式 f ( n ) = ( n − 1 ) ∗ ( f ( n − 1 ) + f ( n − 2 ) ) f(n) = (n - 1) * (f(n - 1) + f(n - 2)) f(n)=(n−1)∗(f(n−1)+f(n−2))。
易得,f(1) = 0, f(2) = 1
。
所以 P = f ( n ) / A n n P = f(n) / A_n^n P=f(n)/Ann 。
代码实现
:
#include
using namespace std;
// 求无人获奖的情况数
long long getFunc(int n)
{
if (n == 2)
return 1;
if (n == 1)
return 0;
return (n - 1) * (getFunc(n - 1) + getFunc(n - 2));
}
// 求排列数
long long getSum(int n)
{
if (n == 1)
return 1;
return n * getSum(n - 1);
}
int main()
{
int n;
while (cin >> n)
{
printf("%.2f%%\n", 1.0 * getFunc(n) / getSum(n) * 100);
}
return 0;
}
#include
#include
using namespace std;
int main()
{
int n;
vector<long long> sum(21), v(21);
sum[1] = 1;
sum[2] = 2;
v[1] = 0;
v[2] = 1;
for (int i = 3; i <= 20; ++i)
{
sum[i] = i * sum[i - 1];
v[i] = (i - 1) * (v[i - 1] + v[i - 2]);
}
while (cin >> n)
{
printf("%2.2f%%", 1.0 * v[n] / sum[n] * 100);
}
return 0;
}
原题链接
:抄送列表
- 法一:逐个遍历,控制条件切割
算法思想
:
代码实现
:
#include
#include
#include
#include
using namespace std;
int main()
{
string s, tar;
while (getline(cin, s))
{
getline(cin, tar);
vector<string> v;// 记录名字
int flag = 0;// 判断语句是否在""中,0代表不在其中,1代表在其中
int len = 0;// 记录一个名字的长度
int begin = 0;// 记录一个名字的起始位置
for (int i = 0; i < s.size(); ++i)
{
// 当在""中,并且此时到了""名字的结尾,遇到了另一个"
if (flag && s[i] == '"')
{
// 将名字保存
v.push_back(s.substr(begin, len));
// 重置len和flag
len = 0;
flag = 0;
}
// 当在""且没有遇到另一个"时,直接名字长度++即可
else if (flag)
len++;
// 当遇到"时,进入""语句
else if (s[i] == '"')
{
// 记录名字开始位置
begin = i + 1;
// 切换状态
flag = 1;
}
// 当遇到,并且此时记录的名字长度不为0,说明这个名字到结尾了
else if (s[i] == ',' && len != 0)
{
// 储存
v.push_back(s.substr(begin, len));
// 重置len
len = 0;
// 调整begin位置
begin = i + 1;
}
// 当遇到,并且此时记录的名字长度为0,说明一个新名字刚刚开始
else if (s[i] == ',' && len == 0)
{
// 调整begin位置
begin = i + 1;
continue;
}
// 在两个,间,并且不在""中时
else
{
len++;
}
}
// 当名字长度不为0时,说明此时最后一个名字没有被存储
if (len != 0)
v.push_back(s.substr(begin, len));
if (find(v.begin(), v.end(), tar) != v.end())
cout << "Ignore" << endl;
else
cout << "Important!" << endl;
}
return 0;
}
- 法二:查找切割
算法思想
:
上一个思路思想较难理解而且代码较长,下面我们换个好理解的思路:
遇到"
直接找下一个"
的位置,将""
中的子串切割下来,就是一个名字。
不然,就找下一个,
的位置,将两个,
间的子串切割,也是一个名字。
代码实现
:
#include
#include
#include
#include
using namespace std;
int main()
{
string s, tar;
while (getline(cin, s))
{
getline(cin, tar);
vector<string> v;
for (int i = 0; i < s.size(); ++i)
{
if (s[i] == '"')
{
// 找下一个"
size_t pos = s.find('"', i + 1);
// 切割""中间的子串
v.push_back(s.substr(i + 1, pos - i - 1));
// 调整i的位置
i = pos + 1;
}
else
{
// 找,
size_t pos = s.find(',', i);
// 找到了
if (pos != string::npos)
{
// 切割子串
v.push_back(s.substr(i, pos - i));
// 调整i的位置
i = pos;
}
// 到字符串末尾了
else
{
// 直接切割上一个,到末尾的内容
v.push_back(s.substr(i));
break;
}
}
}
if (find(v.begin(), v.end(), tar) != v.end())
cout << "Ignore" << endl;
else
cout << "Important!" << endl;
}
return 0;
}
原题链接
:Rational Arithmetic
算法思想
:
3/5 --> 1 2/3
。8/6 --> 1 1/3
。-7/6 --> (-1 1/6)
。Inf
。eg. 3/4 / 0 = Inf
。代码实现
:
#include
using namespace std;
// 分数类
struct rnum
{
friend ostream& operator<<(ostream& out, const rnum& n);
// 加法
void add(rnum& n)
{
cout << *this << " + " << n << " = ";
// 通分
long comdeno = getMinmul(deno, n.deno);// 公分母
if (!zero && !n.zero)
{
// 通分直接加,构造一个类输出
rnum ans(fnumer * (comdeno / deno) + n.fnumer * (comdeno / n.deno), comdeno);
cout << ans << endl;
}
else
{
if (zero)
cout << n << endl;
else
cout << *this << endl;
}
}
// 乘法
void mul(rnum& n)
{
cout << *this << " * " << n << " = ";
if (!zero && !n.zero)
{
// 直接分数乘法,构造类输出
rnum ans(fnumer * n.fnumer, deno * n.deno);
cout << ans << endl;
}
else
cout << 0 << endl;
}
// 减法
void sub(rnum& n)
{
cout << *this << " - " << n << " = ";
// 和加法逻辑差不多
long comdeno = getMinmul(deno, n.deno);
if (!zero && !n.zero)
{
rnum ans(fnumer * (comdeno / deno) - n.fnumer * (comdeno / n.deno), comdeno);
cout << ans << endl;
}
else
{
if (zero)
cout << rnum(-n.fnumer, n.deno) << endl;
else
cout << *this << endl;;
}
}
// 除法
void div(rnum& n)
{
cout << *this << " / " << n << " = ";
if (!zero && !n.zero)
{
// 除法时,特别注意一个点,题目要求符号必须在分子上
// 所以,新分母如果为负数,必须分子,分母交换符号
long newfnumer = fnumer * n.deno;
long newdeno = deno * n.fnumer;
if (newdeno < 0)
{
newfnumer *= -1;
newdeno *= -1;
}
rnum ans(newfnumer, newdeno);
cout << ans << endl;
}
else
{
if (zero)
cout << 0 << endl;
else
cout << "Inf" << endl;
}
}
// 获取最小公倍数
long getMinmul(long a, long b)
{
return a * b / getComdiv(a, b);
}
// 获取最大公因数
long getComdiv(long a, long b)
{
if (a < b)
swap(a, b);
if (a < 0)
a = -a;
if (b < 0)
b = -b;
long c = 0;
c = a % b;
while (c != 0)
{
a = b;
b = c;
c = a % b;
}
return b;
}
// 构造函数
rnum(const long& n1, const long& n2)
:fnumer(n1)
, deno(n2)
{
zero = fnumer == 0;
// 非0时
if (!zero)
{
negative = fnumer < 0;
inter = fnumer / deno;
rnumer = fnumer - inter * deno;
// 约分化简
if (rnumer)
{
// 最大公因数
long com = getComdiv(rnumer, deno);
if (com != 1)
{
// 化简
fnumer /= com;
rnumer /= com;
deno /= com;
}
}
}
else
{
inter = 0;
rnumer = 0;
negative = false;
}
}
long fnumer;// 化简前假分数的分子
long rnumer;// 化简后真分数的分子
long deno;// 分母
long inter;// 整数部分
bool negative;// 是否为负
bool zero;// 是否为0
};
// 重载<<操作符,输出有理分数
ostream& operator<<(ostream& out, const rnum& n)
{
// 如果这个数是0,直接输出0
if (n.zero)
{
out << 0;
return out;
}
// 如果这个数是负数
if (n.negative)
{
out << "(-";
// 如果这个数整数部分和分数部分都不为0
if (n.inter && n.rnumer)
out << -n.inter << " " << -n.rnumer << "/" << n.deno << ")";
// 整数部分为0
else if (n.rnumer)
out << -n.rnumer << "/" << n.deno << ")";
// 分数部分为0
else
out << -n.inter << ")";
}
// 正数
else
{
// 如果这个数整数部分和分数部分都不为0
if (n.inter && n.rnumer)
out << n.inter << " " << n.rnumer << "/" << n.deno;
// 分数部分为0
else if (n.inter)
out << n.inter;
// 整数部分为0
else
out << n.rnumer << "/" << n.deno;
}
return out;
}
int main()
{
int num1, num2;
scanf("%d/%d", &num1, &num2);
rnum n1(num1, num2);
scanf("%d/%d", &num1, &num2);
rnum n2(num1, num2);
n1.add(n2);
n1.sub(n2);
n1.mul(n2);
n1.div(n2);
return 0;
}
原题链接
:Pre-Post
原题链接
:Pre-Post
这道题对于数据结构要求能力比较高,第一次做没有思路很正常。但是其实只要理解了思路,这道题还算比较好实现(比上一道题好实现)。
算法思想
:
中序遍历序列 + 前序遍历序列/后序遍历序列
才能确定唯一一棵二叉树,但是这道题目就是不给你中序遍历,而只给你前序遍历序列 + 后序遍历序列
,并且更变态的一点是,题目要求这棵树是一棵多叉树。输入分叉数 前序遍历序列 后序遍历序列
,求满足所给前序遍历序列和后序遍历序列的多叉树所有可能的构型数。中序遍历序列 + 前序遍历序列/后序遍历序列
可以唯一构成一棵二叉树
前序遍历 ---- 头节点 左子树 右子树
后序遍历 ---- 左子树 右子树 头节点
中序遍历 ---- 左子树 头节点 右子树
观察上面的遍历顺序,我们发现只有中序遍历的左右子树是在头节点的两侧的,而前序和后序都是头节点在一侧,左右子树在另一侧。
- 所以,我们可以在前序遍历序列中找到头节点(就是序列中第一个元素,如果是后序序列,则是最后一个元素)。
- 找到头节点后,再在中序序列中找到这个元素,左右两边就是左右子树。
- 确定左子树元素个数
n
后,在前序序列中,头节点后n
个元素就是左子树的前序序列,同理可得到右子树的前序序列。- 这样,就得到了左右子树的前序序列和中序序列,再重复1,2,3过程,就可以得到一棵二叉树。
前序遍历 ---- 头节点 左子树 右子树
后序遍历 ---- 左子树 右子树 头节点
一定要记住上面的遍历顺序,这很关键!
我们用一道例题来讲解:
输入:13
abejk
jkeba
#include
#include
#include
using namespace std;
// 获取组合数,使用动态规划获取组合数
// 使用的递推规则是组合数公式:C[n][m] = c[n - 1][m - 1] + c[n - 1][m]
void getCombNum(vector<vector<int>>& c)
{
c[1][0] = c[1][1] = 1;
for (int i = 2; i < 21; ++i)
c[i][0] = 1;
for (int i = 2; i < 21; ++i)
{
for (int j = 1; j < 21; ++j)
{
c[i][j] = c[i - 1][j - 1] + c[i - 1][j];
}
}
}
// 计算树的可能构型
int calTreeNum(string pre, string post, int n, vector<vector<int>>& c)
{
// 当序列为空或者为1(叶子节点),返回1
if (pre.size() == 1 || pre.empty())
return 1;
int prei = 1, posti = 0;// 前序遍历指针,后序遍历指针
int num = 1;// 构型数
int cnt = 0;// 子树数量
// 遍历完前序序列
while (prei < pre.size())
{
// 在后序序列中找根结点
int pos = posti;
while (pos < post.size() - 1)
{
if (pre[prei] == post[pos])
break;
else
pos++;
}
cnt++;// 子树数量+1
int len = pos - posti + 1;// 确定子树长度
num *= calTreeNum(pre.substr(prei, len), post.substr(posti, len), n, c);// 递归计算子树可能构型数
prei += len;// 下一个根结点
posti += len;// 下一个查找位置
}
return num * c[n][cnt];
}
int main()
{
vector<vector<int>> c(21, vector<int>(21, 0));
getCombNum(c);
int n;
string pre, post;
while (cin >> n >> pre >> post)
{
cout << calTreeNum(pre, post, n, c) << endl;
}
return 0;
}
本次题目难度有提升很多,这次题目主要集中在数据结构
和条件控制
这两个面试最爱考的题目,比较考验大家的逻辑思维以及代码实现能力,相信大家做完会有所收获。
这个是一个新的系列——《笔试经典编程题目》,隶属于【刷题日记】系列,白晨开这个系列的目的是向大家分享经典的笔试编程题,以便于大家参考,查漏补缺以及提高代码能力。如果你喜欢这个系列的话,不如关注白晨,更快看到更新呀。
本文是笔试经典编程题目的第六篇,如果喜欢这个系列的话,不如订阅【刷题日记】系列专栏,更快看到更新哟
如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。
如果大家喜欢这个系列,还请大家多多支持啦!
如果这篇文章有帮到你,还请给我一个大拇指
和小星星
⭐️支持一下白晨吧!喜欢白晨【刷题日记】系列的话,不如关注
白晨,以便看到最新更新哟!!!
我是不太能熬夜的白晨,我们下篇文章见。