第二部分:排序
350.两个数组的交集II(简单)
题目:给你两个整数数组 nums1
和 nums2
,请你以数组形式返回两数组的交集。返回结果中每个元素出现的次数,应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)。可以不考虑输出结果的顺序。
示例 1:
输入:nums1 = [1,2,2,1], nums2 = [2,2] 输出:[2,2]
第一种思路:
写了之前的题目之后,发现指针很有用(从基础小白到有算题后),这题同样可以使用指针,为了避免元素的重复(题目中:应与元素在两个数组中都出现的次数一致(如果出现次数不一致,则考虑取较小值)),可以先将两个数组进行排序处理,然后两个指针q ,p从头开始指向数组,之后进入循环有三种情况:
如果两个指针指向的数组元素相等:将数组元素添加到一个与两个数组中最短数组等长数组中(不是最终结果数组是因为不知道有多少满足条件的元素,但一定不会比最小数组长度还长,称为数组re),然后re数组指针向后移动一个单位,q,p(分别指向数组nums1,nums2的指针)也都向后移动一个单位。
如果nums1[q] < nums2[p] : q指针向后移动一个单位。
如果nums1[q] > nums2[p] : p指针向后移动一个单位。
最后对re数组进行最后的处理(避免这个数组含有错误数组0),使用System.arraycopy(拷贝数组,拷贝数组从哪开始,目标数组,目标数组从哪开始,需要拷贝元素个数),需要拷贝元素个数通过 r 指针可以确认。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
int[] re = new int[nums1.length <= nums2.length ? nums1.length : nums2.length];
Arrays.sort(nums1);
Arrays.sort(nums2);
int q = 0, p = 0;
int r = 0;
while (q < nums1.length && p < nums2.length) {
if (nums1[q] == nums2[p]) {
re[r++] = nums1[q];
q++;
p++;
} else if (nums1[q] < nums2[p])
q++;
else
p++;
}
int[] result = new int[r];
System.arraycopy(re, 0, result, 0, r);
return result;
}
}
第二种思路:
没想到自己的思路就是官方的两种思路🥰,下面这种通过字典的形式先分别存储两个数组中每个元素出现次数的方法一开始也想过了,但是经过刷了一些题目之后已经知道通过哈希Map的方式大概率会降低性能。但是,看完才知道是自己水平不够,完全可以不用把两个数组都通过哈希表处理,只需要处理一个数组,然后遍历另一个数组,如果在哈希表中存在这个数字,则将该数字添加到答案,并减少哈希表中该数字出现的次数。
完整思路是:先遍历第一个数组,并在哈希表中记录该数组中每个数字以及出现的次数,然后遍历第二个数组,对于第二个数组中的每个数字,如果在哈希表中存在这个数字,则将该数字添加到答案数组中,同时减少哈希表中该数字出现的次数。
优化:为了降低空间复杂度,提高系统性能,可以先遍历较短的数组并在哈希表中记录每个数字以及对应出现的次数,然后遍历较长的数组得到交集。
class Solution {
public int[] intersect(int[] nums1, int[] nums2) {
if (nums1.length > nums2.length) {
return intersect(nums2, nums1);
}
Map<Integer, Integer> map = new HashMap<>();
for (int num : nums1) {
int count = map.getOrDefault(num, 0) + 1;
//map.getOrDefault(num, 0):这个方法从 map 中获取键 num 对应的值(即该数字的计数)。如果 num 不存在于 map 中,它会返回默认值 0。这样避免了在 map 中查找不存在的键造成的空指针异常。
map.put(num, count);
}
int[] result = new int[nums1.length];
int index = 0;
for (int num : nums2) {
int count = map.getOrDefault(num, 0);
if (count > 0) {
result[index++] = num;
count--;
if (count > 0) {
map.put(num, count);
} else {
map.remove(num);
}
}
}
return Arrays.copyOfRange(result, 0, index);
//Arrays.copyOfRange(result, 0, index); 是 Java 中 Arrays 类的一个方法,用于创建一个指定范围的新数组。下面是对这个方法的详细解释,又是个好方法,狠狠学到了
}
}
虽然这段代码经过了优化,但是其性能还是比双指针的性能要差的!
406.根据身高重建队列(中等)
题目:假设有打乱顺序的一群人站成一个队列,数组 people
表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki]
表示第 i
个人的身高为 hi
,前面 正好 有 ki
个身高大于或等于 hi
的人。
请你重新构造并返回输入数组 people
所表示的队列。返回的队列应该格式化为数组 queue
,其中 queue[j] = [hj, kj]
是队列中第 j
个人的属性(queue[0]
是排在队列前面的人)。
示例 1:
输入:people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]] 输出:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 解释: 编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。 编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。 编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。 编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。 编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。 编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。 因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。
有两种思路:
一种是先将队伍从低到高排序,另一种是从高到低排序。
但是从高到低的思路更容易理解:
将每个人按照身高从大到小进行排序,处理身高相同的人使用的方法类似,即:按照 h i为第一关键字降序,k i为第二关键字升序进行排序。按照排完序后的顺序依次将每个人放入队列中就有两种情况(假设现在放第 i 个人):
前 i 个人比第 i 个人高,会对其造成影响
后 i + n 个人比第 i 个人矮,不会对其造成影响
然后可以采用「插空」的方法,依次给每一个人在当前的队列中选择一个插入的位置。也就是说,当放入第 i 个人时,只需要将其插入队列中,使得他的前面恰好有 k i个人即可。
class Solution {
public int[][] reconstructQueue(int[][] people) {
Arrays.sort(people, new Comparator<int[]>() {
public int compare(int[] p1, int[] p2) {
if (p1[0] != p2[0]) {
return p2[0] - p1[0]; // 按身高降序
} else {
return p1[1] - p2[1];// 身高相同按前面的人数升序
}
}
});
List<int[]> re = new ArrayList<int[]>();
for (int[] p : people) {
re.add(p[1], p);
}
/*
创建一个空的 `List` 作为结果队列。
遍历排序后的 `people` 数组,按照每个人第二个参数(即前面有多少人的值)将其插入到对应位置。
由于已经按照身高排序,所以插入的顺序可以保证队列的正确性。
*/
return re.toArray(new int[re.size()][]);
}
}
这里牵扯到了其他一些需要学习的知识
Arrays.sort()自定义排序方法的用法
Arrays.sort()
是 Java 中用于对数组进行排序的方法。它可以接收一个数组和一个比较器(Comparator
)作为参数,允许用户自定义排序的逻辑。以下是它的用法:public int compare(Type obj1, Type obj2) { // 自定义比较逻辑 return comparisonResult; // 返回负数、零或正数 } });
new Comparator()
中的<>
是一个简化的编译器语法,称为"菱形语法"(diamond syntax),它是在 Java 7 中引入的。这种语法用来推断类型,能够减少冗余的代码。具体来说:
Comparator
是一个接口,用来比较两个int[]
类型的对象。在这里,int[]
表示你比较的对象是整数数组。当你使用
new Comparator()
时,你明确表示你想要实现这个Comparator
接口,用于比较两个int[]
。在
<>
中,如果你在右边没有指定类型参数,编译器会根据左边的声明(Comparator
)来自动推断出类型。这样,你可以省略显式翻译或重复的类型声明,使代码更简洁。在你的代码中,比较逻辑是根据数组的第一个元素(一般代表身高)进行降序排序,如果第一个元素相同,则根据第二个元素(通常代表序号)进行升序排序。
在 Java 中,
new Comparator() { ... }
后接一个{}
代表了一个匿名内部类的定义。这个匿名内部类实现了Comparator
接口并重写了其中的方法,通常是compare
方法。以下是这个结构的主要要点:
匿名类:
new Comparator()
创建了一个没有名字的类,这个类直接实现了Comparator
接口。匿名类的优势是你可以在定义它的同时实现接口,不需要单独为这个类写一个名称。重写方法:在
{}
中,定义了compare
方法。这实际上是实现了Comparator
接口所需的方法。你可以在这个方法里编写比较逻辑来对int[]
类型的对象进行比较。上下文:匿名内部类可以直接访问它所处的上下文中的变量(包括外部类的
final
变量),因此它可以与外部的状态交互。用法:这种形式一般用于需要一个临时的、一次性的实现类的场合,比如你只在某个地方用到这个比较器。而不需要为比较器单独创建一个类。
re.toArray(new int[])
这段代码的作用是将一个集合(
ans
)转换为一个二维整数数组。具体来说:
ans.toArray(...)
是将集合ans
转换为一个数组的方法。
new int[ans.size()][]
创建了一个新的二维整数数组,其中第一维的大小是ans
的大小。第二维的大小在此时不确定,因为它根据集合中每个元素的实际长度来动态决定。在排序中,排序的正负关系决定了元素的顺序。我们在比较两个元素时需要注意以下几点:
return p2[0] - p1[0]
的含义是:如果p2[0]
大于p1[0]
,则返回正值,这样p2
就会排在p1
前面,从而实现降序。而
return p1[0] - p2[0]
则意味着:如果p1[0]
大于p2[0]
,结果为正值,反而会让p1
变得排在p2
前面,这与我们的目标相悖。