第三部分:排序
324.摆动排序II(中等)
题目:给你一个整数数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。给你一个整数数组 nums
,将它重新排列成 nums[0] < nums[1] > nums[2] < nums[3]...
的顺序。
你可以假设所有输入数组都可以得到满足题目要求的结果。你可以假设所有输入数组都可以得到满足题目要求的结果。
示例 1:
输入:nums = [1,5,1,1,6,4] 输出:[1,6,1,5,1,4] 解释:[1,4,1,5,1,6] 同样是符合题目要求的结果,可以被判题程序接受。
第一种思路:
这道题目挺值得思考的~
摆动排序的目标是使数组遵循特定的模式:
nums[0] < nums[1] > nums[2] < nums[3] > nums[4]
,依此类推。因此,对于给定的数组nums
,可以将其分成两个部分:较小的元素和较大的元素,然后交替插入这两个部分的元素。
排序:
首先对数组进行排序,以便我们可以轻松地访问较小的和较大的元素。排序后,较小的元素在数组的前面,较大的元素在后面。
划分数组:
找到中间位置
mid
,它将用于分别指向较小元素和较大元素的范围。对于长度为n
的数组,较大的元素是从mid
开始的。交替填充:
使用一个循环,依次将较小的元素和较大的元素填入原数组nums中。具体地:
在偶数位置(nums[0], nums[2], ...)填入较小的元素(从排序后的数组的中间向前),j 从 mid - 1 开始,向前遍历(较小元素),
在奇数位置(nums[1], nums[3], ...)填入较大的元素(从排序后的数组的最后向前),k 从 mid - 1 开始,向前遍历(较大元素)。
通过条件检查,确保在创建新的排序结构时不会越界。
class Solution {
public void wiggleSort(int[] nums) {
int[] arr = nums.clone();
Arrays.sort(arr);
int n = nums.length;
int mid = (n + 1) / 2; // 中间索引
int j = mid - 1; // 较小元素的最后索引
int k = n - 1; // 较大元素的最后索引
// 将新数组中的元素交替填充到原数组
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
nums[i] = arr[j--]; // 偶数索引填充较小元素
} else {
nums[i] = arr[k--]; // 奇数索引填充较大元素
}
}
}
}
官方的其他方法看得有点迷~,这里就不讲述了。
56.合并区间(中等)
题目:以数组 intervals
表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi]
。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。
示例 1:
输入:intervals = [[1,3],[2,6],[8,10],[15,18]] 输出:[[1,6],[8,10],[15,18]] 解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].
第一种思路:
看完题目立刻想到(406.根据身高重建队列)可以先将数组进行排序,这样排完序后的二维数组就是:
以前边界为第一关键字升序,以后边界为第二关键字升序排列,然后遍历循环这个二维数组,先比较第 i 个数组后边界和第 i + 1个数组的前边界:
如果第 i 个数组后边界 >= 第 i + 1个数组的前边界代表存在重叠,再比较第 i 个数组后边界和第 i + 1个数组的后边界:
如果第 i 个数组后边界 <= 第 i + 1个数组的后边界:合并区间,更新下一个区间的起始值为当前区间的起始值。
如果第 i 个数组后边界 > 第 i + 1个数组的后边界:如果当前区间的结束值大于下一个区间的结束值 直接将下一个区间替换为当前区间。、
如果没有重叠,添加当前区间到结果中
最后返回合并后的区间,转换为二维数组
class Solution {
public int[][] merge(int[][] intervals) {
// 如果仅有一个区间,直接返回
if (intervals.length == 1)
return intervals;
// 对区间进行排序,首先根据起始值排序,如果起始值相同则根据结束值排序
Arrays.sort(intervals, new Comparator<int[]>() {
public int compare(int[] left, int[] right) {
// 比较起始值
if (left[0] != right[0]) {
return left[0] - right[0];
} else {
// 如果起始值相同,比较结束值
return left[1] - right[1];
}
}
});
// 用 ArrayList 存储合并后的区间
List<int[]> re = new ArrayList<>();
// 遍历所有区间,尝试进行合并
for (int i = 0; i < intervals.length - 1; i++) {
// 如果当前区间的结束值大于等于下一个区间的起始值,则存在重叠
if (intervals[i][1] >= intervals[i + 1][0]) {
// 如果当前区间的结束值小于等于下一个区间的结束值
if (intervals[i][1] <= intervals[i + 1][1]) {
// 合并区间,更新下一个区间的起始值为当前区间的起始值
intervals[i + 1][0] = intervals[i][0];
} else {
// 如果当前区间的结束值大于下一个区间的结束值
// 直接将下一个区间替换为当前区间
intervals[i + 1] = intervals[i];
}
// 如果到达倒数第二个区间,添加合并后的区间
if (i == intervals.length - 2)
re.add(intervals[i + 1]);
continue; // 继续下次迭代
} else {
// 如果没有重叠,添加当前区间到结果中
if (i == intervals.length - 2)
re.add(intervals[i + 1]);
re.add(intervals[i]);
}
}
// 返回合并后的区间,转换为二维数组
return re.toArray(new int[re.size()][]);
}
}
虽然代码有些可以改进简化的地方,但好歹是自己写出来的而且性能还勉强看得过去😁(82%)
代码的改进:
简化合并逻辑:
使用
LinkedList
来处理合并后的区间,避免了多重条件判断,通过直接访问merged.getLast()
来获取最新的合并区间。避免重复更新:
直接在重叠的情况下更新最后一个合并区间的结束值,避免了在不同条件下的多次更新。
使用增强的 for 循环:
采用传统的
for
循环,由于需要访问索引,所以这里没用增强的for
循环,但其他部分的逻辑简化了。这样修改后的代码更加简洁明了,可读性更好,并且易于维护。合并区间的逻辑也变得更加直接。
class Solution {
public int[][] merge(int[][] intervals) {
if (intervals.length == 0) return new int[0][0];
// 排序
Arrays.sort(intervals, (a, b) -> Integer.compare(a[0], b[0]));
// 使用 LinkedList 存储合并后的区间
List<int[]> merged = new LinkedList<>();
// 添加第一个区间
merged.add(intervals[0]);
for (int i = 1; i < intervals.length; i++) {
int[] current = intervals[i];
int[] lastMerged = merged.getLast();
// 如果当前区间与上一个合并区间重叠
if (current[0] <= lastMerged[1]) {
// 更新合并区间的结束值
lastMerged[1] = Math.max(lastMerged[1], current[1]);
} else {
// 不重叠,则直接添加当前区间
merged.add(current);
}
}
// 转换为二维数组
return merged.toArray(new int[merged.size()][]);
}
}