美文网首页
算法第一课(复杂度与逐步优化思想)

算法第一课(复杂度与逐步优化思想)

作者: 锅与盆 | 来源:发表于2017-02-25 15:17 被阅读0次

    该笔记整理自七月在线网(https://www.julyedu.com )的算法视频课程

    一、介绍

    算法很重要,是解决问题的步骤,具有有穷性,确定性、可行性、输入输出。
    面试时面试官看重的是解决问题的能力,从暴力解法改良到高效解法。即使不能一下子想到最优算法,也要有自己想法,然后一步步优化去冗余步骤(下面会用一道例题体现这个过程)。

    二、复杂度

    常用大O表示法。
    时间:基本操作次数(汇编指令条数)
    空间:占用内存字节数
    区别:空间可再利用

    常见时间复杂度分析方法 :

    1. 数循环次数
    2. 均摊分析
    3. 递归式——主定理

    基本运算如+-*/:O(1)
    问题规模可按分数减小,如二分查找:O(logn)
    线性查找:O(n)
    朴素最近点对,选择排序:O(n²)
    Floyd,普通矩阵乘法:O(n³)
    O(nlogn),快排期望复杂度,一般为归并排序,堆排序以及相关的,也是基于比较排序的算法下界。

    总结:
    优秀:O(1)<O(logn)<O(n)<O(nlogn) 面试时当复杂度是这些时一般就不用再优化了
    可能需要继续优化:O(n²)<O(n³)<O(n!)

    三、分治法主定理(只是一个公式,面试一般不考,了解即可)

    递归式与分治方法是紧密相关的,因为使用递归式可以清晰的刻画分治算法的运行时间。
    主方法如下:
    T(n) = aT(n/b) + f(n)

    a>=1 b>1 f(n) 是给定的函数。这种形式的递归式很常见。刻画了一个分治算法。生成a个子问题。每个子问题是原来的1/b。分解和合并步骤共消耗f(n)
    主方法是计算时间复杂度的时候用的。


    利用上面的这个定理就可以计算递归式的时间复杂度了,这里面的1)与3)换句话说就是1)f(n)nlogb^a
    T(n) = 9T(n/3) + n 那么计算其渐进界就是nlog39=n2

    四、均摊法

    多个操作一起算复杂度,再除操作次数得每次操作复杂度。基本理念是,给定一连串操作,大部分的操作是非常廉价的,有极少的操作可能非常昂贵,因此一个标准的最坏分析可能过于消极了。因此,其基本理念在于,当昂贵的操作特别少的时候,他们的成本可能会均摊到所有的操作上。

    五、例题

    leetcode 53. Maximum Subarray 注意思考优化问题复杂度,去除冗余步骤的过程

    题目: 给定数组a[1…n],求最大子数组和,即找出1<=i<=j<=n, 使a[i]+a[i+1]+…+a[j]最大

    PS:建议直接在leetcode上写代码,培养好代码习惯,训练徒手写代码能力。

    1.暴力枚举O(n³)

    首先可以想到用二重循环

        public class Solution {
            public int maxSubArray(int[] nums) {
                int max=nums[0];
                int n = nums.length;
                for(int i=0;i<n;i++){
                    for(int j=i;j<n;j++){
                        int result = 0;
                        for(int k=i;k<=j;k++){                 
                            result=result+nums[k];
                        }
                        if(result>max)
                            max=result;
                    }
                }
                
                    return max;
            }
        }
    

    提交后发现超时,(面试时拿到问题如果没有好的想法可以先讲一下暴力解法,比较差的算法面试官也可以接受,一定要有思路。面试官一定会继续问能不能优化,接下来继续思考有没有冗余的步骤可以去掉

    2.优化枚举,二重循环

    思考上述过程是否有冗余步骤:计算过子数组A的和后,想计算子数组AB的和,可以像上面一样找到AB,循环累加AB,但是计算A的和的过程重复了。可以直接在A的和基础上继续累加B。
    时间复杂度 O(n2) 附加空间复杂度 O(1)

        public class Solution {
            public int maxSubArray(int[] nums) {
                int max=nums[0];
                int n = nums.length;
                for(int i=0;i<n;i++){
                    int result = 0;
                    for(int j=i;j<n;j++){
                        result=result+nums[j];
                        if(result>max)
                            max=result;
                    }
                }
                return max;
            }
        }
    

    虽然复杂度降低,但提交后依然超时。

    3.贪心法,一重循环,O(n)

    此时面试官应该会继续提醒你优化。继续思考,刚刚我们找了所有的子数组及其累加和,方法2也只是优化了累加部分,令循环累加O(n)变成O(1)。但各子数组是有重叠的,如下图,



    当发现子数组A的和小于零时,其实我们就不用计算子数组AB的和了,直接计算B即可,因为A的和小于零,AB一定小于B,也就是所有以A为前缀的子数组都不用计算了。换言之,只有当A的和>0时,A才会对子数组累加结果增长有帮助。

    贪心法可以理解为只要对自己好的,对自己不利的一律不要。若A<0,它对后面子数组求和结果的贡献一定是负的,所以不要,若A>0,A的贡献是正的(可以令子数组和变更大),要它。不要小于零的,只要大于零的。由此想到下面算法。

    时间复杂度 O(n) 附加空间复杂度 O(1)。
    累加子数组,当发现sum小于零时,令sum=0(丢弃前面的累加和,继续计算后面的和,也就是当发现前面的A的sum为零时,跳过A为前缀的子数组,直接计算A后面的和),丢弃之前已经把前面的最大子数组和保存在ans里,不用担心最大子数组和出现在前面怎么办(每次计算sum后都比较更新ans)。

        public class Solution {
            public int maxSubArray(int[] nums) {
                int max=nums[0];
                int n = nums.length;
                int sum = 0;
                for(int i=0;i<n;i++){
                    sum+=nums[i];
                    if(sum>max)
                        max=sum;
                    if(sum<0){
                        sum=0;
                        }
                    
                }
                return max;
            }
        }
    

    AC!
    时间复杂度O(n),此时如果面试官还问可不可以优化时,我们就可以自信的说,这应该已经是最优解了😁
    这道题很经典,理解拿到一个问题给出一个解然后逐步优化的思想。

    PS:在算法优化中还有一种用空间换时间的想法,类似桶排序那种思路,现在暂时想不到例题,这篇笔记就不写了,以后遇到类似问题再总结。

    相关文章

      网友评论

          本文标题:算法第一课(复杂度与逐步优化思想)

          本文链接:https://www.haomeiwen.com/subject/crtnwttx.html