2020暑期实习笔试题

算法笔试

Posted by WenlSun" on April 27, 2020

腾讯

1. 打怪兽游戏

小Q在玩一款打怪兽的游戏,他在之前的关卡已经获得了足够多的金币,当前关有n个怪兽,每个怪兽有$C_i$的血量,打死它可以获得$W_i$的金币, 问小Q通过当前关卡最多可以多获得多少金币。

输入:

输入两个数,n,m 。n表示怪兽的数量,m表示一个金币可以购买的血量

接下来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
#include <iostream>

using namespace std;

const int N = 1010;

int c[N], w[N];

int main() {
    int n, m;
    while (cin >> n >> m) {
        for (int i = 0; i < n; i++) {
            cin >> c[i] >> w[i];
        }
        int cost = 0;   // 记录买血花了多少钱
        int blood = 0;  // 记录自己拥有的血量
        int gain = 0;   // 记录打怪兽获得的金币
        // 遍历每一只怪兽,可以选择打或者不打
        for (int i = 0; i < n; i++) {
            // 先购买可以打死当前怪兽的血量
            int cnt = 0;
            while (blood < c[i]) {
                cnt++;
                blood += m;
            }
            //如果买血花的金币小于等于打死获得的金币,说明值得打
            if (cnt - w[i] <= 0) {
                cost += cnt;
                blood -= c[i];
                gain += w[i];
                // 否则选择不打
            } else {
                blood -= cnt * m;
            }
        }
        cout << gain - cost << endl;
    }
    return 0;
}

2. 抛物线与直线围成的面积

求抛物线 $y^2 = 2Ax$ 和直线 $y = Bx + C$ 所围成的面积.

输入:A B C

输出:面积

思路:联合两个方程求解交点的y坐标,$y=\frac{A+-\sqrt{A^2-2ABC}}{B}$,

$\int_{y_1}^{y^2} \frac{y-C}{B}-\frac{y^2}{2A} dy $

然后对这个式子进行积分

参考代码

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
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

int f(int A, int B, int C) {
    int delta = pow(A, 2) - 2 * A * B * C;
    if (delta < 0) return 0;
    double y1 = (A - sqrt(delta)) / B, y2 = (A + sqrt(delta)) / B;
    double x2 = pow(y2, 2) / (2 * B) - C / B * y2 - pow(y2, 3) / (6 * A);
    double x1 = pow(y1, 2) / (2 * B) - C / B * y1 - pow(y1, 3) / (6 * A);
    return x2 - x1;
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        int A, B, C;
        cin >> A >> B >> C;
        double res = f(A, B, C);
        cout << res << endl;
    }
    return 0;
}

3. 牢房冲突

有n个牢房,编号为1~n,每个牢房中都包含一个人,每个人都可以在1~m中选择一个数字,如果有相邻牢房选择的数字相同,则会发生冲突。求发生冲突的情况有多少种?结果取mod100003.

输入: m ,n

输出: 发生冲突的情况数。

思路:数学推导,所有情形数-任意相邻房间数不同的情形,即m^n - m* (m-1)^(n-1),考察点:快速幂算法:有模板!!!

参考代码

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
#include <iostream>

using namespace std;

const int mod = 100003;

// 快速幂模板 m^k%p 时间复杂度O(logk)
int qmi(int m, int k, int p) {
    int res = 1 % p, t = m;
    while (k) {
        if (k & 1) res = res * t % p;
        t = t * t % p;
        k >>= 1;
    }
    return res;
}

int main() {
    LL m, n;
    while (cin >> m >> n) {
        int x1 = qmi(m, n, mod);
        int x2 = m * qmi(m - 1, n - 1, mod);
        cout << x1 - x2 << endl;
    }
}

4. 完美数

有n个数,每个数有k个属性值,对于任意的两个数$a_i$和$a_j$,如果 $a_{i1}$+$a_{j1} $= $a_{i2}$+$a_{j2}$ = …=$a_{ik}$+$a_{jk}$,则成$a_i$和$a_j$是一对完美数。 求这n个数中总共有多少对完美数.

输入: n, k 。n个数,每个数有k个属性。

接下来n行,每行k个属性

输出:

总共有多少对完美数

思路:记录属性的变化值,让变化值作为键,变化值与当前相反的就是完美数对, 如1,2,3,4 和 4,3,2,1 它们的差值分别是 1,1,1 和 -1,-1,-1. 因为差值相反,所以它们是一对完美数

参考代码

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
#include <iostream>
#include <map>
#include <vector>

using namespace std;

typedef pair<int, int> PII;

