字节跳动2017后端工程师实习生笔试题

题目一

描述

有 n 个字符串,每个字符串都是由 A-J 的大写字符构成。现在你将每个字符映射为一个 0-9 的数字,不同字符映射为不同的数字。这样每个字符串就可以看做一个整数,唯一的要求是这些整数必须是正整数且它们的字符串不能有前导零。现在问你怎样映射字符才能使得这些字符串表示的整数之和最大?

输入描述

每组测试用例仅包含一组数据,每组数据第一行为一个正整数 n , 接下来有 n 行,每行一个长度不超过 12 且仅包含大写字母 A-J 的字符串。 n 不大于 50,且至少存在一个字符不是任何字符串的首字母。
输出描述
输出一个数,表示最大和是多少。

输入例子

2
ABC
BCA

输出例子

1875

思路

这道题首先把输入字符串看作十进制数,以输入例子为例,即 ABC = 100 * A + 10 * B + 1 * C, BCA = 100 * B + 10 * C + 1 * A,则它们的和为 ABC + BCA = 110 * B + 101 * A + 11 * C(按系数大小降序排列),由此可知系数较大的字符应映射较大的数字,这里可取 B = 9, A = 8, C = 7,得到结果1875。此外应注意按系数排序后的首个字符可能映射为 0 的情况,此时应把该字符替换为最近的映射为非 0 的字符,其余各项向后移动填充空缺。

代码

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
 
bool comparator(const pair<uint64_t, bool>& lhs, const pair<uint64_t, bool>& rhs) {
    return lhs.first < rhs.first;
}
 
uint64_t maxNum(vector<string>& strs) {
    vector<pair<uint64_t, bool>> memo(10, make_pair(0, false));
    for (auto& s : strs) {
        uint64_t digit = 1;
        for (int i = s.size() - 1; i >= 0; --i) {
            memo[s[i] - 'A'].first += digit;
            if (i == 0) memo[s[i] - 'A'].second = true;
            digit *= 10;
        }
    }
    sort(memo.begin(), memo.end(), comparator);
    uint64_t res = 0;
    if (memo[0].second) {
        int firstNoZero = 1;
        while (memo[firstNoZero].second) {
            ++firstNoZero;
        }
        auto tmp = memo[firstNoZero];
        for (uint64_t i = firstNoZero; i > 0; --i) {
            memo[i] = memo[i - 1];
        }
        memo[0] = tmp;
    }
    for (uint64_t i = 0; i <= 9; ++i) {
        res += i * memo[i].first;
    }
    return res;
}
 
int main() {
    int n;
    while (cin >> n) {
        vector<string> strs(n);
        int i = 0;
        while (i < n && cin >> strs[i++]);
        cout << maxNum(strs) << endl;
    }
    return 0;
}

题目二

描述

有一个由很多木棒构成的集合,每个木棒有对应的长度,请问能否用集合中的这些木棒以某个顺序首尾相连构成一个面积大于 0 的简单多边形且所有木棒都要用上,简单多边形即不会自交的多边形。

初始集合是空的,有两种操作,要么给集合添加一个长度为 L 的木棒,要么删去集合中已经有的某个木棒。每次操作结束后你都需要告知是否能用集合中的这些木棒构成一个简单多边形。

输入描述

每组测试用例仅包含一组数据,每组数据第一行为一个正整数 n 表示操作的数量(1 ≤ n ≤ 50000) , 接下来有n行,每行第一个整数为操作类型 i (i ∈ {1,2}),第二个整数为一个长度 L(1 ≤ L ≤ 1,000,000,000)。如果 i=1 代表在集合内插入一个长度为 L 的木棒,如果 i=2 代表删去在集合内的一根长度为 L 的木棒。输入数据保证删除时集合中必定存在长度为 L 的木棒,且任意操作后集合都是非空的。

输出描述:
对于每一次操作结束有一次输出,如果集合内的木棒可以构成简单多边形,输出 “Yes” ,否则输出 “No”。

