Skip to content

2025 题库(阶段三)

01、 非递减数组

给定一个长度为 n 的数组,每次只能对其中的一个元素进行加 1 操作,最少可以通过多少次操作使其变成非递减的数组。非递减数组:a1 ≤ a2 ≤ ... ≤ an。

解答:

    public static int makeNonDecreasing(int[] arr) {
        if (arr.length < 2) {
            return 0;
        }
        int count = 0;
        int prev = arr[0];
        for (int i = 1; i < arr.length; i++) {
            if (arr[i] < prev) {
                count += prev - arr[i];
            } else {
                prev = arr[i];
            }
        }
        return count;
    }

02、 满二叉树编号(LCA)

给定无穷大满二叉树,从其根节点开始,一层一层的从左向右依次编号,根节点编号为 1,求 a b 两个节点的最近公共祖先的编号。

思路:

在按层从左到右编号(堆式编号,根为 1)的无限满二叉树中,任意节点 x 的父节点是 x/2。因此,两个节点 a,b 的最近公共祖先(LCA)可以通过不断把较大的那个节点上移到其父节点,直到二者相等为止。

  • 父节点关系:parent(x) = x / 2(整数除法)
  • 终止条件:当 a == b 时,该值即为 LCA

时间复杂度:O(logmax(a,b)),空间复杂度:O(1)

解答:

    public static long lca(int a, int b) {
        if (a <= 0 || b <= 0) {
            return -1;
        }
        while (a != b) {
            if (a > b) {
                a >>= 1;
            } else {
                b >>= 1;
            }
        }
        return a;
    }

03、 好数组

一个组数,定义好数组的规则为不同元素的个数与数组的长度相等。比如{1,2,3}是好数组,{1,1,2,3}不是,但{}是好数组。

给定一个数组,每次可以删除任意 k 个元素,但删除前需要满足当前数组的长度大于等于 k,可以执行 0 次或任意次,如果能得到好数组输出 yes,否则输出 no。

示例:

测试数据组数 2,初始数组元素个数 5,4,k 值为 3,2,输出 no,yes

输入:
2
5 3
1 1 1 1 1
4 2
1 2 3 4
输出:
no
yes

思路:数组长度对 k 取余的值 小于等于 数组去重后的长度就是好数组。

解答:

    public static void isGoodNumArr(int[] arr, int k) {
        int[] distinct = Arrays.stream(arr).distinct().toArray();
        boolean flag = arr.length % k <= distinct.length;
        System.out.println(flag ? "yes" : "no");
    }

04、 未出现字符值的和

给定一个全部由大写字母 A~Z 组成的字符串,找出未出现的字符,计算这些字符 ASCII 码的和。

    public static int asciiSum(String s) {
        int[] marks = new int[26];
        Arrays.fill(marks, 0);
        for (char c : s.toCharArray()) {
            if ('A' <= c && c <= 'Z') {
                marks[c - 'A'] = 1;
            }
        }
        int sum = 0;
        for (int i = 0; i < marks.length; i++) {
            if (marks[i] == 0) {
                sum += 'A' + i;
            }
        }
        return sum;
    }

05、 数组整除

给定一个数字 num,依次判断这个数字是否可以被各个数位上的数字整除,统计能整除 num 的数位的个数。

    public static int countDivisibility(int num) {
        int count = 0;
        int n = num;
        int d;
        while (n != 0) {
            d = n % 10;
            if (num % d == 0) {
                count++;
            }
            n /= 10;
        }
        return count;
    }

06、 满足条件的数字对

给定两个数 A 和 B,且有 0 ≤ x ≤ y ≤ 2^31-1

  • x <= y
  • x & y = A
  • x | y = B

计算有多少对 x, y 满足以上条件。

