leetcode
leetcode 2501 ~ 2550
交换得到字典序最小的数组

交换得到字典序最小的数组

难度:

标签:

题目描述

You are given a 0-indexed array of positive integers nums and a positive integer limit.

In one operation, you can choose any two indices i and j and swap nums[i] and nums[j] if |nums[i] - nums[j]| <= limit.

Return the lexicographically smallest array that can be obtained by performing the operation any number of times.

An array a is lexicographically smaller than an array b if in the first position where a and b differ, array a has an element that is less than the corresponding element in b. For example, the array [2,10,3] is lexicographically smaller than the array [10,2,3] because they differ at index 0 and 2 < 10.

 

Example 1:

Input: nums = [1,5,3,9,8], limit = 2
Output: [1,3,5,8,9]
Explanation: Apply the operation 2 times:
- Swap nums[1] with nums[2]. The array becomes [1,3,5,9,8]
- Swap nums[3] with nums[4]. The array becomes [1,3,5,8,9]
We cannot obtain a lexicographically smaller array by applying any more operations.
Note that it may be possible to get the same result by doing different operations.

Example 2:

Input: nums = [1,7,6,18,2,1], limit = 3
Output: [1,6,7,18,1,2]
Explanation: Apply the operation 3 times:
- Swap nums[1] with nums[2]. The array becomes [1,6,7,18,2,1]
- Swap nums[0] with nums[4]. The array becomes [2,6,7,18,1,1]
- Swap nums[0] with nums[5]. The array becomes [1,6,7,18,1,2]
We cannot obtain a lexicographically smaller array by applying any more operations.

Example 3:

Input: nums = [1,7,28,19,10], limit = 3
Output: [1,7,28,19,10]
Explanation: [1,7,28,19,10] is the lexicographically smallest array we can obtain because we cannot apply the operation on any two indices.

 

Constraints:

  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 109
  • 1 <= limit <= 109

代码结果

运行时间: 232 ms, 内存: 35.4 MB


/*
 * 思路:
 * 1. 使用贪心算法,从左到右遍历数组。
 * 2. 每次尽可能交换当前数字和之后尽量小的符合条件的数字。
 * 3. 使用TreeSet来维持一个窗口,用于快速找到可以交换的最小元素。
 * 4. 使用Java Streams来实现。
 */
import java.util.*;
import java.util.stream.IntStream;

public class Solution {
    public int[] minLexicoArray(int[] nums, int limit) {
        TreeSet<Integer> set = new TreeSet<>();
        IntStream.range(0, nums.length).forEach(i -> {
            set.add(nums[i]);
            Integer minInRange = set.ceiling(nums[i] - limit);
            if (minInRange != null && minInRange <= nums[i] + limit) {
                set.remove(minInRange);
                set.add(nums[i]);
                nums[i] = minInRange;
            }
        });
        return nums;
    }
}

解释

方法:

这个题解的思路是首先对数组中的元素按照其值的大小进行索引排序,以确定字典序的顺序。然后,利用limit条件来确定哪些元素可以彼此交换。通过检查相邻元素的差是否小于或等于limit,可以将整个数组分割成若干组,每组内的元素都可以任意交换。因此,每组内部按照原始索引进行排序,然后将组内元素按原始顺序填充到结果数组中,以确保每组内部元素在结果数组中的顺序是字典序最小的。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在题解中,为什么首先要对数组中的元素按照其值的大小进行索引排序?这一步的目的具体是什么?
这一步的主要目的是为了确定哪些元素可以交换以达到字典序最小化的目的。通过按值大小对索引进行排序,我们可以直观地看到元素按照字典序排列的顺序,从而在接下来的步骤中更容易分组和判断哪些元素可以在给定的limit条件下交换。排序后的索引数组将帮助我们理解在不违反交换限制的情况下,数组可以如何通过交换操作达到字典序最小。
🦆
题解提到,可以将整个数组分割成若干组,每组内的元素都可以任意交换。请问如何确定哪些元素可以组成一组?具体的逻辑是什么?
哪些元素可以组成一组是基于它们之间的差值是否小于或等于limit来决定的。具体来说,当我们按照元素值排序索引后,会检查连续的元素(按排序后的顺序)之间的差值。如果这个差值小于或等于limit,这意味着这两个元素可以交换位置,因此它们可以放在同一组中。通过这种方式,连续的符合条件的元素就会被划分为同一组,每一组内的元素可以自由交换以达到该组内字典序最小。
🦆
在处理分组并排序的过程中,为什么选择先对元素按值进行排序,然后又在每个组内部按照原始索引进行排序?这样做的优势是什么?
首先对元素按值进行排序是为了确定可以互换的元素,并将它们分组。每个组内部再按原始索引排序的原因是为了保证在最终的数组中,每个组内的元素顺序是按照原始顺序出现的,这样可以确保在满足交换条件下,组内的元素顺序是最小字典序。这种方法的优势在于,它不仅考虑了元素值的排序,还保持了元素的相对位置,从而确保了最终结果的一致性和正确性。
🦆
题解中提到,当当前元素与前一个元素的差超过limit时,前一个分组结束。这种方法是否可能导致某些本可以交换的元素没有被正确处理?
这种情况是有可能发生的。在题解中提到的方法采用了连续性的分组逻辑,即只有当元素连续且差值不超过limit时才划分为同一组。这可能导致跳跃式的元素组合(例如,索引更远的元素对在limit范围内)不能被考虑到,因此这种策略可能不是全局最优的。在极端情况下,可能会有更好的分组方法能够进一步减小最终数组的字典序,但这将需要更复杂的算法来实现。

相关问题