CodingTour
ARTS #59

Algorithm

本周选择的算法题是:Median of Two Sorted Arrays

规则如下:

There are two sorted arrays nums1 and nums2 of size m and n respectively.

Find the median of the two sorted arrays. The overall run time complexity should be O(log (m+n)).

You may assume nums1 and nums2 cannot be both empty.

Example 1:

nums1 = [1, 3]
nums2 = [2]

The median is 2.0

Example 2:

nums1 = [1, 2]
nums2 = [3, 4]

The median is (2 + 3)/2 = 2.5

Solution

这题的重点在于 O(log (m+n)),既是限制,也是提示。

先忽略看常规解法:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def _merge():
            nums = []
            nums1_begin, nums2_begin = 0, 0
            while nums1_begin < len(nums1) and nums2_begin < len(nums2):
                if nums1[nums1_begin] < nums2[nums2_begin]:
                    nums.append(nums1[nums1_begin])
                    nums1_begin += 1
                else:
                    nums.append(nums2[nums2_begin])
                    nums2_begin += 1
            while nums1_begin < len(nums1):
                nums.append(nums1[nums1_begin])
                nums1_begin += 1

            while nums2_begin < len(nums2):
                nums.append(nums2[nums2_begin])
                nums2_begin += 1
            return nums
        
        nums = _merge()
        if len(nums) % 2 == 0:
            return (nums[len(nums)//2] + nums[len(nums)//2-1]) / 2
        else:
            return nums[len(nums)//2]

这个解法用到了归并排序的思想,将两个源数据二路合并成一个排序数据,然后取中位数。

其实不需要做完整的排序,只需要记录中位数的值即可,这样可以降低空间复杂度:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        nums1_begin, nums2_begin, half_len = 0, 0, (len(nums1) + len(nums2)) // 2
        left, right = 0, 0
        while half_len >= 0:
            half_len -= 1
            left = right
            if nums1_begin < len(nums1) and (nums2_begin >= len(nums2) or nums1[nums1_begin] < nums2[nums2_begin]):
                right = nums1[nums1_begin]
                nums1_begin += 1
            else:
                right = nums2[nums2_begin]
                nums2_begin += 1
        
        if (len(nums1) + len(nums2)) % 2 == 0:
            return (left + right) / 2
        else:
            return right

上述两个解法都不符合时间复杂度的要求,做完只当是热热身,吃透这道题。

评论区看到的解法,通过寻找第 k 小的数字巧妙的将问题分解:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        def _kth(nums1_begin, nums2_begin, k) -> int:
            len1 = len(nums1) - nums1_begin
            len2 = len(nums2) - nums2_begin
            if len1 == 0: return nums2[nums2_begin + k - 1]
            if len2 == 0: return nums1[nums1_begin + k - 1]
            if k == 1: return min(nums1[nums1_begin], nums2[nums2_begin])

            i = nums1_begin + min(k // 2, len1) - 1
            j = nums2_begin + min(k // 2, len2) - 1

            if nums1[i] < nums2[j]:
                return _kth(i + 1, nums2_begin, k - (i - nums1_begin + 1))
            else:
                return _kth(nums1_begin, j + 1, k - (j - nums2_begin + 1))

        total_len = len(nums1) + len(nums2)
        if total_len % 2 == 0:
            return (_kth(0, 0, total_len // 2) + _kth(0, 0, total_len // 2 + 1)) / 2
        else:
            return _kth(0, 0, total_len // 2 + 1)

官方解法,对两个数组进行二分,边界条件很多:

class Solution:
    def findMedianSortedArrays(self, nums1: List[int], nums2: List[int]) -> float:
        m, n = len(nums1), len(nums2)
        if m > n: return self.findMedianSortedArrays(nums2, nums1)
        
        imin, imax = 0, m
        while imin <= imax:
            i = (imin + imax) // 2
            j = (m + n + 1) // 2 - i

            if i < m and nums2[j-1] > nums1[i]: # i 太小
                imin = i + 1
            elif i > 0 and nums1[i-1] > nums2[j]: # i 太大
                imax = i - 1
            else: # i 合适
                if i == 0: max_left_num = nums2[j-1]
                elif j == 0: max_left_num = nums1[i-1]
                else: max_left_num = max(nums1[i-1], nums2[j-1])

                if (m + n) % 2 == 0:
                    if i == m: min_right_num = nums2[j]
                    elif j == n: min_right_num = nums1[i]
                    else: min_right_num = min(nums1[i], nums2[j])
                    
                    return (max_left_num + min_right_num) / 2
                else:
                    return max_left_num

Review

Swift globals and static members are atomic and lazily computed

从这篇文章中了解到 Swift 相比 OC 做的一些优化。

Swift 的全局或静态成员是原子和懒加载的,它们的初始化方法是通过 dispatch_once 包裹的,除此之外,如果是 let 还有线程安全的作用,由于 let 本身代表了不可变,因此这也是合理的操作。

Tip

zsh-autosuggestions 插件真香!

Share

一篇关于 《软件架构设计》 的读后感,传送门