算法学习【6】寻找最小的k个,

1、输入n个整数,输出其中最小的k个。

      思路1:先快速排序,然后取前k个数。时间复杂度:O(n * log n)+O(k)=O(n * log n)


      思路2:a)定义一个长度为k的数组result,将前k个整数放入result;

                  b)采用选择或交换排序找到result数组的最大值max;

                  c)依次遍历后n-k个整数,若其>max,说明其不是最小k个数之一并跳过,若其<max,则使max = 该数,并重复b)过程。

                  这种算法最差情况下的时间复杂度:O(k)+(n-k)*O(k)=O(n*k)


      思路3:a)选择一个数pivot将数组分成两部分Sa和Sb,使得Sa的元素都小于等于pivot,Sb的元素都大于pivot;

                  b)如果k<Sa元素个数,则返回Sa中较小最小的k个元素,即递归Sa;

                  c)如果k=Sa元素个数,则Sa中元素就是最小的k个元素,

                  d)如果k>Sa元素个数,则返回Sa中所有元素和Sb中(k-|Sa|)个元素,即递归Sb。

                  这种平均时间复杂度:O(n*logk),二分法!

      

      思路1代码如下:

<span style="font-size:14px;">public class CodeFour{
	public static void main(String[] args){
		int[] num = new int[]{1,5,5,6,8,10,131,2,1,3};
		mySort(num,0,num.length-1);
		int k = 3;
		for(int i=0;i<k;i++){
			System.out.print(num[i]+" ");
		}
	}

	public static void mySort(int[] arr,int left, int right){
		if(right-left<=1){
			if(arr[left]>arr[right])
				swap(arr,left,right);
			return;
		}
		int i = left;
		int j = right;
		int pivot = arr[i];

		while(left<right){
			while(arr[right]>pivot&&left<right){
				right--;
			}
			while(arr[left]<=pivot&&left<right){
				left++;
			}
			if(left<right)
				swap(arr,left,right);
		}
		swap(arr,i,right);
		if(i<right-1)
			mySort(arr,i,right-1);
		if(right+1<j)
			mySort(arr,right+1,j);
	}

	public static void swap(int[] arr, int index1, int index2){
		int temp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = temp;
	}
}</span>

      思路2代码如下:

<span style="font-size:14px;">public class CodeThree{
	public static void main(String[] args){
		int[] arr = new int[]{1,5,5,6,8,10,131,2,1,3};
		int k = 3;
		minK(arr,k);
	}

	public static void minK(int[] arr, int k){
		int[] result = new int[k];
		for(int i=0;i<k;i++){
			result[i] = arr[i];
		}
		findMax(result,k);
		for(int i=k;i<arr.length;i++){
			if(arr[i]<result[0]){
				result[0] = arr[i];
				findMax(result,k);
			}
		}
		for(int i=0;i<k;i++){
			System.out.print(result[i]+" ");
		}
	}

	public static void findMax(int[] arr, int len){
		for(int i=1;i<len;i++){
			if(arr[i]>arr[0])
				swap(arr,i,0);
		}
	}

	public static void swap(int[] arr, int index1, int index2){
			int temp = arr[index1];
			arr[index1] = arr[index2];
			arr[index2] = temp;
	}
}
</span>

      思路3代码如下:

<span style="font-size:14px;">public class CodeOne{
	public static void main(String[] args){
		int[] arr = new int[]{5,4,3,2,1};
		int k = 3;
		minK2(arr,0,arr.length-1,k);
	}

	public static void minK2(int[] arr, int left, int right,int k){
		int pivot = arr[left];
		int i = left;
		int j = right;

		while(i<j){
			while(arr[j]>pivot&&i<j){
				j--;
			}
			while(arr[i]<=pivot&&i<j){
				i++;
			}
			if(i<j)
				swap(arr,i,j);
		}
		swap(arr,left,j);

		if(i-left+1==k){
			for(int c=left;c<=i;c++)
				System.out.println(arr[c]);
			return;
		}if(i-left+1>k){
			minK2(arr,left,i,k);
		}if(i-left+1<k){
			for(int c=left;c<=i;c++)
				System.out.println(arr[c]);
			minK2(arr,i+1,right,k-(i-left+1));
		}
	}

	public static void swap(int[] arr, int index1, int index2){
		int temp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = temp;
	}
}
</span>


2、两个升序序列A=(a1,...,ak)和B=(b1,...,bk),求k个最小的(ai+bj),其中1<i, j<n

      思路1:两个数组相加得到k*k个数,转变成求新数组的k个最小值的问题;

                   时间复杂度:O(k*k)+(k*k)*O(k)=O(k*k*k)

      思路2:a)找到最小的整数n,满足n*(n+1)/2>=k;

                   b)若n*(n+1)/2=k,则满足 i+j<=n 的n*(n+1)/2个数(arr1[i]+arr2[j])即为所求,输出结果程序结束;

                   c)若n*(n+1)/2>k,则满足 i+j<=n-1 的(n-1)*n/2个数属于所求,另外k-(n-1)*n/2)个结果在满足 i+j=n 的n个数中;

                   d)求满足 i+j=n 的n个数中最小的k-(n-1)*n/2)个数,即上文题1

                   采用快速排序方法下的时间复杂度:O(sqrt(k))+O(sqrt(k) * logsqrt(k))= O(sqrt(k)* log sqrt(k))


      对上述算法解释一下,因为自己想到的,好得意,啊哈哈~欢迎打脸~~~

      A、B两个数组中数两两相加可得k*k个数,形成一个新的二维数组 C[i, j] = A[i] + B[j],要求的就是数组 C 中的k个最小值因为数组A、B是升序序列,则一定有C[i, j]<=C[m, n],其中m+n>i+j。

      以A={1,2,3,4,5}、B={3,4,5,6,7}为例,k=5,满足n*(n+1)/2>=k的最小正整数为n=3,则最小的k个数一定在i+j<n(i,j从0开始)中。


      从上图可以看出,在不计算数组C中元素值的情况下,仅凭索引号可知越靠近左上角值越小,处于同一45°直线上(图中以同一颜色标出)元素之间的大小必须通过计算才能得知。k个最小数也都在左上角,k=5,n=3,则属于 i+j<n-1 范围内的(n-1)*n/2个数一定属于k个最小数,剩下的数都在满足i+j = n-1的直线上,因此仅需要对该直线上的元素判断即可。


      思路2代码如下:

	public static void myfun(int[] arr1, int[] arr2, int k){
		int n=1;
		while(n*(n+1)/2<k){
			n++;
		}

		for(int i=0;i<n-1;i++)
			for(int j=0;j<n-i-1;j++)
				System.out.println((arr1[i]+arr2[j]));

		if(n*(n+1)/2==k){
			for(int i=0;i<n;i++)
				System.out.println((arr1[i]+arr2[n-i-1]));
		}else{
			//满足i+j=n-1的数共有n个,找出这n个数中前(k-(n-1)*n/2)个最小值即可
			int[] num = new int[n];
			for(int i=0;i<n;i++){
				num[i]=arr1[i]+arr2[n-i-1];
			}
			minK(num, k-(n-1)*n/2);
		}
	}


      其他思路:最小堆(http://www.lxway.com/285890664.htm)、辅助数组(http://blog.csdn.net/sunnianzhong/article/details/8932374)




  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值