来自PTA Basic Level的三只小野兽

261 阅读5分钟

最近利用闲暇时间做了一下 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/