学习笔记第一周

261 阅读3分钟

前置知识

链表

链表是一种通过指针串联在一起的线性结构,每个节点都由两部分组成,一个是数据域,一个是指针域(存放指向下一个节点的指针)最后一个节点的指针域指向null
链接的入口节点称为链表的头节点head

链表类型

  • 单链表

单链表

  • 双链表

    每个节点都有2个指针域,一个指向上个节点,一个指向下个节点,既可以向前查询也可以向后查询

双链表

  • 循环链表

    顾名思义,就是首尾相连的链表,其中著名的有约瑟夫环问题(待定)

循环链表

链表的存储方式

链表的存储方式在内存中不是连续的,它是散乱分布在内存里,通过指针域的指针来链接在内存上的各个节点。

存储方式

这个链表的起点节点是2,终点节点是7,其余各个节点散乱在内存中,通过指针进行链接。


算法的时间复杂度和空间复杂度

  • 时间复杂度

    是指执行当前算法所消耗的时间,「 大O符号表示法 」,即 T(n) = O(f(n)) demo

      for(let i=0; i<n; i++) {
        console.log(i);
      }
    

    这段执行算法的时间复杂度就是O(n) 常见的时间复杂度的例子有:

    1. 常数阶O(1)
     无论代码执行了多少行,只要没有循环等复杂结构,那么这个代码的时间复杂度都是O(1)
    
     var i = 1; 
     var j = 1;
      ++i;
      j++;
    var m = i + j
    
    1. 对数阶O(logN)
     var i = 1;
     while(i<n){
       i = i*2;
     }
    
      在while循环里面,每次i都乘以2i距离n的大小就越来越近,假设循环x次之后,i就大于n了,这个循环就退出,那么2的x方等于n,x=log2 (n);当n无限大的时候,底数可以忽略,因此这个代码的时间复杂度为O(logn)
    
    1. 线性阶O(n)
     一开始的demo中,for循环代码会执行n遍,因此它消耗的时间是随着n的变化而变化,这类代码都可用O(n)来表示它的时间复杂度
    
    1. 线性对数阶O(nlogN)
     就是将时间复杂度为O(logn)的代码循环N遍的话,那么它的时间复杂度就是n*O(logn),也就是O(nlogN)
    
      for(m=1; m<n,m++){
        i=1;
        while(i<n){
          i =i*2;
        }
      }
    
    1. 平方阶O(n²)
      如果把O(n)的时间复杂度代码在循环一遍,那它的时间复杂度就是O(n^2), 还有如O(m*n)的时间复杂度,其实也是嵌套循环了。
    
    1. 立方阶O(n³)
    2. K次方阶O(n^k)
     立方阶相当于三层n循环,k次方代表k层n循环了
    
    1. 指数阶(2^n)
     通常情况下斐波那契的递归算法属于指数阶的时间复杂度。如下代码,a(0)=a(1)=1;同时a(n)=a(n-1)+a(n-2)+1, 当n=1时,复杂度为O(1),当n>1时,n会被一层一层拆分。指数级别的增长,所以时间复杂度O(2^n)
    
      function a(n) {
        if(n<=1){
          return 1;
        }
        return a(n-1)+a(n-2)
      }
    

    O(1) < O(log n) < O(√n) < O(n) < O(nlog n) < O(n^2) < O(2^n) < O(n!)

  • 空间复杂度

    既然时间复杂度用来计算程序具体耗时的,那么空间复杂度就是程序具体占用的空间。 常用的也是有O(1),O(n), O(n^2)

    1. 空间复杂度O(1)
    如果算法的临时空间不会随着某个变量n的变化而变化,那么它的算法空间复杂度为一个常量,O(1)
    
    1. 空间复杂度O(n)
     随着某个变量n的变化,临时空间也随之变化,空间复杂度即为O(n).
     demo:
     var nn = [];
     for(var i=0; i<n;i++){
       nn[i] = i+1;
     }
    
