1189.”气球”的最大数量

目标

给你一个字符串 text,你需要使用 text 中的字母来拼凑尽可能多的单词 "balloon"(气球)。

字符串 text 中的每个字母最多只能被使用一次。请你返回最多可以拼凑出多少个单词 "balloon"。

示例 1:

输入:text = "nlaebolko"
输出:1

示例 2:

输入:text = "loonbalxballpoon"
输出:2

示例 3:

输入:text = "leetcode"
输出:0

提示:

  • 1 <= text.length <= 10^4
  • text 全部由小写英文字母组成

注意:本题与 2287.重排字符形成目标字符串 相同。

思路

使用字符串 text 中的字母拼凑单词 balloon,求最多能拼出多少个。

单词由 1a1b1n2l2o 组成。对 text 中的字母计数,统计相关字母频次的最小值即可(lo 需要除以 2)。

代码


/**
 * @date 2026-06-22 9:05
 */
public class MaxNumberOfBalloons1189 {

    public int maxNumberOfBalloons_v1(String text) {
        char[] cnt = new char[26];
        for (char c : text.toCharArray()) {
            cnt[c - 'a']++;
        }
        int res = Integer.MAX_VALUE;
        res = Math.min(res, cnt[0]);
        res = Math.min(res, cnt[1]);
        res = Math.min(res, cnt[11] / 2);
        res = Math.min(res, cnt[13]);
        res = Math.min(res, cnt[14] / 2);
        return res;
    }

}

性能

1833.雪糕的最大数量

目标

夏日炎炎,小男孩 Tony 想买一些雪糕消消暑。

商店中新到 n 支雪糕,用长度为 n 的数组 costs 表示雪糕的定价,其中 costs[i] 表示第 i 支雪糕的现金价格。Tony 一共有 coins 现金可以用于消费,他想要买尽可能多的雪糕。

注意:Tony 可以按任意顺序购买雪糕。

给你价格数组 costs 和现金量 coins ,请你计算并返回 Tony 用 coins 现金能够买到的雪糕的 最大数量 。

你必须使用计数排序解决此问题。

示例 1:

输入:costs = [1,3,2,4,1], coins = 7
输出:4
解释:Tony 可以买下标为 0、1、2、4 的雪糕,总价为 1 + 3 + 2 + 1 = 7

示例 2:

输入:costs = [10,6,8,7,7,8], coins = 5
输出:0
解释:Tony 没有足够的钱买任何一支雪糕。

示例 3:

输入:costs = [1,6,3,1,2,5], coins = 20
输出:6
解释:Tony 可以买下所有的雪糕,总价为 1 + 6 + 3 + 1 + 2 + 5 = 18 。

说明:

  • costs.length == n
  • 1 <= n <= 10^5
  • 1 <= costs[i] <= 10^5
  • 1 <= coins <= 10^8

思路

costs[i] 表示第 i 支雪糕的数量,求使用现金 coins 所能购买雪糕的最大数量。

贪心策略:优先购买价格较低的雪糕。使用计数排序记录每种价格的雪糕数量,从小到大遍历价格,累加当前所能购买的雪糕数量,如果超过剩余金额直接返回。

代码


/**
 * @date 2026-06-22 9:27
 */
public class MaxIceCream1833 {

    public int maxIceCream_v1(int[] costs, int coins) {
        int max = 0;
        for (int cost : costs) {
            max = Math.max(max, cost);
        }
        int[] cnt = new int[max + 1];
        for (int cost : costs) {
            cnt[cost]++;
        }
        int res = 0;
        for (int i = 1; i < cnt.length; i++) {
            if (cnt[i] == 0) {
                continue;
            }
            if (coins < i) {
                return res;
            }
            int amount = Math.min(coins / i, cnt[i]);
            coins -= amount * i;
            res += amount;
        }
        return res;
    }

}

性能

1732.找到最高海拔

目标

有一个自行车手打算进行一场公路骑行,这条路线总共由 n + 1 个不同海拔的点组成。自行车手从海拔为 0 的点 0 开始骑行。

给你一个长度为 n 的整数数组 gain ,其中 gain[i] 是点 i 和点 i + 1 的 净海拔高度差(0 <= i < n)。请你返回 最高点的海拔 。

示例 1:

输入:gain = [-5,1,5,0,-7]
输出:1
解释:海拔高度依次为 [0,-5,-4,1,1,-6] 。最高海拔为 1 。

示例 2:

输入:gain = [-4,-3,-2,-1,4,3,2]
输出:0
解释:海拔高度依次为 [0,-4,-7,-9,-10,-6,-3,-1] 。最高海拔为 0 。

说明:

  • n == gain.length
  • 1 <= n <= 100
  • -100 <= gain[i] <= 100

思路

n + 1 个海拔点 altitudealtitude[0] = 0gain[i] 表示从海拔点 ii + 1 的增量,即 altitude[i + 1] = altitude[i] + gain[i],返回最大的海拔。

依题意模拟即可。

代码


/**
 * @date 2026-06-19 8:08
 */
public class LargestAltitude1732 {

    public int largestAltitude(int[] gain) {
        int altitude = 0;
        int res = 0;
        for (int g : gain) {
            altitude += g;
            res = Math.max(res, altitude);
        }
        return res;
    }
}

性能

1344.时钟指针的夹角

目标

给你两个数 hour 和 minutes 。请你返回在时钟上,由给定时间的时针和分针组成的较小角的角度(60 单位制)。

示例 1:

输入:hour = 12, minutes = 30
输出:165

示例 2:

输入:hour = 3, minutes = 30
输出;75

示例 3:

输入:hour = 3, minutes = 15
输出:7.5

示例 4:

输入:hour = 4, minutes = 50
输出:155

示例 5:

输入:hour = 12, minutes = 0
输出:0

说明:

  • 1 <= hour <= 12
  • 0 <= minutes <= 59
  • 与标准答案误差在 10^-5 以内的结果都被视为正确结果。

思路

计算时钟时针与分针的较小夹角。

问题的关键是时针转动的角度不能直接按 hour 来算,根据分针的指向有一定的偏移,偏移的角度是 minutes / 60 * 30,其中 30 是一个小时格子的角度。hour / 12 * 360 + minutes / 60 * 30 - minutes / 60 * 360 = 30 * hour - minutes * 5.5

代码


/**
 * @date 2026-06-18 9:17
 */
public class AngleClock1344 {

    public double angleClock(int hour, int minutes) {
        double res = Math.abs(30 * hour - minutes * 5.5);
        return Math.min(res, 360 - res);
    }
}

性能

3612.用特殊操作处理字符串I

目标

给你一个字符串 s,它由小写英文字母和特殊字符:*、# 和 % 组成。

请根据以下规则从左到右处理 s 中的字符,构造一个新的字符串 result:

  • 如果字符是 小写 英文字母,则将其添加到 result 中。
  • 字符 '*' 会 删除 result 中的最后一个字符(如果存在)。
  • 字符 '#' 会 复制 当前的 result 并 追加 到其自身后面。
  • 字符 '%' 会 反转 当前的 result。

在处理完 s 中的所有字符后,返回最终的字符串 result。

示例 1:

输入: s = "a#b%*"
输出: "ba"
解释:
i s[i] 操作 当前 result
0 'a' 添加 'a' "a"
1 '#' 复制 result "aa"
2 'b' 添加 'b' "aab"
3 '%' 反转 result "baa"
4 '*' 删除最后一个字符 "ba"
因此,最终的 result 是 "ba"。

示例 2:

输入: s = "z*#"
输出: ""
解释:
i s[i] 操作 当前 result
0 'z' 添加 'z' "z"
1 '*' 删除最后一个字符 ""
2 '#' 复制字符串 ""
因此,最终的 result 是 ""。

说明:

  • 1 <= s.length <= 20
  • s 只包含小写英文字母和特殊字符 *、# 和 %。

思路

根据规则从左到右处理字符串 s,如果时小写字母添加到结果 result,如果是 * 删除 result 的最后一个字符,如果是 #result 追加到自身的后面,如果是 % 则反转当前的 result

字符串长度最大为 20,可以暴力模拟。

代码


/**
 * @date 2025-07-14 9:15
 */
public class ProcessStrQ1 {

    public String processStr(String s) {
        StringBuilder sb = new StringBuilder();
        int n = s.length();
        for (int i = 0; i < n; i++) {
            char c = s.charAt(i);
            switch (c) {
                case '*':
                    if (sb.length() > 0) {
                        sb.deleteCharAt(sb.length() - 1);
                    }
                    break;
                case '#':
                    sb.append(sb);
                    break;
                case '%':
                    sb.reverse();
                    break;
                default:
                    sb.append(c);
            }
        }
        return sb.toString();
    }

}

性能

2095.删除链表的中间节点

目标