输入例子

5
1 1
1 1
1 1
2 1
1 2

输出例子

No
No
Yes
No
No

思路

这道题也没有用到复杂的算法,只需利用一条多边形的特性:最小的 n - 1 条边之和应大于最大边。每次添加或删除操作用升序排列的红黑树记录即可。

代码

#include <iostream>
#include <vector>
#include <algorithm>
#include <map>
#include <string>
using namespace std;
 
bool isPolygon(map<int64_t, int64_t>& memo) {
    auto end = memo.end();
    if (memo.begin() == end) return false;
    --end;
    int64_t sumOfLeastEdge = 0;
    int64_t largeEdge = end->first;
    int64_t largeNum = end->second;
    for (auto it = memo.begin(); it != end; ++it) {
        sumOfLeastEdge += (it->first * it->second);
    }
    for (int64_t i = 0; i < largeNum - 1; ++i) {
        sumOfLeastEdge += largeEdge;
    }
    if (sumOfLeastEdge > largeEdge) return true;
    else return false;
}
 
void printPolygon(vector<vector<int64_t>>& data) {
    map<int64_t, int64_t, less<int64_t>> memo;
    int64_t n = data.size();
    for (int64_t i = 0; i < n; ++i) {
        if (data[i][0] == 1) {
            ++memo[data[i][1]];
        }
        else {
            --memo[data[i][1]];
            if (memo[data[i][1]] == 0) {
                memo.erase(data[i][1]);
            }
        }
        string res = isPolygon(memo) ? "Yes" : "No";
        cout << res << endl;
    }
}
 
int main() {
    int64_t n, i, L;
    while (cin >> n) {
        vector<vector<int64_t>> data(n, vector<int64_t>(2, 0));
        int64_t j = 0;
        while (j < n && cin >> i && cin >> L) {
            data[j][0] = i;
            data[j][1] = L;
            ++j;
        }
        printPolygon(data);
    }
    return 0;
}

题目三

描述

给出 n 个字符串,对于每个 n 个排列 p,按排列给出的顺序(p[0] , p[1] … p[n-1])依次连接这 n 个字符串都能得到一个长度为这些字符串长度之和的字符串。所以按照这个方法一共可以生成 n! 个字符串。

一个字符串的权值等于把这个字符串循环左移 i 次后得到的字符串仍和原字符串全等的数量,i 的取值为 [1 , 字符串长度]。求这些字符串最后生成的 n! 个字符串中权值为 K 的有多少个。

注:定义把一个串循环左移 1 次等价于把这个串的第一个字符移动到最后一个字符的后面。

输入描述

每组测试用例仅包含一组数据,每组数据第一行为两个正整数 n, K , n 的大小不超过 8 , K 不超过 200。接下来有 n 行,每行一个长度不超过 20 且仅包含大写字母的字符串。

输出描述

输出一个整数代表权值为 K 的字符串数量。

输入例子

3 2
AB
RAAB
RA

输出例子

3

思路

这道题思路也不难,但是一开始写出的代码总是在效率上通不过。看了解析的代码(答案解析)后才知道,比对循环左移字符串时不需要真的去移动字符串,要找出原字符串和循环左移字符串相等的特性;另外,生成全排列时不用写出完整的排列算法,只需调用 std::next_permutation,拿到字符串数组索引的排列就可以了。

代码

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <cstring>
 
using namespace std;
 
bool isEqualAfterMove(const string& str, const size_t offset) {
    size_t len = str.size();
    if (offset == len) return true;
    // 左移 offset 位数后,与原串相等的情况
    // 情况一:每 offset 个数据块都要相等
    // 所以这种情况的字符串,长度首先必须是 offset 的 整数倍。
    // 情况二:串长不是 offset 的 整数倍,像这种: ABABAB , offset = 4
    // 这种情况可用递归解决
    if (len % offset != 0) { // 情况二
        if (len % offset > len / 2) return false;
        return isEqualAfterMove(str, len % offset);
    }
    // 情况一
    char* s = (char*)&str[0];
    for (size_t loop = 0, max_loop = len / offset - 1;
        loop < max_loop; ++loop) {
        if (0 != strncmp(s, s + offset, offset)) {
            return false;
        }
        s += offset;
    }
    return true;
}
 
