这是我参与2022首次更文挑战的第2天,活动详情查看:2022首次更文挑战
题目1 - 存在重复元素
分析
首先是范围:
- 1 <= nums.length <= 10^5^
- -10^9^ <= nums[i] <= 10^9^
- 0 <= k <= 10^5^
既然nums[i]范围这么大,就不好用桶排序的方式来确定元素分布顺序了。
一个简单的方式是:维护一个长度为k的set,
- 遇到一个数存一个数,发现原来有了那就返回true;
- 如果达到k了还没有遇到,那就把第一个加入的元素移除,这个可以通过k以及当前索引确定。
那么先试试这个方案,代码如下:
public static boolean containsNearbyDuplicate(int[] nums, int k) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if(set.size()>k){
set.remove(nums[i-k-1]);
}
if(set.contains(nums[i])){
return true;
}
set.add(nums[i]);
}
return false;
}
可以AC,结果如下:
执行用时:20 ms, 在所有 Java 提交中击败了44.02%的用户
内存消耗:53.2 MB, 在所有 Java 提交中击败了5.10%的用户
题目2 - 优势洗牌
题目:leetcode-cn.com/problems/ad…
分析
所求的是:
- 令A中对应位置的元素比B大的次数最多的排列。
那么做以下分析:
- 对于B中任意位置而言,取A中按顺序排列下恰好比B该位置的数大的数,留着更大的数来让B后面的数再次匹配;
- 如果不存在,那么就取最小的数和这个B对应,让后面能尽可能地比B对应位置的数大。
那么就需要做:
- 对A做排序,方便每一次取恰好大的数和最小的数。
根据这个思路拟定一个基础的版本:
public static int[] advantageCount(int[] nums1, int[] nums2) {
Set<Integer> select = new HashSet<>();
Arrays.sort(nums1);
int[] res = new int[nums1.length];
int bottom = 0;
for (int i = 0; i < nums2.length; i++) {
boolean flag = false;
for (int j = 0 ;j < nums1.length;j++){
if(select.contains(j)) continue;
if(nums2[i]<nums1[j]){
res[i] = nums1[j];
select.add(j);
flag = true;
break;
}
}
if(!flag){
while(select.contains(bottom)) bottom++;
select.add(bottom);
res[i] = nums1[bottom];
bottom++;
}
}
return res;
}
结果直接OT了。
考虑一下,这样有效率上的问题:
- B中的数并不是有序的,每次找数都需要对A进行一次从头的遍历。从效率上来说,就是O(n^2^)。
那么我们试着将查找的逻辑改为二分查找,这样子时间复杂度就会变成O(nlogn);然而这样子编码会比较麻烦,因为此种方法还得考虑已经选择的值,边界不好确定。
那么我们考虑自己做对应的idMapping,将b同时做排序并让之与原本位置的索引位置做对应:
/**
* int[0]:整理好的,int[1]:原本索引位置
*/
public static int[][] sortWithIdx(int[] num){
int[][] res = new int[2][num.length];
res[0] = num;
for (int i = 0; i < res[1].length; i++) res[1][i] = i;
sortR(res[0],res[1],0,num.length-1);
return res;
}
public static void sortR(int[] num,int[] idx,int start,int end){
if(start>=end) return;
int val = num[start];
int tmpL = start,tmpR = end;
while(tmpL<tmpR){
while(tmpR>=tmpL&&num[tmpR]>=val) tmpR--;
while(tmpL<tmpR&&num[tmpL]<=val) tmpL++;
if(tmpL<tmpR){
swap(num,tmpL,tmpR);
swap(idx,tmpL,tmpR);
}
}
swap(num,tmpL,start);
swap(idx,tmpL,start);
sortR(num,idx,tmpL+1,end);
sortR(num,idx,start,tmpL-1);
}
public static void swap(int[] num,int tmpL,int tmpR){
int tmp = num[tmpR];
num[tmpR] = num[tmpL];
num[tmpL] = tmp;
}
这里提供的方法是做一个自己的排序;同时考虑到最低值和往前计算填入的值可能会撞的问题,丢到一个set里做记录。
public static int[] advantageCount2(int[] nums1, int[] nums2){
int n = nums2.length;
Arrays.sort(nums1);
int[][] sortRes = sortWithIdx(nums2);
int j = 0,bottom = 0;
int[] res = new int[n];
Set<Integer> bottomSkip = new HashSet<>(n);
for (int i = 0; i < n; i++) {
while(j<n && nums1[j]<=sortRes[0][i])j++;
if(j==n){
while(bottomSkip.contains(bottom)) bottom++;
res[sortRes[1][i]] = nums1[bottom];
bottomSkip.add(bottom);
}else{
res[sortRes[1][i]] = nums1[j];
bottomSkip.add(j);
j++;
}
}
return res;
}
这样时间复杂度就是2个二分排序加上对于两个数组的单次顺序查询,复杂度为O(nlogn) (排序A)+ O(nlogn)(排序B) + O(n)(读取B) + O(n) (读取A),最终为O(nlogn)。
结果:
执行用时:38 ms, 在所有 Java 提交中击败了99.69%的用户
内存消耗:54.6 MB, 在所有 Java 提交中击败了57.44%的用户