3633.最早完成陆地和水上游乐设施的时间I

目标

给你两种类别的游乐园项目:陆地游乐设施 和 水上游乐设施。

  • 陆地游乐设施
    • landStartTime[i] – 第 i 个陆地游乐设施最早可以开始的时间。
    • landDuration[i] – 第 i 个陆地游乐设施持续的时间。
  • 水上游乐设施
    • waterStartTime[j] – 第 j 个水上游乐设施最早可以开始的时间。
    • waterDuration[j] – 第 j 个水上游乐设施持续的时间。

一位游客必须从 每个 类别中体验 恰好一个 游乐设施,顺序 不限 。

  • 游乐设施可以在其开放时间开始,或 之后任意时间 开始。
  • 如果一个游乐设施在时间 t 开始,它将在时间 t + duration 结束。
  • 完成一个游乐设施后,游客可以立即乘坐另一个(如果它已经开放),或者等待它开放。

返回游客完成这两个游乐设施的 最早可能时间 。

示例 1:

输入:landStartTime = [2,8], landDuration = [4,1], waterStartTime = [6], waterDuration = [3]
输出:9
解释:
方案 A(陆地游乐设施 0 → 水上游乐设施 0):
在时间 landStartTime[0] = 2 开始陆地游乐设施 0。在 2 + landDuration[0] = 6 结束。
水上游乐设施 0 在时间 waterStartTime[0] = 6 开放。立即在时间 6 开始,在 6 + waterDuration[0] = 9 结束。
方案 B(水上游乐设施 0 → 陆地游乐设施 1):
在时间 waterStartTime[0] = 6 开始水上游乐设施 0。在 6 + waterDuration[0] = 9 结束。
陆地游乐设施 1 在 landStartTime[1] = 8 开放。在时间 9 开始,在 9 + landDuration[1] = 10 结束。
方案 C(陆地游乐设施 1 → 水上游乐设施 0):
在时间 landStartTime[1] = 8 开始陆地游乐设施 1。在 8 + landDuration[1] = 9 结束。
水上游乐设施 0 在 waterStartTime[0] = 6 开放。在时间 9 开始,在 9 + waterDuration[0] = 12 结束。
方案 D(水上游乐设施 0 → 陆地游乐设施 0):
在时间 waterStartTime[0] = 6 开始水上游乐设施 0。在 6 + waterDuration[0] = 9 结束。
陆地游乐设施 0 在 landStartTime[0] = 2 开放。在时间 9 开始,在 9 + landDuration[0] = 13 结束。
方案 A 提供了最早的结束时间 9。

示例 2:

输入:landStartTime = [5], landDuration = [3], waterStartTime = [1], waterDuration = [10]
输出:14
解释:
方案 A(水上游乐设施 0 → 陆地游乐设施 0):
在时间 waterStartTime[0] = 1 开始水上游乐设施 0。在 1 + waterDuration[0] = 11 结束。
陆地游乐设施 0 在 landStartTime[0] = 5 开放。立即在时间 11 开始,在 11 + landDuration[0] = 14 结束。
方案 B(陆地游乐设施 0 → 水上游乐设施 0):
在时间 landStartTime[0] = 5 开始陆地游乐设施 0。在 5 + landDuration[0] = 8 结束。
水上游乐设施 0 在 waterStartTime[0] = 1 开放。立即在时间 8 开始,在 8 + waterDuration[0] = 18 结束。
方案 A 提供了最早的结束时间 14。​​​​​​​

说明:

  • 1 <= n, m <= 100
  • landStartTime.length == landDuration.length == n
  • waterStartTime.length == waterDuration.length == m
  • 1 <= landStartTime[i], landDuration[i], waterStartTime[j], waterDuration[j] <= 1000

思路

有两种游乐场项目,landStartTime[i]landDuration[i] 分别表示陆上项目 i 的开始时间与持续时间,waterStartTime[i]waterDuration[i] 分别表示水上项目 i 的开始时间与持续时间。游客需要分别游玩一个陆上项目和一个水上项目,返回最早的结束时间。

暴力枚举每一个陆上项目,与每一个水上项目的开始结束区间比较,如果不相交,结束时间是最大的结束时间,否则最早结束时间为最早的开始时间加上它们的持续时间。

代码


/**
 * @date 2026-06-02 23:45
 */
public class EarliestFinishTime3633 {

