3517.最小回文排列I

目标

给你一个 回文 字符串 s。

返回 s 的按字典序排列的 最小 回文排列。

如果一个字符串从前往后和从后往前读都相同,那么这个字符串是一个 回文 字符串。

排列 是字符串中所有字符的重排。

如果字符串 a 按字典序小于字符串 b,则表示在第一个不同的位置,a 中的字符比 b 中的对应字符在字母表中更靠前。

如果在前 min(a.length, b.length) 个字符中没有区别,则较短的字符串按字典序更小。

示例 1:

输入: s = "z"
输出: "z"
解释:
仅由一个字符组成的字符串已经是按字典序最小的回文。

示例 2:

输入: s = "babab"
输出: "abbba"
解释:
通过重排 "babab" → "abbba",可以得到按字典序最小的回文。

示例 3:

输入: s = "daccad"
输出: "acddca"
解释:
通过重排 "daccad" → "acddca",可以得到按字典序最小的回文。

说明:

  • 1 <= s.length <= 10^5
  • s 由小写英文字母组成。
  • 保证 s 是回文字符串。

思路

有一个回文字符串 s,将其重新排列成回文字符串,使得字典序最小。

记录字符串中字符的出现次数,然后按字典序从两边向中间填充,如果出现次数为奇数,那么该字符为中间元素。

代码


/**
 * @date 2026-07-28 9:03
 */
public class SmallestPalindrome3517 {

    public String smallestPalindrome(String s) {
        int[] cnt = new int[26];
        int n = s.length();
        char[] res = new char[n];
        for (char c : s.toCharArray()) {
            cnt[c - 'a']++;
        }
        char mid = 0;
        int cur = 0;
        for (int i = 0; i < 26; i++) {
            if (cnt[i] % 2 == 1) {
                mid = (char) ('a' + i);
            }
            int k = cnt[i] / 2;
            char c = (char) ('a' + i);
            for (int j = 0; j < k; j++) {
                res[cur + j] = c;
                res[n - cur - j - 1] = c;
            }
            cur += k;
        }
        if (n % 2 == 1) {
            res[n / 2] = mid;
        }
        return new String(res);
    }

}

性能

1464.数组中两元素的最大乘积

目标

给你一个整数数组 nums,请你选择数组的两个不同下标 i 和 j,使 (nums[i]-1)*(nums[j]-1) 取得最大值。

请你计算并返回该式的最大值。

示例 1:

输入:nums = [3,4,5,2]
输出:12 
解释:如果选择下标 i=1 和 j=2(下标从 0 开始),则可以获得最大值,(nums[1]-1)*(nums[2]-1) = (4-1)*(5-1) = 3*4 = 12 。 

示例 2:

输入:nums = [1,5,4,5]
输出:16
解释:选择下标 i=1 和 j=3(下标从 0 开始),则可以获得最大值 (5-1)*(5-1) = 16 。

示例 3:

输入:nums = [3,7]
输出:12

说明:

  • 2 <= nums.length <= 500
  • 1 <= nums[i] <= 10^3

思路

从数组中取两个不同下标的元素相乘并返回最大的乘积。

找到数组最大的两个元素相乘即可。

代码


/**
 * @date 2026-07-27 8:55
 */
public class MaxProduct1464 {

    public int maxProduct(int[] nums) {
        Arrays.sort(nums);
        int n = nums.length;
        return (nums[n - 1] - 1) * (nums[n - 2] - 1);
    }
}

性能

628.三个数的最大乘积

目标

给你一个整型数组 nums ,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

输入:nums = [1,2,3]
输出:6

示例 2:

输入:nums = [1,2,3,4]
输出:24

示例 3:

输入:nums = [-1,-2,-3]
输出:-6

说明:

  • 3 <= nums.length <= 10^4
  • -1000 <= nums[i] <= 1000

思路

从数组中取三个不同的下标,返回其乘积的最大值。

