思路
贪心
类似于田忌赛马
对于任意的nums2[i]
,找到nums1
中大于nums2[i]
的元素中的最小值,这样可以使得对于后续的元素,能够得到的优势更大。
暴力做法的时间复杂度为O(n^2),显然会超时。
容易想到的一种方法是将nums1
进行排序,然后对于每个nums2[i]
去num1
中进行二分查找,找到第一个大于它的数,
若不存在nums[j] > nums2[i]
,则下标i
位置上应放上nums1
余下元素中最小的一个(好像不太对?),可以使得优势最大化。
此时存在一个问题,就是
nums1
中每次会被取出一个元素,然后该元素应该从数组中移除,而移除的方法时间复杂度又是O(n)的,导致最终的时间复杂度是O(n^2)。
因此二分查找的思路也不太可行。从田忌赛马的思想来考虑:对于nums1
中最小的元素,
- 如果比
nums2
中最小的元素大,则可以得到一个优势位置 - 如果没有
nums2
中最小的元素大,则与nums2
中最大的元素抵掉。
此时nums1
和nums2
中各剩下n-1
个元素,变成了一个相同的子问题,继续进行上述判断并填放元素即可。
从上面的考虑可以看出,需要将nums1
和nums2
都进行排序,而nums2
排序后还需要保留其原始下标位置。
经过上述考虑后想到的代码如下:
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums1.length;
int[] ans = new int[n];
//visit[i]=true表示nums1[i]已经被判断过
boolean[] visit = new boolean[n];
Arrays.sort(nums1);
ArrayList<int[]> list = new ArrayList<>();
for (int i = 0; i < n; i++) {
list.add(new int[]{nums2[i], i});
}
list.sort(Comparator.comparingInt(a -> a[0]));
int i, j = 0;
for (i = 0; i < n; i++) {
int[] pair = list.get(i);
int val = pair[0];
int idx = pair[1];
while(j<n && (visit[j] || nums1[j] <= val)){
j++;
}
if (j<n){
ans[idx] = nums1[j];
visit[j] = true;
} else {
break;
}
}
//此时在nums1中已经找不到大于list.get(i)[0]的数了,j=n
//因此 nums1[j]&&!visit[j] 倒序加入ans中
j = n-1;
while(i < n){
int[] pair = list.get(i);
int val = pair[0];
int idx = pair[1];
while(j >= 0 && visit[j]){
j--;
}
ans[idx] = nums1[j--];
i++;
}
return ans;
}
}
没有想到的地方:两个方向上可以同时进行,这里一个循环只做了一个方向上的填充。
更优雅的代码
参考灵佬解析。
class Solution {
public int[] advantageCount(int[] nums1, int[] nums2) {
int n = nums1.length;
Arrays.sort(nums1);
Integer[] ids = new Integer[n];
int[] ans = new int[n];
for (int i = 0; i < n; i++) {
ids[i] = i;
}
Arrays.sort(ids, Comparator.comparingInt(i -> nums2[i]));
//双指针,指向排序后nums2的左右边界
int l = 0, r = n-1;
for (int i = 0; i < n; i++) {
if (nums1[i] > nums2[ids[l]]){
ans[ids[l]] = nums1[i];
l++;
} else {
ans[ids[r]] = nums1[i];
r--;
}
}
return ans;
}
}