思路:

  • 若存在某一位使得 A 为 1 而 B 为 0,则不可能满足(因为该位既要求两数都是 1,又要求至少一个是 0),即:
  • 若 (A \& ~B) ≠ 0,答案为 0。
  • 除去非法情况后,对于每一位:
  • 若 Ai=1,则两数该位固定为 1;
  • 若 Bi=0,则两数该位固定为 0(此时 Ai 必为 0);
  • 若 Ai=0, Bi=1,则该位两数必须一 0 一 1,有两种分配:(0,1) 或 (1,0)。
  • 设“自由位”数 k = bitcount(A ⊕ B)(在合法情况下即为 B 是 1 而 A 是 0 的位数)。
  • 若不考虑 x ≤ y,每个自由位 2 种分配,总计 2^k 个有序对。
  • 加上 x ≤ y:
    • 若 k=0,唯一解为 x=y=A,答案 1。
    • 若 k>0,不存在 x=y(至少有一位不同),且通过“交换所有自由位上 x、y 的取值”可成双成对地构造大小相反的一一对应,因此恰好一半满足 x<y。答案为 2^k-1。

同时还需保证 A、B 都在 [0, 2^31−1],否则无解(因为 x、y 只能到 2^31−1,其按位或不可能产生第 31 位为 1)。

位运算的计算性质,x ⊕ y = (x | y) - (x & y) = B - A。

解答:

    public static long numberPairCount(long a, long b) {
        final long limit = 1L << 31; // 2^31
        // 范围检查:x,y <= 2^(31-1) -> A,B 也必须在此范围内且非负
        if (a < 0 || b < 0 || a >= limit || b >= limit) {
            return 0;
        }
        // 合法性检查:A 的 1 位不能出现在 B 的 0 位上
        if ((a & ~b) != 0) {
            return 0;
        }
        // 自由位个数:A 与 B 不同的位(在合法时即 A=0,B=1 的位)
        int k = Long.bitCount(a ^ b);
        // k=0 => x=y=A,唯一解;k>0 -> 2^(k-1)
        return k == 0 ? 1L : 1L << (k - 1);
    }

07、 树节点染色

一棵有 n 个节点的树,根节点为 1 号节点,给树的一部分节点染成红色,然后有 q 次询间,每次询问需要给出某点的子树中红色节点的数量。

示例:

输入4个整数代表2~5号节点的父节点
1 2 1 4
输入长度为5的"W"和"R"组成的字符串,W表示不染色,R表示红色
WWRRR
则树的样子为

      1W
      /\
    2W  4R
   /      \
 3R        5R

q为3,表示询问3次
询问3号节点的子树中红色节点的数量,返回1
询问1号节点的子树中红色节点的数量,返回3
询问4号节点的子树中红色节点的数量,返回2

解答:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        // 节点2~n的父节点编号
        int[] parents = Arrays.stream(sc.nextLine().split(" ")).mapToInt(Integer::parseInt).toArray();
        // 节点数量(包含根节点1
        int n = parents.length + 1;
        // 树的邻接表表示
        List<List<Integer>> tree = new ArrayList<>();
        for (int i = 0; i <= n; i++) { // 这里使用 i <= n 是因为节点从1开始编号,多一个可以避免越界
            tree.add(new ArrayList<>());
        }
        for (int i = 0; i < parents.length; i++) {
            int number = i + 2; // 节点编号从2开始
            int parent = parents[i];
            tree.get(parent).add(number);
        }
        // 读取染色字符串
        String colorStr = sc.nextLine().trim();
        int[] colors = new int[colorStr.length() + 1]; // 同样为了避免越界
        for (int i = 1; i <= colorStr.length(); i++) {
            colors[i] = colorStr.charAt(i - 1) == 'R' ? 1 : 0;
        }
        // 创建答案数组并进行DFS计算
        int[] counts = new int[colors.length];
        dfs(1, tree, colors, counts);
        // 处理查询
        int q = sc.nextInt();
        while (q-- > 0) {
            System.out.println(counts[sc.nextInt()]);
        }
    }

    private static void dfs(int i, List<List<Integer>> tree, int[] colors, int[] counts) {
        int total = colors[i]; // 当前节点的红色状态
        for (int child : tree.get(i)) {
            dfs(child, tree, colors, counts); // 递归处理子节点
            total += counts[child]; // 累加子树的红色节点数量
        }
        counts[i] = total;
    }