由于存在负数,分情况讨论,定义 max1max2max3 分别为前三大元素,min1min2 分别是前二小元素:

  • 如果 max1 < 0, 乘积只能为负,要使乘积最大,那么其绝对值应该最小,因此取 max1 * max2 * max3
  • 如果 max2 < 0, 最大乘积可以为正,取最小的两个负数相乘,乘积最大,取 max1 * min1 * min2
  • 如果 max3 < 0,乘积可能为正也可能为负(只有三个元素),不论那种情况,都可取 max1 * min1 * min2
  • 如果 max3 > 0, 最大值可能是 max1 * max2 * max3 或者 max1 * min1 * min2

代码


/**
 * @date 2026-07-27 16:37
 */
public class MaximumProduct628 {

    public int maximumProduct_v1(int[] nums) {
        int max1 = -1001, max2 = -1001, max3 = -1001;
        int min1 = 1001, min2 = 1001;
        for (int num : nums) {
            if (num > max1) {
                max3 = max2;
                max2 = max1;
                max1 = num;
            } else if (num > max2) {
                max3 = max2;
                max2 = num;
            } else if (num > max3) {
                max3 = num;
            }
            if (num < min1) {
                min2 = min1;
                min1 = num;
            } else if (num < min2) {
                min2 = num;
            }
        }
        return Math.max(max1 * max2 * max3, max1 * min1 * min2);
    }

}

性能

3536.两个数字的最大乘积

目标

给定一个正整数 n。

返回 任意两位数字 相乘所得的 最大 乘积。

注意:如果某个数字在 n 中出现多次,你可以多次使用该数字。

示例 1:

输入: n = 31
输出: 3
解释:
n 的数字是 [3, 1]。
任意两位数字相乘的结果为:3 * 1 = 3。
最大乘积为 3。

示例 2:

输入: n = 22
输出: 4
解释:
n 的数字是 [2, 2]。
任意两位数字相乘的结果为:2 * 2 = 4。
最大乘积为 4。

示例 3:

输入: n = 124
输出: 8
解释:
n 的数字是 [1, 2, 4]。
任意两位数字相乘的结果为:1 * 2 = 2, 1 * 4 = 4, 2 * 4 = 8。
最大乘积为 8。

说明:

  • 10 <= n <= 10^9

思路

给定一个数字 n,选择数位中的两个数字相乘,返回乘积的最大值。

找到最大的两个数字相乘即可。

代码


/**
 * @date 2026-07-27 14:22
 */
public class MaxProduct3536 {

    public int maxProduct(int n) {
        int max = 0;
        int preMax = 0;
        int res = 0;
        while (n > 0) {
            int d = n % 10;
            if (d >= preMax) {
                preMax = Math.min(max, d);
                max = Math.max(max, d);
                res = Math.max(res, max * preMax);
            }
            n /= 10;
        }
        return res;
    }

}

性能

3867.数对的最大公约数之和

目标

给你一个长度为 n 的整数数组 nums。

构造一个数组 prefixGcd,其中对于每个下标 i:

  • 令 mxi = max(nums[0], nums[1], ..., nums[i])。
  • prefixGcd[i] = gcd(nums[i], mxi)。

在构造 prefixGcd 之后:

  • 将 prefixGcd 按 非递减 顺序排序。
  • 通过取 最小的未配对 元素和 最大的未配对 元素来形成数对。
  • 重复此过程,直到无法再形成更多数对。
  • 对于每个形成的数对,计算 两个元素的最大公约数 gcd。
  • 如果 n 是奇数,prefixGcd 数组中的 中间 元素保持 未配对 状态,并应被忽略。

返回一个整数,表示所有形成数对的 最大公约数之和。

术语 gcd(a, b) 表示 a 和 b 的 最大公约数。

示例 1:

输入: nums = [2,6,4]
输出: 2
解释:
构造 prefixGcd:
i nums[i] mxi prefixGcd[i]
0    2    2      2
1    6    6      6
2    4    6      2
prefixGcd = [2, 6, 2]。排序后形成 [2, 2, 6]。
将最小和最大的元素配对:gcd(2, 6) = 2。剩下的中间元素 2 被忽略。因此,总和为 2。

