2020提前批笔试题

算法笔试

Posted by WenlSun" on June 8, 2020

vivo提前批(6.7)

1.种花

现有一块长方形的地种植花草,因受到阳光水分等因素的影响,相邻的区域不能种植,假如用一个数列表示土地上的种植情况,0表示未种植,1表示种植,问不影响原有花草的情况下,最多可以种花草的数量是多少? LeetCode605

输入: 第一行一个数字,表示土地的长度n。第二行n个0 1组成的数列。
输出:最大可种植的数量

思路:如果当前位置未种植且前一个位置未种植且后一个位置未种植,则当前位置可以种植。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <bits/stdc++.h>

using namespace std;

int main() {
    int n;
    while (cin >> n) {
        vector<int> nums(n);
        for (int i = 0; i < n; i++) cin >> nums[i];
        int idx = 0, cnt = 0;
        while (idx < nums.size()) {
            // 判断当前位置是否可以种植
            if (nums[idx] == 0 && (idx == 0 || nums[idx - 1] == 0) &&
                (idx == nums.size() - 1 || nums[idx + 1] == 0)) {
                nums[idx] = 1;
                cnt++;
            }
            idx++;
        }
        cout << cnt << endl;
    }
    return 0;
}

2.高楼扔手机(高楼扔鸡蛋,高楼仍碗)

你将获得 K 个鸡蛋,并可以使用一栋从 1 到 N  共有 N 层楼的建筑。每个蛋的功能都是一样的,如果一个蛋碎了,你就不能再把它掉下去。 你知道存在楼层 F ,满足 0 <= F <= N 任何从高于 F 的楼层落下的鸡蛋都会碎,从 F 楼层或比它低的楼层落下的鸡蛋都不会破。每次移动,你可以取一个鸡蛋(如果你有完整的鸡蛋)并把它从任一楼层 X 扔下(满足 1 <= X <= N)。你的目标是确切地知道 F 的值是多少。无论 F 的初始值如何,你确定 F 的值的最小移动次数是多少?

思路:dp[k][m] 表示用 k 个鸡蛋移动 m 步可以“保证求解”的最大楼层数。所谓“求解”,意思就是给定楼层 N,我们能否找到临界楼层 F(F <= N),使得鸡蛋从 F 层掉落刚好不会被摔碎。所谓“保证求解”,意思就是即使每次丢鸡蛋的结果都很差,最终仍能求解。比如,给定 1 个鸡蛋移动 1 步,那么可以求解的最大楼层数为 1,即从 1 楼丢下,如果鸡蛋碎了,求得 F=0,如果鸡蛋没碎,求得 F=1。在这种情况下,假如我们给出一个 2 层的楼,就无法保证求解了,因为无论从哪一层丢出鸡蛋,都没有十足的把握能够一次求得 F,换句话说,虽然我们仍有一定的机会能够求解,但无法“保证求解”。
下面回到正题:
假设我们有 k 个鸡蛋可以移动 m 步,考虑某一步 t 应该在哪一层丢鸡蛋?一个正确的选择是在 dp[k-1][t-1] + 1 层丢鸡蛋,结果分两种情况:
1. 如果鸡蛋碎了,我们首先排除了该层以上的所有楼层(不管这个楼有多高),而对于剩下的 dp[k-1][t-1] 层楼,我们一定能用 k-1 个鸡蛋在 t-1 步内求解。因此这种情况下,我们总共可以求解无限高的楼层。可见,这是一种非常好的情况,但并不总是发生。
2. 如果鸡蛋没碎,我们首先排除了该层以下的 dp[k-1][t-1] 层楼,此时我们还有 k 个蛋和 t-1 步,那么我们去该层以上的楼层继续测得 dp[k][t-1] 层楼。因此这种情况下,我们总共可以求解 dp[k-1][t-1] + dp[k][t-1] + 1 层楼。
容易想象,在所有 m 步中只要有一次出现了第一种情况,那么我们就可以求解无限高的楼层。但“保证求解”的定义要求我们排除一切运气成分,因此我们只得认为每次移动都遇到第二种情况。于是得到递推公式:dp[k][t] = dp[k-1][t-1] + dp[k][t-1] + 1

基本的问题已经解决了,但是我们还遗留了一个问题:为什么要选择在 dp[k-1][t-1] + 1 层丢鸡蛋?
现在我们已经知道,如果我们每一步都在 dp[k-1][t-1] + 1 层丢鸡蛋,最终是一定能够求解的。但如果我们选择在更低的层或者更高的层丢鸡蛋会怎样呢?我们分两种情况讨论:
1. 在更低的楼层丢鸡蛋。同样能够“保证求解”,但最终得到的并不是“最大”楼层数,我们没有充分挖掘鸡蛋数和移动次数的潜力,最终求解时会剩余一定量的鸡蛋或移动次数。
2. 在更高的楼层丢鸡蛋。不妨假设高了一层,即在第 dp[k-1][t-1] + 2 层丢鸡蛋。如果鸡蛋碎掉了,我们仍然可以排除该层以上的所有楼层(不管这个楼有多高),但接下来就不好办了,因为我们剩下的 k-1 个鸡蛋在 t-1 步内只能“保证求解” dp[k-1][t-1] 的楼层,而现在剩余的楼层却是 dp[k-1][t-1] + 1,多了一层,因此无法“保证求解”!综上,我们用排除法证明了每一步都应该在第 dp[k-1][t-1] + 1 层丢鸡蛋。

