每周至少写一道算法题,阅读并点评至少一篇英文文章,学习至少一个技术技巧,分享一篇有观点和思考的技术文章。本周的算法是 leetcode 的 69 题,原本题目挺简单,只是在其中学习到了牛顿迭代法,所以记录下来,后面学习二分查找又挑了个 leetcode 33 题,之后的英文阅读是关于 AI 发展的文章,Share 是也是关于 AI 的入门文章,Tip 部分是 Timsort 和 牛顿迭代法的相关资料。

Algorithm

算法是与二分查找相关的两道题。

不要使用自带的方式来求开方,并且是要整数部分,不需要求小数,其中的 x 会很大,大到需要用到 java 中的 long long 类型来保存。

看似一个很简单的题其实也有很多种方式来解答,首先,想到的方法就是二分查找,对于数字 x 的平方根,其大小一定小于等于 \frac{x}{2} ,所以,接下来要做的就是在 0 和 \frac{x}{2} 之间使用二分查找,找到该 \sqrt{x} 的整数部分。

class Solution:
    def mySqrt(self, x: int) -> int:
        # 二分法
        # 对于需要求的开方来说,一个数字的开方一定小于等于 x/2
        l, r = 0, x//2+1
        while l <= r:
            mid = (l+r)//2
            square = mid * mid
            if square > x:
                r = mid - 1
            else:
                l = mid + 1
        return r

这里需要注意两边的边界问题,在我这种写法中,首先是对 1 的判断,可以单独拿出来对 x 判断是否为 1 ,之后返回 1 ,或者直接对右半边进行加 1 的操作,将 1 的情况包含进去。之后需要注意的是退出循环的条件判断,要注意 l 和 r 的赋值要以 mid 为界划分两边,否则可能会出现死循环的现象。而最后当左半边 l 大于右半边 r 的时候就退出循环,返回值较小的右半边就是所求的答案了。

image-20190731204259118

上面是该代码的提交运行用时,看起来并不是最快的,这时候就引出了一个叫牛顿迭代法的数学方式,该方法相比于二分查找有更快的迭代速度,总的来说就是二分查找是一次次寻找中间值来缩短区间,而牛顿迭代法是通过查找函数的切线来缩短区间,效率上会快上不少,后面的 Tip 部分给出了相关的参考资料,可以翻阅查看,下面看代码实现:

class Solution:
    def mySqrt(self, x: int) -> int:
        ans = x//2+1
        while ans*ans > x:
            ans = (ans+x//ans) >> 1
        return ans

对于求 x 的平方根,写成函数的形式就是 f(x)=x^2-a ,也就是求 f(x) 的根,根据牛顿迭代法公式:x_{n+1}=x_{n}-\frac{f\left(x_{n}\right)}{f^{\prime}\left(x_{n}\right)} ,将 f(x) 带入进去可以得,x_{n+1}=\frac{x_n+\frac{a}{x_n}}{2} ,也就有了代码中的 (ans+x//ans) >> 1 ,这里做个位移操作,并且题目要求只求出整数部分,所以进行了整除操作,其中的 a 也就是 x ,ans 也就是 x_{n+1} ,在一次次遍历求 x_{n+1} 的时候,只要某次 x_{n+1 } 的平方比 x 小的时候,就是我们需要的那个值了,下面的是这份代码的运行时间,会发现比二分查找要快上很多。

image-20190801000134431

牛顿迭代法的功能主要是求出函数的根,不仅仅只是求开方。

在有序循环数组中查找指定元素的值,数组中没有重复元素,并且要求时间复杂度在 O(logn)

二分查找的变体问题,与正常的二分查找不同的是,这个不仅仅只是判断中间的数,而是要将区间两端的数也要进行判断。

class Solution:
    def search(self, nums, target: int) -> int:
        l, r = 0, len(nums)-1
        while l <= r:
            mid = (l+r) >> 1
            # 判断是否是目标值
            if nums[mid] == target:
                return mid
            # 以中间值和最左边的值来判断左半边是否是上升区间
            elif nums[mid] >= nums[l]:
                # 判断目标值是否在这个左半边区间内
                if nums[l] <= target <= nums[mid]:
                    r = mid
                else:
                    l = mid + 1
            else:
                # 如果左半边不是上升区间,则说明上升区间在右半边
                if nums[mid] <= target <= nums[r]:
                    l = mid
                else:
                    r = mid - 1
        return -1

依据题目意思,也就是在分半的时候,一定有一半是属于连续区间的,这个时候就抓住这个连续区间的特性,判断目标值是否在这个连续区间中,相应改变左右两个端点的值。这时候需要注意,在判断完属于这个区间的时候,需要包含中间值,因为判断目标值是否是在该区间的时候是带有等于号的,如果不是该区间的话才可以去掉中间值的情况。另外在判断中间值与两端的值的时候,要注意中间值与两端的值相等(或其中一个相等)的情况,这里的处理是将相等的情况视为左半边处理。

二分查找

上面是二分查找的一些知识点,最主要的还是在变体问题下对各个点的判断,很容易漏掉一些情况。

Review

为人工智能指明正确的方向,文章根据 AI 的发展所带来的四个问题逐一进行讲解,指明正确的 AI 在人类生活中应该担当的角色,以及未来的发展方向。同时也在阐述人工智能到来毋庸置疑也无法阻挡,我们应该顺应时代,在自己的行业或生活中一同打造人工智能的未来。

翻译见:文章翻译 | Steering the right course for AI

Tip

学习过程中的知识点记录。

Timsort

这是 Python 自带的排序中使用的排序算法(Python 2.3 版本开始使用),这个算法结合了合并排序(merge sort)和插入排序(insertion sort),在实际使用中有很好的效率。最好时间复杂度为 O(n) ,最坏和平均时间复杂度为 O(nlogn)​,不过不是原地排序算法,需要而外的空间开销 O(n) ,属于稳定排序。

Timsort 排序不是以一个单独的数字作为排序单位,而是以一个分区(run)作为单位,大体上来说就是先用快排排序小区间(run)中的元素 ,之后用归并排序排序 run。

参考:

牛顿迭代法

在编写求根公式(Leetcode 69 题)的时候查询到的方法,对于一些没有求根公式的式子,在需要开方的时候,可以使用二分查找的方式求的,不过这个牛顿迭代法可以以更快的收敛速度来求得结果。

参考:

Share

机器学习入门文章,原本机器学习是个门槛很高的事情,在自己查阅资料的情况下可能会迷失方向,这一系列的几篇文章就很好的把握的机器学习的大局观,通俗易懂的概括了机器学习的基本原理以及应用,给出一种更直观的方式,方便在之后的学习中有侧重点,同时也提高了机器学习的兴趣。

下面是一系列文章的网址,原文是英文,有中文翻译版本(最后两篇还未有中文版本,这里给出英文链接),每篇文章我都列出了关键词,方便查阅。

分类: 生活

0 条评论

发表回复

您的电子邮箱地址不会被公开。