    public int earliestFinishTime(int[] landStartTime, int[] landDuration, int[] waterStartTime, int[] waterDuration) {
        int n = landStartTime.length;
        int m = waterStartTime.length;
        int res = Integer.MAX_VALUE;
        for (int i = 0; i < n; i++) {
            int start = landStartTime[i];
            int end = landStartTime[i] + landDuration[i];
            for (int j = 0; j < m; j++) {
                int ws = waterStartTime[j];
                int we = ws + waterDuration[j];
                if (start >= we) {
                    res = Math.min(res, end);
                } else if (end <= ws) {
                    res = Math.min(res, we);
                } else {
                    res = Math.min(res, Math.min(ws, start) + landDuration[i] + waterDuration[j]);
                }
            }
        }
        return res;
    }
}

性能

2144.打折购买糖果的最小开销

目标

一家商店正在打折销售糖果。每购买 两个 糖果,商店会 免费 送一个糖果。

免费送的糖果唯一的限制是:它的价格需要小于等于购买的两个糖果价格的 较小值 。

  • 比方说,总共有 4 个糖果,价格分别为 1 ,2 ,3 和 4 ,一位顾客买了价格为 2 和 3 的糖果,那么他可以免费获得价格为 1 的糖果,但不能获得价格为 4 的糖果。

给你一个下标从 0 开始的整数数组 cost ,其中 cost[i] 表示第 i 个糖果的价格,请你返回获得 所有 糖果的 最小 总开销。

示例 1:

输入:cost = [1,2,3]
输出:5
解释:我们购买价格为 2 和 3 的糖果,然后免费获得价格为 1 的糖果。
总开销为 2 + 3 = 5 。这是开销最小的 唯一 方案。
注意,我们不能购买价格为 1 和 3 的糖果,并免费获得价格为 2 的糖果。
这是因为免费糖果的价格必须小于等于购买的 2 个糖果价格的较小值。

示例 2:

输入:cost = [6,5,7,9,2,2]
输出:23
解释:最小总开销购买糖果方案为:
- 购买价格为 9 和 7 的糖果
- 免费获得价格为 6 的糖果
- 购买价格为 5 和 2 的糖果
- 免费获得价格为 2 的最后一个糖果
因此,最小总开销为 9 + 7 + 5 + 2 = 23 。

示例 3:

输入:cost = [5,5]
输出:10
解释:由于只有 2 个糖果,我们需要将它们都购买,而且没有免费糖果。
所以总最小开销为 5 + 5 = 10 。

说明:

  • 1 <= cost.length <= 100
  • 1 <= cost[i] <= 100

思路

商店打折销售糖果,cost[i] 表示第 i 颗糖果的价格。每销售两个糖果 a 和 b,可以赠送价格小于等于 min(cost[a], cost[b]) 的糖果,求购买所有糖果所需的最小开销。

贪心策略,排序后从价格高到低枚举,每购买两个跳过下一个。

代码


/**
 * @date 2026-06-01 9:03
 */
public class MinimumCost2144 {

    public int minimumCost(int[] cost) {
        int n = cost.length;
        Arrays.sort(cost);
        int res = 0;
        int cnt = 0;
        for (int i = n - 1; i >= 0; i--) {
            if (cnt == 2) {
                cnt = 0;
                continue;
            }
            res += cost[i];
            cnt++;
        }
        return res;
    }
}

性能

3300.替换为数位和以后的最小元素

目标

给你一个整数数组 nums 。

请你将 nums 中每一个元素都替换为它的各个数位之 和 。

请你返回替换所有元素以后 nums 中的 最小 元素。

示例 1:

输入:nums = [10,12,13,14]
输出:1
解释:
nums 替换后变为 [1, 3, 4, 5] ,最小元素为 1 。

示例 2:

输入:nums = [1,2,3,4]
输出:1
解释:
nums 替换后变为 [1, 2, 3, 4] ,最小元素为 1 。

示例 3:

输入:nums = [999,19,199]
输出:10
解释:
nums 替换后变为 [27, 10, 19] ,最小元素为 10 。

说明:

1 <= nums.length <= 100
1 <= nums[i] <= 10^4

思路

将数组中的元素换成其各位数字之和,返回替换后数组的最小值。

代码


/**
 * @date 2026-05-29 0:03
 */
public class MinElement3300 {

    public int minElement(int[] nums) {
        int res = Integer.MAX_VALUE;
        for (int num : nums) {
            int sum = 0;
            while (num > 0){
                sum += num % 10;
                num /= 10;
            }
            res = Math.min(res, sum);
        }
        return res;
    }
}