周一题目
  1. leetcode: 环形链表

    解题思路: 首先真的是无从下手,翻阅资料,写了关于链表的一些定义,当然都是从网上摘录的,就感觉和数组比较相像,不过,数组在内存中是一块连续的需要预留的空间。 感想:看了一圈别人的解题思路,大部分用的快慢指针的方式,还有个比较有意思,也是自己目前没理解的反转链表。

      快慢方式:
      var hasCycle = function(head) {
          if(head === null){
            return false;
          }
          let fast = head;
          let slow = head;
          while(fast !== null && fast.next !==null){
            //慢指针每次走一步
            slow = slow.next;
            //快指针每次走两步
            fast = fast.next.next;
            //如果相遇,说明有环,直接返回true
            if (slow === fast) {
              return true;
            }                 
          }
          return false;
      }
    
      反转链表:
      var hasCycle = function(head) {
        const rev = reverseList(head);
        if(head !== null && head.next !== null && rev === head){
          return true;
        }
        return false;
    
      }
      function reverseList(head) {
        let newHead = null;
        while(head !== null){
          //先保存访问的节点的下一节点
          //留着下一步访问
          let temp = head.next;
          //每次访问的原链表节点都会成为新链表的头节点,其实就是把新链表挂到访问的元链表节点后面就可以了
          head.next = newHead;
          // 更新新链表
          newHead = head;
          // 重新赋值,继续访问
          head = temp;
        }
        return newHead;
        
      }
    
  2. leetcode: 环形链表Ⅱ

    解题思路:由于有了上次第一题目的思路,可以拿到是否有环,那么看看在快慢指针相遇也即等于的时候能否做一些事情。 看过比较好理解的解题思路的:定义2个指针,快和慢,如果有环,快慢肯定会相遇,我们定义了快指针是慢指针速度的2倍,那么他们相遇时候慢指针走了k步数,快指针就肯定是2k步数。(肯定再环内循环走)我们定义环的入口到相遇点距离是m,那么头节点到相遇点就是k-m,而快指针

    快慢方式:
    var detectCycle = function(head) {
        if(head === null){
          return null;
        }
        let fast = head;
        let slow = head;
        let start = head;
        let meet = null;
        while(fast !== null && fast.next !==null){
          //慢指针每次走一步
          slow = slow.next;
          //快指针每次走两步
          fast = fast.next.next;
          //如果相遇,说明有环,
          if (slow === fast) {
            meet = slow;
            break;
          }                 
        }
        if(meet === null) {
          return meet;
        }
        while(start !== meet){
          start = start.next;
          meet = meet.next;
        }
        return start;
    }
    
  3. leetcode:快乐数

    解题思路: 看到对应的数据结构,应该和递归有比较大的关系,先写出伪代码,再进行完善。 运行时,发现数组开辟的空间太大,直接报错,可见这代码写的是多low。

     报错代码,开辟的空间太大了
      var isHappy = function(n) {
        let nums= n.toString().split("");
        let num = 0;
        nums.forEach(i=>{
          num += Math.pow(i,2);
        });
        if(num === 1){
          return true;
        }
        if(num >(Math.pow(2,31)-1)){
          return false;
        }
        return isHappy(num);
        
      }
    

    看了关于题目的讲解,有对应的快慢指针法:

       var isHappy = function(n){
         let slow = n;
         let fast = getNext(n);
         while(fast !== 1 && fast !== slow){
            slow = getNext(slow);
            fast = getNext(getNext(fast));
         }
         return fast === 1;
    
       }
       function getNext(n){
         return n.toString().split("").map(i=>Math.pow(i,2)).reduce((a,b)=>a+b);
       }
    
  4. leetcode: 反转链表

    解题思路:根据第一题的思路,可以将链表进行反转,要求是迭代或者递归。

      双指针
      var reverseList = function(head) {
         let pre = null; //前置
         let curr = head; // 当前节点
         let temp = null; // 保存当前节点的下一节点
         while(curr !== null) {
            temp = curr.next; 
            curr.next = pre; // 翻转操作
            //更新pre和curr的指针
            pre = curr;
            curr = temp;
         }
         return pre;
      };
    
    

1.gif javascript 递归 5. leetcode: 反转链表Ⅱ

 解题思路:根据现有认知,是先把对应位置的节点先删除,再插入,但是题目要求可否一次性的扫描完成。所以现有认知的情况肯定不符合,那么看下别人优秀的咋写的。
 ```javascript
 头插法:
 /**
  * @param {ListNode} head
  * @param {number} left
  * @param {number} right
  * @return {ListNode}
  */
  var reverseBetween = function(head, left, right) {
    if(left === right){
      return head;
    }
    const dummy = new ListNode(-1);
    dummy.next = head;
    let pre = dummy;
    for(let i=0; i<left-1;i++){
      pre = pre.next;
    }
    let curr = pre.next;
    for(let i =0; i<right-left; i++){
      const next = curr.next;
      curr.next = next.next;
      next.next = pre.next;
      pre.next = next;
    }
    return dummy.next;


  };
 ```
