题目 215 (“数组中的第 K 个最大元素”) 和题目 4 (“寻找两个正序数组的中位数”) 之间的联系主要体现在它们都涉及到寻找一个有序集合中的第 k 个元素的问题。尽管这两个问题的具体应用场景和所处理的数据结构不同,它们共享相似的算法思想和技术。
此题的解决方案涉及到快速选择算法,这是快速排序的一个变体。快速选择算法通过选择一个枢轴来划分数组,并基于枢轴的位置来决定继续在左边或右边搜索目标元素。该方法的目标是找到数组中第 k 个最大的元素。
在这个问题中,目标是找到两个有序数组合并后的中位数。解决方案同样涉及到一种选择方法,即在两个数组中找到第 k 小的元素。这通常通过二分查找来实现,需要在两个数组中找到合适的分割线,以确保左右两侧元素的数量相等(或相差一个)。
有序集合中的元素选择:两个问题都涉及到在有序集合中选择特定顺序的元素(第 k 个最大或第 k 个最小)。
分治和递归思想:这两个问题都可以通过分治法和递归思想来解决。在问题 215 中,快速选择通过递归地划分数组来找到第 k 个最大元素;在问题 4 中,二分查找通过递归或迭代的方式在两个数组中找到正确的中位数位置。
时间复杂度优化:这两个问题的高效解法都不需要完整地排序整个数组或合并两个数组,而是通过局部排序或局部选择来达到目的,从而优化了时间复杂度。
尽管题目 215 和题目 4 处理的具体问题不同,但它们在算法实现上有着类似的思想和技术。这表明了在算法设计中,不同的问题往往可以通过共享的核心算法思想来解决。
做这道题之前,建议先做一下快排,非常有帮助,912. 排序数组
public int findKthLargest(int[] nums, int k) {
int n=nums.length;
List<Integer>list=new ArrayList<>();
for(int i=0;i<n;i++){
list.add(nums[i]);
}
int ans=dfs(list,k);
return ans;
}
int dfs(List<Integer>list,int p){
Random rand=new Random();
int base=list.get(rand.nextInt(list.size()));
List<Integer>s=new ArrayList<>();
List<Integer>m=new ArrayList<>();
List<Integer>l=new ArrayList<>();
for(int k=0;k<list.size();k++){
if(list.get(k)<base){
s.add(list.get(k));
}else if(list.get(k)==base){
m.add(list.get(k));
}else{
l.add(list.get(k));
}
}
// 在数组中的第p大等于在数组中的第list.size()-p+1大
if(s.size()>=list.size()-p+1){
// 第二个参数决定在small数组中,这个数是第几大的数
return dfs(s,s.size()-(list.size()-p+1)+1);
}else if(s.size()+m.size()>=list.size()-p+1){
return m.get(0);
}else{
return dfs(l,p);
}
}
public int findKthLargest(int[] nums, int k) {
int n=nums.length;
List<Integer>list=new ArrayList<>();
for(int i=0;i<n;i++){
list.add(nums[i]);
}
int ans=dfs(list,k);
return ans;
}
// 使用不同的划分区间算法,将数组划分成三个区域:小于、等于和大于
int dfs2(int[]nums,int l, int r, int k){
Random rand=new Random();
int pivot=nums[l];
int p=l,q=r;
for(int i=l;i<=r;){
if(nums[i]>pivot){
swap(nums,i,q--);
}else if(nums[i]<pivot){
swap(nums,i,p++);
}else{
i++;
}
}
int len=r-l+1;
if(p>=len-k+1){
return dfs2(nums,l,p-1,p-(len-k+1)+1);
}else if(q>=len-k+1){
return dfs2(nums,q+1,r,k);
}else{
return nums[q];
}
}
void swap(int[]nums, int i, int j){
int t=nums[i];
nums[i]=nums[j];
nums[j]=t;
}
public double findMedianSortedArrays2(int[] nums1, int[] nums2) {
int length1 = nums1.length, length2 = nums2.length;
int totalLength = length1 + length2;
if (totalLength % 2 == 1) {
int midIndex = totalLength / 2;
double median = getKthElement(nums1, nums2, midIndex + 1);
return median;
} else {
int midIndex1 = totalLength / 2 - 1, midIndex2 = totalLength / 2;
double median = (getKthElement(nums1, nums2, midIndex1 + 1) + getKthElement(nums1, nums2, midIndex2 + 1)) / 2.0;
return median;
}
}
public int getKthElement(int[] nums1, int[] nums2, int k) {
/* 主要思路:要找到第 k (k>1) 小的元素,那么就取 pivot1 = nums1[k/2-1] 和 pivot2 = nums2[k/2-1] 进行比较
* 这里的 "/" 表示整除
* nums1 中小于等于 pivot1 的元素有 nums1[0 .. k/2-2] 共计 k/2-1 个
* nums2 中小于等于 pivot2 的元素有 nums2[0 .. k/2-2] 共计 k/2-1 个
* 取 pivot = min(pivot1, pivot2),两个数组中小于等于 pivot 的元素共计不会超过 (k/2-1) + (k/2-1) <= k-2 个
* 这样 pivot 本身最大也只能是第 k-1 小的元素
* 如果 pivot = pivot1,那么 nums1[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums1 数组
* 如果 pivot = pivot2,那么 nums2[0 .. k/2-1] 都不可能是第 k 小的元素。把这些元素全部 "删除",剩下的作为新的 nums2 数组
* 由于我们 "删除" 了一些元素(这些元素都比第 k 小的元素要小),因此需要修改 k 的值,减去删除的数的个数
*/
int length1 = nums1.length, length2 = nums2.length;
int index1 = 0, index2 = 0;
int kthElement = 0;
while (true) {
// 边界情况
if (index1 == length1) {
return nums2[index2 + k - 1];
}
if (index2 == length2) {
return nums1[index1 + k - 1];
}
if (k == 1) {
return Math.min(nums1[index1], nums2[index2]);
}
// 正常情况
int half = k / 2;
int newIndex1 = Math.min(index1 + half, length1) - 1;
int newIndex2 = Math.min(index2 + half, length2) - 1;
int pivot1 = nums1[newIndex1], pivot2 = nums2[newIndex2];
if (pivot1 <= pivot2) {
k -= (newIndex1 - index1 + 1);
index1 = newIndex1 + 1;
} else {
k -= (newIndex2 - index2 + 1);
index2 = newIndex2 + 1;
}
}
}