给你一个链表的头节点 head 。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。

长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于 x 的最大整数。

对于 n = 1、2、3、4 和 5 的情况,中间节点的下标分别是 0、1、1、2 和 2 。

示例 1:

输入:head = [1,3,4,7,1,2,6]
输出:[1,3,4,1,2,6]
解释:
上图表示给出的链表。节点的下标分别标注在每个节点的下方。
由于 n = 7 ,值为 7 的节点 3 是中间节点,用红色标注。
返回结果为移除节点后的新链表。 

示例 2:

输入:head = [1,2,3,4]
输出:[1,2,4]
解释:
上图表示给出的链表。
对于 n = 4 ,值为 3 的节点 2 是中间节点,用红色标注。

示例 3:

输入:head = [2,1]
输出:[2]
解释:
上图表示给出的链表。
对于 n = 2 ,值为 1 的节点 1 是中间节点,用红色标注。
值为 2 的节点 0 是移除节点 1 后剩下的唯一一个节点。

说明:

  • 链表中节点的数目在范围 [1, 10^5] 内
  • 1 <= Node.val <= 10^5

思路

删除链表的中间节点。

使用快慢指针,开始时都指向头节点,快指针每次走两步,慢指针走一步。当快指针指向最后一个节点或者 null 时,慢指针指向中间节点。因此需要在慢指针更新前将节点删除掉。

代码


/**
 * @date 2026-06-15 8:59
 */
public class DeleteMiddle2095 {

    public ListNode deleteMiddle(ListNode head) {
        if (head.next == null) {
            return null;
        }
        ListNode fast = head;
        ListNode slow = head;
        while (true) {
            fast = fast.next.next;
            if (fast == null || fast.next == null) {
                slow.next = slow.next.next;
                break;
            }
            slow = slow.next;
        }
        return head;
    }

}

性能

2130.链表最大孪生和

目标

在一个大小为 n 且 n 为 偶数 的链表中,对于 0 <= i <= (n / 2) - 1 的 i ,第 i 个节点(下标从 0 开始)的孪生节点为第 (n-1-i) 个节点 。

  • 比方说,n = 4 那么节点 0 是节点 3 的孪生节点,节点 1 是节点 2 的孪生节点。这是长度为 n = 4 的链表中所有的孪生节点。

孪生和 定义为一个节点和它孪生节点两者值之和。

给你一个长度为偶数的链表的头节点 head ,请你返回链表的 最大孪生和 。

示例 1:

输入:head = [5,4,2,1]
输出:6
解释:
节点 0 和节点 1 分别是节点 3 和 2 的孪生节点。孪生和都为 6 。
链表中没有其他孪生节点。
所以,链表的最大孪生和是 6 。

示例 2:

输入:head = [4,2,2,3]
输出:7
解释:
链表中的孪生节点为:
- 节点 0 是节点 3 的孪生节点,孪生和为 4 + 3 = 7 。
- 节点 1 是节点 2 的孪生节点,孪生和为 2 + 2 = 4 。
所以,最大孪生和为 max(7, 4) = 7 。

示例 3:

输入:head = [1,100000]
输出:100001
解释:
链表中只有一对孪生节点,孪生和为 1 + 100000 = 100001 。

说明:

  • 链表的节点数目是 [2, 10^5] 中的 偶数 。
  • 1 <= Node.val <= 10^5

思路

有一个节点个数为偶数的链表,节点 A 的孪生节点 B 需满足 Ahead 的距离等于 Btail 的距离,通俗来说就是关于中轴线对称。每对孪生节点的和称为孪生和,求链表最大的孪生和。

快慢指针找到中间节点,然后反转链表后半部分,空间复杂度为 O(1)。

代码


/**
 * @date 2026-06-15 14:06
 */
public class PairSum2130 {

    public int pairSum(ListNode head) {
        int res = 0;
        ListNode slow = head;
        ListNode fast = head;
        int cnt = 0;
        while (fast != null && fast.next != null) {
            fast = fast.next.next;
            slow = slow.next;
            cnt++;
        }
        ListNode prev = slow;
        slow = slow.next;
        prev.next = null;
        while (slow != null) {
            ListNode tmp = slow.next;
            slow.next = prev;
            prev = slow;
            slow = tmp;
        }
        for (int i = 0; i < cnt; i++) {
            res = Math.max(res, prev.val + head.val);
            prev = prev.next;
            head = head.next;
        }
        return res;
    }

}

性能

