经过一道面试题目,讲一讲递归算法的时间复杂度!

相信不少同窗对递归算法的时间复杂度都很模糊,那么这篇来给你们通透的讲一讲。面试

「同一道题目,一样使用递归算法,有的同窗会写出了O(n)的代码,有的同窗就写出了O(logn)的代码」算法

这是为何呢?ide

若是对递归的时间复杂度理解的不够深刻的话,就会这样!url

那么我经过一道简单的面试题,模拟面试的场景,来带你们逐步分析递归算法的时间复杂度,最后找出最优解,来看看一样是递归,怎么就写成了O(n)的代码。spa

面试题:求x的n次方code

想一下这么简单的一道题目,代码应该如何写呢。最直观的方式应该就是,一个for循环求出结果,代码以下:blog

int function1(int x, int n) {
    int result = 1;  // 注意 任何数的0次方等于1
    for (int i = 0; i < n; i++) {
        result = result * x;
    }
    return result;
}

时间复杂度为O(n),此时面试官会说,有没有效率更好的算法呢。递归

「若是此时没有思路,不要说:我不会,我不知道了等等」图片

能够和面试官探讨一下,询问:“可不能够给点提示”。面试官提示:“考虑一下递归算法”。ci

那么就能够写出了以下这样的一个递归的算法,使用递归解决了这个问题。

int function2(int x, int n) {
    if (n == 0) {
        return 1; // return 1 一样是由于0次方是等于1的
    }
    return function2(x, n - 1) * x;
}

面试官问:“那么这个代码的时间复杂度是多少?”。

一些同窗可能一看到递归就想到了O(logn),其实并非这样,递归算法的时间复杂度本质上是要看: 「递归的次数 * 每次递归中的操做次数」

那再来看代码,这里递归了几回呢?

每次n-1,递归了n次时间复杂度是O(n),每次进行了一个乘法操做,乘法操做的时间复杂度一个常数项O(1),因此这份代码的时间复杂度是 n * 1 = O(n)。

这个时间复杂度就没有达到面试官的预期。因而又写出了以下的递归算法的代码:

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

面试官看到后微微一笑,问:“这份代码的时间复杂度又是多少呢?” 此刻有些同窗可能要陷入了沉思了。

咱们来分析一下,首先看递归了多少次呢,能够把递归抽象出一颗满二叉树。刚刚同窗写的这个算法,能够用一颗满二叉树来表示(为了方便表示,选择n为偶数16),如图:

图片 递归算法的时间复杂度

当前这颗二叉树就是求x的n次方,n为16的状况,n为16的时候,进行了多少次乘法运算呢?

这棵树上每个节点就表明着一次递归并进行了一次相乘操做,因此进行了多少次递归的话,就是看这棵树上有多少个节点。

熟悉二叉树话应该知道如何求满二叉树节点数量,这颗满二叉树的节点数量就是2^3 + 2^2 + 2^1 + 2^0 = 15,能够发现:「这实际上是等比数列的求和公式,这个结论在二叉树相关的面试题里也常常出现」

这么若是是求x的n次方,这个递归树有多少个节点呢,以下图所示:(m为深度,从0开始)

图片


「时间复杂度忽略掉常数项-1以后,这个递归算法的时间复杂度依然是O(n)」
。对,你没看错,依然是O(n)的时间复杂度!

此时面试官就会说:“这个递归的算法依然仍是O(n)啊”, 很明显没有达到面试官的预期。

那么O(logn)的递归算法应该怎么写呢?

想想刚刚给出的那份递归算法的代码,是否是有哪里比较冗余呢。

因而又写出以下递归算法的代码:

int function4(int x, int n) {
    if (n == 0) {
        return 1;
    }
    int t = function4(x, n / 2);// 这里相对于function3,是把这个递归操做抽取出来
    if (n % 2 == 1) {
        return t * t * x;
    }
    return t * t;
}

再来看一下如今这份代码时间复杂度是多少呢?

依然仍是看他递归了多少次,能够看到这里仅仅有一个递归调用,且每次都是n/2 ,因此这里咱们一共调用了log以2为底n的对数次。

「每次递归了作都是一次乘法操做,这也是一个常数项的操做,那么这个递归算法的时间复杂度才是真正的O(logn)」

此时你们最后写出了这样的代码而且将时间复杂度分析的很是清晰,相信面试官是比较满意的。

总结

对于递归的时间复杂度,毕竟初学者有时候会迷糊,刷过不少题的老手依然迷糊。

「本篇我用一道很是简单的面试题目:求x的n次方,来逐步分析递归算法的时间复杂度,注意不要一看到递归就想到了O(logn)!」

一样使用递归,有的同窗能够写出O(logn)的代码,有的同窗还能够写出O(n)的代码。

对于function3 这样的递归实现,很容易让人感受这是O(logn)的时间复杂度,其实这是O(n)的算法!

int function3(int x, int n) {
    if (n == 0) {
        return 1;
    }
    if (n % 2 == 1) {
        return function3(x, n / 2) * function3(x, n / 2)*x;
    }
    return function3(x, n / 2) * function3(x, n / 2);
}

能够看出这道题目很是简单,可是又很考究算法的功底,特别是对递归的理解,这也是我面试别人的时候用过的一道题,因此整个情景我才写的如此逼真,哈哈。

大厂面试的时候最喜欢用“简单题”来考察候选人的算法功底,注意这里的“简单题”可并不必定真的简单哦!

若是认真读完本篇,相信你们对递归算法的有一个新的认识的,同一道题目,一样是递归,效率但是不同的!

就酱,「代码随想录」是技术公众号里的一抹清流,值得介绍给身边的朋友同窗们!

打算从头开始打卡的录友,能够在「算法汇总」这里找到历史文章,不少录友都在从头打卡,你并不孤单!

图片

相关文章
相关标签/搜索