08、 最长合法前缀

一个全部由'('和')'组成的字符串,求该字符串的最长合法前缀,合法前缀定义为:(),(()) 都合法,而 )() 不合法。

解答:

    public static int longestLength(String s) {
        int balance = 0;
        int best = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '(') {
                balance++;
            } else {
                balance--;
            }
            if (balance < 0) {
                break;
            }
            if (balance == 0) {
                best = i + 1;
            }
        }
        return best;
    }

09、去除字符

给定一个字符串 s,例如 2012,或 2012+2013+2015-2019+2020,其中的符号只有加减号,目前只去掉一个字符,例如 2012 去掉 0 得到 212,保证去掉一个字符后,得到的结果最大,输出这个结果。

    public static void removeOneChar(String str) {
        long max = Long.MIN_VALUE;
        for (int i = 0; i < str.length(); i++) {
            String s = str.substring(0, i) + str.substring(i + 1);
            max = Math.max(max, calc(s));
        }
        System.out.println(max);
    }

    public static long calc(String s) {
        if (s.isEmpty()) {
            return 0;
        }
        long result = 0;
        String[] pArr = s.split("\\+");
        String[] mArr;
        String text;
        long parsed;
        for (String pStr : pArr) {
            if (pStr.isEmpty()) {
                continue;
            }
            mArr = pStr.split("-");
            for (int j = 0; j < mArr.length; j++) {
                text = mArr[j];
                parsed = text.isEmpty() ? 0 : Long.parseLong(text);
                if (j == 0) {
                    result += parsed;
                } else {
                    result -= parsed;
                }
            }
        }
        return result;
    }

10、 函数求和

给出函数 f(x),如 f(12)=f(0b1100)=4(0b100),f(4)=f(0b100)=4(0b100),f(1)=f(0b1)=1,即 f(x)的输出是 x 的二进制表示从右往左数出现第一个 1 的二进制大小。

输入整数 N,求 1/f(1)+2/f(2)+i/f(i)+...+f(N)

解答:

    public static long sum(long n) {
        long sum = 0;
        for (long i = 1; i <= n; i++) {
            sum += i / lsb(i);
        }
        return sum;
    }

    /**
     * Last Set Bit
     */
    public static long lsb(long x) {
        return x & (-x);
    }

11、 牛牛太空人

牛有一艘太空飞船,牛牛想驾驶太空飞船飞出大气层,当前飞船的坐标是(x,y,z),速度向量是(vx,vy,vz),将大气层看做一个半径为 r 的球体,问什么时刻 t 飞船能到达大气层边缘(要求精度不低于 1*10^(-5))。

    public static void timeToSphere(double x, double y, double z, double vx, double vy, double vz, double r) {
        double a = vx * vx + vy * vy + vz * vz;
        double b = 2 * (vx * x + vy * y + vz * z);
        double c = x * x + y * y + z * z - r * r;
        double delta = b * b - 4 * a * c;
        double result;
        if (delta < 0) {
            result = 0;
        } else if (delta == 0) {
            result = -b / 2 * a;
        } else {
            double t1 = (-b + Math.sqrt(delta)) / (2 * a);
            double t2 = (-b - Math.sqrt(delta)) / (2 * a);
            if (t1 > 0 && t2 > 0) {
                result = Math.min(t1, t2);
            } else if (t1 > 0) {
                result = t1;
            } else if (t2 > 0) {
                result = t2;
            } else {
                result = 0;
            }
        }
        System.out.printf("%.5f", result);
    }

12、 牛牛切割数

牛牛有一个数字 n,可以对 n 进行切割,把切割后的数字相加得到结果,问有几种不同的结果。

示例:

输入:123

情况1:1,2,3,结果为6
情况2:12,3, 结果为15
情况3:1,23,结果为24
情况4:123,  结果为123