输入:鸡蛋个数k,楼层n。
输出:最少多少次

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
#include <bits/stdc++.h>

using namespace std;

int main() {
    int k, n;
    while (cin >> k >> n) {
        vector<vector<int>> dp(k + 1, vector<int>(n + 1));
        for (int i = 1; i <= n; i++) dp[1][i] = i;  // base case
        for (int i = 2; i <= k; i++) {              // 枚举鸡蛋
            for (int j = 1; j <= n; j++) {          // 枚举楼层
                int minV = INT_MAX;
                for (int m = 1; m <= j; m++) {  // 选择从哪个楼层扔
                    // 状态更新
                    minV = min(minV, max(dp[i - 1][m - 1], dp[i][j - m]) + 1);
                }
                dp[i][j] = minV;
            }
        }
        cout << dp[k][n] << endl;
    }
}

/**
 * 时间复杂度:O(KN^2)
 * 状态表示:dp[i][j]表示有i个鸡蛋,面对j层楼测出F的最少次数
 * 状态计算:如果我在m层仍(1<m<j),有两种情况,碎了,i-1即鸡蛋少一个,楼层区间变为1~m-1,如果没碎,i不变,楼层区间变为j-m
 */
int superEggDrop(int k, int n) {
    vector<vector<int>> dp(k + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; i++) dp[1][i] = i;  // base case
    for (int i = 2; i <= k; i++) {              // 枚举鸡蛋
        for (int j = 1; j <= n; j++) {          // 枚举楼层
            int minV = INT_MAX;
            for (int m = 1; m <= j; m++) {  // 选择从哪个楼层扔
                // 状态更新
                minV = min(minV, max(dp[i - 1][m - 1], dp[i][j - m]) + 1);
            }
            dp[i][j] = minV;
        }
    }
    return dp[k][n];
}

/**
 * 时间复杂度:O(KN)
 * 空间复杂度:O(KN)
 * 状态表示:dp[i][j]表示用i个鸡蛋和允许j次移动可以“保证求解”的最大楼层。
 * 状态计算:参考解析思路
 */
int superEggDrop(int k, int n) {
    vector<vector<int>> dp(k + 1, vector<int>(n + 1));
    for (int i = 1; i <= n; i++) {
        dp[0][i] = 0;
        for (int j = 1; j <= k; j++) {
            dp[j][i] = dp[j][i - 1] + dp[j - 1][i - 1] + 1;
            if (dp[j][i] >= n) return i;
        }
    }
    return n;

    // 空间优化
    vector<int> dp(k + 1, 1);
    int m = 2;
    dp[0] = 0;
    while (dp[k] < n) {
        for (int i = k; i >= 1; i--) {
            dp[i] = dp[i] + dp[i - 1] + 1;
        }
        m++;
    }
    return m - 1;
}

3.合并k个有序链表

合并k个有序链表。LeetCode23

输入:第一行输入n,表示n个链表,接下来n行,每一行都是一个链表。
输出:合并后的链表

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
#include <bits/stdc++.h>

using namespace std;

struct ListNode {
    int val;
    ListNode* next;
    ListNode(int x) : val(x), next(nullptr) {}
};

vector<int> split(string s) {
    s += ' ';
    vector<int> res;
    int pos = s.find(' ');
    while (pos != s.npos) {
        res.push_back(stoi(s.substr(0, pos)));
        s = s.substr(pos + 1);
        pos = s.find(' ');
    }
    return res;
}

ListNode* mergeLists(vector<ListNode*>& lists) {
    auto dummy = new ListNode(-1);
    auto p = dummy;
    while (true) {
        int minV = INT_MAX;
        bool flag = true;
        int idx = 0;
        for (int i = 0; i < lists.size(); i++) {
            if (lists[i] && lists[i]->val < minV) {
                minV = lists[i]->val;
                idx = i;
                flag = false;
            }
        }
        if (flag) break;
        p->next = lists[idx];
        lists[idx] = lists[idx]->next;
        p = p->next;
    }
    return dummy->next;
}

int main() {
    int n;
    cin >> n;
    cin.ignore();
    vector<ListNode*> lists;
    for (int i = 0; i < n; i++) {
        auto dummy = new ListNode(-1);
        auto p = dummy;
        int t;
        while (cin >> t) {
            p->next = new ListNode(t);
            p = p->next;
            if (cin.get() == '\n') break; // 注意这儿的读取结束的条件
        }
        // string s;
        // getline(cin, s);
        // vector<int> tmp = split(s);
        // for (auto t : tmp) {
        //     p->next = new ListNode(t);
        //     p = p->next;
        // }
        lists.push_back(dummy->next);
    }
    auto res = mergeLists(lists);
    while (res) {
        cout << res->val << ' ';
        res = res->next;
    }
    cout << endl;
    return 0;
}