小灰算法之旅笔记

数据结构

分类

  • 线性结构 - 数组,链表,栈,队列,哈希表

  • 图(graph) 多对多关联关系

衡量指标:

  • 时间复杂度
    • 把执行时间简化为一个数量级
    • 二分查找O(logn)
  • 空间复杂度
    • 把代码运行过程中占用的空间成本
数组
  • 顺序存储
  • 适用于读取操作:O(1),插入/删除:O(n)
  • 超长度插入,则会创建新数组:创建一个长度为旧数组长度2倍的新数组,将旧数组中的值依次加入到新数组
链表
  • 绿皮火车车箱,类型地下党,只知上级下级,不知其他
  • 随机存储
  • 读取:O(n), 插入/删除:O(1)
栈/队列
  • 栈 stack ,追溯历史,面包屑导航,后进先出
  • 队列 Queue 历史回放, 先进先出
散列表
  • 也叫哈希表,键值对映射
  • 本质上是数组,下标不是数组,而是key
  • 哈希函数,将key和数组下标进行转换,通过位运算

读写操作

  1. 将key转换为数组下标值
  2. 查找数组下标有无对应值,无则填充,有则改写
哈希冲突

通过哈希函数将key转换为数组下标,当已有相同的下标时,称为哈希冲突

如何解决?

  • 开放寻址法, 向后推一位
  • 链表法, java中的hashMap,有冲突,变为链表
扩容
  1. 创建一个长度为之前2倍的新数组
  2. 将之前的元素依次放入新数组中

节点,根节点,叶子结点

可以通过链表或数组实现

二叉树

遍历:

  • 前序遍历: 根节点,左子树,右子树 (深度优先)
  • 中序遍历: 左子树,根节点,右子树(深度优先)
  • 后序遍历: 左子树,右子树,根节点(深度优先)
  • 层序遍历- 广度优先,通过队列实现
二叉堆

有序完全二叉树

最大堆

最小堆

自我调整

  • 插入,O(logn), 从最后一个位置,向上浮动对比
  • 删除,O(logn), 从堆顶的位置,堆顶元素删除后,将最后一个位置临时补到堆顶的位置,之后向下调整

顺序存储

计算下标: 如果父节点下标为parent,左孩子下标为 2parent + 1, 右孩子下标为2parent + 2

优先队列

最大优先队列,最大元素优先出队

最小优先队列,最小元素优先出队

使用二叉堆来实现