int main() {
    int n, k;
    while (cin >> n >> k) {
        int g[n][k];
        for (int i = 0; i < n; i++)
            for (int j = 0; j < k; j++) cin >> g[i][j];

        map<vector<int>, int> hash;
        for (int i = 0; i < n; i++) {
            vector<int> key;
            // 记录属性的差值
            for (int j = 0; j < k - 1; j++)
                key.push_back(g[i][j + 1] - g[i][j]);
            // 更新属性差值的次数
            hash[key]++;
        }
        map<vector<int>, int>::iterator it;
        int res = 0;
        for (it = hash.begin(); it != hash.end(); it++) {
            auto item = *it;
            vector<int> tmp = item.first;
            int val = item.second;
            vector<int> inv;
            bool flag = false;  // 用来标记差值是否为0
            for (int i = 0; i < tmp.size(); i++) {
                inv.push_back(-tmp[i]);
                if (tmp[i] != 0) flag = true;
            }
            if (flag)  // 不为0,则完美数对数为当前次数与与它差值相反的次数的乘积
                res += val * hash[inv];
            else  // 为0的情况
                res += val * (val - 1);
        }
        // 因为每一对都计算了两次,需要除以2
        cout << res / 2 << endl;

        // 暴力法
        // int res = 0;
        // for (int i = 0; i < n - 1; i++) {
        //     for (int j = i + 1; j < n; j++) {
        //         int t = 0;
        //         int sum = g[i][0] + g[j][0];
        //         for (t = 1; t < k; t++) {
        //             if (g[i][t] + g[j][t] != sum) break;
        //         }
        //         if (t == k) res++;
        //     }
        // }
        // cout << res << endl;
    }
    return 0;
}

5. 最大关系网

有n对关系,比如A和B有关系,B和C有关系,则ABC关系。求这些关系中能构成的最大关系网中的人数。

输入:

n 。n对关系

接下来n行,每行是一对关系

输出:

这些关系中构建出来的最大关系网中的人数

参考代码

1
2
3
4
5
6
7
8
9
#include <iostream>

using namespace std;

int main(){
    // 待填
    
    return 0;
}

阿里

1. 翻转01串

给你一串01组成的字符串,可以选择一个位置进行翻转,即0变1,1变0,同时与该位置相邻的位置也要进行翻转 如 11011 在0的位置翻转变为10101,问给定一个这样的串,最少经过多少次翻转可以变成全0串,如果变不成输出NO。

样例:

“01” –> NO
“011” –> 1

思路:每一个位置要么不翻转,要么只翻转一次,翻转两次相当于没翻转,然后深度优先搜索。LeeCode1284

参考代码

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
#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
   public:
    int reverse(string s) {
        if (s == "1") {
            return 1;
        } else if (s == "0") {
            return 0;
        }
        int count = 0;
        if (helper(s, 0, 0)) {
            return res;
        } else {
            return -1;
        }
    }

   private:
    int res = 0;  // 记录翻转次数
    bool helper(string s, int index, int count) {
        string temp(s.size(), '0');  // 和s等长的全0串
        if (s == temp) {  // 如果等于全0串,则记录翻转次数,返回
            res = count;
            return true;
        }
        // 如果翻转的位置已经到达s的末尾,则不能翻转成全0串
        if (index == s.size()) {
            return false;
        }
        // 递归,要么翻转当前位置,要么不翻转当前位置
        // 翻转当前位置 调用reverse函数翻转,计数器count+1,并移动到下一个位置
        // 若不翻转,移到下一个位置,
        return helper(s, index + 1, count) ||
               helper(reverse(s, index), index + 1, count + 1);
    }

    // 翻转字符串中的每一个位置,注意边界情况
    string reverse(string s, int index) {
        int n = s.size() - 1;
        if (index == 0) {
            s[0] = s[0] == '0' ? '1' : '0';
            s[1] = s[1] == '0' ? '1' : '0';
        } else if (index == n) {
            s[n] = s[n] == '0' ? '1' : '0';
            s[n - 1] = s[n - 1] == '0' ? '1' : '0';
        } else if (index > 0 && index < n) {
            s[index - 1] = s[index - 1] == '0' ? '1' : '0';
            s[index] = s[index] == '0' ? '1' : '0';
            s[index + 1] = s[index + 1] == '0' ? '1' : '0';
        }
        return s;
    }
};

int main() {
    Solution solu = Solution();
    string str = "011111";
    int res = solu.reverse(str);
    if (res != -1) {
        cout << res << endl;
    } else {
        cout << "No";
    }
    return 0;
}

2.射鹿

有n头鹿,m只箭,每头鹿都有一定的血量,每只箭也有相应的伤害值,使用每只箭都有相应的花费,当箭的伤害值大于等于鹿的血量时,可以将其射死,问如果能将所有鹿射死,求需要的最少花费是多少,如果不能全部射死,就输出NO。

样例:

1 // 测试用例的组数
3 3 // n,m
1 2 3 // 鹿的血量
2 3 4 // 箭的伤害值
1 2 3 // 箭的花费
6 // 输出最小花费

思路:将鹿按血量排序,定义一个箭的结构体,包含伤害值和花费两个属性,将箭按照伤害值排序, 然后对每只鹿,用能将其射死且花费最小的箭射它,这儿用到按花费排序的优先队列。

参考代码

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
76
77
78
#include <algorithm>
#include <iostream>
#include <queue>
#include <vector>

using namespace std;

struct Arrow {
    int damage;
    int cost;
    Arrow(int x, int y) : damage(x), cost(y) {}
};

struct cmp {
    bool operator()(Arrow* a1, Arrow* a2) { return a1->cost > a2->cost; }
};

bool static compare(Arrow* a1, Arrow* a2) { return a1->damage < a2->damage; }