void getCount(const vector<string>& strs,
    const vector<int>& pos,
    int& res, const int k) {
    size_t cnt = 1;
    string newstring;
    for (size_t i = 0; i < strs.size(); ++i) {
        newstring += strs[pos[i]];
    }
    const int len = newstring.size();
    for (int offset = 1; offset < len; ++offset) {
     
        if (newstring[offset] == newstring[0]) {
            if (isEqualAfterMove(newstring, offset)) {
                ++cnt;
            }
        }
    }
 
    if (cnt == k) {
        ++res;
    }
}
 
int main() {
    int n, k;
    while (cin >> n) {
        cin >> k;
        vector<string> strs(n);
        int i = 0;
        int res = 0;
        while (i < n && cin >> strs[i++]);
        vector<int> pos(n);
        for (int i = 0; i < n; ++i) pos[i] = i;
        do {
            getCount(strs, pos, res, k);
        } while (std::next_permutation(pos.begin(), pos.end()));
        cout << res << endl;
    }
    return 0;
}

题目四

描述

给定 x, k ,求满足 x + y = x | y 的第 k 小的正整数 y 。 | 是二进制的或(or)运算,例如 3 | 5 = 7。

比如当 x=5,k=1时返回 2,因为5+1=6 不等于 5|1=5,而 5+2=7 等于 5 | 2 = 7。

输入描述

每组测试用例仅包含一组数据,每组数据为两个正整数 x , k。 满足 0 < x , k ≤ 2,000,000,000。

输出描述

输出一个数y。

输入例子

5 1

输出例子

2

思路

这道题考察位运算。引用这位大佬的解析(答案解析

x+y=x|y
这里可以推出一个结论,x&y=0。也就是说,在二进制上看,x取1的地方,y必定不能取1。从最低位考虑,若x与y在某一位上同时取1,则x+y在该位上为0,x|y在该位上为1,前面说这是最低一位x, y同时取1,也就是说没有更低位加法的进位,所以这里两个结果不相等,出现了矛盾。
例子:
x = 001010
y = 110110
x + y = 1000000
x | y = 111110
偏差产生的原因是倒数第二位,x+y=0 x|y=1 且倒数第一位加法没有进位。
结论:x在二进制取1的位上,y不能做出改变,只能取0
有了上述结论,可以进一步推出只要在x取0的地方,y可以做出改变
例如
x = 10010010011
y = 00000000(0)00 k = 0
y = 00000000(1)00 k = 1
y = 0000000(1)(0)00 k = 2
y = 0000000(1)(1)00 k = 3
y = 00000(1)0(0)(0)00 k = 4
y = 00000(1)0(0)(1)00 k = 5

注意观察括号里的数,为x取0的比特位,而如果把括号里的数连起来看,正好等于k。
得出结论,首先把 y 初始化为 0,把 k 表示成二进制数,将 k 的二进制位从低到高依次填入 x 取 0 的比特位,x 取 1 的比特位保持为 0,得到 y

代码

#include <iostream>
using namespace std;
 
uint64_t getRes(uint64_t x, uint64_t k) {
    uint64_t y = 0;
    uint64_t bit = 1;
    while (k) {
        if ((x & bit) == 0) {
            y += bit * (k & 1);
            k >>= 1;
        }
        bit <<= 1;
    }
    return y;
}
 
int main() {
    uint64_t x, k;
    while (cin >> x && cin >> k) {
        uint64_t y = getRes(x, k);
        cout << y << endl;
    }
    return 0;
}

发布了30 篇原创文章 · 获赞 97 · 访问量 21万+

猜你喜欢

转载自blog.csdn.net/HerosOfEarth/article/details/104305542