思路:
既然数组长度为nnn只包含了0到n−1n-1n−1的数字,那么如果数字没有重复,这些数字排序后将会与其下标一一对应。那我们就可以考虑遍历数组,每次检查数字与下标是不是一致的,一致的说明它在属于它的位置上,不一致我们就将其交换到该数字作为下标的位置上,如果交换过程中,那个位置已经出现了等于它下标的数字,那肯定就重复了。
具体做法:
import java.util.*;
public class Solution {
/**
* 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
*
*
* @param numbers int整型一维数组
* @return int整型
*/
public int duplicate (int[] numbers) {
// write code here
for (int i = 0; i < numbers.length; i++) {
//下标和对应的下标元素值不等,则交换
while (i != numbers[i]) {
//交换前,i位置元素已经等于numbers[i]位置元素
if (numbers[i] == numbers[numbers[i]]) {
return numbers[i];
}
int temp = numbers[numbers[i]];
numbers[numbers[i]] = numbers[i];
numbers[i] = temp;
}
}
return -1;
}
}
思路:
因为我们在归并排序过程中会将数组划分成最小为1个元素的子数组,然后依次比较子数组的每个元素的大小,依次取出较小的一个合并成大的子数组。
//取中间
int mid = (left + right) / 2;
//左右划分合并
merge(divide(left, mid, data, temp), divide(mid + 1, right, data, temp));
这里我们也可以用相同的方法划分,划分之后相邻一个元素的子数组就可以根据大小统计逆序对,而不断往上合并的时候,因为已经排好序了,我们逆序对可以往上累计。我们主要有以下三个阶段。
具体做法:
public class Solution {
int count = 0;
public int InversePairs(int [] array) {
if (array == null || array.length <= 1) {
return 0;
}
//先建好一个长度等于原数组长度的临时数组,避免递归中频繁开辟空间
int[] temp = new int[array.length];
mergeSort(array, 0, array.length - 1, temp);
return count;
}
private void mergeSort(int[] array, int left, int right, int[] temp) {
if (left == right) {
return;
}
int mid = left + (right - left) / 2;
//左边归并排序,使得左子序列有序
mergeSort(array, left, mid, temp);
//右边归并排序,使得右子序列有序
mergeSort(array, mid + 1, right, temp);
//将两个有序子数组合并操作
merge(array, left, mid, right, temp);
}
//mid代表左半边的最后一个元素位置
private void merge(int[] array, int left, int mid, int right, int[] temp) {
//指向左边的第一个位置
int i = left;
//指向右边的第一个位置
int j = mid + 1;
//指向临时数组的第一个位置
int k = 0;
while (i <= mid && j <= right) {
if (array[i] > array[j]) {
temp[k++] = array[j++];
count = (count + mid - i + 1) % 1000000007;
} else {
temp[k++] = array[i++];
}
}
//将左边剩余元素填充进temp中
while (i <= mid) {
temp[k++] = array[i++];
}
//将右序列剩余元素填充进temp中
while (j <= right) {
temp[k++] = array[j++];
}
k = 0;
//将temp中的元素全部拷贝到原数组中
while (left <= right) {
array[left++] = temp[k++];
}
}
}
思路:
要找到最小的k个元素,只需要准备k个数字,之后每次遇到一个数字能够快速的与这k个数字中最大的值比较,每次将最大的值替换掉,那么最后剩余的就是k个最小的数字了。
如何快速比较k个数字的最大值,并每次替换成较小的新数字呢?我们可以考虑使用优先队列(大根堆),只要限制堆的大小为k,那么堆顶就是k个数字的中最大值,如果需要替换,将这个最大值拿出,加入新的元素就好了。
//较小元素入堆
if(q.peek() > input[i]){
q.poll();
q.offer(input[i]);
}
具体做法:
import java.util.*;
public class Solution {
public ArrayList<Integer> GetLeastNumbers_Solution(int [] input, int k) {
ArrayList<Integer> list = new ArrayList<>();
if (input.length == 0 || k == 0) {
return list;
}
// o1 - o2为小顶堆,o2-o1为大顶堆
PriorityQueue<Integer> queue = new PriorityQueue<>((o1, o2) -> o2 - o1);
for (int i = 0; i < input.length; i++) {
queue.add(input[i]);
if (queue.size() > k) {
queue.poll();
}
}
while (!queue.isEmpty()) {
list.add(0, queue.poll());
}
return list;
}
}
除了插入排序,我们换种思路,因为插入排序每次要遍历整个已经有的数组,很浪费时间,有没有什么可以找到插入位置时能够更方便。
我们来看看中位数的特征,它是数组中间个数字或者两个数字的均值,它是数组较小的一半元素中最大的一个,同时也是数组较大的一半元素中最小的一个。那我们只要每次维护最小的一半元素和最大的一半元素,并能快速得到它们的最大值和最小值,那不就可以了嘛。这时候就可以想到了堆排序的优先队列。
具体做法:
import java.util.*;
public class Solution {
//小顶堆,存放较大的数
PriorityQueue<Integer> min = new PriorityQueue<>();
//大顶堆,存放较小的数
PriorityQueue<Integer> max = new PriorityQueue<>((o1, o2) -> o2 - o1);
public void Insert(Integer num) {
//要加入的数为第奇数个
if(max.size() == min.size()){
max.add(num);
min.add(max.poll());
//要加入的数为第偶数个
}else{
min.add(num);
max.add(min.pol1l());
}
}
public Double GetMedian() {
if(max.size() == min.size()){
return (min.peek() + max.peek()) / 2.0;
}else{
return min.peek() * 1.0;
}
}
}