int main() {
    int T;
    cin >> T;
    for (int i = 0; i < T; i++) {
        int N, M;
        cin >> N >> M;
        vector<int> nums(N, 0);
        vector<Arrow*> arrows;
        for (int i = 0; i < N; i++) {
            cin >> nums[i];
        }
        vector<int> damage(M, 0);
        for (int i = 0; i < M; i++) {
            cin >> damage[i];
        }
        vector<int> cost(M, 0);
        for (int i = 0; i < M; i++) {
            cin >> cost[i];
        }
        for (int i = 0; i < M; i++) {
            Arrow* arrow = new Arrow(damage[i], cost[i]);
            arrows.push_back(arrow);
        }

        if (N > M) {
            cout << "NO";
        } else {
            // 定义优先队列 用于按花费大小存储伤害值高于血量的箭
            priority_queue<Arrow*, vector<Arrow*>, cmp> q;
            int res = 0;                                  // 记录结果
            bool can = true;                              // 能否全部射死
            sort(nums.begin(), nums.end());               // 按血量排序
            sort(arrows.begin(), arrows.end(), compare);  // 按伤害值排序

            int j = M - 1;
            for (int i = N - 1; i >= 0; i--) {
                // 将能射死当前鹿的箭入优先队列
                while (j >= 0 && arrows[j]->damage >= nums[i]) {
                    q.push(arrows[j]);
                    j--;
                }
                // 如果没有能射死当前鹿的箭,则不能全部射死
                if (q.size() == 0) {
                    cout << "NO";
                    can = false;
                    break;
                    //否则拿出花费最小的箭射死当前鹿
                } else {
                    Arrow* arrow = q.top();
                    q.pop();
                    res += arrow->cost;
                }
            }
            if (can) {
                cout << "Total cost: " << res;
            }
        }
    }
}

华为

1. 名字选举

输入一串字符串,表示人名,输出票数最多的人的名字,票数一样多的按名字字典序排序(如:Tom和Lily,Lily在前),字典序一样的,名字短的在前面(比如:Tom和Tommy,Tom在前),其中每个人名只有首字母大写,用逗号隔开。

输入 : Tom Lily Mike Lily Tom

输出:Lily

参考代码

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
#include <algorithm>
#include <iostream>
#include <map>
#include <string>
#include <vector>

using namespace std;

typedef pair<string, int> PII;

vector<PII> names;
map<string, int> m;

bool check(string s) {
    if (s.empty()) return false;
    if (s[0] < 'A' || s[0] > 'Z') return false;
    for (int i = 1; i < s.size(); i++) {
        if (s[i] < 'a' || s[i] > 'z') return false;
    }
    m[s]++;
    return true;
}

bool split(string s) {
    int len = s.size();
    if (!len) return false;
    s += ' ';
    string cur;
    int p = 0;
    while (p <= len) {
        if (s[p] == ' ') {
            bool ok = check(cur);
            if (!ok) return false;
            cur = "";
        } else {
            cur += s[p];
        }
        p++;
    }
    return true;
}

bool cmp(PII p1, PII p2) {
    if (p1.second != p2.second) return p1.second > p2.second;
    // if (p1.first.size() == p2.first.size()) return p1.first < p2.first;
    // if (p1.first.find(p2.first) != p1.first.npos) return false;
    // if (p2.first.find(p1.first) != p2.first.npos) return true;
    return p1.first < p2.first;
}

int main() {
    string s;
    getline(cin, s);
    bool isValid = split(s);
    if (!isValid) {
        cout << "error.0001" << endl;
    } else {
        for (auto item : m) {
            names.push_back({item.first, item.second});
        }
        sort(names.begin(), names.end(), cmp);
        cout << names[0].first << endl;
    }
    return 0;
}

2. 字符串匹配

