zoukankan      html  css  js  c++  java
  • 《编程之美》读书笔记一

    PB15151793 陈灿


     

    为什么要读《编程之美》?

    最近接连面试,发现算法真得很重要,想要快速掌握面试中可能问道的算法题目,于是想到了“微软面经”的《编程之美》。因为此书涉及到的算法还是比较多的,而且每个题目都不是那么一下子就能做出来的,因此预计要花3个星期才能看完这本书,每个星期我都会更新自己的博客作为自己的读书笔记。
    这星期主要看的是《编程之美》第二章《数字之魅》。我觉得开头的一句话非常有意义:面试是双方平等交流的过程,有时分不清谁在面试谁。联系之前自己的面试经历,觉得自己表现得是有点被动了,每次都是被面试官问得团团转,在气场上就弱了下来。我想对于以后的面试,尽力还是以一种平等的姿态对话吧。前提是自己得变得至少在某一方面和面试官一样强,这就需要不断地完善自己了。
    好了进入算法的正题了,我还是先总结一下我看过的这些题的总体思想。从时间复杂度上讲,我觉得这些代码最核心的思想分为两点:“只计算必要的信息”和“空间换时间”。

     

    何为“只计算必要的信息”?

    我觉得具体可以细分为两个方面。从一方面来说,之前已经计算过这个信息,后面可以不用再计算了,就是充分地利用之前的信息,这样可以减少计算量,总结一下就是“减少重复的计算”。举个例子,计算1个一维数组的最大值和最小值,我们需要比较多少次?最一般的想法就是,遍历数组2次,每次分别求得最大值和最小值,这样需要比较2N次。但是这里比较的2N次是否都是计算必要的信息尼?在第一次比较这个数组求最大值的时候,我们比较了A[i]和A[j],悲剧的是,第二次比较的时候我们可能还会重复的比较A[i]和A[j],这一点就很不好了,那么我们能否只让他们仅仅只比较一次了,当然是可以的。我们可以每次比较相邻的2个元素,以2为间隔把数组扫面一遍,每次比较A[i] < A[i+1]的时候,我们拿A[i]去和min相比,拿A[i+1]和max相比,做出相应更新。这样我们就把比较次数降低到了1.5N次。
    从另外一方面来说,就是对于一个具体的问题,确定这个问题答案的信息量是1bit,那么我们的代码就只能计算这1bit的信息,不能有冗余的计算。这个和前面有所不同,在于前面是不计算已经计算过的信息,而这儿是不计算不需要计算而且没被计算的信息。举个例子吧,Tango水王帖子数目超过Tango上所有其他用户的帖子总和,求水王帖子的ID。计算这个问题直观的思路是我把Tango上出现的所有帖子按照ID排序,这样最后数组的N/2项一定是水王帖子的ID,时间复杂度为O(NlogN),但是我们仔细想想我们真的需要把所有的帖子ID都排序吗?这里的信息又有多少是我们真正需要的尼?有了这个指导思想,我们就必须想缩减问题的信息量,这就必须结合问题所给的条件,为什么要结合问题所给的条件?因为从相对论意义上,条件减少了熵。具体想法是设立一个candidate变量和size变量(初值为0),当读入一个ID并且发现当前没人占着位子(即size为0的时候),把这个ID作为可能的水王放入位子,并且记下candidate = ID,当读入一个ID发现有人占着位子的时候(size > 0),这个时候我们比较一下candidate和ID,如果相同,size++,表示又多了一个相同的ID;如果不同,两两抵消,size--。这里用到的主要思想就是每次我们拿两个不同的ID出来,这样到最后剩下的ID一定是水王的ID,因为最坏的情况也就是水王的ID每次都参与了抵消,但是他最后还是能被剩下,非常clever的一个idea。

     

    何为空间换时间?

    个人感觉空间换时间都有一种"hash"的思想在里面,即通过精心设计的函数把一个或者一组相关元素放进特定的区域,这种操作一般都是O(1)的,但是耗费了一定的空间。这里就直接举个简单例子吧。给定一个数组Arr[1-N],求出相邻2个数的最大差值。定义X与Y相邻即,不存在数组中其他元素位于X与Y之间。这个题目看起来直接做一个排序,然后算一下差值就能解出来了。但是这样做时间复杂度是O(NlogN)。这里其实要结合鸽巢原理,先求出Arr的max和min,把max-min分配进入中间N个数组所组成的空里, 至少有一个间隔是大于delta:
    delta = (max - min)/(N-1)
    那么相邻2个数的最大差值也一定大于delta。然后我们按照delta作为一个基本单元,实现空间的基本划分,将每个数组元素hash到各个划分里,注意一个划分里的任意2个元素是不可能取到最大差值的,最大差值只可能在相邻2个划分取得。这样只要记录下每个划分的最大值和最小值就可以了,时间复杂度和空间复杂度均为O(N)。非常clever的一个idea。这里为什么能减少计算量?最主要是各个元素对应到划分的hash操作,使得数组基本有序了。为什么这样基本有序了,我们就能马上求出想要的结果?这主要是借助我们的鸽巢原理,所以离散数学学好很关键啊!

     

    其他

    接下来,简单讨论一下我觉得还比较有意思的一些题目。觉得《编程之美》这本书最好的一个地方在于:对于一个问题的分析,能够循序渐进,而不是在一开始就给出了最优解法。
    比如第一题,求二进制中1的个数。最朴素的思想就是每次除以2,求余数。后来补充这个最朴素的思想也可以用位运算来实现。但是这两种做法的复杂度都是O(logV),而我们的目的只是计算V中1的个数,所以很多计算是不必要的,如果仅仅能只与1的个数有关系,那才是比较好的解法。这里实际上就有一个小trcik了,如果V是一个二进制数,那么通过
    V= V&(V-1)
    就能消除最右边的那个1了。只要判断这个运算能做多少次,就行了。但实际上我们还可以采用空间换时间的算法,即把每个数字都映射到对应1的个数,还是hash的思想。
    另外还可以学到一个拓展的trick,即判断n是否是2的幂次:

    n > 0 && ( (n&(n-1)) == 0)
    

    觉得Tango问题的扩展也很有意思,即有3个发帖人的发帖数目都多于1/4,求这3个人的ID。这样我们求解的时候,就要设立3个candidate记录3个可能成为水王的不同ID,唯一不同的一点在于我们每次碰到与3个candidate不同的ID的时候,我们用这个ID同时抵消这3个candidate的ID,这样最坏的情况下(即每次3个水王都被抵消了),最后3个candidate依然存放着不同candidate的ID。
    还有一个觉得很有意思的点是Fibonacci数列通项的求解,作者给了一种有趣的求法,之前其实在学线性代数的时候求过,只是当时没意识到重要性。
    记矩阵A

    A = (1 1)
        (1 0)
    

    那么(Fn, Fn-1) = (Fn-1, Fn-2)*A,这样一直递归下去,利用矩阵的知识可以化简运算,我觉得非常有趣!
    其实还有很多很多有趣的问题,这里就不一一列出了,期待在接下来的阅读中能收获更多的东西!

  • 相关阅读:
    es6---let和const
    node.js开发指南系列(1)partial is not defined
    input唤起键盘影响移动端底部fixed定位
    vue滑动吸顶以及锚点定位
    nodejs开发准备工作(2)
    nodejs开发准备工作(1)
    php基础小知识
    php基础
    git基础
    ps基础
  • 原文地址:https://www.cnblogs.com/moee/p/8951088.html
Copyright © 2011-2022 走看看