笔试 | 字节跳动秋招第二场题目&解答

第一题

题目描述
  • 给定一颗二叉树,二叉树每个节点都有唯一的正整数值代表节点,在遍历时,我们使用节点的数值作为标记。给定二叉树的前序和中序遍历结果,求二叉树的叶子节点个数。

输入
  • 第一行,输入二叉树节点个数N,其中0 < N < 30000

  • 第二行与第三行,分别输入二叉树的前序和中序遍历结果,每个节点对应唯一整数值

输出
  • 二叉树叶子节点个数

样例输入
  • 3

  • 1 3 4

  • 3 1 4

样例输出
  • 2

分析
  • 这是leetcode一个经典的题的拓展:给定一个二叉树的前序和中序遍历,让你重构一个二叉树。如果leetcod这个题会的话只需要简单加个统计就能解出字节这个题了。

  • 大概的思路就是用递归的方法。利用这个关键信息:前序遍历的第一个节点一定是根节点root,而在中序遍历中root节点左侧的节点一定是它左子树的所有节点。

  • 对于这个题的话,我们其实没必要去重构二叉树。当我们在重构的过程中发现root左侧的节点只有一个的时候,那么它左侧的这个节点一定是叶子节点。右侧的节点同理。

解法
#include <bits/stdc++.h>

using namespace std;

const int maxn = 30001;
int ans;
vector<int> pre(maxn, 0), mid(maxn, 0);

// 当前树的所有节点,在前序序列中的范围是[l, r],在中序序列中的范围是[ml, mr]
void dfs(int l, int r, int ml, int mr) {
    if (l > r) return; // 当前树没节点了
    if (l == r) { // 如果这个树只有一个节点,那它肯定是叶子节点
        ans++;
        return;
    }
    int root = pre[l]; // 当前树的跟节点
    int index;
    for (int i = ml; i <= mr; ++i) { // 在中序序列中找这个树的左子树范围和右子树范围
        if (mid[i] == root) {
            index = i;
            break;
        }
    }
    int lcnt = index - ml; // 左子树的节点数,利用中序序列中root节点左侧的是左子树,右侧的是右子树
    int rcnt = mr - index; // 右子树的节点树
    dfs(l + 1, l + lcnt, ml, index - 1); // 递归处理左子树,下同
    dfs(l + lcnt + 1, r, index + 1, mr);
}

int main() {
    int n;
    while(cin >> n) {
        for (int i = 1; i <= n; ++i) cin >> pre[i];
        for (int i = 1; i <= n; ++i) cin >> mid[i];
        ans = 0;
        dfs(1, n, 1, n);
        cout << ans << endl;
    }
    return 0;
}

n = int(input())
pre = input().split()
mid = input().split()

ans = 0

def dfs(l, r, ml, mr):
    global ans
    if l > r: return
    if l == r:
        ans += 1
        return
    root = pre[l]
    index = ml
    for i in range(ml, mr + 1):
        if mid[i] == root:
            index = i
            break

    lcnt = index - ml
    rcnt = mr - index
    dfs(l + 1, l + lcnt, ml, index - 1)
    dfs(l + lcnt + 1, r, index + 1, mr)

dfs(0, n - 1, 0, n - 1)
print(ans)

import java.util.Scanner;
public class zijie2_1 {
    public static int ans;

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int n = input.nextInt();
        int []pre = new int[n + 1];
        int []mid = new int[n + 1];
        for (int i = 1; i <= n; ++i) {
            pre[i] = input.nextInt();
        }
        for (int i = 1; i <= n; ++i) {
            mid[i] = input.nextInt();
        }
        ans = 0;
        dfs(1, n, 1, n, pre, mid);
        System.out.println(ans);
    }
    public static void dfs(int l, int r, int ml, int mr, int[]pre , int[] mid) {
        if (l > r) return;
        if (l == r) {
            ans++;
            return;
        }
        int root = pre[l], index = ml;
        for (int i = ml; i <= mr; ++i) {
            if (mid[i] == root) {
                index = i;
                break;
            }
        }
        int lcnt = index - ml, rcnt = mr - index;
        dfs(l + 1, l + lcnt, ml, index -1, pre, mid);
        dfs(l + lcnt + 1, r, index + 1, mr, pre, mid);
    }
}

第二题

题目描述
  • 小明在学习一种编码协议,二进制数据可以用一串16进制的字符来表示(0-9, A-F),其中的“0010”是这个协议的特殊字符串,不允许出现。

  • 现在给出一串16进制的字符串s,问:

  • 最少去掉几个字符,使得剩下的字符串中不存在“0010”

输入
  • 第一行t(1<= t <= 1000),表示有t组测试数据

  • 每个样例一行字符串s,s的长度不超过1e5

输出
  • 每个样例一行,输出最少去掉字符数

样例输入
  • 2

  • 0123456789ABCDEF

  • 0010123456789ABCDEF00

样例输出
  • 0

  • 1

分析
  • 如果出现了“0010”,那么我们考虑只需要去掉任意一个字符让它不构成“0010”就好了。但是如果由于我们去掉一个字符导致后面出现了新的“0010”,那就不太划算了。比如原来的是"001010",去掉第一个1之后出现了新的“0010”,这个时候去掉第三个0是最好的。所以可以寻找“0010”出现的位置,如果后面跟着的是1,那么去掉最后这个0;如果后面跟着的是0,那么去掉1是最好的。这么看来只需要寻找出现了几个"0010"就好了,只不过下次寻找的时候需要考虑上已经找到的"0010"中的最后这个0。

解法
#include<bits/stdc++.h>
using namespace std;

