图文详解 | #牛的体重排序#

牛的体重排序

https://www.nowcoder.com/practice/1afd5afef2aa49a8a39b63bb9d2821f9

思路:二分查找

两个数组都是从小到大排好了的,找出中位数,只需要归并成一个从小到大排好的数组,返回中间的数就可以了。但这么做的时间复杂度是O(m+n),而题目要求的是O(log(m+n)),这就意味着我们甚至不能把数组里的数都看一遍。这和二分查找很像,都是从小到大排好的数组,都是O(log(n))时间复杂度。但这里我们有两个数组,把两个数组都掰成两段后,我们就会得到四段:

我们要找到AB整体的中位数,它在这四个段的哪个段里呢?它应该在AB归并成的数组的中间:

注意到,AB整体被两个中轴分成了三段,并且AB整体的三段跟A的两段、B的两段有关系:

整体的左段来自A的左段和B的左段,整体的中段来自A的右段和B的左段,整体的右段来自A的右段和B的右段。

有些段是不可能存在中位数的,我们可以将这些段排除掉:

一轮轮淘汰,将数组A和数组B分之又分,最终剩下的,就是我们要找的中位数。

以上就是解决该问题的大致思路,如果还想知道更多细节的话,可以继续往下看。

难点:将问题转化成找第k小的数

在总长度是偶数的情况下,找中位数需要找两个数,而这两个数可能都在A,可能都在B,也可能A,B各一个。如果我们写的函数能找任意第k小的数,那就不必这么麻烦分类讨论了。在偶数的情况下,我们可以分两次找,第一次找第size/2小的数,第二次找第size/2 + 1小的数:

double findMedianSortedArrays(vector<int>& weightsA, vector<int>& weightsB) {
        // write code here
        int size = weightsA.size() + weightsB.size();
        if (size & 1) {
			// 奇数情况
            return findKth(size / 2, weightsA, weightsB);
        } else {
			// 偶数情况
            return 0.5 * (findKth(size / 2 - 1, weightsA, weightsB) + findKth(size / 2, weightsA, weightsB);
        }
    }

难点:明确中轴、左段、右段

要二分,要递归,就得弄清楚这些问题:数组中间元素的下标是什么,左段的长度是多少,右段从哪开始,数组的长度如果是偶数又会怎么样?当弄不清楚时,不妨举个例子,画个图,琢磨出明确的定义:

难点:什么时候排除左段?什么时候排除右段?

排除掉第k小的数不可能在的段

比如说,第k小的数位于整体的中段时,就可以把A的左段排除掉,因为A的左段必然在整体的左段,B的右段同理:

但是怎么知道第k小的数位于整体的哪里呢?我们可没有上帝视角,我们不过是观测了数组里的两个数:

我们不确定第k小的数在哪,我们甚至都不知道我们要找的第k小的数会不会刚好是10或11。

但AB整体跟A、B确实是有一些关系的:

整体的左段来自A的左段和B的左段,整体的中段来自A的右段和B的左段,整体的右段来自A的右段和B的右段(不妨设A的中轴比B的中轴更小)。

当k太小或太大时

当k比A左段的长度还小时:

我们可以排除掉A的右段和B的右段,因为这段区间里的数,要么来自A的左段,要么来自B的左段:

同理,当k比A的长度加上B的左段长度还大时:

因为这段区间里的数,要么来自A的右段,要么来自B的右段,所以可以排除掉A的左段和B的左段。

据此,我们可以写出以下代码:

    int findKth(int kth, vector<int>& A, int Abegin, int Asize, vector<int>& B, int Bbegin, int Bsize) {
        int Ahalf = Asize / 2;
        int Bhalf = Bsize / 2;
        if (A[Abegin + Ahalf] <= B[Bbegin + Bhalf]) {
            if (kth < Ahalf) {
				// 排除A的右段和B的右段
                return findKth(kth, A, Abegin, Ahalf, B, Bbegin, Bhalf);
            } else if (Asize + Bhalf <= kth) {
				// 排除A的左段和B的左段
                return findKth(kth - Ahalf - Bhalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin + Bhalf, Bsize - Bhalf);
            }
        } else {
            // B[Bbegin + Bhalf] < A[Abegin + Ahalf]时同理
            return findKth(kth, B, Bbegin, Bsize, A, Abegin, Asize);
        }
    }

好,这样就能排除掉一半的数了,不过如果k不大不小呢?

当k不大不小时

其实只要k在整体的左边,我们就可以排除掉B的右段:

因为B右段里的数去不到那么靠左:

B右段里的数局限在整体的右边:

同理,只要k在整体的右边,我们就可以排除掉A的左段:

因为A左段里的数去不到那么靠右:

A左段里的数局限在整体的左边:

无论k是多少,都可以排除掉一段

综上所述,代码如下:

    int findKth(int kth, vector<int>& A, int Abegin, int Asize, vector<int>& B, int Bbegin, int Bsize) {
        int Ahalf = Asize / 2;
        int Bhalf = Bsize / 2;
        if (A[Abegin + Ahalf] <= B[Bbegin + Bhalf]) {
            if (kth < Ahalf) {
                // 排除A的右段和B的右段
                return findKth(kth, A, Abegin, Ahalf, B, Bbegin, Bhalf);
            } else if (Asize == 1 || kth < Ahalf + Bhalf) {
                // 排除B的右段
                return findKth(kth, A, Abegin, Asize, B, Bbegin, Bhalf);
            } else if (Asize + Bhalf <= kth) {
                // 排除A的左段和B的左段
                return findKth(kth - Ahalf - Bhalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin + Bhalf, Bsize - Bhalf);
            } else {
                // Ahalf + Bhalf <= kth
                // 排除A的左段
                return findKth(kth - Ahalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin, Bsize);
            }
        } else {
            // B[Bbegin + Bhalf] < A[Abegin + Ahalf]时同理
            return findKth(kth, B, Bbegin, Bsize, A, Abegin, Asize);
        }
    }

每轮我们都可以排除掉A的一半或B的一半,因此时间复杂度是O(log(m+n))。

难点:处理边界情况

不断地将数组二分,当数组的长度变成0,或者1时,就分不了了:

如果我们是要二分查找,那么此时我们已经大功告成了。但是我们有两个数组,当A的长度变成1时,B的长度还可能很长,此时我们希望继续对B进行二分。然而当k的取值恰好让我们决定对A继续二分时,我们就有可能会进入死循环:

发生上述情况时,我们不应排除A的左段,而应排除B的右段,不然就什么也排除不了。

再看看当B的长度为1时:

不会导致什么也排除不了,因此可以不管。

综上所述,代码如下:

    int findKth(int kth, vector<int>& A, int Abegin, int Asize, vector<int>& B, int Bbegin, int Bsize) {
        if (Asize == 0)
            return B[Bbegin + kth];
        if (Bsize == 0)
            return A[Abegin + kth];
        if (Asize == 1 && Bsize == 1) {
            if (kth == 0)
                return min(A[Abegin], B[Bbegin]);
            else
                // kth == 1
                return max(A[Abegin], B[Bbegin]);
        }
        int Ahalf = Asize / 2;
        int Bhalf = Bsize / 2;
        if (A[Abegin + Ahalf] <= B[Bbegin + Bhalf]) {
            if (kth < Ahalf) {
                // 排除A的右段和B的右段
                return findKth(kth, A, Abegin, Ahalf, B, Bbegin, Bhalf);
            } else if (kth < Ahalf + Bhalf) {
                // 排除B的右段
                return findKth(kth, A, Abegin, Asize, B, Bbegin, Bhalf);
            } else if (Asize + Bhalf <= kth) {
                // 排除A的左段和B的左段
                return findKth(kth - Ahalf - Bhalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin + Bhalf, Bsize - Bhalf);
            } else {
                // Ahalf + Bhalf <= kth
                if (Asize == 1) {
                    // 排除B的右段
                    return findKth(kth, A, Abegin, Asize, B, Bbegin, Bhalf);
                }
                // 排除A的左段
                return findKth(kth - Ahalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin, Bsize);
            }
        } else {
            // B[Bbegin + Bhalf] < A[Abegin + Ahalf]时同理
            return findKth(kth, B, Bbegin, Bsize, A, Abegin, Asize);
        }
    }

};