3838.带权单词映射

目标

给你一个字符串数组 words,其中每个字符串表示一个由小写英文字母组成的单词。

同时给你一个长度为 26 的整数数组 weights,其中 weights[i] 表示第 i 个小写英文字母的权重。

单词的 权重 定义为其所有字符权重的 总和。

对于每个单词,将其权重对 26 取模,并将结果按字母倒序映射到一个小写英文字母(0 -> 'z', 1 -> 'y', ..., 25 -> 'a')。

返回一个由所有单词映射后的字符按顺序连接而成的字符串。

示例 1:

输入: words = ["abcd","def","xyz"], weights = [5,3,12,14,1,2,3,2,10,6,6,9,7,8,7,10,8,9,6,9,9,8,3,7,7,2]
输出: "rij"
解释:
"abcd" 的权重是 5 + 3 + 12 + 14 = 34。对 26 取模的结果是 34 % 26 = 8,映射为 'r'。
"def" 的权重是 14 + 1 + 2 = 17。对 26 取模的结果是 17 % 26 = 17,映射为 'i'。
"xyz" 的权重是 7 + 7 + 2 = 16。对 26 取模的结果是 16 % 26 = 16,映射为 'j'。
因此,连接映射字符后形成的字符串是 "rij"。

示例 2:

输入: words = ["a","b","c"], weights = [1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1]
输出: "yyy"
解释:
每个单词的权重均为 1。对 26 取模的结果是 1 % 26 = 1,映射为 'y'。
因此,连接映射字符后形成的字符串是 "yyy"。

示例 3:

输入: words = ["abcd"], weights = [7,5,3,4,3,5,4,9,4,2,2,7,10,2,5,10,6,1,2,2,4,1,3,4,4,5]
输出: "g"
解释:
"abcd" 的权重是 7 + 5 + 3 + 4 = 19。对 26 取模的结果是 19 % 26 = 19,映射为 'g'。
因此,连接映射字符后形成的字符串是 "g"。

说明:

  • 1 <= words.length <= 100
  • 1 <= words[i].length <= 10
  • weights.length == 26
  • 1 <= weights[i] <= 100
  • words[i] 仅由小写英文字母组成。

思路

已知小写字母的权重数组中 weights,计算每个单词的权重之和对 26 取模,将结果倒序映射回小写字母,即 0 -> z1 -> y ……

根据题意模拟即可。

代码


/**
 * @date 2026-06-15 14:51
 */
public class MapWordWeights3838 {

    public String mapWordWeights(String[] words, int[] weights) {
        StringBuilder sb = new StringBuilder();
        for (String word : words) {
            int sum = 0;
            for (int i = 0; i < word.length(); i++) {
                sum += weights[word.charAt(i) - 'a'];
            }
            char c = (char) ('z' - sum % 26);
            sb.append(c);
        }
        return sb.toString();
    }

}

性能

3558.给边赋权值的方案数I

目标

给你一棵 n 个节点的无向树,节点从 1 到 n 编号,树以节点 1 为根。树由一个长度为 n - 1 的二维整数数组 edges 表示,其中 edges[i] = [ui, vi] 表示在节点 ui 和 vi 之间有一条边。

一开始,所有边的权重为 0。你可以将每条边的权重设为 1 或 2。

两个节点 u 和 v 之间路径的 代价 是连接它们路径上所有边的权重之和。

选择任意一个 深度最大 的节点 x。返回从节点 1 到 x 的路径中,边权重之和为 奇数 的赋值方式数量。

由于答案可能很大,返回它对 10^9 + 7 取模的结果。

注意: 忽略从节点 1 到节点 x 的路径外的所有边。

示例 1:

输入: edges = [[1,2]]
输出: 1
解释:
从节点 1 到节点 2 的路径有一条边(1 → 2)。
将该边赋权为 1 会使代价为奇数,赋权为 2 则为偶数。因此,合法的赋值方式有 1 种。

示例 2:

输入: edges = [[1,2],[1,3],[3,4],[3,5]]
输出: 2
解释:
最大深度为 2,节点 4 和节点 5 都在该深度,可以选择任意一个。
例如,从节点 1 到节点 4 的路径包括两条边(1 → 3 和 3 → 4)。
将两条边赋权为 (1,2) 或 (2,1) 会使代价为奇数,因此合法赋值方式有 2 种。

提示:

  • 2 <= n <= 10^5
  • edges.length == n - 1
  • edges[i] == [ui, vi]
  • 1 <= ui, vi <= n
  • edges 表示一棵合法的树。