输入一串字符串,找出特定字符串后面的特定类型的值,保证’[‘前的字符要和第一个关键词就是read 是一样的,中间也要保证十六进制。

输入:“read read[addr=0x17,mask=0xff,val=0x7], read_his[addr=0xff,mask=0xff,val=0x1], read[addr=0xf0,mask=0xff,val=0x80]“

输出:addr mask val 所对应16进制值

参考代码

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
76
77
78
79
80
81
82
83
84
85
#include <iostream>
#include <string>
#include <vector>

using namespace std;

// 需要记录的信息很多时,可以定义一个结构体
struct Info {
    string addr;
    string mask;
    string val;
};

string content, pattern;
vector<Info> res;

bool check16(string s) {
    int len = s.size();
    if (len < 3) return false;
    int idx = 0;
    if (s[idx] != '0') return false;
    ++idx;
    if (s[idx] != 'x' && s[idx] != 'X') return false;
    ++idx;
    string num = s.substr(idx);
    for (int i = 0; i < num.size(); ++i) {
        char ch = num[i];
        if (ch >= '0' && ch <= '9') continue;
        if (ch >= 'A' && ch <= 'F') continue;
        if (ch >= 'a' && ch <= 'f') continue;
        return false;
    }
    return true;
}

void check(string s) {
    int len = s.size();
    string head = s.substr(0, pattern.size());
    if (head != pattern) return;
    int idx = pattern.size();
    if (idx < len && s[idx] != '[') return;
    idx++;
    if (s.substr(idx, 5) != "addr=") return;
    idx += 5;
    int pos = s.find(',', idx);
    if (pos == s.npos || !check16(s.substr(idx, pos - idx))) return;
    string addr = s.substr(idx, pos - idx);
    idx = pos + 1;
    if (s.substr(idx, 5) != "mask=") return;
    idx += 5;
    pos = s.find(',', idx);
    if (pos == s.npos || !check16(s.substr(idx, pos - idx))) return;
    string mask = s.substr(idx, pos - idx);
    idx = pos + 1;
    if (s.substr(idx, 4) != "val=") return;
    if (!check16(s.substr(idx))) return;
    string val = s.substr(idx);
    res.push_back({addr, mask, val});
}

void solve(string s) {
    int len = s.size();
    s += ',';
    int idx = 0;
    while (idx <= len) {
        int pos = s.find("],", idx);
        if (pos == s.npos) break;
        check(s.substr(idx, pos - idx));
        idx = pos + 2;
    }
}

int main() {
    cin >> pattern >> content;
    solve(content);
    if (res.size() == 0) {
        cout << "FAIL" << endl;
    } else {
        for (int i = 0; i < res.size(); i++) {
            cout << res[i].addr << ' ' << res[i].mask << ' ' << res[i].val
                 << endl;
        }
    }
    return 0;
}

3. 最大开销栈

函数a 的空间是30,函数a调用了函数b(空间是60),函数b调用了函数c(空间是30),则最大开销栈是30+60+30=120。

输入:第一行n,表示n个函数,编号为1-n, 第二行n个数,表示n个函数每个函数调用的子函数个数,接下来n行分别是每个函数的编号,空间,调用的子函数编号

输出:最大开销

测试例: 6
2 3 1 0 0 0 // 表示每个函数调用多少个子函数
1 20 2 3
2 30 3 4 5
3 50 4
4 60
5 80
6 90

输出:160,解释1-2-3-4

参考代码

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 <stdio.h>
#include <string.h>

#include <iostream>
#include <map>
#include <string>
#include <vector>

using namespace std;

vector<int> callnum, fsize;
vector<vector<int>> G;

int ans;

map<string, int> mp;
vector<bool> vis;

bool dfs(int cur, int sum) {
    vis[cur] = true;
    ans = max(ans, sum);
    for (int i = 0; i < G[cur].size(); ++i) {
        int nxt = G[cur][i];
        if (vis[nxt]) return false;
        bool ret = dfs(nxt, sum + fsize[nxt]);
        if (!ret) return false;
    }
    vis[cur] = false;
    return true;
}

int main() {
    int n;
    cin >> n;
    for (int i = 0; i < n; ++i) {
        int num;
        cin >> num;
        callnum.push_back(num);
    }
    int id = 0;
    for (int i = 0; i < n; ++i) {
        string name;
        cin >> name;
        if (mp.count(name) == 0) {
            mp[name] = ++id;
        }
        int sz;
        cin >> sz;
        fsize.resize(id + 5);
        fsize[mp[name]] = sz;
        for (int j = 0; j < callnum[i]; ++j) {
            string s;
            cin >> s;
            if (mp.count(s) == 0) mp[s] = ++id;
            G.resize(id + 5);
            G[mp[name]].push_back(mp[s]);
        }
    }
    if (id != n) {
        cout << "NA" << endl;
        return 0;
    }
    vis.resize(id + 5);
    for (auto o : mp) {
        int enter = o.second;
        for (int i = 1; i <= id; ++i) vis[i] = false;
        bool ret = dfs(enter, fsize[enter]);
        if (!ret) {
            cout << "R" << endl;
            return 0;
        }
    }
    cout << ans << endl;
    return 0;
}

4. 字符串全排列

给定一个字符串(最多包含8个字符),可能包含重复的字母,返回有多少种不同的排列组合。类似LeeCode47题

输入:字符串

输出:不重复的排列组合数

样例:
输入:abc
输出:6

参考代码

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
#include <algorithm>
#include <iostream>
#include <string>

using namespace std;

int res = 0;

void dfs(string s, int begin, vector<bool>& used) {
    if (begin == s.size()) {
        res += 1;
        return;
    }
    for (int i = 0; i < s.size(); i++) {
        if (used[i] || (i > 0 && s[i] == s[i - 1] && used[i - 1])) continue;
        used[i] = true;
        dfs(s, begin + 1, used);
        used[i] = false;
    }
}

int main() {
    string s;
    while (getline(cin, s)) {
        if (s.size() <= 0 || s.size() >= 8) {
            cout << 0 << endl;
            continue;
        }
        int len = s.size();
        res = 0;
        sort(s.begin(), s.end());
        vector<bool> used(s.size(), false);
        dfs(s, 0, used);
        cout << res << endl;
    }
    return 0;
}

5. 字符串秘钥

给定一个字符串M和一个整数k,可以删除M中任意k个字符,返回字典序最小的那一个作为秘钥。

输入:字符串M和k值

输出:秘钥

样例:
输入:bacaa 1
输出:acaa

思路:遍历这个字符串,类似于冒泡算法不断比较相邻的字符。如果遇到逆序对就删掉第一个字符,k–,然后继续比较剩余的字符;如果全部逆序对都删除了k仍然大于0,就删掉后面的剩余字符; 如果当前枚举的字符串比当前最优ans要大就不需要比较了,直接返回。

参考代码

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
#include <bits/stdc++.h>

using namespace std;

void dfs(int k, string m, string& res) {
    if (k == 0) {
        res = min(res, m);
        return;
    }
    int i;
    for (i = 0; i < m.size() - 1; i++) {
        if (m[i] > m[i + 1]) {
            string tmp = m;
            m.erase(i, 1);
            if (m < res) dfs(k - 1, m, res);
            m = tmp;
        }
    }
    if (i == m.size() - 1 && k > 0) dfs(0, m.erase(m.size() - k), res);
}

int main() {
    string m;
    int k;
    while (cin >> m >> k) {
        string res = m;
        dfs(k, m, res);
        cout << res << endl;
    }
    return 0;
}

6. 最短路径

A要去B的城市找他玩,但是去B的城市有很多路径,A身上的钱也有限,请你帮他找到去B的城市的最短路径,路费也要足够。城市之间有单向的路径,每条路径有一定的路费。这题默认A在1号城市,B在N号城市。输出最短路径。如果钱不够到达B的城市,则输出-1.

输入:k,n,r //分别表示路费,城市数量,边的数量
接下来r行,每行s, d, l, t 分别表示起点,终点,距离,花费

输出:最短路径

样例:输入:
5
6
7
1 2 2 4
2 4 3 3
3 4 2 4
1 3 4 1
4 6 2 1
3 5 2 0
5 4 3 2
输出:11 // 1->3->5->4->6

参考代码

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
#include <stdio.h>

#include <climits>
#include <cstring>
#include <iostream>

using namespace std;

const int N = 110, M = 100010, INF = 100000000;

int g[N][N], dist[N];
int cost[N][N], val[N];
bool st[N];

int k, n, r;

void dijkstra() {
    for (int i = 1; i <= n; i++) {
        dist[i] = INF;
        val[i] = INF;
    }
    dist[1] = 0;
    val[1] = 0;
    for (int i = 0; i < n; i++) {
        int id, mind = INF;
        for (int j = 1; j <= n; j++) {
            // 这儿需要根据花费来选,选取到起点花费最小的点
            if (!st[j] && val[j] < mind) { 
                id = j;
                mind = val[j];
            }
        }
        st[id] = true;
        for (int j = 1; j <= n; j++) {
            if (val[j] > val[id] + cost[id][j]) {
                dist[j] = dist[id] + g[id][j];
                val[j] = val[id] + cost[id][j];
            } else if (val[j] > val[id] + cost[id][j] &&
                       dist[j] > dist[id] + g[id][j]) {
                dist[j] = dist[id] + g[id][j];
            }
        }
    }
}

int main() {
    while (cin >> k >> n >> r) {
        for (int i = 1; i <= n; i++) {
            for (int j = 1; j <= n; j++) {
                g[i][j] = INF;
                cost[i][j] = INF;
            }
        }
        for (int i = 0; i < r; i++) {
            int s, d, l, t;
            cin >> s >> d >> l >> t;
            g[s][d] = l;
            cost[s][d] = t;
        }
        dijkstra();
        // cout << dist[n] << ' ' << val[n] << endl;
        if (dist[n] != INF && val[n] < k) {
            cout << dist[n] << endl;
        } else {
            cout << -1 << endl;
        }
    }
    return 0;
}

快手

美团

1. 优秀学生奖励

给n个学生的m科成绩,如果一个学生的某一科是单科最高,那么这个学生获得奖励,即便该学生有多科最高,也只获得一次奖励。求获得奖励的学生人数。

输入:n, m 表示n个学生m个科目,接下来n行,每行是一个学生的m科成绩

输出:获得奖励的学生数

思路:直接遍历每个科目求最大值就行了,要注意的细节是,可能有很多学生获得该科最高分,此时所有最高分都有奖励。

参考代码

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
#include <iostream>
#include <vector>

using namespace std;

int main() {
    int n, m;
    cin >> n >> m;
    vector<vector<int>> data(n);
    for (int i = 0; i < n; ++i) {
        for (int j = 0; j < m; ++j) {
            int tmp;
            cin >> tmp;
            data[i].push_back(tmp);
        }
    }
    vector<int> flag(n); // 用于标记获得奖励的学生
    for (int j = 0; j < m; ++j) {
        int max_value = 0;
        vector<int> max_index; // 用来存最大值对应的同学的索引
        for (int i = 0; i < n; ++i) {
            if (data[i][j] > max_value) {
                max_value = data[i][j];
                max_index.clear();
                max_index.push_back(i);
            } else if (data[i][j] == max_value) {
                max_index.push_back(i);
            }
        }
        // 将当前科最大值的同学打上标记
        for (int i = 0; i < max_index.size(); ++i) {
            flag[max_index[i]] = 1;
        }
    }
    int ans = 0;
    // 统计结果
    for (int i = 0; i < n; ++i) {
        ans += flag[i];
    }
    cout << ans << endl;
    return 0;
}

2. 递推式的循环起点

给a,b,x,m四个数,给定递推式 x = (a * x + b ) % m,这个x不停算会循环,问递推第几次起x开始循环。

输入:a,b,x,m

输出:第几次开始循环

思路:因为取模m,x取值最多m种,维护一个m长度的flag数组,见到没见过的x就标记,如果见过了就代表循环了。根据抽屉原理,最多m+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 <iostream>
#include <map>

using namespace std;

typedef long long LL;

int main() {
    LL a, b, m, x;
    cin >> a >> b >> m >> x;
    map<LL, int> mp;
    int res = 0;
    while (true) {
        x = (a * x + b) % m;
        if (mp.count(x)) { // 遇到见过的,说明重复了
            cout << res << endl;
            break;
        }
        mp[x]++;
        res++;
    }
    return 0;
}

3. 第K大有序数对

给定n个数,这n个数两两结合构成一共$n^2$个有序数对(i, j)(可以自己和自己构成有序数对)。给定k,求第k大的有序数对是哪一个。例如给定1,2,2那么9个有序数对应该是: (1,1)(1,2)(1,2) (2,1)(2,2)(2,2) (2,1)(2,2)(2,2) 这是第5大的应该是(2,1)而不是第二行第二列的(2,2)

输入: n,k 表示n个数,第k大。接下来n个数

输出:第K大的有序对

思路:先排序得到数列data,有序数对中第一个数越小那么有序数对越小。第一想法很容易想到 (data[(k-1)/n], data[(k-1)%n])是答案,但这样忽略了数列中可能存在相同元素。(这样写答案大概得64%)。这时候先定位第k大所在的有序数对中第一个数是什么,很明显是data[(k-1)/n] 再数这个数重复出现了几次,假如出现了p次。再数比这个数小的有几个,假如有q个。 那么答案就是(data[(k-1)/n], data[(k-n*q-1)/p])。

参考代码

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
#include <algorithm>
#include <iostream>
#include <vector>

using namespace std;

typedef long long LL;

int main() {
    int n;
    LL k;
    vector<int> data;
    cin >> n >> k;
    for (int i = 0; i < n; ++i) {
        int tmp;
        cin >> tmp;
        data.push_back(tmp);
    }
    sort(data.begin(), data.end());
    LL a = (k - 1) / n;
    LL l = 0, row = 0;
    while (data[l] != data[a]) ++l;
    while (data[l + row] == data[a]) ++row;
    LL aa = (k - 1 - l * n) / row;  // 这个索引牛皮!!!
    printf("(%d,%d)", data[a], data[aa]);
    return 0;
}

4. 伪中位数

给定有序数列伪中位数的定义,m=a[floor((n-1)/2)],其实就是当n为奇数时就是正常中位数,n是偶数时取中间两个数较小的那个数。给定一个数列,和一个值k,问至少添加几个数,使得该序列的伪中位数等于k。

输入:n个数和k

输出:需要添的数的个数

思路:稍微分析就可以知道,如果需要往数列里加数,不停加k就行了,现在求需要加几个k。先遍历数列,统计出比k小的数small个,跟k一样大的数equal个,比k大的数big个。很容易发现,如果k是伪中位数,就一定有small+equal >= bigbig + equal > small两个式子同时成立。第二个式子可以变换成 big + equal >= small+1,最后解出equal满足 equal >= big-small 且 equal >= small-big+1。所以equal比二者较大值大,即equal>=max(big-small, small-big+1)。需要补的k的个数即max(big-small, small-big+1) - equal。注意上式可能为负,跟0再取个最大即可。

参考代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

using namespace std;

int n, k;

int main() {
    cin >> n >> k;
    int cnt_big = 0, cnt_equal = 0, cnt_small = 0;
    for (int i = 0; i < n; i++) {
        int t;
        cin >> t;
        if (t < k)
            cnt_small++;
        else if (t == k)
            cnt_equal++;
        else
            cnt_big++;
    }
    int res = max(0, max(cnt_small - cnt_big + 1, cnt_big - cnt_small) - cnt_equal);
    cout << res << endl;
    return 0;
}

5. 子串和子序列

给两个字符串s,t求s的子串与t的子序列相同的情况有多少种。字串的定义是连续的子字符串。 子序列的定义是不必连续的子序列。abcde的子序列可以是ace。

输入:两个字符串

输出:相同的情况数

思路:动态规划,定义f[i][j]为字符串s到第i个位置为止,且用上了第i个位置的字符;字符串t到第j个位置为止;此时的相同数目。

状态转移方程,分两种情况: 第一种是s串的子串就是s[i]自己,(长度为1的字串),那么此时种类数是cntj[s[i]],这个数的意思是字符串t的前j个字符里,有多少个s[i]。这种情况很好理解。 第二种是s串字串长度大于2,且包含s[i]。这种情况下考虑s[i]和t中的哪一个字符配对。事实上总共有cntj[s[i]]种配对方法(这个数的意思详见上面)。假设这些s[i]分别出现在t串的p1,p2,p3,…,pc (c=cntj[s[i]])这些位置那么种类数是f[i-1][p1-1]+f[i-1][p2-1]+...+f[i-1][pc-1]。 综上,f[i][j] = cntj[s[i]] + (f[i-1][p1-1]+f[i-1][p2-1]+...+f[i-1][pc-1]) 最终答案为f[1][nt] + f[2][nt] + ... + f[ns][nt] ns和nt为s串和t串的长度。

参考代码

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
#include <iostream>
#include <string>
#include <unordered_map>
#include <vector>

using namespace std;

const int M = 1000000007;
int f[5001][5001], ns, nt;
string s, t;

int main() {
    cin >> s >> t;
    ns = s.size();
    nt = t.size();

    unordered_map<char, int> cnt;
    unordered_map<char, vector<int>> value;
    for (int j = 1; j <= nt; ++j) {
        char tt = t[j - 1];
        cnt[tt]++;
        if (value.find(tt) == value.end()) {
            value[tt].resize(ns + 1);
        }
        for (int i = 1; i <= ns; ++i) {
            char ss = s[i - 1];
            f[i][j] = cnt[ss];
            if (value.find(ss) != value.end()) {
                f[i][j] = (f[i][j] + value[ss][i - 1]) % M;
            }
            value[tt][i] = (value[tt][i] + f[i][j - 1]) % M;
        }
    }

    int ans = 0;
    for (int i = 1; i <= ns; ++i) {
        ans = (ans + f[i][nt]) % M;
    }
    cout << ans;
    return 0;
}

拼多多

1. 买三赠一 (4.10)

买商品,买三送一,送价钱最低的,准备买n个商品(编号1-n),其中第i个商品的价格为Ai,现在找到策略,花最少的钱买到所有商品。

输入:
第一行:一个整数n,$1\le n \le 10,000$;第二行:n个整数$A_i$,分别表示n个商品的价格,$1 \le A_i \le 1,000,000$

输出:最少需要花费多少钱?

样例:
输入:
4
100 200 300 400
输出:800 // 解释:买200 300 400省200,再加上100。

思路:先递减排序,然后三个一组计算

参考代码

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
#include <algorithm>
#include <iostream>

using namespace std;

const int N = 100010;
int n;
int p[N];

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) {
        cin >> p[i];
    }
    sort(p, p + n);
    reverse(p, p + n);
    int cost = 0;
    int count = 0;
    int index = 0;
    while (index < n) {
        cost += p[index];
        count++;
        if (count == 3) {
            cost -= p[index];
            count = 0;
        }
        index++;
    }
    cout << cost << endl;
}