排序算法

  • 冒泡排序

    • 双层循环

      function bubbleSort(arr){
          for(var i=0; i<arr.length-1; i++) {
              for(var j=0;j<arr.length-1-i;i++){
                  if(arr[j]>arr[j+1]){
                      [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
                  }
              }
          }
      }
      
    • 优化点:当数组已经是有序的时候,直接break

      function bubbleSort(arr){
          for(var i=0; i<arr.length-1; i++) {
              // 有序标记,每轮初始值为true
              var isSorted = true
              for(var j=0;j<arr.length-1-i;i++){
                  if(arr[j]>arr[j+1]){
                      [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
                      // 元素有交换,说明无序
                      isSorted = false
                  }
                  if(isSorted) break
              }
          }
      }
      
    • 优化二:记录最后一次元素交换的位置,该位置则为无序数列的边界,后面是有序区

      function bubbleSort(arr){
          // 记录最后一次交换的位置
          let lastChangeIndex = 0
          // 无序数列的边界,每次比较到这里就可以了
          let sortBorder = arr.length -1
          for(var i=0; i<arr.length-1; i++) {
              // 有序标记,每轮初始值为true
              var isSorted = true
              for(var j=0;j < sortBorder;i++){
                  if(arr[j]>arr[j+1]){
                      [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
                      // 元素有交换,说明无序
                      isSorted = false
                      // 更新最后一次交换的位置
                      lastChangeIndex = j
                  }
                  sortBorder = lastChangeIndex
                  if(isSorted) break
              }
          }
      }
      
  • 鸡尾酒排序 -基于冒泡排序的一种升级排序法

    • 比较和交换过程是双向的,第一轮从左到右,第二轮从右到左,依次进行

      function fn(arr) {
          let tmp = 0
          for(var i=0; i<arr.length/2; i++) {
              var isSorted = true //有序标记
              //从左到右
              for(var j = i; i<arr.length-1-i; j++){
                  if(arr[j]> arr[j+1]){
                      [arr[j], arr[j+1]] = [arr[j+1], arr[j]]
                      // 元素有交换,说明无序
                      isSorted = false
                  }
              }
              if(isSorted) break
              
              //从右到左
              isSorted = true //有序标记重新初始
              for(var j = arr.length-1-i;j>i;j--){
                   if(arr[j-1]> arr[j]){
                      [arr[j-1], arr[j]] = [arr[j], arr[j-1]]
                      // 元素有交换,说明无序
                      isSorted = false
                  }
              }
              if(isSorted) break
          }
      }
      
  • 选择排序

  • 插入排序

  • 快速排序

    • 分治法, O(nlogn),最坏情况O(n*n)

    • 选择基准元素(pivot),一般第一个元素

    • 双边循环法

      首元素为基准元素,首尾元素为left和right,从right开始对比基准元素,如果大于基准,切换成left, 对比基准元素,依次进行,当指针重合,循环结束

    • 单边循环法

      function quickSort(arr){
          if(arr.length<=1) return arr
          let pivot = arr.shift()
          let left = []
          let right = []
          for(var i = 0;i<arr.length;i++){
              if(i>pivot){
                  right.push(arr[i])
              }else{
                  left.push(arr[i])
              }
          }
          return quickSort(left).concat(pivot, quickSort(right))
      }
      

      非递归实现

      
      
  • 归并排序

  • 堆排序

    1.把无序数组构建成二叉堆,需要从小到大排序则构建成最大堆,需要从大到小排序,则构建最小堆

    2.循环删除堆顶元素,替换到二叉堆的末尾,调整堆产生新的堆顶

    时间复杂度O(nlogn)

  • 计数排序

    不需要元素之间比较,通过数组下标来确定元素的正确位置

    创建一个统计数组,长度为arr的最大值和最小值的差值,遍历arr,新数组对应的下标符合则加1

    适用于一定范围内的整数排序

  • 桶排序

    时间复杂度O(n)

    针对计数排序的局限性(一定范围内的整数排序)的升级

算法题

  • 如何判断一个链表是否有环?

    • 利用hashmap,遍历的时候,加入hashMap,对比在hashMap中是否有相同的

      时间 O(n),空间O(n)

    • 双指针

      时间 O(n),空间O(1)

      创建两个指针,先都指向第一个节点,每次遍历时,第一个指针走一步,第一个指针走两步,依次对比

      function double(node) {
          let p1 = node.head
          let p2 = node.head
          while(p2 != null && p2.next != null) {
              p1 = p1.next
              p2 = p2.next.next
              if(p1 == p2) return true
          }
          return false
      }
      
  • 实现最小栈

    栈a,和备胎栈b,依次进栈a,最小值进栈b,依次进行,碰到比b栈顶小的元素,则再进栈b

    进栈出栈取最小值的时间复杂度都是O(1)

  • 如何求出最大公约数

    • 暴力法 O(min(a,b))

      取最小值的一半,开始遍历,如果满足被两个数模为0(%/i == 0),则最大公约数为i

    • 辗转相除法 --欧几里得算法

      a和b的最大公约数等于 a % b,和 b 的最大公约数,然后递归 这里a>b,

    function mord(a,b) {
        let big = a> b?a:b
        let small = a<b ?a :b
        if(big % small == 0) return small
        
        return mord(big%small, small)
    }
    
    • 更相减损术 O(max(a-b))

      和欧几里得算法的区别是 mord(big - small, small), 是差值不是模值,其他一样

      避免俩个大整数取模的性能问题,但是当差值过大时,会遍历多次,比如最大值1000,最小值1,得遍历999次

    • 位运算符 O(log(max(a-b)))

      解决上面的问题,将上面两种算法优势结合起来

      a&1 == 0代表偶数,否则为奇数

      function gcd(a, b) {
          if(a == b) return a
          if(a&1 == 0 && b&1 ==0){
              return gcd(a>>1, b>>1)<<1
          }else if(a&1 == 0 && b&1 !=0){
              return gcd(a>>1, b)
          }else if(a&1 != 0 && b&1 ==0){
              return gcd(a, b>>1)
          }else{
              let big = a>b?a:b
              let small = a<b?a:b
              return gcd(big-small, small)
          }
      }
      
  • 如何判断一个数是否是2的整数次幂
// O(logn)
function rr(n){
    let temp = 1
    while(temp <= n){
        if(temp == n)return true
        temp = temp *2
        // 位移替代 * ,性能高
        //temp = temp<<1
    }
    return false
}

​ O(1)复杂度解法

// 利用2的次幂转换为2进制
function rr3(n) {
    return (n&n-1) == 0
}

  • 无序整数数组排序(有序)后的,任意两个相邻元素的最大差值

    • 先排序,再遍历算两两相邻的差值,性能差
    • 利用计数排序的思想
    • 利用桶排序的思想
  • 如何用栈实现队列

    用两个栈来实现

    元素依次进入栈a,栈a中元素出栈进入栈b

    入队进栈a,出队用栈b

    function likeQueue() {
        let stackA = new Stack()
        let stackB = new Stack()
        
        // 入对 O(1)
        let enQueue = (el)=>{
            statckA.push(el)
        }
        // 出对 O(n)
        let deQueue = ()=>{
            if(statckB.isEmpty()) {
                if(statckA.isEmpty()) return null
                transfer()
            }
            return statckB.pop()
        }
        //将栈a元素移动到栈b
        let transfer = ()=>{
            while(!statckA.isEmpty()) {
                statckB.push(statckA.pop())
            }
        }
        return {
            enQueue,
            deQueue
        }
    }
    
  • 给出一个正整数,请找出这个正整数所有数字全排列的下一个数

    就是在数字的全部组合中个,找到一个大于且仅大于原数的新整数

    eg: 输入12345,result: 12354

    输入12354,则返回:12435

    ​ 输入12435,则返回:12453

    • 逆排序,最大54321

    • 顺排序,最小12345

      eg: 12354

      • 从后向前查看逆序区域(54),找到逆序区域的前一位(3)
      • 让逆序区域的前一位(3)和逆序区域(54)中最小的数字(4),交换位置(12453)
      • 把现在的逆序区域(53)转为顺序(35)=》 12435
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容

  • 在C语言中,五种基本数据类型存储空间长度的排列顺序是: A)char B)char=int<=float C)ch...
    夏天再来阅读 3,340评论 0 2
  • 排序算法说明 (1)排序的定义:对一序列对象根据某个关键字进行排序; 输入:n个数:a1,a2,a3,…,an 输...
    code武阅读 658评论 0 0
  • SwiftDay011.MySwiftimport UIKitprintln("Hello Swift!")var...
    smile丽语阅读 3,830评论 0 6
  • 第五章******************************************************...
    fastwe阅读 678评论 0 0
  • 西安的天气最近变得很好,一场雪刷走了停留很久的雾霾,空气里冷冰冰的,但是很清新,阳光也温柔灿烂,暖暖地笼罩我。 一...
    浅其阅读 343评论 3 3