输出:4

解答:

    public static int splitNum(String numStr) {
        int length = numStr.length();
        int size = 1 << (length - 1);
        Set<Integer> result = new HashSet<>();
        StringBuilder builder;
        for (int mark = 0; mark < size; mark++) {
            builder = new StringBuilder();
            for (int i = 0; i < length; i++) {
                builder.append(numStr.charAt(i));
                if (i < size && (mark >> i & 1) == 1) {
                    builder.append('+');
                }
            }
            int sum = Arrays.stream(builder.toString().split("\\+")).mapToInt(Integer::parseInt).sum();
            result.add(sum);
        }
        return result.size();
    }

13、 小红的区间数

小红拿到一个数组,取连续的一段区间,使区间中的数相乘不为 0,问这个区间的最大长度。

示例:

输入:1 0 2 3 5 0 1 1
输出:3

解答:

    public static int maxLen(int[] arr) {
        int max = 0;
        int len = 0;
        for (int i : arr) {
            if (i == 0) {
                len = 0;
            } else {
                len++;
                max = Math.max(max, len);
            }
        }
        return max;
    }

14、 牛牛扔骰子

牛牛有一个骰子共 n 面,每面有一个数字(数字可重复),牛牛的第一个幸运数为 A,第二个幸运数为 B。牛牛可以扔两次骰子,求当且仅当第一次得到 A,第二次得到 B 的期望次数(输出保留 1 位小数)。

输入:
3 1 2 (即 n A B)
1 2 3 (n 面上的数)
输出:
9.0

解答:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int a = sc.nextInt();
        int b = sc.nextInt();
        int countA = 0;
        int countB = 0;
        int v;
        for (int i = 0; i < n; i++) {
            v = sc.nextInt();
            if (v == a) countA++;
            if (v == b) countB++;
        }
        double pA = (double) countA / (double) n;
        double pB = (double) countB / (double) n;
        double result = 1d / (pA * pB);
        System.out.printf("%.1f", result);
    }

15、 牛牛大胃王

牛牛的胃口为 m,给定一个数组 vec(数组长度为 n),里面存放了 n 个食物的所能补充的能量,问牛牛能不能吃饱。能吃饱输出“Yes”,否则输出“No”

解答:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        for (int i = 0; i < t; i++) {
            int n = sc.nextInt();
            int m = sc.nextInt();
            int sum = 0;
            for (int j = 0; j < n; j++) {
                sum += sc.nextInt();
            }
            if (sum >= m) {
                System.out.println("Yes");
            } else {
                System.out.println("No");
            }
        }
    }

16、 报数游戏

牛牛和牛妹玩游戏,牛牛喊 x,牛妹只能喊 x+1,x+2,x+3(比如牛牛喊 2,则牛妹只能喊 [3,4,5] 中的一个),两个人互相轮询,如果喊的数超过 n 则判输,开始的数字为 k(不能喊 k,至少 k+1),输入 n,牛牛先开始喊,如果要牛牛必胜,列举 k 值。

示例:

输入:6
输出:1 3 4 5
解释:
n = 6
k = 1 时,牛妹喊3 4 5,牛牛喊6必胜
k = 3 时,牛牛喊6必胜

解答:

    public static void listNums(int n) {
        List<Integer> list = new ArrayList<>();
        for (int k = 1; k < n; k++) {
            if ((n - k) % 4 != 0) {
                list.add(k);
            }
        }
        for (int i = 0; i < list.size(); i++) {
            if (i > 0) System.out.print(" ");
            System.out.print(list.get(i));
        }
        System.out.println();
    }

17、 3 的倍数

一个由 0~9 组成的字符串,字符串长度 3~10^5,取任意个字符组成一个数,返回是 3 的倍数的最大值,如果没有返回-1。

要点:

一个数能否被 3 整除只取决于各位数字和对 3 的余数。

