题目描述
输入n个整数,输出其中最小的k个。(此题也可引申为求最大的k个数或求最小/最大的第k个数,最小的第k个数为arr[k-1])。
分析与解法
1.直接对数组排序,for循环遍历输出。排序方法:Java自带的Array.sort()方法或快速排序方法。快速排序平均所费时间为n*logn,然后再遍历序列中前k个元素输出即可。因此,总的时间复杂度:O(n * log n)+O(k)=O(n * log n)。
//先将数组排序然后找到最小的k个数
public static void Sortselect(int[] a, int n) {
//Arrays.sort(a);//用Java自带数组排序方法
QuickSort(a, 0, a.length - 1);//用快排
for (int i = 0; i < n; i++) {
System.out.println(a[i]);
}
//System.out.println(a[a.length - n]);//第k小的元素
}
快速排序方法:QuickSort(int[] arr, int begin, int end),该排序算法关键在于寻找到临界值int index =partition(int[] arr, int begin, int end)。有两种方法,1.1.左右指针法:一头一尾两个指针往中间扫描,如果头指针遇到的数比主元大且尾指针遇到的数比主元小,则交换头尾指针所分别指向的数字。定义两个变量left和right,left指向首,right指向尾。再设定一个关键值为key=arr[begin],若key设定为最左边则right先开始移动,反之则left先移动。right从后往前找到第一个不大于key的元素停止,left从前往后找到第一个不小于key的元素停止,交换left和right所对应的元素,直到left= end,while循环结束。然后交换begin(key)和left,返回left或right=index(临界值)。此时key左边全为小于它的元素,key右边全为大于它的元素。分为两个数组,arr[begin,index-1]和arr[index+1,end],然后递归调用这两个数组。
public static int partition(int[] arr, int begin, int end) {
int key = arr[begin];//若选取最左边为key,则end先开始走
//定义left和right两个变量,left指向最左边,right指向最右边,来与key做比较
int left = begin;
int right = end;
while (left < right) {
while (arr[right] >= key && right > left) {
right--;
}
while (arr[left] <= key && left < right) {
left++;
}
swap(arr, left, right);
}
swap(arr, begin, left);
return left;
}
1.2.快慢指针法:快慢指针法。以最右边为基准,fast指针指向数组首位置,slow指针指向fast前一个位置。For循环遍历数组,fast指针指向小于基准的数时,slow+1,且slow不等于fast时交换,循环结束后,交换基准值arr.length-1与slow+1,返回slow+1=index。分别递归再进行左右两数组的排序。QuickSort(arr, begin, index1 - 1);QuickSort(arr, index1 + 1, end);
//快排的快慢指针实现,先在数组中选择一个数字为比较值,把数组中的数字分为两部分,比选择的数字小的数字移到数组的左边,比选择的数字大的数字移到数组的右边。
//以最右边为基准,fast指针指向小于基准的数时,slow+1且不等于fast,交换,循环结束后,交换基准值与slow+1,分别递归再进行左右两数组的排序
public static int partition1(int[] arr, int begin, int end) {
if (begin >= end) {
return begin;
}
int slow = begin - 1;
int pivot = arr[end];
for (int fast = begin; fast < end; fast++) {
if (arr[fast] < pivot) {
slow++;
if (slow != fast) {
swap(arr, fast, slow);
}
}
}
swap(arr, slow + 1, end);
return slow + 1;
}
//用快速排序对数组排序
//partition过程有以下两种实现:
//一头一尾两个指针往中间扫描,如果头指针遇到的数比主元大且尾指针遇到的数比主元小,则交换头尾指针所分别指向的数字;
//一前一后两个指针同时从左往右扫,如果前指针遇到的数比主元小,则后指针右移一位,然后交换各自所指向的数字。
public static void QuickSort(int[] arr, int begin, int end) {
int index = partition(arr, begin, end);
//begin..left-1 left left+1...end
if (index > begin) { //边缘条件限制,必须加,否则会导致访问数组以外的地址,溢出
QuickSort(arr, begin, index - 1);
}
if (index < end) { //同上
QuickSort(arr, index + 1, end);
}
}
public static void QuickSort1(int[] arr, int begin, int end) {
int index1 = partition1(arr, begin, end);
QuickSort(arr, begin, index1 - 1);
QuickSort(arr, index1 + 1, end);
}
2.进一步想想,题目没有要求最小的k个数有序,也没要求最后n-k个数有序。既然如此,就没有必要对所有元素进行排序。这时,咱们想到了用选择或交换排序。SwapSelect(int[] n, int k)For循环遍历将最先遍历到的k个数放入k数组,n1放入剩余的数据。先遍历k数组找出其中最大值kmax,下标为maxIndex,然后遍历n1数组,用kmax进行比较,若kmax>n1[i],交换元素, k[maxIndex]与n1[i],刷新k数组的最大值,后重复此过程。输出karr[k-1].找最大值需要遍历这k个数,时间复杂度为O(k),
每次遍历,更新或不更新数组的所用的时间为O(k)
或O(0)
。故整趟下来,时间复杂度为n*O(k)=O(n*k)
。
//不排序,将n个数的数组分成k和n-k两个数组,先遍历找出k数组的最大值max,再遍历n-k数组,将元素x与max进行比较,若小于则替换;
// 并回到第二步重新找出k个元素的数组中最大元素kmax‘;如果x >= kmax,则继续遍历不更新数组
public static void SwapSelect(int[] n, int k) {
int[] karr = new int[k];
//把最先遍历到的k个数放入k数组
for (int i = 0; i < k; i++) {
karr[i] = n[i];
}
int[] n1 = new int[n.length - karr.length];//n1为n-k数组,存储剩余的数据
System.arraycopy(n, k, n1, 0, n1.length);
for (int i = 0; i < karr.length; i++) {
int kmax = karr[0];
//遍历找出k数组的最大值max
int j, maxIndex = 0;
for (j = 1; j < karr.length; j++) {
if (karr[j] > kmax) {
kmax = karr[j];
maxIndex = j;
}
}
//遍历n-k数组,将元素x与max进行比较,若小于则替换
for (int b = 0; b < n1.length; b++) {
if (kmax > n1[b]) {
//交换两个数组的元素
int temp = n1[b];
n1[b] = karr[maxIndex];
karr[maxIndex] = temp;
}
}
}
System.out.println(Arrays.toString(karr));//最小的k个数
//System.out.println(karr[k - 1]);
//求第k小的数,先求出k数组的最大值kmax,再与n-k数组找到元素比较,把小于kmax的元素替换
//求第k大的数,先求k数组的最小值,再比较,把大元素替换进来
}
3.快排亲兄弟-快速选择算法。求第k小的数,该算法类主要逻辑在于类似于快排取出临界值p ,partition(int[] arr, int begin, int end),该方法返回临界值p,若k<p,那么最小的k个数一定在p的左边begin...p-1,若k>p,那么最小的k个数一定在p的右边p+..end.若相等,返回第k小的元素,arr[p-1];在平均情况下,时间复杂度为O(N)
。
//快速选择算法求第k小的数
//对比快排先找出临界点p,若p<k,那么k一定在p+1-end之间,反之则在0-p-1之间
public static int findKLargest(int[] arr, int k) {
int begin = 0, end = arr.length - 1;
while (begin <= end) {
int p = partition(arr, begin, end);
if (p < k) {
//第k小的元素在p+1...end
begin = p + 1;
} else if (p > k) {
//第k小的元素在begin...p-1
end = p - 1;
} else {
//找到第K小的元素
return arr[p - 1];
}
}
return -1;
}
4. 扩展练习:输入是两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数怎么做?Get_SumArrayofmin(int[] a, int[] b, int k) A数组有n个元素,B数组有m个元素,那么他们的和就有n*m个结果,假设A数组和B数组都为有序数组,那么A[1]+B[1]<A[1]+B[2]<A[1]+B[3]<… ,A[2]+B[1]<A[2]+B[2]<A[2]+B[3]<…,那么就是求A[n]+B[m]数组最小的k个数。先将A,B数组排序,定义一个和数组,len=A.length*B.length。双重for循环遍历,将每一个结果存入数组。求该数组的前k个最小数(可采用以上三种方法)。
//Excise1,输入两个整数数组,他们任意两个数的和又可以组成一个数组,求这个和中前k个数
public static void Get_SumArrayofmin(int[] a, int[] b, int m) {
//对两个数组排序
QuickSort(a, 0, a.length - 1);
QuickSort(b, 0, b.length - 1);
int[] sums = new int[a.length * b.length];
Stack<Integer> stack = new Stack<>();
for (int i = 0; i < a.length; i++) {
for (int j = 0; j < b.length; j++) {
stack.add(a[i] + b[j]);//利用栈来存储和数组
}
}
while (!stack.empty()) {
for (int i = 0; i < sums.length; i++) {
sums[i] = stack.pop();//将栈中的数据存到数组
}
}
System.out.println(findKLargest(sums, k));//采用快速选择解法
}
5.类似于快慢指针,介绍插入排序算法。
插入排序将数组分为有序和无序两部分,将无序数组的元素取出,与有序数组中的元素比较并添加进去。在内层循环中,j一直和j+1所指向的元素进行比较,若大于,则进行类似于冒泡排序的交换,j又指向有序数组的末尾元素,j+1则变成了原来j指向的元素。 for (int i = 1; i < arr.length; i++) {for (int j = i - 1; j >= 0; j--) {
if (arr[j] > arr[j + 1]) {
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
} elsebreak;}}
刚开始有序数组放入第一个元素也是有序的因此i从1开始,j指向i-1,该位置是有序数组的末尾位置,要跟有序数组里面所有元素比较插入到适当位置,因此j--,j+1指向新加入的元素。
//插入排序 314,298,508,123,486,145
//将数组分为有序和无序两部分,将无序数组的元素取出,与有序数组中的元素比较并添加进去。
//在内层循环中,j一直和j+1所指向的元素进行比较,若大于,则进行类似于冒泡排序的交换,j--,j指向有序数组末尾前一个位置的元素,j+1又指向了新元素,新元素与之前的元素进行对比
public static void insertSort(int[] arr) {
for (int i = 1; i < arr.length; i++) {
for (int j = i - 1; j >= 0; j--) {//移动新元素到有序数组,j指向i-1,该位置是有序数组的末尾位置
if (arr[j] > arr[j + 1]) {//j+1指向新加入的元素
int temp = arr[j];
arr[j] = arr[j + 1];
arr[j + 1] = temp;
} else
break;
}
}
System.out.println(Arrays.toString(arr));
}