2. 和谐区 (4.10)

某个小区有1-n棵树,每棵树有个和谐值$A_i$,如果一段连续的树,他们和谐值之和可以被$m$整除,那么这个区间整体就是和谐的。求有多少个这样的区间? LeeCode974

输入:
第一行两个整数 n, m
第二行n个整数,分别表示第i棵树的和谐值,$0\le A_i \le 1,000,000$

输出:满足的区间数量

样例:
输入:
5 2
1 2 3 4 5
输出:6 //解释 [2],[4],[1,2,3],[3,4,5],[1,2,3,4],[2,3,4,5]

思路:前缀和 + 哈希 + 同余定理
如果两个数的差能被K整除,就说明这两个数 mod K得到的结果相同,只要找有多少对 mod k 相同的数就可以组合一下得到结果。

同余定理:如果整数a和整数b满足$a-b$能够被m整除,即$(a-b)/m$是一个整数$(a-b)\%m==0$,则a对m取余与b对m取余相等,即$a\%m==b\%m$。

参考代码

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
#include <bits/stdc++.h>

using namespace std;

const int N = 100010;

int n, m;
int va[N];

int main() {
    while (cin >> n >> m) {
        for (int i = 0; i < n; i++) {
            cin >> va[i];
        }
        unordered_map<int, int> hash;
        hash[0] = 1;  // 考虑单个元素就可以整除m的情况
        for (int i = 0; i < n; i++) {
            // 前缀和对m取余
            va[i] = ((i == 0 ? 0 : va[i - 1]) + va[i]) % m;
            hash[va[i]]++;
        }
        int res = 0;
        for (auto item : hash) {
            // 对m取余相同的情况进行组合
            res += (item.second * (item.second - 1)) / 2;
        }
        cout << res << endl;
    }
    return 0;
}

