欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

在数组中找到第k大的元素

程序员文章站 2022-03-13 12:32:47
...
给出数组 [9,3,2,4,8],第三大的元素是 4
给出数组 [1,2,3,4,5],第一大的元素是 5,第二大的元素是 4,第三大的元素是 3,以此类推

要求时间复杂度为O(n),空间复杂度为O(1)

public static int kthLargestElement(int k, int[] nums) {
        // write your code here
        if(nums == null || nums.length == 0){
        	return -1;
        }
        int len = nums.length;
        return helper(nums, 0, len - 1, len - k + 1);
        

        
    }
	private static int helper(int[] nums, int l, int r, int k) {
		// TODO Auto-generated method stub
		
		if(l == r){
			return nums[l];
		}
		int position = quitsort(nums, l, r);
		if(position + 1 == k){
			return nums[position];
		}else if(position + 1 < k){
			return helper(nums, position +1 , r, k);
		}else{
			return helper(nums, l, position  - 1, k);
		}
		
		
		
	}
	private static int quitsort(int[] a, int l, int r) {
		// TODO Auto-generated method stub
		int left = l;
		int right = r;
		int key = a[left];
		while(right>left){
             //从后往前比较
             while(right>left&&a[right]>=key)  
                 right--;
             if(a[right]<=key){
                 swap(a, left, right);
             }
             //从前往后比较
             while(right>left&&a[left]<=key)//如果没有比关键值大的,比较下一个,直到有比关键值大的交换位置
                left++;
             if(a[left]>=key){
                 swap(a, left, right);
             }
         
         }
		return left;
	}
	private static void swap(int[] nums, int left, int right) {
		// TODO Auto-generated method stub
		int temp = nums[left];
		nums[left] = nums[right];
		nums[right] = temp;
		
	}
	 
	
};

对于快速排序,算法复杂度是O(N*logN)。而这个算法的算法复杂度是O(N)。为什么呢?

其实这个地方的算法复杂度分析很有意思。第一次交换,算法复杂度为O(N),接下来的过程和快速排序不同,快速排序是要继续处理两边的数据,再合并,合并操作的算法复杂度是O(1),于是总的算法复杂度是O(N*logN)(可以这么理解,每次交换用了N,一共logN次)。但是这里在确定枢纽元的相对位置(在K的左边或者右边)之后不用再对剩下的一半进行处理。也就是说第二次插入的算法复杂度不再是O(N)而是O(N/2),这不还是一样吗?其实不一样,因为接下来的过程是1+1/2+1/4+........ < 2,换句话说就是一共是O(2N)的算法复杂度也就是O(N)的算法复杂度。

/***************************************************************************** 
 函 数 名  : small_heap_adjust 
 功能描述  : 根据数组构建小顶堆  
 输入参数  : array  待调整的堆数组 
             index  待调整的数组元素的位置 
             length 数组的长度 
 输出参数  : 无 
 返 回 值  : 无  
 修改历史      : 
  1.日    期   : 2012/09/10 
    作    者   : liguangting 
    修改内容   :  
*****************************************************************************/  
void small_heap_adjust(int *array, int index, int length)  
{  
    int child;  
    int temp = array[index];  
      
    if (2 * index + 1 >= length)  
    {  
        return;  
    }  
  
    //子结点位置 = 2 * 父结点位置 + 1  
    child = 2 * index + 1;  
          
    //得到子结点中较小的结点   
    if (child < length - 1 && array[child + 1] < array[child])  
    {  
        ++child;  
    }  
              
    //如果较小的子结点小于父结点那么把较小的子结点往上移动,替换它的父结点   
    if (temp > array[child])  
    {  
        array[index] = array[child];  
    }  
    else  
    {  
        return;  
    }  
          
    //最后把需要调整的元素值放到合适的位置   
    array[child] = temp;  
      
    small_heap_adjust(array, child, length);  
}  
  
/***************************************************************************** 
 函 数 名  : find_kmax_value 
 功能描述  : 查找数组中第K大元素  
 输入参数  : array  待查询的数组  
             length 数组的长度 
             K      第K大  
 输出参数  : 无 
 返 回 值  : 返回第K大元素  
 修改历史      : 
  1.日    期   : 2012/09/10 
    作    者   : liguangting 
    修改内容   :  
*****************************************************************************/  
int find_kmax_value(int *array, int length, int k)  
{  
    int i = 0;  
      
    //把子数组array[0...k-1]构造成小顶堆   
    for (i = k / 2 - 1; i >= 0; i--)  
    {  
        small_heap_adjust(array, i, k);  
    }  
      
    //子数组array[k...length-1]的所有元素与堆顶元素进行比较,若大于堆顶元素  
    //则交换,并重新调整堆   
    for (i = k; i < length; i++)  
    {  
        if (array[i] > array[0])  
        {  
            swap(array[0], array[i]);  
            small_heap_adjust(array, 0, k);  
        }  
    }  
      
    return array[0];  
}  

利用堆排序的思想来查询数组中第K大元素。首先提取子数组array[0...K-1]并构造小顶堆,然后把剩下子数组array[K...N-1]中的所有元素与堆顶元素array[0]进行比较,若大于堆顶元素,则进行交换并重新构造子数组array[0...K-1]使其满足小顶堆的要求。这样的话,最后子数组array[0...K-1]就是N个元素中的前K个最大元素,堆顶array[0]就是N个元素中的第K大元素。

时间复杂度为O(NlogK),额外空间为O(K)