性能

3120.统计特殊字母的数量I

目标

给你一个字符串 word。如果 word 中同时存在某个字母的小写形式和大写形式,则称这个字母为 特殊字母 。

返回 word 中 特殊字母 的数量。

示例 1:

输入:word = "aaAbcBC"
输出:3
解释:
word 中的特殊字母是 'a'、'b' 和 'c'。

示例 2:

输入:word = "abc"
输出:0
解释:
word 中不存在大小写形式同时出现的字母。

示例 3:

输入:word = "abBCab"
输出:1
解释:
word 中唯一的特殊字母是 'b'。

说明:

  • 1 <= word.length <= 50
  • word 仅由小写和大写英文字母组成。

思路

有一个字符串 word,返回其中大小写同时存在的的字母个数。

使用哈希表记录已经出现的字母,如果已经同时出现过大小写,需要跳过,避免重复计数。否则,如果当前字母是小写,判断对应大写是否在哈希表中,如果当前字母是大写,判断对应小写是否在哈希表中,将当前字母加入哈希表。

代码


/**
 * @date 2026-05-26 8:51
 */
public class NumberOfSpecialChars3120 {

    public int numberOfSpecialChars(String word) {
        Set<Character> set = new HashSet<>();
        int n = word.length();
        int res = 0;
        for (int i = 0; i < n; i++) {
            char c = word.charAt(i);
            if (set.contains(c) && (set.contains((char) (c + 32)) || set.contains((char) (c - 32)))) {
                continue;
            }
            if ((c < 'a' && set.contains((char) (c + 32))) || (c >= 'a' && set.contains((char) (c - 32)))) {
                res++;
            }
            set.add(c);
        }
        return res;
    }

}

性能

1752.检查数组是否经排序和轮转得到

目标

给你一个数组 nums 。nums 的源数组中,所有元素与 nums 相同,但按非递减顺序排列。

如果 nums 能够由源数组轮转若干位置(包括 0 个位置)得到,则返回 true ;否则,返回 false 。

源数组中可能存在 重复项 。

注意:数组 A 在轮转 x 个位置后得到长度相同的数组 B ,使得对于每一个有效的下标 i,满足 B[i] == A[(i+x) % A.length]。

示例 1:

输入:nums = [3,4,5,1,2]
输出:true
解释:[1,2,3,4,5] 为有序的源数组。
可以轮转 x = 2 个位置,使新数组从值为 3 的元素开始:[3,4,5,1,2] 。

示例 2:

输入:nums = [2,1,3,4]
输出:false
解释:源数组无法经轮转得到 nums 。

示例 3:

输入:nums = [1,2,3]
输出:true
解释:[1,2,3] 为有序的源数组。
可以轮转 x = 0 个位置(即不轮转)得到 nums 。

说明:

  • 1 <= nums.length <= 100
  • 1 <= nums[i] <= 100

思路

判断循环数组能否通过若干右移变成非递减。

根据题目要求,循环数组最多只能出现一次严格递减。

代码


/**
 * @date 2026-05-26 10:16
 */
public class Check1752 {

    public boolean check(int[] nums) {
        int n = nums.length;
        int cnt = 0;
        if (nums[n - 1] > nums[0]) {
            cnt++;
        }
        for (int i = 1; i < n; i++) {
            if (nums[i] < nums[i - 1]) {
                cnt++;
            }
            if (cnt > 1) {
                return false;
            }
        }
        return true;
    }
}

性能

2540.最小公共值

目标

给你两个整数数组 nums1 和 nums2 ,它们已经按非降序排序,请你返回两个数组的 最小公共整数 。如果两个数组 nums1 和 nums2 没有公共整数,请你返回 -1 。

如果一个整数在两个数组中都 至少出现一次 ,那么这个整数是数组 nums1 和 nums2 公共 的。

示例 1:

输入:nums1 = [1,2,3], nums2 = [2,4]
输出:2
解释:两个数组的最小公共元素是 2 ,所以我们返回 2 。

示例 2:

输入:nums1 = [1,2,3,6], nums2 = [2,3,4,5]
输出:2
解释:两个数组中的公共元素是 2 和 3 ,2 是较小值,所以返回 2 。

说明:

  • 1 <= nums1.length, nums2.length <= 10^5
  • 1 <= nums1[i], nums2[j] <= 10^9
  • nums1 和 nums2 都是 非降序 的。

