最近利用闲暇时间做了一下 PTA Basic Level[1] 里的题,里面现在一共有 95 道题,这些题大部分很基础,对于刷倦了 leetcode 的小伙伴可以去里面愉快的玩耍哦。
这里我挑了三个挺有意思的题来做个简单记录,欢迎和大家一起讨论交流。
请听题:
第一题:1009 说反话
题目描述
给定一个英语句子,各个单词之间用空格分隔。要求你编写程序,将句中所有单词的顺序颠倒输出。
输入示例
Hello World Here I Come
输出示例
Come I Here World Hello
没骗你吧,是不是很简单。这道题我们确实可以很容易的解决,但你的思路是什么呢?你设计的算法很完美么?
相信会有一部分小伙伴的思路是这样滴:
首先string
来保存输入的句子,再split
分隔提取单词,将单词保存在vector
中,最后倒序输出。
vector<string> split(string sentence) {
//将句子中的单词提取,返回
}
int main() {
string sentence;
cin >> sentence;
vector<string> allWords = split(sentence);
for (string word : allWords) {
cout << word << ' ';
}
return 0;
}
这样没毛病哈,我第一次就是这么干的。但对于这种简单题,这样做显得太麻烦了,然后我在网上看到了这段代码:
int main() {
stack<string> v;
string s;
while(cin >> s) v.push(s);
cout << v.top();
v.pop();
while(!v.empty()) {
cout << " " << v.top();
v.pop();
}
return 0;
}
当时真的惊艳到我了,上面代码充分利用了cin
以空格分隔各个变量输入的特点,直接提取到了各个单词。
之后巧妙利用栈后进先出
的特点,将单词依次压入stack
,最后再依次弹出,就得到了逆序的单词序列,这两行代码用的着实巧妙!
第二题:1060 爱丁顿数
题目描述
据说天文学家爱丁顿为了炫耀自己的骑车功力,定义了一个“爱丁顿数” ,即满足有 天骑车超过 英里的最大整数 。
现给定某人 天的骑车距离,请你算出对应的爱丁顿数 ()。
输入第一行给出一个正整数 ,即连续骑车的天数;第二行给出 N 个非负整数,代表每天的骑车距离。
在一行中输出 N 天的爱丁顿数。
输入示例
10
6 7 6 9 3 10 8 2 7 8
输出示例
6
看到 我以为不能暴力求解,但旺旺没想到,暴力竟然也能过。首先我们来看一下暴力思路是咋样的。
通过题目我们可以确定 的取值范围是 0 ~ N
,也就是说我们要在0 ~ N
中搜索一个符合条件的尽可能大的数。
暴力思路就是这样的:
for (int i = N; i >= 0; i--) {
//判断i是否符合条件
if (isOk(i, a, n)) {
cout << i;
break;
}
}
这个时间复杂度是 ,因为外层for
的遍历需要 ,判断每个 是否符合条件也需要 。
要优化其实也很简单。既然是要在0 ~ N
的空间中搜索,而且0 ~ N
也是单调的,那正好符合二分的使用条件。我们就可以用二分来代替第一层for
循环,这样时间复杂度就是 了。
二分优化后的代码:
// 判断k是否符合条件
bool isOk(int k, int a[], int n) {
int cnt = 0;
for (int i = 0; i < n; i++)
if (a[i] > k)
cnt++;
return cnt >= k ? true : false;
}
int main() {
int n, a[100001];
cin >> n;
for (int i = 0; i < n; i++)
cin >> a[i];
int l = 1, r = n;
while (l <= r) {
int mid = l + ((r - l) >> 1);
if (isOk(mid, a, n))
l = mid + 1;
else
r = mid - 1;
}
cout << r;
return 0;
}
像这种使用二分优化搜索空间的题型 leetcode 上也有很多,比如 875. 爱吃香蕉的珂珂[2],1011. 在 D 天内送达包裹的能力[3],1231. 分享巧克力[4],对这块不熟悉的小伙伴可以去做做。
第三题:1070 结绳
题目描述
给定若干段绳子,你需要把它们串成一条绳。每次串连的时候,是把两段绳子对折,再套接在一起。这样得到的绳子又被当成是另一段绳子,可以再次对折去跟另一段绳子串连。每次串连后,原来两段绳子的长度就会减半。
给定 段绳子的长度,你需要找出它们能串成的绳子的最大长度。
输入示例
8
10 15 12 3 4 13 1 15
输出示例
14
经典算法中有两类算法特别考验解题思维,一是动态规划,二是贪心思想。
这道题就是一道简单的贪心题。根据题目的意思,我们需要找到一种串连方式,使得最终得到的绳子的长度最长,也就是使得 段绳子损失的长度最小。
那么如何尽可能减小绳子长度的损失呢?每次尽可能的选用短绳对折连接,从而避免长绳对折,采用这种方式进行连接绳子长度损失是最小的。
在程序中我们可以使用优先队列priority_queue
让队首元素保持值最小,代码如下:
int main() {
priority_queue<int, vector<int>, greater<int>> q;
int n;
cin >> n;
for (int i = 0; i < n; i++) {
int v;
cin >> v;
q.push(v);
}
while (q.size() > 1) {
int l1 = q.top(); q.pop();
int l2 = q.top(); q.pop();
q.push((l1 + l2)/2);
}
cout << q.top();
return 0;
}
对贪心不熟悉的小伙伴可以看看这篇文章:初识贪心思想。
总结
这三道题不会很难吧,但要写出令人眼前一亮的代码不仅要有扎实的算法基础,还要能够灵活的使用数据结构。所以在做题的时候要多思考,多总结!
今天是小年,祝大家小年快乐!
参考资料
[1]PTA Basic Level: https://pintia.cn/problem-sets/994805260223102976/problems/type/7
[2]875. 爱吃香蕉的珂珂: https://leetcode-cn.com/problems/koko-eating-bananas/
[3]1011. 在 D 天内送达包裹的能力: https://leetcode-cn.com/problems/capacity-to-ship-packages-within-d-days/
[4]1231. 分享巧克力: https://leetcode-cn.com/problems/divide-chocolate/