3. 找靓号 (4.10)

给定字符串长度为N,字符串只会包括’0’-‘9’,要求修改字符串使得至少有一个字符出现K次,修改每个字符的代价为对应数字的变化大小,求最小代价,并输出最小代价下的最小字符串。例如:对于字符串 ‘787855’, K = 5, 则最小代价应该为4, 并且输出最小字符串’777757’(不能是’777775’, 因为不满足最小字符串)牛客原题

输入:第一行包含2个整数 N、K,分别表示手机号码数字个数以及靓号至少有 K 个数字相同。第二行包含 N 个字符,每个字符都是一个数字(‘0’-‘9’),数字之间没有任何其他空白符。表示小多的手机号码。数据范围:$2 \le K \le N \le 10,000$.

输出:第一行包含一个整数,表示修改成一个靓号,最少需要的金额。第二行包含 N 个数字字符,表示最少花费修改的新手机号。若有多个靓号花费都最少,则输出字典序最小的靓号。

样例:
输入:
6 5
787585
输出:
4
777577 // 说明:花费为4的方案有两种:777577与777775,前者字典序更小。

思路:是一道贪心求解的题。由于一共就10个数字,求每一位数字作为靓号的花费。例如对于787585这个号,n=6,k=5。首先判断设置5个9的情况,再针对设置每个数字到9的花费进行排序。这里利用了一个结构体,包含了当前数的在号码的位置,其初始值,其目标值,以及从初始值转换到目标值所需要的花费。将花费从小到大进行排序,对于花费一致的数,这里需要输出字典序最小的结果,因此当当前数比目标数小,优先处理后面的数。

