前端基础算法总结- 链表

链表

链表在react的更新中有所使用,在面试中也是比较常见;比较基础的链表格式有两种:单链表环形链表node

前置

建立链表

class LinkNode {
    constructor(value){
      this.value = value;
      this.next = null;
    }
 }
复制代码

建立整个链表

function createLink(){
    this.head = null;
 }
 createLink.prototype.add=function(value){
   let linknode = new LinkNode(value)
   if(!this.head){
     this.head = linknode;
   }else{
     //进行遍历当前链表 添加到最后面
     this.insertNode(linknode)
   }
 }
 createLink.prototype.insertNode=function(node){
   let curr = this.head;
   while(curr){ 
     if(!curr.next){
       curr.next = node;
       return ;
     }
     curr = curr.next;
   } 
 }

复制代码

单链表

获取链表长度

function getLinkLength(link){
   if(!link) return 0
   let number = 1;
   let curr = link;
   while(curr){
     number++;
     curr = curr.next;
   }
   return  number;
 }
复制代码

输出单链表的倒数第k个节点

  • 输出最后一个节点
  • 从前日后遍历链表,若是遍历的+第k==链表长度则是当前节点

从头至尾开始查找react

/** * 获取倒数第k个元素 * @param {*} link * @param {*} k */
function getKlineNode(link,k){
  //获取当前link长度
  let linkSize = getLinkLength(link);
  if(linkSize <k) return null;
  let curr = link,nu =1;
  while(curr){
    if(k+nu == linkSize){
      return curr.value;
    }
    nu++;
    curr = curr.next;
  }
  return null;
}
复制代码

递归方法

  • 定义num = k
  • 使用递归方式遍历至链表末尾。
  • 由末尾开始返回,每返回一次 num 减 1
  • 当 num 为 0 时,便可找到目标节点
let num = 0
function dgGetKlineNode(link,k){
  num = k;
  if(!link) return null;
  let curr = dgGetKlineNode(link.next,k)
  //当前存在值 返回继续遍历
  if(curr){
     return curr;
  }else{
    //不存在值 说明当前一次遍历结束
    num --;//递归一次 减去0
    if(num ==0){//num为0 说明到了节点
       return link.value
    }
  }
  return null
}
复制代码

image.png

图1 找节点倒数第k个节点

双指针法

function dobuleGetKeyLink(link,key){
  if(!link) return null
  let curr = link,curr2 =link;
  //curr2先走k步
  for(var i =0;i<key;i++){
    curr2 = curr2.next;
  }
  //curr2到头,此时curr的位置正好是倒数第k的位置
  while(curr2){
    curr2 = curr2.next;
    curr = curr.next;
  }
  return curr.value 
}

复制代码

* 从尾到头打印链表

借助三方空间存储当前已经遍历过的树形结构面试

非递归

function printReverseLink(link){
  //借助stack存储已经遍历的结构
  let stack = [];
  let curr = link;
  while(curr){
    stack.unshift(curr.value)
    curr=curr.next;
  }
  return stack.join("->")
}
let result = printReverseLink(link.head)
console.log(result)
复制代码

递归

let stackdB = []
function dbPrintReverseLink(link){
  //当前节点不为空
    if(link){
      //继续遍历下一个元素
      if(link.next!=null){
        dbPrintReverseLink(link.next)
      }
      //而后在进行存储当前节点,保证后面的节点都在当前节点的前面
      stackdB.push(link.value)
    }
}
dbPrintReverseLink(link.head);
console.log(stackdB.join("->"))
复制代码

* 反转链表

链表的反转,即 先后置换,头结点标称尾部markdown

  • 链表遍历
  • 定义前pre节点
  • 定义后node节点,向后移动的时候进行指针的指向调转
function reverseLink(link){
  if(!link) return null; 
  let pre = null,pReversedHead;
  let curr = link; 
  while(curr){
    let needNext = curr.next;//建立一个临时的next 
    //调换当前的pre 和curr的指向 
    curr.next = pre;
    pre = curr;  
    curr = needNext;   
  }    
  return pre 
}
复制代码

