字节秋招笔试 20230903

130 阅读2分钟

字节秋招笔试 20230903

前言:菜鸟,浅浅记录总结一下,欢迎大佬们批判。

Q1 生成字符串(模拟)

题目描述:给定整数 kk ,生成字符串,该字符串由二十六个小写字母组成,每个字母至少出现两次,且相同字母之间的距离为 kk (1k25 1 \leq k \leq 25).

public static String solve(int k) {
    StringBuilder sb = new StringBuilder();
    Deque<Character> queue = new ArrayDeque<>();
    int[] cnt = new int[26];

    // 依次放入26个字母
    for (int i = 0; i < 26;) {
        // 倒数第k个字母数量少于2时优先加入
        if (queue.size() >= k && cnt[queue.peekFirst() - 'a'] < 2) {
            sb.append(queue.peekFirst());
            queue.offerLast(queue.peekFirst());
            cnt[queue.peekFirst() - 'a']++;
        } else { // 倒数第k个字母数量满足后,加入下一个字母
            char c = (char)('a' + i);
            sb.append(c);
            queue.offerLast(c);
            cnt[i]++;
            i++;
        }
        // 字符队列始终记录后k个字符
        if (queue.size() > k) {
            queue.pollFirst();
        }
    }

    // 当最后一个字符数量未达到2时一直循环加入倒数第k个字符
    while (cnt[25] < 2) {
        sb.append(queue.peekFirst());
        queue.offerLast(queue.peekFirst());
        cnt[queue.peekFirst() - 'a']++;
        queue.pollFirst();
    }
    return sb.toString();
}

Q2 求连通块的权重(找规律)

题目描述:给定一个无限大小的由 0,10,1 组成矩阵,矩阵中每两个相邻元素均不相同(即0,1间隔排列)。给定连通块的权重定义为:元素 11 的数量减去元素 00 的数量。给定连通块元素数量 kk,求出最大权重。

public static long solve(long k) {
    if (k == 0 || k == 1) {
        return k;
    }
    // 从第二个元素起找规律
    k--;
    // 连通块最大权重的变化规律为 -1, +1, +1, +1 ... -1, +1, +1, +1.
    long res = k / 4 * 2;
    k -= k / 4 * 4;
    // k 的取值为 0 / 1 / 2 / 3.
    if (k == 1) {
        res -= 1;
    }
    if (k == 2 || k == 3) {
        res += k - 2;
    }
    // 加上第一个元素 1.
    res++;
    return res;
}

Q3 红色连通块数量(并查集)

题目描述:给定 nn 个节点和它们对应的颜色字符串 colorcolor,对应的字符为 ww 代表白色,为 rr 代表红色。edgesedges 为节点间的边。当一个连通块的所有节点均为红色时,称为一个红色连通块。请给出将第 ii 个节点染红后,红色连通块的数量。

(相同思路笔试时超时了(没有去除一些情况),目前版本不知是否能通过,暂且记录QAQ)

public static void solve(String color, int[][] edges) {
    // 并查集维护连通量和联通关系
    Map<Integer, Integer> root = new HashMap<>();
    int count = 0;
    int n = color.length();
    List<Integer>[] adjlist = new List[n];
    for (int i = 0; i < n; i++) {
        adjlist[i] = new ArrayList<>();
        root.put(i, i);
        // 初始连通分量大小为红色节点数量
        if (color.charAt(i) == 'r') {
            count++;
        }
    }
    // 对于红色节点间的边直接通过并查集维护,红白之间则放入adjlist中待之后染色使用
    for (int[] edge : edges) {
        int u = edge[0], v = edge[1];
        if (color.charAt(u) == 'r' && color.charAt(v) == 'r') {
            count = union(u, v, root, count);
        }
        if (color.charAt(u) != 'r' && color.charAt(v) == 'r') {
            adjlist[u].add(v);
        }
        if (color.charAt(u) == 'r' && color.charAt(v) != 'r') {
            adjlist[v].add(u);
        }
    }
    // 结果列表
    int[] res = new int[n];
    for (int i = 0; i < n; i++) {
        // 原本就是红色节点,结果即为当前连通分量
        if (color.charAt(i) == 'r') {
            res[i] = count;
        } else {
        // 原本为白色节点,染色并合并相应的边
            Map<Integer, Integer> tempRoot = new HashMap<>(root);
            int tempCount = count + 1;
            for (int v : adjlist[i]) {
                tempCount = union(i, v, tempRoot, tempCount);
            }
            res[i] = tempCount;
        }
    }
    System.out.print(Arrays.toString(res));
}

// 并查集——查找操作
public static int find(int u, Map<Integer, Integer> root) {
    if (u == root.get(u)) {
        return u;
    }
    return find(root.get(u), root);
}

// 并查集——合并操作
public static int union(int u, int v, Map<Integer, Integer> root, int count) {
    int rootU = find(u, root);
    int rootV = find(v, root);
    if (rootU != rootV) {
        root.put(rootU, rootV);
        count--;
    }
    return count;
}

Q4 养蛊!(背包问题)

题目描述:给定一个整数数组 hpshps 代表怪物血量,每次可选出两只怪物进行决斗,血量高的怪物存活,并且扣除另一只怪物相应的血量。要求输出最后一只怪物的血量,要求血量越少越好,并且输出决斗的序列。

参考:最后一块石头的重量 II

public static int solve(int[] hps) {
    int sum = 0;
    for (int hp : hps) {
        sum += hp;
    }
    int n = hps.length, m = sum / 2;
    // dp 用于记录前 i 个怪物里,j 为最多血量,最多的血量和
    int[][] dp = new int[n + 1][m + 1];
    // choose 用于记录是否选取该怪物
    boolean[][] choose = new boolean[n + 1][m + 1];

    for (int i = 1; i < n + 1; i++) {
        int hp = hps[i - 1];
        for (int j = m; j >= 0; j--) {
            if (j >= hp) {
                if (dp[i - 1][j] > dp[i - 1][j - hp] + hp) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = dp[i - 1][j - hp] + hp;
                    choose[i][j] = true;
                }
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    // 蛊王的血量 = 怪物总血量 - 一半血量下的最高血量 * 2
    int res = sum - 2 * dp[n][m];
    // 用两个列表维护怪物阵营
    List<Integer> list1 = new ArrayList<>();
    List<Integer> list2 = new ArrayList<>();
    for (int i = n, j = m; i > 0; i--) {
        if (choose[i][j]) {
            list1.add(i);
            j -= hps[i - 1];
        } else {
            list2.add(i);
        }
    }
    // 输出怪物决斗的顺序
    int i = 0, j = 0;
    int hp1 = 0;
    int hp2 = 0;
    while (i < list1.size() && j < list2.size()) {
        if (hp1 == 0) {
            hp1 = hps[list1.get(i) - 1];
        }
        if (hp2 == 0) {
            hp2 = hps[list2.get(j) - 1];
        }
        System.out.println("Monster " + list1.get(i) + " VS " 
                           + "Monster " + list2.get(j));
        if (hp1 == hp2) {
            hp1 = 0;
            hp2 = 0;
            i++;
            j++;
        } else if (hp1 > hp2) {
            hp1 -= hp2;
            hp2 = 0;
            j++;
        } else {
            hp2 -= hp1;
            hp1 = 0;
            i++;
        }
    }
    // 输出最后的怪物和剩余血量
    if (hp2 == res || hp1 == res) {
        System.out.println("King of Monster: " + list2.get(j));
        System.out.println("HP: " + res);
    }
    return res;
}