int main() {
    int n;
    string s;
    string p = "0010";
    while(cin >> s) {
        int cnt = 0;
        size_t found = s.find(p, 0);
        while(found != string::npos) {
            ++cnt;
            found += 3;
            if(found >= s.size()) break;
            found = s.find(p, found);
        }
        cout << cnt << endl;
    }
    return 0;
}

第三题

题目描述
  • 某视频网站有N个视频,每个视频时长为 秒。产品经理找到你,希望你帮忙在其中插入M个广告。

  • 一个视频里的两个广告必须间隔一段时间,当然间隔时间可以为0。

  • 为了用户体验,他希望这个间隔时间尽可能长。为了方便实现,间隔时间是一个整数。

  • 请你帮忙计算一下,这个间隔时间最大可以设置为多少秒呢?如果不能插入广告,则输出0。

输入
  • 第一行有两个整数N, M(1<=N<=100000, N<M<=5000000)

  • 第二行有N个整数 ,表示视频的时长(1<= <=1e9)

输出
  • 一个整数,表示最大的间隔时间

样例输入
  • 3 9

  • 90 100 120

样例输出
  • 45

说明
  • 最长广告间隔为45秒。第一个视频时长90秒,可以在第0秒,第45秒,第90秒分别插入一个广告,总共3个广告。

分析
  • 这个题目是寻找在一定条件限制下的最大值,可以用二分法快速解。

代码
#include <bits/stdc++.h>

using namespace std;

vector<int> arr(10000, 0);
int main() {
    int n, m;
    while(cin >> n >> m) {
        int l = 0, r = 0;
        int ans;
        for (int i = 0; i < n; ++i) {
            cin >> arr[i];
            r = max(r, arr[i]);
        }
        while (l <= r) {
            int mid = (l + r) >> 1; // 检查mid间隔能播放多少广告
            int tmp_cnt = 0;
            for (int i = 0; i < n; ++i) {
                tmp_cnt += (arr[i] / mid + 1);
            }
            if (tmp_cnt >= m) {
                ans = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        cout << ans << endl;
    }
    return 0;
}

第四题

题目描述
  • 在n(1<=n<=35)个正整数中,任意挑选k个(不可重复挑选,0<=k<=n)数字的和记为sum,另有一个正整数m,请问sum%m的最大值是多少?

输入描述
  • 第一行两个整数,分别为n, m

  • 第二行为n个正整数

输出描述
  • 一个数x,x表示sum%m的最大值

样例输入
  • 5 5

  • 1 2 3 4 5

样例输出
  • 4

题目分析
  • 如果n比较小的话,我们可以用暴力的方式拿到所有这n个元素能组成的和,然后求最大值即可。复杂度是O(2^n)。但是这个题n最大可以达到35,小本本一算,2^35=34359738368,妥妥的超时。:(

  • 所以我们可不可以尝试把问题分解一下。将原数组分解成两个数组 ,对于每个数组都算出来利用这些元素能组成哪些和,得到两个数组B和C。这个的复杂度是可以接受的,O(

  • 最大的结果可能是B中某元素,也可能是C中某元素,也可能是B中某元素和C中某元素的和对m求余数。如果只来自B或者C的话,很容易得到答案。复杂的是如何从B中选一个 ,再从C中选一个 ,让它两加起来的和模m最大。

  • 把B和C数组都排序之后,B和C中所有元素加起来的和的范围是0~2m-2。那么他们的和模m的范围是0~m-1,所以中间可能会有一个跳跃点,从(b+c)%m最大,然后b或者c继续增大的话b+c超过了m,所以(b+c)%m就又到了一个低谷值。因此我们可以尝试用two pointer的思路,一个指向B的开始,一个指向C的结尾,然后看这两指针所指位置加和模m与前一个位置的比较。如果加起来要比前一个位置大,说明我们有继续逼近最大值的希望,那么可以尝试增大左指针;否则说明我们尝试过了劲儿,越过了最大点,那么这时候就要减少右指针所指位置的值了。

实现代码
#include <bits/stdc++.h>

using namespace std;
using ll = long long;

vector<ll> a, x, y;
ll m;

void dfs(vector<ll> &vec, int index, int end, ll sum) {
    if (index == end) {
        return;
    }
    vec.emplace_back(sum);
    dfs(vec, index + 1, end, vec.back());
    vec.emplace_back((sum + a[index]) % m);
    dfs(vec, index + 1, end, vec.back());
}

int main() {
    int n;
    ll d, sum = 0;
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> d;
        d %= m;
        if (d != 0) {
            a.emplace_back(d);
            sum += d;
        }
    }
    if (sum < m) {
        cout << sum << endl;
        return 0;
    }
    n = a.size();
    dfs(x, 0, n >> 1, 0);
    dfs(y, n >> 1, n, 0);
    sort(x.begin(), x.end());
    sort(y.begin(), y.end());
    x.erase(unique(x.begin(), x.end()), x.end());
    y.erase(unique(y.begin(), y.end()), y.end());
    auto left = x.begin();
    auto right = y.rbegin();
    ll ans = max(x.back(), y.back()), pre = 0;
    while (ans < m - 1 && left != x.end() && right != y.rend()) {
        ll temp = (*left + *right) % m;
        ans = max(ans, temp);
        if (temp > pre) {
            ++left;
        } else {
            --right;
        }
    }
    cout << ans << endl;
    return 0;
}

面鲸,专注于互联网招聘笔试、面试经验分享,希望能给您带来一丝帮助,欢迎关注~ 后台回复「联系」可添加小编微信加入面试笔试刷题交流群,这里有各种语言的题解,我们在这里等你~

据说点赞的都拿到了offer

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值