* 删除链表中节点、要求时间复杂度o(1)

  • 遍历链表 若是存在key == linkNode.value则进行删除
  • 注意边界值的删除,多是最后一个元素
function deleteSingleNode(link,node){
  if(!link) return null;
  let curr = link,pre=null;
  while(curr){
     //当前存在 则删除
    if(curr.value == node){ 
      //不是删除的在最后一个
      if(curr.next){
        curr.value = curr.next.value;
        curr.next =  curr.next.next;
        break;
      }else{
        //最后一个元素
        pre.next = null;
        break;
      }  
    } 
    pre = curr;
    curr  = curr.next; 
  }
  //清除掉空的元素
}
复制代码

删除链表中重复的节点

是否保留一个节点 给定一个有序链表,如有一个以上相同节点,则将该元素值的节点均删除。函数

  • 给定条件为有序 这样保证了咱们不须要重复去遍历了;
  • 若是链表是是无序的,则不须要进行删除;

思路-:遍历链表,记录元素,若是存在相同的则进行所有删除

function deleteMoreNode(link){
    if(!link) return null
    let curr = link,pre = link;
    //第一个特殊
    if(curr.next && curr.value == curr.next.value){
      var isFirst = true;
    }
    while(curr){
      //比较当前和后面一个是否相等
      if(curr.next){
        //两个元素相等
        if(curr.value == curr.next.value){
          //持续遍历 直到不相等
          while(curr.next && curr.value == curr.next.value ){
            curr = curr.next;
          }  
          curr = curr.next;  
          pre.next = curr;   
        }else{ 
          pre = curr; 
          curr = curr.next;  
        }   
      } else{
        curr=curr.next;
      }
    }
    //第一元素相等 删除第一个元素 
    if(isFirst){  
      if(link.next){
        link.value = link.next.value;
        link.next = link.next.next || null; 
      }else{ 
        link.value = null
      }    
    }  
 }
复制代码

其余思路:

遍历链表,保留在栈中,每次进行比较栈中是否需存在该元素,存在则进行删除,不存在则继续oop

function deleteMoreNodeStack(link){
  if(!link) return null;
  let stack =[];
  stack.push(link.value)
  let curr = link.next,pre = link; 
  while(curr){ 
    if(stack.indexOf(curr.value)>-1){   
      //存在当前元素 进行删除 
      while(stack.indexOf(curr.value)>-1){  
         curr = curr.next;
         //此时说明元素已经不存在了
         if(!curr){
           pre.value = null;
           pre.next = null
           return ;
         }
      }   
      if(curr){
        stack.push(curr.value)
        pre.value = curr.value;
        pre.next = curr.next;
      }     
    }else{ 
      stack.push(curr.value)
      pre = curr;   
    }
    curr &&  (curr = curr.next) 
  } 
   
}
复制代码

* 两个链表中的第一个公共节点

思路:ui

单链表的指针是指向下一个节点的,若是两个单链表的第一个公共节点就说明他们后面的节点都是在一块儿的。相似下图,因为两个链表的长度多是不一致的,因此首先比较两个链表的长度m,n,而后用两个指针分别指向两个链表的头节点,让较长的链表的指针先走|m-n|个长度,若是他们下面的节点是同样的,就说明出现了第一个公共节点。this

const getLinkLength = require('./link-length')
 function findSameNode(link1,link2){
   if(!link1 || !link2) return null;
   //计算两个链表长度 
   let size1 = getLinkLength(link1);
   let size2 = getLinkLength(link2);
   let  n = size1- size2;
   let mH = null,sH = null;//mH存储长链表 sH存储短链表
   if(n>0){
      mH=link1;
      sH=link2
   }else{
     mH=link2;
     sH=link1
   }
   //大的先走
   n =Math.abs(n)
   while(n){
     mH = mH.next;
     n--;
   }  
   //两个链表 mH 和 sH长度都相等
   while( (mH && sH) && mH.value !=sH.value){
     mH= mH.next;
     sH = sH.next;
   }
   return mH;
 }
复制代码

使用链表实现大数加法