示例 2:

输入: nums = [3,6,2,8]
输出: 5
解释:
构造 prefixGcd:
i nums[i] mxi prefixGcd[i]
0    3     3      3
1    6     6      6
2    2     6      2
3    8     8      8
prefixGcd = [3, 6, 2, 8]。排序后形成 [2, 3, 6, 8]。
形成数对:gcd(2, 8) = 2 和 gcd(3, 6) = 3。因此,总和为 2 + 3 = 5。

说明:

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

思路

有一个正整数数组 nums,构造数组 prefixGcdprefixGcd[i] = gcd(maxi, nums[i]),其中 maxi[0, i] 的最大值。将 prefixGcd 排序后首尾配对,忽略中间未配对元素,求每对 gcd 之和。

根据题目模拟即可。

代码


/**
 * @date 2026-07-16 9:34
 */
public class GcdSum3867 {

    public long gcdSum(int[] nums) {
        int n = nums.length;
        int[] prefixGcd = new int[n];
        int max = 0;
        for (int i = 0; i < n; i++) {
            max = Math.max(max, nums[i]);
            prefixGcd[i] = gcd(max, nums[i]);
        }
        Arrays.sort(prefixGcd);
        long res = 0;
        int l = 0, r = n - 1;
        while (l < r) {
            res += gcd(prefixGcd[l++], prefixGcd[r--]);
        }
        return res;
    }

    public int gcd(int a, int b) {
        while (b != 0) {
            int tmp = b;
            b = a % b;
            a = tmp;
        }
        return a;
    }
}

性能

1331.数组序号转换

目标

给你一个整数数组 arr ,请你将数组中的每个元素替换为它们排序后的序号。

序号代表了一个元素有多大。序号编号的规则如下:

  • 序号从 1 开始编号。
  • 一个元素越大,那么序号越大。如果两个元素相等,那么它们的序号相同。
  • 每个数字的序号都应该尽可能地小。

示例 1:

输入:arr = [40,10,20,30]
输出:[4,1,2,3]
解释:40 是最大的元素。 10 是最小的元素。 20 是第二小的数字。 30 是第三小的数字。

示例 2:

输入:arr = [100,100,100]
输出:[1,1,1]
解释:所有元素有相同的序号。

示例 3:

输入:arr = [37,12,28,9,100,56,80,5,12]
输出:[5,3,4,2,8,6,7,1,3]

说明:

  • 0 <= arr.length <= 10^5
  • -10^9 <= arr[i] <= 10^9

思路

有一个整数数组 arr,返回每个元素排序后的序号,序号从 1 开始,如果元素值相同那么序号也相同,并且后面的序号无需考虑前面重复的元素,比如 [10 20 20 30],返回 [1, 2, 2, 3] 而不是 [1, 2, 2, 4]

排序 arr 的下标数组 index,根据排序后的 index 返填原数组的序号,注意去重即可。

代码


/**
 * @date 2026-07-14 10:11
 */
public class ArrayRankTransform1331 {

    public int[] arrayRankTransform(int[] arr) {
        int n = arr.length;
        Integer[] index = new Integer[n];
        Arrays.setAll(index, i -> i);
        Arrays.sort(index, (a, b) -> (arr[a] - arr[b]));
        int prev = Integer.MIN_VALUE;
        int sort = 0;
        for (int i = 0; i < n; i++) {
            if (prev != arr[index[i]]){
                sort++;
            }
            prev = arr[index[i]];
            arr[index[i]] = sort;
        }
        return arr;
    }

}

性能

1288.删除被覆盖区间

目标

给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。

只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。

在完成所有删除操作后,请你返回列表中剩余区间的数目。

示例:

输入:intervals = [[1,4],[3,6],[2,8]]
输出:2
解释:区间 [3,6] 被区间 [2,8] 覆盖,所以它被删除了。