思路

有一颗含有 n 个节点的树,编号为 1 ~ n,根节点编号是 1edges[i] = [ui, vi],表示节点 uivi 之间有一条边。定义节点 uv 之间的代价为它们之间路径上边的权重之和。可以为每一条边赋予权重 12,求使得根节点到最远叶子节点代价为奇数的赋权方案数。

定义到达当前节点代价为奇数或偶数的方案数为 dp[i][1]dp[i][0],状态转移方程为 dp[i][0] = dp[prev][0] + dp[prev][1]dp[i][1] = dp[prev][0] + dp[prev][1]

观察发现 dp[i][0] == dp[i][1],因此代价为奇数的方案数为 dp[i][1] = 2 * dp[prev][1]。假设树的深度为 d,方案数为 2^(d - 1)

求出树的深度 d,从中选取奇数条边赋值为 1,方案数为 2^(d - 1)

代码


/**
 * @date 2026-06-11 10:16
 */
public class AssignEdgeWeights3558 {

    public int assignEdgeWeights(int[][] edges) {
        int n = edges.length + 1;
        List<Integer>[] g = new ArrayList[n + 1];
        Arrays.setAll(g, i -> new ArrayList<>());
        for (int[] edge : edges) {
            g[edge[0]].add(edge[1]);
            g[edge[1]].add(edge[0]);
        }
        int d = dfs(0, 1, g);
        return pow(2, d - 1, 1000000007);
    }

    public int dfs(int fa, int cur, List<Integer>[] g) {
        int res = 0;
        for (Integer next : g[cur]) {
            if (next == fa) {
                continue;
            }
            res = Math.max(res, dfs(cur, next, g) + 1);
        }
        return res;
    }

    public int pow(int base, int exp, int mod) {
        long res = 1L;
        while (exp > 0) {
            if ((exp & 1) == 1) {
                res = res * base % mod;
            }
            base = (int) ((long) base * base % mod);
            exp >>= 1;
        }
        return (int) res;
    }

}

性能

3689.最大子数组总值I

目标

给定一个长度为 n 的整数数组 nums 和一个整数 k。

你必须从 nums 中选择 恰好 k 个非空子数组 nums[l..r]。子数组可以重叠,同一个子数组(相同的 l 和 r)可以 被选择超过一次。

子数组 nums[l..r] 的 值 定义为:max(nums[l..r]) - min(nums[l..r])。

总值 是所有被选子数组的 值 之和。

返回你能实现的 最大 可能总值。

子数组 是数组中连续的 非空 元素序列。

示例 1:

输入: nums = [1,3,2], k = 2
输出: 4
解释:
一种最优的方法是:
选择 nums[0..1] = [1, 3]。最大值为 3,最小值为 1,得到的值为 3 - 1 = 2。
选择 nums[0..2] = [1, 3, 2]。最大值仍为 3,最小值仍为 1,所以值也是 3 - 1 = 2。
将它们相加得到 2 + 2 = 4。

示例 2:

输入: nums = [4,2,5,1], k = 3
输出: 12
解释:
一种最优的方法是:
选择 nums[0..3] = [4, 2, 5, 1]。最大值为 5,最小值为 1,得到的值为 5 - 1 = 4。
选择 nums[1..3] = [2, 5, 1]。最大值为 5,最小值为 1,所以值也是 4。
选择 nums[2..3] = [5, 1]。最大值为 5,最小值为 1,所以值同样是 4。
将它们相加得到 4 + 4 + 4 = 12。

说明:

  • 1 <= n == nums.length <= 5 * 10^4
  • 0 <= nums[i] <= 10^9
  • 1 <= k <= 10^5

思路

定义子数组的值是最大值与最小值之差。已知一个长度为 n 的非负整数数组 nums,从中选择 k 个子数组(允许重复选择),求所选子数组的最大总值(即子数组值之和)。

由于可以重复选择,都选区间 [0, n - 1],区间范围越大,最大值越大,最小值越小,差值越大。

代码


/**
 * @date 2026-06-09 9:07
 */
public class MaxTotalValue3689 {

    public long maxTotalValue(int[] nums, int k) {
        int max = Integer.MIN_VALUE;
        int min = Integer.MAX_VALUE;
        for (int num : nums) {
            max = Math.max(max, num);
            min = Math.min(min, num);
        }
        return (long) (max - min) * k;
    }
}

性能