问题描述 两个用链表表明的整数,其中每一个节点包含一个数字。数字存储按照在原来整数中相反的顺序,使得第一个数字位于链表的开头。写出一个函数将两个整数相加,用链表形式返回和。spa

输入
3->1->5->null
5->9->2->null,
输出
8->0->8->null
复制代码
  • 链表进行加法时候
  • 是否有进位
  • 最后结束后是否存在未遍历完成的部
function comSumNode(link1,link2){
   if(!link1 || !link2) {
     return link1 || link2
   }
   let curr1 =link1,curr2 = link2;
   //t用来进行计算的总会 sum表示是否有进位
   let t = new createLink(),sum=0;
   while(curr1 && curr2){ 
     let value = curr2.value + curr1.value; 
     //判断前一位是否有进位
     value = sum ? value+1 : value;
     sum = 0 
     //当前的t.value是否大于0 若是是 则表示下一位须要进位
     if(value>10){
       sum=1;
       value =value -10;
     }  
    t.add(value)
    curr2 = curr2.next;
    curr1 = curr1.next; 
   }
   //查看sum ,curr2和curr1是否存在剩余的元素
   while(curr1){
     value = sum? curr1.value+sum:curr1.value;
     sum =0;
     if(value>=10){
       sum =1
       value = value-10
     }
     t.add(value)
     curr1= curr1.next;
   }
   while(curr2){
    value = sum? curr2.value+sum:curr2.value;
    sum =0;
    if(value>=10){
      sum =1
      value = value-10
    }
    t.add(value)
    curr2= curr2.next;
  }
  //还存在sum
  if(sum){
    t.add(sum)
  }
   return t.head;
 }
复制代码

有序链表合并

题目:将两个有序链表合并为一个新的有序链表并返回。新链表是经过拼接给定的两个链表的全部节点组成的。.net

示例:
输入:
1->2->4,
1->3->4
输出:
1->1->2->3->4->4
复制代码

常规作法

  • a. 遍历两个链表,比较两个链表的大小
  • b. 将小的元素压入到新的链表中
/** * 合并两个有序链表 常规作法 * @param {*} link1 * @param {*} link2 */
function mergeLink(link1,link2){
  if(!link1 && !link2){
    return link2 || link1;
  }
  let newLink = new createLink();
  let curr1=link1,curr2=link2;
  while(curr1 && curr2){ 
    if(curr1.value > curr2.value){
      //若是link2的元素小将2的元素压入到新链表中 则2的指针向后移动
      newLink.add(curr2.value);
      curr2 =curr2.next;
    }else{
      newLink.add(curr1.value);
      curr1 =curr1.next;
    }
  }
  //查看是否还存在剩余元素
  while(curr2){
    newLink.add(curr2.value);
    curr2 = curr2.next;
  }
  while(curr1){
    newLink.add(curr1.value);
    curr1 = curr1.next;
  }
  return newLink;
}

let result = mergeLink(link2.head,link.head)
result.print()
复制代码

递归思想

  • 对空链表存在的状况进行处理,假如 pHead1 为空则返回 pHead2 ,pHead2 为空则返回 pHead1。
  • 比较两个链表第一个结点的大小,肯定头结点的位置
  • 头结点肯定后,继续在剩下的结点中选出下一个结点去连接到第二步选出的结点后面,而后在继续重复(2 )(3) 步,直到有链表为空。
//递归实现
function dgMergeLink(link1,link2){
  let newLink = new LinkNode();
  //link1 或者link2为空 则返回对方
  if(link1 ==null){
    return link2;
  }else if(link2 ==null){
    return link1
  }else{
     //比较两个的大小
     if(link2.value > link1.value){
       newLink = link1; //将link1压入 继续遍历link1.next
       newLink.next = dgMergeLink(link1.next,link2)
     }else{
      newLink = link2; //将link2压入 继续遍历link2.next
      newLink.next = dgMergeLink(link1,link2.next)
     }
     return newLink 
  } 
}
let result1 = dgMergeLink(link2.head,link.head) 
let r = new createLink();
r.header = result1;
r.print()
复制代码