说明:

  • 1 <= intervals.length <= 1000
  • 0 <= intervals[i][0] < intervals[i][1] <= 10^5
  • 对于所有的 i != j:intervals[i] != intervals[j]

思路

有一个区间列表,删除被其它区间覆盖的区间,返回剩余的区间。区间 [a, b] 被区间[c, d] 覆盖需满足 c <= a && b <= d

根据左端点从小到大排序,右端点从大到小排序。按顺序遍历排序后的数组,当前区间的左端点一定大于等于之前的区间的左端点,只需要判断右端点是否超过前面记录的最长右端点即可。

代码


/**
 * @date 2026-07-06 9:00
 */
public class RemoveCoveredIntervals1288 {

    public int removeCoveredIntervals(int[][] intervals) {
        int n = intervals.length;
        Arrays.sort(intervals, (a, b) -> {
            int compare = a[0] - b[0];
            return compare != 0 ? compare : b[1] - a[1];
        });
        int r = intervals[0][1];
        int res = 0;
        for (int i = 1; i < n; i++) {
            if (intervals[i][1] <= r) {
                res++;
            } else {
                r = intervals[i][1];
            }
        }
        return n - res;
    }
}

性能

1846.减小和重新排列数组后的最大元素

目标

给你一个正整数数组 arr 。请你对 arr 执行一些操作(也可以不进行任何操作),使得数组满足以下条件:

  • arr 中 第一个 元素必须为 1 。
  • 任意相邻两个元素的差的绝对值 小于等于 1 ,也就是说,对于任意的 1 <= i < arr.length (数组下标从 0 开始),都满足 abs(arr[i] - arr[i - 1]) <= 1 。abs(x) 为 x 的绝对值。

你可以执行以下 2 种操作任意次:

  • 减小 arr 中任意元素的值,使其变为一个 更小的正整数 。
  • 重新排列 arr 中的元素,你可以以任意顺序重新排列。

请你返回执行以上操作后,在满足前文所述的条件下,arr 中可能的 最大值 。

示例 1:

输入:arr = [2,2,1,2,1]
输出:2
解释:
我们可以重新排列 arr 得到 [1,2,2,2,1] ,该数组满足所有条件。
arr 中最大元素为 2 。

示例 2:

输入:arr = [100,1,1000]
输出:3
解释:
一个可行的方案如下:
1. 重新排列 arr 得到 [1,100,1000] 。
2. 将第二个元素减小为 2 。
3. 将第三个元素减小为 3 。
现在 arr = [1,2,3] ,满足所有条件。
arr 中最大元素为 3 。

示例 3:

输入:arr = [1,2,3,4,5]
输出:5
解释:数组已经满足所有条件,最大元素为 5 。

说明:

  • 1 <= arr.length <= 10^5
  • 1 <= arr[i] <= 10^9

思路

有一个正整数数组 arr,可以将任意元素变成比它更小的正整数,也可以按任意顺序重新排列元素。需要将 arr 变成目标数组,使得第一个元素为 1,且相邻元素差的绝对值不超过 1。返回目标数组可能的最大值。

根据题目要求,第一个元素必须是 1,且相邻元素差的绝对值不超过 1,要使元素值变大只能 +1,且该元素只能由大于等于它的元素转化而来。

贪心策略,从小到大排序数组,遍历数组记录最大值,由于元素值都大于 0,都可以操作成 1,当元素值小于最大值时直接跳过,否则,将元素操作成最大值加一。越大的元素越晚操作,可以使最大值更大。

由于最大值不超过 n,可以将大于 n 的元素都视为 n,这样可以使用计数排序优化。

代码


/**
 * @date 2026-06-29 18:11
 */
public class MaximumElementAfterDecrementingAndRearranging1846 {

    public int maximumElementAfterDecrementingAndRearranging(int[] arr) {
        Arrays.sort(arr);
        int res = 0;
        for (int num : arr) {
            if (num <= res) {
                continue;
            }
            res++;
        }
        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;
    }

}

性能

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;
    }
}

性能