参考代码

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
#include <bits/stdc++.h>

using namespace std;

const int N = 10010;

// 定义每个数字的结点
struct Node {
    int cost;    //记录保存当前值转换为目标值的花费
    int idx;     // 记录当前数字在字符串中的位置
    int target;  // 记录要转换的目标值
    int ori;     // 记录原始的数字
};

int nums[N];  // 保存将字符串转为数组
Node nds[N];  // 记录每个数字的信息
int res[N];   // 记录答案
int n, k;

// 先按照花费大小进行排序,如果花费一样,如果当前数比目标数小的,则优先处理后面的数
bool cmp(Node nd1, Node nd2) {
    if (nd1.cost != nd2.cost) return nd1.cost < nd2.cost;
    if (nd1.idx < nd2.idx) return nd1.target < nd1.ori;
    return nd2.target > nd2.ori;
}

int main() {
    while (cin >> n >> k) {
        string s;
        cin >> s;
        // 转换成数字
        for (int i = 0; i < s.size(); i++) {
            nums[i] = s[i] - '0';
        }
        int cnt = INT_MAX, tmp;  // cnt 记录最小花费
        // 暴力尝试每一个数字0-9作为相同的数字
        for (int i = 0; i <= 9; i++) {
            // 遍历所有数字,计算花费
            for (int j = 0; j < n; j++) {
                nds[j].cost = abs(nums[j] - i);
                nds[j].idx = j;
                nds[j].target = i;
                nds[j].ori = nums[j];
            }
            // 按照排序规则排序
            sort(nds, nds + n, cmp);
            tmp = 0;
            // 计算当使i作为相同数字时的花费
            for (int j = 0; j < k; j++) tmp += nds[j].cost;
            // 如果比之前的花费小,则更新花费和答案数组
            if (tmp < cnt) {
                cnt = tmp;
                // 先将原来的数字拷贝一份
                for (int j = 0; j < n; j++) res[j] = nums[j];
                // 再将需要修改的地方进行修改
                for (int j = 0; j < k; j++) res[nds[j].idx] = i;
            }
        }
        for (int i = 0; i < n; i++) {
            cout << res[i];
        }
        cout << endl;
    }
    return 0;
}