设各位数字和的余数为 m = sum mod 3
若 m = 0:全部保留,按降序排列即最大
若 m = 1:删除“总余数为 1”的最小代价:
  优先删 1 个最小的余 1 数字(1,4,7 中最小的)
  否则删 2 个最小的余 2 数字(2,5,8 中最小的两位)
若 m = 2:对称地处理:
  优先删 1 个最小的余 2 数字(2,5,8 中最小的)
  否则删 2 个最小的余 1 数字(1,4,7 中最小的两位)

删除后将剩余数字按降序拼接即为最大值
特殊处理:若最终只剩下若干个 0,答案应输出为 "0";若一个数字也没有,输出 "-1"

解答:

    public static String createMultiples3(String str) {
        int[] cnt = new int[10]; // 统计0-9在字符串中出现的次数
        long sum = 0;
        int num;
        for (char ch : str.toCharArray()) {
            num = ch - '0';
            cnt[num]++;
            sum += num;
        }

        int mod = (int) (sum % 3);
        if (mod == 1) {
            if (!removeNum(cnt, new int[]{1, 4, 7}, 1)) {
                if (!removeNum(cnt, new int[]{2, 5, 8}, 2)) {
                    return "-1";
                }
            }
        } else if (mod == 2) {
            if (!removeNum(cnt, new int[]{2, 5, 8}, 1)) {
                if (!removeNum(cnt, new int[]{1, 4, 7}, 2)) {
                    return "-1";
                }
            }
        }

        int total = 0;
        StringBuilder sb = new StringBuilder();
        for (int d = 9; d >= 0; d--) {
            total += cnt[d];
            for (int k = 0; k < cnt[d]; k++) {
                sb.append((char) ('0' + d));
            }
        }
        if (total == 0) {
            return "-1";
        }
        if (sb.charAt(0) == '0') { // 若全为 0,输出单个 "0"
            return "0";
        } else {
            return sb.toString();
        }
    }

    private static boolean removeNum(int[] cnt, int[] choices, int need) {
        for (int choice : choices) {
            while (need > 0 && cnt[choice] > 0) {
                cnt[choice]--;
                need--;
            }
            if (need == 0) return true;
        }
        return false;
    }

18、 回文数组

小红有一个整数数组,长度为 n,他希望通过一系列操作将数组变为一个回文数组。每次操作可以选择数组中任务相邻的两个元素 a(i) 和 a(i+1),将他们的值同时加 1。请你计算至少需要多少次操作使得数组变为一个回文数组,如果不可能,则输出-1。

思路:

设对每条相邻边 (i, i+1) 的操作次数为 x[i] ≥ 0。最终数组为:

  • (b_1 = a_1 + x_1)
  • (bj = a_j + x{j-1} + x_j)(2 ≤ j ≤ n−1)
  • (bn = a_n + x{n-1})

回文条件 (bi = b{n+1-i}) 化为关于 x 的线性方程。定义:

  • (gi = a_i - a{n+1-i})(i=1..⌊n/2⌋)
  • (yi = x_i - x{n-i}),并令 (y_0 = 0)

可推出递推关系:

y_{i-1} + y_i = -g_i (i=1..m), m = n / 2

据此自左向右得到唯一的 (y_1 ... y_m)。

  • 若 (n) 为偶数,则中间那条边与自己成对,必须满足 (y_m = 0),否则无解(输出 -1)。
  • 该方法不要求元素非负,整数即可。

解答:

    public static long palindrome(int[] nums) {
        int n = nums.length;
        int m = n / 2;
        long yPrev = 0;
        long count = 0;

        for (int i = 1; i <= m; i++) {
            long gi = nums[i - 1] - nums[n - i]; // g_i
            long yi = -gi - yPrev;               // y_i = -g_i - y_{i-1}
            if ((n % 2 == 0) && i == m) {
                if (yi != 0) { // 偶数长度的中间边需满足 y_m == 0 才可行
                    return -1;
                }
            } else {
                count += Math.abs(yi);
            }
            yPrev = yi;
        }
        return count;
    }

19、 01 翻转