周三题目
  1. leetcode:K个一组翻转链表 解题思路:和周一的第五题有点相似啊,只是把right-left换成k,那么按照周一第五题的思路来试试看

        var reverseKGroup = function (head, k){
        if(k === 1) {
          return head;
        }
        const l = getLength(head);
        if(l=== 0 || l=== 1){
          return head;
        }
        const kl = Math.floor(l/k);
        return insertHead(head, kl,k);
      }
      /**
       * 链表头插法
      */
      function insertHead(head, kl,k){
        const dummy = new ListNode(-1);
        dummy.next = head;
        let pre = dummy;
        let curr = pre.next;
        for(let i =0; i<kl; i++){ 
          for(let j=0;j<k-1;j++){
            const next = curr.next;
            curr.next = next.next;
            next.next = pre.next;
            pre.next = next;
          }
           //移动pre 和curr
          pre = curr;
          curr = pre.next;
          
        }
        return dummy.next;
      } 
      /**
       * 获取链表的长度
      */
      function getLength(head){
        if(head === null) {
          return 0;
        }
        let l = 0;
        let curr = head;
        while(curr !== null) {
          l++;
          curr = curr.next;
        }
        return l;
    
      }
    
      }
    
  2. leetcode: 旋转链表 解题思路:如果我把它想象成一个圆环,记录一下一开始的节点,然后在圆环上顺时针转动k个位置,再把圆环按照一开始的节点拆成单链表?但是从例子上看,移动的位置如k,那么就位移k次,循环从尾部插入到头部岂不是更好?(发现循环次数多了,不通过)

    也是利用圆环:
    var rotateRight = function (head, k) {
     if(head === null || k === 0){
       return head;
     }
     let curr = head;
     let last = head;
     let l = 0;
     // 找到最后一个结点
     while(curr !==null){
       last = curr;
       curr = curr.next;
       l++;
     }
     if(k%l===0){
         return head;
     }
     last.next = head; // 最后的节点指向开头
     let move =  l - (k%l);
     last = head;
     while(move>0){
       curr = last;
       last = last.next;
       move--;
     }
     curr.next = null;
     return last;
      
    }
    
  3. leetcode: 两两交换链表中的节点 解题思路:和第一题的思路相同,把k个一组改成2个一组进行转换,小于2的话,直接返回。按照这个思路来进行解题试试~~

      /**
       * @param {ListNode} head
      * @return {ListNode}
      */
      var swapPairs = function(head) {
        if(head === null || head.next === null){
          return head;
        }
        /**
         * 获取链表长度
        */
        let l = 0;
        let ll = head;
        while(ll!==null){
          ll = ll.next;
          l++;
        }
        let dummy = new ListNode(-1);
        dummy.next = head;
        let pre = dummy;
        let curr = head;
        const m = Math.floor(l/2);
        while(m--){
          const next = curr.next;
          cur.next = next.next;
          pre.next = next;
          next.next = cur;
          pre = cur;
          cur = pre.next;
    
        }
        return dummy.next;
    
    
      };
    
  4. leetcode:删除链表的倒数第N个节点 解题思路:用双指针吗?是否断掉相连的节点就可以了?

      /**
       * @param {ListNode} head
       * @param {number} n
       * @return {ListNode}
       */
      var removeNthFromEnd = function(head, n) {
        if(head === null) {
          return head;
        }
        // 拿到head的长度
        let l = 0;
        let ln = head;
        while(ln!==null){
          ln = ln.next;
          l++;
        };
        // 倒数第n个节点,正着数是第几个
        const leftL = l-n+1;
        
        const dummy = new ListNode(-1);
        dummy.next = head;
        let pre = dummy;
        let curr = head;
        if(leftL=== 1) {
          pre.next = curr.next;
          curr.next = null;
          return dummy.next;
        }
        for(let i = 1; i<leftL; i++){
           const next = curr.next;
           pre = curr;
           curr = next;
           if(leftL === i+1){
             pre.next = curr.next;
             curr.next = null;
           }
        } 
        return dummy.next;
    
    
      };
    
  5. leetcode: 删除排序链表中的重复元素 解题思路: 看下链表的数据结构,是否是interface ListNode 中有个val 那么这个val是不是可以判断重复的值?

      /**
       * @param {ListNode} head
       * @return {ListNode}
       */
       var deleteDuplicates = function(head) {
           if(head === null){
               return head;
           }
           let curr = head;
           while(curr !== null && curr.next !==null){
               const next = curr.next;       
               if( curr.val === next.val){
                 curr.next = next.next;
                 next.next = null;
               }else {
                   curr = next;
               }
           }
           return head;
       };