4. 我忘了。。。 (4.10)

题目忘了。。。

输入:

输出:

样例:

思路:

参考代码

1
待填。。。

5. 不同球数的盒子 (5.6)

N个盒子(1~N),盒子$i$中有$A_i$个球,现需使每个盒中球的个数各不相同,盒子里面只能放入不可取出,问题:最少放入多少球,使每个盒子球数各不相同。LeeCode945

输入:第一行盒子个数n,第二行,n个盒子中球的个数

输出:最少放入多少个球。

样例:
输入:
5
2 1 1 2 4
输出:
5

思路:先排序,然后暴力增加。如果当前的数<=前一个数,那么就变成前一个数+1

参考代码

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
#include <bits/stdc++.h>

using namespace std;

typedef long long LL;

const int N = 100010;

LL a[N];
int n;

int main() {
    while (cin >> n) {
        for (int i = 0; i < n; i++) {
            cin >> a[i];
        }
        sort(a, a + n);  // 排序
        LL res = 0;
        // 遍历数组,暴力增加
        for (int i = 1; i < n; i++) {
            // 如果当数小于等于前一个数,则当前数变为前一个数+1,并计算需要添加多少个球
            if (a[i] <= a[i - 1]) {
                res += 1LL * (a[i - 1] - a[i] + 1);
                a[i] = a[i - 1] + 1;
            }
        }
        cout << res << endl;
    }
    return 0;
}

6. 拼正方形 (5.6)

N个木棍(1~N),第i个木棍长$L_i$,用完所有木棍,拼出正方形,一共T组,有些无解,判断T组是否有解,有YES,无NO。$1\le N\le 20$,$1\le L_i \le 10000$。 LeeCode473

输入:第一行T,表示有T组测试数据,第二行,第一个是n,表示有n个木根,接下来n个数表示木棍的长度。

输出:有解输出YES,否则输出NO

样例:
输入:
2
5 1 1 2 2 2
5 3 3 3 3 4
输出:
YES
NO

思路:深度优先搜索 + 剪枝,时间复杂度有点高($O(4^N)$), 可以使用状态压缩dp。

参考代码

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
#include <bits/stdc++.h>

using namespace std;

const int N = 25;

int nums[N];
bool vis[N];
int n;

// 剪枝更彻底
bool dfs2(int idx, int sum) {
    if (sum == 0) return true;
    if (idx == n) return false;
    for (int i = idx; i < n; i++) {
        if (vis[i] || nums[i] > sum) continue;
        vis[i] = true;
        if (dfs2(i + 1, sum - nums[i])) return true;
        vis[i] = false;
    }
    return false;
}

bool dfs(int idx, int a, int b, int c, int d, int sum) {
    if (idx == n) {
        if (a == sum && b == sum && c == sum && d == sum)
            return true;
        else
            return false;
    }
    // 排序就是为了这儿的剪枝
    if (a > sum || b > sum || c > sum || d > sum) return false;
    return dfs(idx + 1, a + nums[idx], b, c, d, sum) ||
           dfs(idx + 1, a, b + nums[idx], c, d, sum) ||
           dfs(idx + 1, a, b, c + nums[idx], d, sum) ||
           dfs(idx + 1, a, b, c, d + nums[idx], sum);
}

int main() {
    int T;
    cin >> T;
    while (T--) {
        cin >> n;
        for (int i = 0; i < n; i++) cin >> nums[i];
        // 排序是为了更快的剪枝
        sort(nums, nums + n), reverse(nums, nums + n);
        int sum = 0;
        for (int i = 0; i < n; i++) sum += nums[i];
        if (sum % 4) return false;
        // if (dfs(0, 0, 0, 0, 0, sum / 4))
        //     cout << "YES" << endl;
        // else
        //     cout << "NO" << endl;

        bool can = true;
        for (int i = 0; i < 4; i++) {
            if (!dfs2(0, sum / 4)) can = false;
        }
        if (can)
            cout << "YES" << endl;
        else
            cout << "NO" << endl;
    }

    return 0;
}

头条

网易互娱