环形链表

单链表中的环是指链表末尾的节点的 next 指针不为 NULL ,而是指向了链表中的某个节点,致使链表中出现了环形结构。

image.png

图2 环形链表结构

对于最后一个节点8 ,并不是指向null,而是next指向了3,造成了环形结构;

判断链表中是否有还

标记记录法

遍历链表,访问过的标记为1,若是遍历到一个节点正好是访问过的,则存在环,不然不存在;

/** * 判断链表是否存在 环 * 穷举法 */
function isCommonRing(link){
  let curr = link;
  while(curr){
    //已经访问过了 则存在 表示 有环
    if(curr.isVisted){
      return true;
    }
    curr.isVisted = 1; 
    curr =curr.next
  }
  return false;
}
复制代码

创建set表

快慢指针

  • 定义两个指针分别为 slow,fast,而且将指针均指向链表头节点。
  • 规定,slow 指针每次前进 1 个节点,fast 指针每次前进两个节点。
  • 当 slow 与 fast 相等,且两者均不为空,则链表存在环。
function isHaveRingFast(link){
  let slow = link;
  let fast = link;

  while(slow && fast.next){
    slow = slow.next;//慢指针
    fast = fast.next.next;//快指针
    if(slow.value == fast.value){
      return true;
    } 
  }
  return false;
}
复制代码

找到环路的入口节点

slow 指针每次前进一个节点,故 slow 与 fast 相遇时,slow 尚未遍历完整个链表。设 slow 走过节点数为 s,fast 走过节点数为 2s。设环入口点距离头节点为 a,slow 与 fast 首次相遇点距离入口点为 b,环的长度为 r。

image.png

图3 环形链表图
设环的长度为r
则slow走过的路程 s=a+b
Fast走过的路程 2s = a+b+n *r (n表示走过的圈数)  
s = n * r; 意味着slow指针走过的长度为环的长度整数倍。
若链表头节点到环的末尾节点度为 L,slow 与 fast 的相遇节点距离环入口节点为 X。
则有:
a+X = s = n * r = (n - 1) * r + (L - a);
a = (n - 1) * r + (L - a - X);(正好指向入口节点,须要推导o)
从 slow 与 fast 相遇点出发一个指针 p1,前进 (L - a - X) 步,则此指针到达入口节点。同时指针 p2 从头结点出发,前进 a 步。当 p1 与 p2 相遇时,此时 p1 与 p2 均指向入口节点。
(L - a - X) 是相遇点到入口点距离
复制代码
function getMeetNode(link){
  let slow = link;
  let fast = link;
  while(slow && fast.next){
    slow = slow.next;//慢指针
    fast = fast.next.next;//快指针
    if(slow.value == fast.value){
      return slow;
    } 
  }
  return null;
}

//根据入口节点 找到入口节点
function findEntry(link){
  let node = getMeetNode(link);
  if(!node) return null;
  let p1 = node;
  let p2 = link;
  while(p1 != p2){
    p1=p1.next;
    p2=p2.next
  }
  return p2
}
复制代码

计算环路长度

根据找到的相遇点进行计算便可; 在图3中找到了 slow 与 fast 的相遇节点,令 solw 与 fast 指针从相遇节点出发,按照以前的前进规则,当 slow 与fast 再次相遇时,slow 走过的长度正好为环的长度。 找到相遇点: 特地画了流程图

image.png

image.png

image.png

图4 计算环形链表图
function comRingLength(link){
  if(!link) return null;
  let fast = link;
  let slow = link;
  //找到相遇点
  while(fast.next && slow){
    fast =fast.next.next;
    slow = slow.next
    if(fast == slow){
      break;
    }
  }
  //继续出发
  slow =slow.next; 
  fast = fast.next.next;
  let num = 1;
  //当它们再次相遇的时候;
  while(slow != fast){
    fast = fast.next.next;
    slow = slow.next; 
    num ++;
  }
  return num; 
}
let number = comRingLength(link.head)
console.log(number)
复制代码

参考文档

相关文章
相关标签/搜索