Leetcode hot100之“结合递归+二分“题目详解

1 总结

题目 215 (“数组中的第 K 个最大元素”) 和题目 4 (“寻找两个正序数组的中位数”) 之间的联系主要体现在它们都涉及到寻找一个有序集合中的第 k 个元素的问题。尽管这两个问题的具体应用场景和所处理的数据结构不同,它们共享相似的算法思想和技术。

题目 215 - 数组中的第 K 个最大元素

此题的解决方案涉及到快速选择算法,这是快速排序的一个变体。快速选择算法通过选择一个枢轴来划分数组,并基于枢轴的位置来决定继续在左边或右边搜索目标元素。该方法的目标是找到数组中第 k 个最大的元素。

题目 4 - 寻找两个正序数组的中位数

在这个问题中,目标是找到两个有序数组合并后的中位数。解决方案同样涉及到一种选择方法,即在两个数组中找到第 k 小的元素。这通常通过二分查找来实现,需要在两个数组中找到合适的分割线,以确保左右两侧元素的数量相等(或相差一个)。

共同点

  1. 有序集合中的元素选择:两个问题都涉及到在有序集合中选择特定顺序的元素(第 k 个最大或第 k 个最小)。

  2. 分治和递归思想:这两个问题都可以通过分治法和递归思想来解决。在问题 215 中,快速选择通过递归地划分数组来找到第 k 个最大元素;在问题 4 中,二分查找通过递归或迭代的方式在两个数组中找到正确的中位数位置。

  3. 时间复杂度优化:这两个问题的高效解法都不需要完整地排序整个数组或合并两个数组,而是通过局部排序或局部选择来达到目的,从而优化了时间复杂度。

结论

尽管题目 215 和题目 4 处理的具体问题不同,但它们在算法实现上有着类似的思想和技术。这表明了在算法设计中,不同的问题往往可以通过共享的核心算法思想来解决。

2 题目一:215. 数组中的第K个最大元素

在这里插入图片描述

2.1 答案

做这道题之前,建议先做一下快排,非常有帮助,912. 排序数组

2.1.1 方法一:每一轮区间划分都采用三个list分别存储小于、等于和大于目标值的三个元素集

    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);
        }
    }

2.1.2 方法二(推荐):改进版的划分区间方式,与方法一相比,不需要计算复杂的k值变化,统一使用索引进行比较,方便理解; 与方法三相比,不需要考虑复杂的函数出口条件

  public int findKthLargest(int[] nums, int k) {
        int n=nums.length;
        int ans=dfs2(nums,0, n-1, n-k);

        return ans;
    }
	// 递归版
    int dfs2(int[]nums,int l, int r, int k){
        int pivot=nums[l];
        int p=l,q=r;
        for(int i=l+1;i<=q;){
            if(nums[i]>pivot){
                swap(nums,i,q--);
            }else if(nums[i]<pivot){
                swap(nums,i++,p++);
            }else{
                i++;
            }
        }
        if(p-1>=k){
            return dfs2(nums,l,p-1,k);
        }else if(q>=k){
            return nums[q];
        }else{
            return dfs2(nums,q+1,r,k);
        }
    }

    void swap(int[]nums, int i, int j){
        int t=nums[i];
        nums[i]=nums[j];
        nums[j]=t;
    }
    // 迭代版本
    int getK(int[]nums,int k){
        int p=0,q=nums.length-1;
        while(true){
            int l=p,r=q;
            int base=nums[l];
            for(int i=l+1;i<=r;){
                if(nums[i]<base){
                    swap(nums,i++,l++);
                }else if(nums[i]>base){
                    swap(nums,i,r--);
                }else{
                    i++;
                }
            }
            if(l-1>=k){
                q=l-1;
            }else if(r>=k){
                q=r;
                break; 
            } else{
                p=r+1;
            }
        }
        return nums[q];
    }

2.1.3 方法三:

	public int findKthLargest(int[] nums, int k) {
        int n=nums.length;
		// n-k表示(第k大或者n-k+1小)的所在元素的下标
        int ans=dfs3(nums,0, n-1, n-k);

        return ans;
    }

	int dfs3(int[]nums,int l, int r, int k){
        if(l>=r)return nums[k];
        Random rand=new Random();
        int pivot=rand.nextInt(r-l+1)+l;
        swap(nums,pivot,r);

        int i=partition(nums,l,r);
        if(k<=i)return dfs3(nums,l,i-1,k);
        return dfs3(nums,i,r,k);
    }
    //只划分小于等于和大于目标元素的两个区间
    public int partition(int[] nums, int l, int r) {
        int pivot = nums[r];
        int i = l - 1;
        for (int j = l; j <= r - 1; ++j) {
            if (nums[j] <= pivot) {
                i = i + 1;
                swap(nums, i, j);
            }
        }
        swap(nums, i + 1, r);
        return i + 1;
    }

3 问题二:4. 寻找两个正序数组的中位数

在这里插入图片描述

3.1 答案

     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;
            }
        }
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值