思路

有两个升序数组 nums1nums2,返回它们最小的公共元素。

双指针,如果不相等优先移动元素值较小的下标。

代码


/**
 * @date 2026-05-19 8:46
 */
public class GetCommon2540 {

    public int getCommon(int[] nums1, int[] nums2) {
        for (int a = 0, b = 0; a < nums1.length && b < nums2.length; ) {
            if (nums1[a] == nums2[b]) {
                return nums1[a];
            } else if (nums1[a] < nums2[b]) {
                a++;
            } else {
                b++;
            }
        }
        return -1;
    }
}

性能

2784.检查数组是否是好的

目标

给你一个整数数组 nums ,如果它是数组 base[n] 的一个排列,我们称它是个 好 数组。

base[n] = [1, 2, ..., n - 1, n, n] (换句话说,它是一个长度为 n + 1 且包含 1 到 n - 1 恰好各一次,包含 n 两次的一个数组)。比方说,base[1] = [1, 1] ,base[3] = [1, 2, 3, 3] 。

如果数组是一个好数组,请你返回 true ,否则返回 false 。

注意:数组的排列是这些数字按任意顺序排布后重新得到的数组。

示例 1:

输入:nums = [2, 1, 3]
输出:false
解释:因为数组的最大元素是 3 ,唯一可以构成这个数组的 base[n] 对应的 n = 3 。但是 base[3] 有 4 个元素,但数组 nums 只有 3 个元素,所以无法得到 base[3] = [1, 2, 3, 3] 的排列,所以答案为 false 。

示例 2:

输入:nums = [1, 3, 3, 2]
输出:true
解释:因为数组的最大元素是 3 ,唯一可以构成这个数组的 base[n] 对应的 n = 3 ,可以看出数组是 base[3] = [1, 2, 3, 3] 的一个排列(交换 nums 中第二个和第四个元素)。所以答案为 true 。

示例 3:

输入:nums = [1, 1]
输出:true
解释:因为数组的最大元素是 1 ,唯一可以构成这个数组的 base[n] 对应的 n = 1,可以看出数组是 base[1] = [1, 1] 的一个排列。所以答案为 true 。

示例 4:

输入:nums = [3, 4, 4, 1, 2, 1]
输出:false
解释:因为数组的最大元素是 4 ,唯一可以构成这个数组的 base[n] 对应的 n = 4 。但是 base[n] 有 5 个元素而 nums 有 6 个元素。所以答案为 false 。

说明:

  • 1 <= nums.length <= 100
  • 1 <= num[i] <= 200

思路

有一个长度为 n + 1 的数组 nums,判断它是否是数组 [1, 2, 3, ……, n - 1, n, n] 的一个排列。

注意到目标数组的元素与下标的关系为 nums[i] = i + 1, i < n,特殊处理后两个位置,使用一个指针 p 指向下标 n - 1。遍历数组 numsn 个元素,如果当前位置的元素值不满足条件,就将其与满足条件的位置(当 nums[i] < n 时,取 nums[i] - 1,当 nums[i] == n 时取 p,当 nums[i] > n 直接返回 false)上的元素值交换,如果目标位置的元素值已经满足条件或者 n 的出现次数超过两次,返回 false。最后返回 nums[n] == n 即可。

代码


/**
 * @date 2026-05-14 9:40
 */
public class IsGood2784 {

    public boolean isGood(int[] nums) {
        int n = nums.length - 1;
        int p = n - 1;
        for (int i = 0; i < n; i++) {
            while (nums[i] != i + 1) {
                int index = nums[i] - 1;
                if (nums[i] > n || (nums[i] < n && nums[index] == nums[i]) || (nums[i] == n && p > n)) {
                    return false;
                }
                if (nums[i] == n) {
                    index = p++;
                }
                int tmp = nums[index];
                nums[index] = nums[i];
                nums[i] = tmp;
            }
        }
        return nums[n] == n;
    }
}

性能

2553.分割数组中数字的数位

目标

给你一个正整数数组 nums ,请你返回一个数组 answer ,你需要将 nums 中每个整数进行数位分割后,按照 nums 中出现的 相同顺序 放入答案数组中。

对一个整数进行数位分割,指的是将整数各个数位按原本出现的顺序排列成数组。

比方说,整数 10921 ,分割它的各个数位得到 [1,0,9,2,1] 。

示例 1:

输入:nums = [13,25,83,77]
输出:[1,3,2,5,8,3,7,7]
解释:
- 分割 13 得到 [1,3] 。
- 分割 25 得到 [2,5] 。
- 分割 83 得到 [8,3] 。
- 分割 77 得到 [7,7] 。
answer = [1,3,2,5,8,3,7,7] 。answer 中的数字分割结果按照原数字在数组中的相同顺序排列。

示例 2:

输入:nums = [7,1,3,9]
输出:[7,1,3,9]
解释:nums 中每个整数的分割是它自己。
answer = [7,1,3,9] 。

说明:

  • 1 <= nums.length <= 1000
  • 1 <= nums[i] <= 10^5

思路

有一个数组 nums,按顺序将数组中的元素进行数位分割,将结果保存到一个新数组中。

通俗讲就是将数组元素都放在一起从左到右按位取每一个数字即可。

代码


/**
 * @date 2026-05-11 9:04
 */
public class SeparateDigits2553 {

    public int[] separateDigits(int[] nums) {
        int n = nums.length;
        List<Integer> list = new ArrayList<>();
        for (int i = n - 1; i >= 0; i--) {
            int num = nums[i];
            while (num > 0) {
                list.add(num % 10);
                num /= 10;
            }
        }
        int l = list.size();
        int[] res = new int[l];
        for (Integer num : list) {
            res[--l] = num;
        }
        return res;
    }
}

性能

796.旋转字符串

目标

给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 。

s 的 旋转操作 就是将 s 最左边的字符移动到最右边。

  • 例如, 若 s = 'abcde',在旋转一次之后结果就是'bcdea' 。

示例 1:

输入: s = "abcde", goal = "cdeab"
输出: true

示例 2:

输入: s = "abcde", goal = "abced"
输出: false

说明:

  • 1 <= s.length, goal.length <= 100
  • s 和 goal 由小写英文字母组成

思路

判断能否将 s 经过任意次旋转变为 goal,每次旋转指将最左边的字符放到最右边。

首先判断 sgoal 的长度是否相等,然后再判断 s + s 是否包含 goal

代码


/**
 * @date 2026-05-06 16:30
 */
public class RotateString796 {

    public boolean rotateString(String s, String goal) {
        return s.length() == goal.length() && (s + s).contains(goal);
    }
}

性能

2833.距离原点最远的点

目标

给你一个长度为 n 的字符串 moves ,该字符串仅由字符 'L'、'R' 和 '_' 组成。字符串表示你在一条原点为 0 的数轴上的若干次移动。

你的初始位置就在原点(0),第 i 次移动过程中,你可以根据对应字符选择移动方向:

  • 如果 moves[i] = 'L' 或 moves[i] = '_' ,可以选择向左移动一个单位距离
  • 如果 moves[i] = 'R' 或 moves[i] = '_' ,可以选择向右移动一个单位距离

移动 n 次之后,请你找出可以到达的距离原点 最远 的点,并返回 从原点到这一点的距离 。

示例 1:

输入:moves = "L_RL__R"
输出:3
解释:可以到达的距离原点 0 最远的点是 -3 ,移动的序列为 "LLRLLLR" 。

示例 2:

输入:moves = "_R__LL_"
输出:5
解释:可以到达的距离原点 0 最远的点是 -5 ,移动的序列为 "LRLLLLL" 。

示例 3:

输入:moves = "_______"
输出:7
解释:可以到达的距离原点 0 最远的点是 7 ,移动的序列为 "RRRRRRR" 。

说明:

  • 1 <= moves.length == n <= 50
  • moves 仅由字符 'L'、'R' 和 '_' 组成

思路

开始时站在在数轴原点,根据数组 moves 移动,L -1 R +1 _ 可以任选方向,求所能到达的距离原点最远的距离。

LR 是固定的,要使距离最远 _ 应该都朝一个方向移动,可以先判断 LR 移动后位于原点的左侧还是右侧,然后 _ 的移动方向与之保持一致即可。

代码


/**
 * @date 2026-04-24 8:50
 */
public class FurthestDistanceFromOrigin2833 {

    public int furthestDistanceFromOrigin(String moves) {
        int n = moves.length();
        int d = 0;
        int c = 0;
        for (int i = 0; i < n; i++) {
            if (moves.charAt(i) == 'L') {
                d--;
            } else if (moves.charAt(i) == 'R') {
                d++;
            } else {
                c++;
            }
        }
        return d > 0 ? d + c : -d + c;
    }
}

性能