输入一个由 0 和 1 构成的字符串 a,有一个字符串 b,包含的 0 和 1 的数量与 a 一样多,每次可以操作相邻的两个字符进行翻转,经过多次翻转后可以得到 b。小红只记得 b 是经过最大次操作得到的,请输出最大的操作次数。

解答:

    // 这个答案没看懂
    public static int reverseCount(String s) {
        int lCount = count(s);
        int rCount = count(new StringBuilder(s).reverse().toString());
        return Math.max(lCount, rCount);
    }

    public static int count(String s) {
        int res = 0;
        int cnt = 0;
        for (int i = 0; i < s.length(); i++) {
            if (s.charAt(i) == '1') {
                res += i - cnt;
                cnt++;
            }
        }
        return res;
    }

20、 买文具

一条街上有 n 个文具店,第 i 个店里卖 yi 个文具,问小红买 m 个文具,最少花费多少钱。

输入:
3 2 // 文具店数 要买文具的总个数
1 1 // 第一家店的文具数量 文具单价
2 2 // 第二家店的文具数量 文具单价
6 3 // 第三家店的文具数量 文具单价

解答:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int n = sc.nextInt();
        int m = sc.nextInt();
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < n; i++) {
            int count = sc.nextInt();
            int price = sc.nextInt();
            for (int j = 0; j < count; j++) {
                list.add(count);
            }
        }
        Collections.sort(list);
        int total = 0;
        for (int i = 0; i < m; i++) {
            total += list.get(i);
        }
        System.out.println(total);
    }

21、 病毒扩散

病毒体积按照 i^2 形式增长(i 为秒数,例如第 1 秒的体积为 1,第二秒增加体积 2*2=4,则总体积 1+4=5);有一种武器每秒可消灭体积为 k 的病毒,消灭不完病毒就会扩散,问人类最多一次可以消灭体积多大的病毒;若无法消灭,则输出 0。

解答:

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        int t = sc.nextInt();
        while (t-- > 0) {
            int k = sc.nextInt();
            int sum = 0;
            int i = 1;
            while (true) {
                if (sum + Math.pow(i, 2) <= k) {
                    sum += (int) Math.pow(i, 2);
                    i++;
                } else {
                    break;
                }
            }
            System.out.println(sum);
        }
    }

22、 数字严格递增或递减

给定 n 组字符串,比如:fbgrgf212345dsf,求最大的连续数字子串,可以是严格递增或递减。复合规则的输出 yes,否则输出 no。

解答:

    public static void detectNumStr(String s) {
        Pattern pattern = Pattern.compile("\\d+");
        Matcher matcher = pattern.matcher(s);
        List<String> list = new ArrayList<>();
        while (matcher.find()) {
            list.add(matcher.group());
        }
        if (list.isEmpty()) {
            System.out.println("false");
            return;
        }

        String flag = "false";
        for (String str : list) {
            if (isConsecutive(str)) {
                flag = "true";
            }
        }
        System.out.println(flag);
    }

    public static boolean isConsecutive(String str) {
        int trend = 0;
        char prev, curr;
        for (int i = 1; i < str.length(); i++) {
            prev = str.charAt(i - 1);
            curr = str.charAt(i);
            if (prev == curr) {
                return false;
            }
            if (trend == 0) {
                trend = curr - prev;
                if (Math.abs(trend) != 1) {
                    return false;
                }
            } else {
                if (curr - prev != trend) {
                    return false;
                }
            }
        }
        return true;
    }

23、 求使得异或值最大的 y 的值

给定一个正整数 x 和一个数 m,选择一个数 y 不大于 m,使得 x xor y 的值最大,求 y 的值。

示例:

输入:
5 4
输出:
2 (最大的异或值为7,则 y = 2)

解答:

    public static int findMaxY(int x, int m) {
        int y = 0;
        int t;
        for (int i = 30; i >= 0; i--) {
            t = 1 << i;
            if ((x & t) == 0 && (y | t) <= m) {
                y |= t;
            }
        }
        return y;
    }