最终代码:

class Solution {
  public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     *
     * @param weightsA int整型vector
     * @param weightsB int整型vector
     * @return double浮点型
     */
    double findMedianSortedArrays(vector<int>& weightsA, vector<int>& weightsB) {
        // write code here
        int size = weightsA.size() + weightsB.size();
        if (size & 1) {
            // 奇数情况
            return findKth(size / 2, weightsA, 0, weightsA.size(), weightsB, 0, weightsB.size());
        } else {
            // 偶数情况
            return 0.5 * (findKth(size / 2 - 1, weightsA, 0, weightsA.size(), weightsB, 0, weightsB.size()) + findKth(size / 2, weightsA, 0, weightsA.size(), weightsB, 0, weightsB.size()));
        }
    }

    int findKth(int kth, vector<int>& A, int Abegin, int Asize, vector<int>& B, int Bbegin, int Bsize) {
        if (Asize == 0)
            return B[Bbegin + kth];
        if (Bsize == 0)
            return A[Abegin + kth];
        if (Asize == 1 && Bsize == 1) {
            if (kth == 0)
                return min(A[Abegin], B[Bbegin]);
            else
                // kth == 1
                return max(A[Abegin], B[Bbegin]);
        }
        int Ahalf = Asize / 2;
        int Bhalf = Bsize / 2;
        if (A[Abegin + Ahalf] <= B[Bbegin + Bhalf]) {
            if (kth < Ahalf) {
                // 排除A的右段和B的右段
                return findKth(kth, A, Abegin, Ahalf, B, Bbegin, Bhalf);
            } else if (kth < Ahalf + Bhalf) {
                // 排除B的右段
                return findKth(kth, A, Abegin, Asize, B, Bbegin, Bhalf);
            } else if (Asize + Bhalf <= kth) {
                // 排除A的左段和B的左段
                return findKth(kth - Ahalf - Bhalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin + Bhalf, Bsize - Bhalf);
            } else {
                // Ahalf + Bhalf <= kth
                if (Asize == 1) {
                    // 排除B的右段
                    return findKth(kth, A, Abegin, Asize, B, Bbegin, Bhalf);
                }
                // 排除A的左段
                return findKth(kth - Ahalf, A, Abegin + Ahalf, Asize - Ahalf, B, Bbegin, Bsize);
            }
        } else {
            // B[Bbegin + Bhalf] < A[Abegin + Ahalf]时同理
            return findKth(kth, B, Bbegin, Bsize, A, Abegin, Asize);
        }
    }

};

全部评论

相关推荐

拒绝无效加班的小师弟很中意你:求职意向没有,年龄、课程冗余信息可以删掉,需要提升项目经历。排版需要修改。
点赞 评论 收藏
分享
Natrium_:这时间我以为飞机票
点赞 评论 收藏
分享
评论
2
1
分享
牛客网
牛客企业服务