JavaScript算法编程——1.链表

193 阅读8分钟

思路来源:b站up主:【香辣鸡排蛋包饭】。本博只是自我学习整理所用
主页: space.bilibili.com/363956974?s…
此篇是按照知识点进行分类的

1.链表

  • 链表: 通过指针串联在一起的线性结构,每一个节点是又两部分组成,一个是数据域一个是指针域(存放指向下一个节点的指针),最后一个节点的指针域指向null(空指针的意思)

  • 节点定义

   var node = function(element) {
       this.element = element; // 存放节点内容
       this.next = null; // 指针
   }

image.png

2.链表算法

1. 一般

1.JZ6 从尾到头打印链表

  • 1.思路

image.png

  • 2. 方法
    1. 代码
function printListFromTailToHead(head)
{
    // write code here
    var str=[]; // 定义预存数组
    while(head){
        str.unshift(head.val); // unshift() 方法可向数组的开头添加一个或更多元素,并返回新的长度
        head = head.next;
    }
    return str;
}
module.exports = {
    printListFromTailToHead : printListFromTailToHead
};

递归法

function printListFromTailToHead(head)
{
    // write code here
    //一直递归到链表尾部,从尾部往前将值放进数组,放进数组的方式是从尾部添加(push)
    var arr=[];
    getArr(head,arr);
    return arr;
}

function getArr(head,arr){
    if(head!==null){
        getArr(head.next, arr);
        arr.push(head.val);
    }
}
module.exports = {
    printListFromTailToHead : printListFromTailToHead
};

2.JZ52 两个链表的第一个公共节点

输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。(注意因为传入数据是链表,所以错误测试数据的提示是用其他方式显示的,保证传入数据是正确的)

数据范围: n \le 1000n≤1000 要求:空间复杂度 O(1)O(1),时间复杂度 O(n)O(n)

  • 1.思路

image.png

image.png

  • 2.方法 1.定义两个头结点 2.只要头结点不相等,就按照特定轨迹移动 结点不为空,就向后一个结点移动,否则就到另一个链表 3.跳出循环,找到了公共节点,pA pB都是空的,所以直接返回pA
  • 3.代码
function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    // a1 → a2 → a3↘
    //              c1 → c2 → c3
    // b1 →b2 →b3↗
    let pA = pHead1, pB = pHead2 // 定义两个头结点
    while (pA !== pB) { // 只要两个头结点不相等, 就按照特定的轨迹移动
        pA = pA ? pA.next : pHead2 // pA不为空,就移动端到pA的后续节点,否则移动到B链表的头结点
        pB = pB ? pB.next : pHead1 //
    }
    return pA
}
module.exports = {
    FindFirstCommonNode : FindFirstCommonNode
};

2.反转

1.JZ24 反转链表

输入一个链表,反转链表后,输出新链表的表头。

  • 1.思路

image.png 链表的指针为单向,所以不能查找目前结点的前一个结点
从头开始,用pre保存上一项,cur当前项,tmp下一项。
pre和cur一组,不停移动,知道cur为null,则pre指向的是新的结点。

pre结点可以用来反转方向,为了避免反转之后链表断开,用temp结点暂时保存next结点

  • 2.方法 1.初始化定义各结点,pre = null,cur = head,temp = null
    2.只要cur没有走到最后的null,存储next结点,断开(next= pre),pre,cur向末移动
    3.返回pre
  • 3.代码
function ReverseList(pHead)
{
    // write code here
    let pre = null; //前结点
    let cur = pHead; //当前结点
    let temp = null; //初始化的下一个结点
    
    while(cur !== null){
        temp = cur.next;
        //断开到下一个结点的指针,然后pre、cur进行同时移动
        cur.next = pre; //pre和next是否同时为null,不是则断开next的(赋值为pre)
        pre = cur; // pre移动
        cur = temp; //temp移动
    }
    return pre
}
module.exports = {
    ReverseList : ReverseList
};

2. NC50 链表中的节点每k个一组翻转

将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。

    1. 思路
    1. 代码
function reverseKGroup( head ,  k ) {
    let pre = null, cur = head, dummy = head
    // 判断链表长度是否够k个翻转
    for (let i = 0; i < k; i++) {
        if(!dummy) return head
        dummy = dummy.next
    }
    // 每k个进行翻转
    for (let i=0; i<k; i++) {
        let temp = cur.next // 暂存下一个内容
        cur.next = pre // 修改指针
        // 更新节点
        pre = cur 
        cur = temp 
    }
    
    // 进行下一次k个翻转
    head.next = reverseKGroup(cur , k)
    return pre
}

3.NC21 链表内指定区间反转

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。

  • 1.思路 BD8AB11F179CE8F8ECDCCAF596468FE3.png
  • 2.方法
  • 3.代码
function reverseBetween( head ,  m ,  n ) {
    // write code here
    let dummy = new ListNode(0)
    dummy.next = head
    
    let temp = dummy // 翻转区间起始前一个的位置
    for (let i = 0; i< m-1; i++) {
        temp = temp.next
    }
    let pre = null
    let cur = temp.next // cur 指向已经翻转的链表的结尾
//     let smHead = temp.next
    for (let i = 0; i<= n-m; i++) {  // <=
        let next = cur.next // next进行链表的翻转
        cur.next = pre 
        // 移动
        pre = cur
        cur = next
    }
    temp.next.next = cur
    temp.next = pre
    return dummy.next
}

4.NC96 判断一个链表是否为回文结构

  • 1.思路 使用快慢指针,将后半段链表进行反转,例如:1->2->3->2->1,反转后成为1->2->3<-2<-1,进行比较,最后将链表还原。

  • 2.方法

  • 3.代码

function isPail( head ) {
    // write code here
    // 1.定义快慢指针法
    let slow = head, 
        fast = head,
        pre 
    if (!fast.next) return true // 1.只有一个,直接返回
    // 2.遍历链表,移动快慢指针
    while (fast && fast.next) {
        pre = slow
        slow = slow.next
        fast = fast.next.next
    }
    pre.next = null // 左右断开
    // 3.反转链表,此时slow到了中点
    let head2
    while(slow) {
        const temp = slow.next
        slow.next = head2
        // 移动结点
        head2 = slow
        slow = temp
    }
    while(head && head2) {
        if (head.val !== head2.val) return false
        head = head.next
        head2 = head2.next
    }
    return true
}

3.合并

1.JZ25 合并两个排序的链表

输入两个单调递增的链表,输出两个链表合成后的链表,当然我们需要合成后的链表满足单调不减规则。

  • 1.思路 image.png
  • 2.方法 1.初始化一个新的链表(内容为空)
    2.判断两连边是否有空
    3.比较两者的值,head→小的那个,小的指针向下移动。head向下移动。 4.判断谁先到头,则将另一个剩下的全部贴给head。
  • 3.代码
function ListNode(x){
    this.val = x;
    this.next = null;
}
function Merge(pHead1, pHead2)
{
    var head = new ListNode(null)//初始化一个节点值为0的空节点
    let dummy = head; //存储新链表的当前结点
    
    if(pHead1 == null) return pHead2;
    if(pHead2 == null) return pHead1;
    if(pHead1 == null && pHead2 == null) return null;
    
    while(pHead1 && pHead2){
        if(pHead1.val < pHead2.val){
            head.next = pHead1
            pHead1 = pHead1.next
        }else{
            head.next = pHead2
            pHead2 = pHead2.next
        }
        head = head.next
    }
    if(!pHead1){
        head.next = pHead2; //pHead1空了的话
    }else if(!pHead2){
        head.next = pHead1
    }
    return dummy.next
}
module.exports = {
    Merge : Merge
};

2.C51 合并k个已排序列表

    输入:[{1,2},{3,4,5},{6}]
    输出:{1,2,3,4,5,6}
  • 1.思路

  • 2.方法 1.遍历所有节点,将结点值提取到数组中

    2.对数组进行升序排序

    3.遍历数组,将数组内容重新转换为链表

  • 3.代码

function mergeKLists( lists ) {
    // write code here
    if (!lists || lists.length == 0) return null
    let arr = []
    
    
    // 1.遍历所有结点,将结点值提取到数组中
    lists.forEach( list => {
        let cur = list // 用一个cur来存储
        while (cur) {
            arr.push(cur.val)
            cur = cur.next
        }
    })
    let res = new  ListNode(0) // 新链表的根节点
    let cur = res // 记录上一个节点 
    // 2.对数组进行升序排序 sort((a,b) => a-b)
    arr.sort((a, b) => a-b).forEach(val => { // 3.遍历数组,将数组内容转化为链表
        let node = new ListNode(val) // 遍历,创建连接关系,将前一个节点耳朵next设置为当前结点
        cur.next = node
        cur = cur.next
    })
    return res.next
}

4.环

1.NC4 NC4 判断链表中是否有环

判断给定的链表中是否有环。如果有环则返回true,否则返回false。

  • 1.思路 快慢指针法

    function hasCycle( head ) {
    if (!head || !head.next) return false // 必须先判断是否能使用快慢指针
    let fast = head
    let slow = head
    while (fast.next && fast.next.next) {
        fast = fast.next.next
        slow = slow.next
        if (slow == fast) return true
        
    }
    return false
}

2.JZ23 链表中环的入口节点

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

image.png

image.png

  • 2.方法 1.采用快慢指针法,快指针走两步,慢指针走一步。两者相遇,及找到相遇节点
    2.从相遇节点节点发出指针index1,从头结点发出index2,两指针步数相同,相遇的地方就是环形入口节点

  • 3.代码

function EntryNodeOfLoop(pHead)
{
    // write code here
    let fast = pHead // 快指针
    let slow = pHead // 慢指针
    while (fast.next && fast.next.next) {// 判断快指针是否可走两步
        fast = fast.next.next
        slow = slow.next
        if (slow == fast) { // 快慢指针相遇,此时从 head 到 相遇点,同时查找环形入口
            let index1 = fast // 相遇节点发出index1
            let index2 = pHead // 从头结点发出index2
            while (index1 !== index2) {
                index1 = index1.next
                index2 = index2.next
            }
            return index2 // 找到了环形的入口
        }
    }
    return null
}

5.删除

1.JZ76 删除链表中重复的节点

在一个排序的链表中,存在重复的结点,请删除该链表中重复的结点,重复的结点不保留,返回链表头指针。 例如,链表 1->2->3->3->4->4->5  处理后为 1->2->5

  • 1.思路 添加一个头节点 头结点连接链表头部

设置pre 和cur指针。
pre指针指向当前确定不重复的节点,cur指针是当前工作的指针.
遇到重复的就跳过,一直向后进行

image.png

  • 2.方法 1.定义新的头结点、头结点指针、当前结点指针
    2.若有相同的,则一直找到最后一个相同的节点,找到后不保留删除,pre.next指向新的节点,更新cur
    3.没有相同的,则两个指针一起移动

  • 3.代码

function deleteDuplication(pHead)
{
    // write code here
    var p = { // 设置一个新的头结点
        val: 0,
        next: pHead
    }
    let pre = p // 头结点指针
    let cur = p.next  // 当前结点指针
    
    while (cur) {
        if (cur.next && cur.val === cur.next.val) {
            // 找到最后一个相同结点
            while (cur.next && cur.val === cur.next.val) { 
                cur = cur.next 
            }
            pre.next = cur.next // 不保留删除,cur往下资源
            cur = cur.next // 更新cur的下一个
        } else {
            pre = pre.next
            cur = cur.next
        }
    }
    return p.next
}

2.JZ18 删除链表中的节点

给定单向链表的头指针和一个要删除的节点的值,定义一个函数删除该节点。返回删除后的链表的头节点。

1.此题对比原题有改动
2.题目保证链表中节点的值互不相同
3.该题只会输出返回的链表和结果做对比,所以若使用 C 或 C++ 语言,你不需要 free 或 delete 被删除的节点

  • 1.思路

image.png

  • 2.方法 1.如果删除的是头结点的值,则直接返回后面的
    2.如果删除的是中间的值,则指针移动
    3.如果没找到值,则递归寻找

    1. 代码
function deleteNode( head ,  val ) {
    // write code here
    if (!head) return null
    if (head.val === val) { // 1.如果删除的是头结点的值,则直接返回后面的
        return head.next
    }
    if (head.next.val === val) { // 2.如果找到值所在的节点,则删除
        head.next = head.next.next
    } else { // 3.如果都不是,则向后移动,递归调用
        deleteNode(head.next, val)
    } 
    return head
}

3.NC53 删除链表的倒数第n个节点

给定一个链表,删除链表的倒数第 n 个节点并返回链表的头指针

  • 1.思路

  • 2.方法 1.设置新的围头结点 2.快慢指针,快指针走n+1 3.慢指针和快指针同事移动,直到fast走到最后,slow到被删除节点之前

  • 3.代码

var removeNthFromEnd = function(head, n) {
    var pre = { // 设置一个新的伪头结点
        val: 0,
        next: head
    }
    var slow = pre;
    var fast = pre;
    // 1. 快指针走n+1步,这样slow才能指向删除节点的上一个节点
    while(n>=0) {
        fast = fast.next;
        n--;
    }
    // 2.快慢指针同时移动
    while(fast){
        fast = fast.next;
        slow = slow.next;
    }
    // 3.直到fast到最后,此时slow在所删除节点的上一位。删除节点,返回pre
    slow.next = slow.next.next;
    return pre.next;
};

6.JZ22链表中最后K个结点

输入一个链表,输出一个链表,该输出链表包含原链表中从倒数第k个结点至尾节点的全部节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。

  • 1.思路 Null处为fast指针,所找点为slow指针。
    先让fast从起点处右移k步,然后slow和fast一起向右移动。
    fast到达NULL时,slow正好在倒数第K个结点上。

  • 2.方法 1.判断头结点为空或k<0;
    2.初始化slow,fast指针
    3.fast先走(循环到<k-1),若fast的下一个结点存在,就向下走,若不存在,则停下(return null) 4.若fast下一个结点存在,fast和slow一起向下走。

  • 3.代码

function FindKthToTail( pHead ,  k ) {
    // write code here
    if (pHead == null || k<=0 ){////如果头节点为空或k的值小于0,就直接返回null
        return null;
    }
    var fast = pHead;
    var slow = pHead;
    for(var i = 0; i< k-1; i++){//让第一个指针走k-1步
        if(fast.next){//如果下一个节点存在就继续往下走,否则k小于链表长度,直接返回null
            fast = fast.next;
        }else{
            return null
        }
    }
    while(fast.next){
        slow = slow.next;
        fast = fast.next;
    }
    return slow;
}
module.exports = {
    FindKthToTail : FindKthToTail
};

7.JZ35 复杂链表的复制

输入一个复杂链表(每个节点中有节点值,以及两个指针,一个指向下一个节点,另一个特殊指针random指向一个随机节点),请对此链表进行深拷贝,并返回拷贝后的头结点。(注意,输出结果中请不要返回参数中的节点引用,否则判题程序会直接返回空)。 下图是一个含有5个结点的复杂链表。图中实线箭头表示next指针,虚线箭头表示random指针。为简单起见,指向null的指针没有画出。

  • 1.思路

image.png
image.png 1.复制节点的值,并且放到原节点的后面
2.

  • 2.方法

  • 3.代码

function RandomListNode(x){
    this.label = x;
    this.next = null;
    this.random = null;
}

function Clone(pHead)
{
    if(pHead === null) return;
    // write code here
    // 1.复制节点的值,并且放到原节点的后面
    var cur = pHead;
    while(cur){
        var curCopy = new RandomListNode(cur.label) //复制每一个节点
        //并将节点插入到后面 【cur】【curCopy】,两个next指针先平移
        curCopy.next = cur.next
        cur.next = curCopy
        cur = curCopy.next 
    }
    
    // 2.构造random,从头结点开始
    cur = pHead
    while(cur){
        if(cur.random) {
            cur.next.random = cur.random.next
        }
        cur = cur.next.next //cur, curCopy,cur.(next)原本的
    }
    
    //3.拆分链表,从头结点开始
    cur = pHead
    var newHead =pHead.next // 记录返回的复杂链表的头结点
    while(cur.next){
        var temp = cur.next
        cur.next = temp.next //间隔相连
        cur = temp
    }
    return newHead
}
module.exports = {
    Clone : Clone
};

8.NC40 两个链表生成相加链表

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。

给定两个这种链表,请生成代表两个整数相加值的结果链表。

  • 1.思路 利用栈来存储两个链表,然后用cnt存储进位,用tempNode进行节点插入

image.png

  • 2.方法
  • 3.代码
function addInList( head1 ,  head2 ) {
    // write code here
    let s1 = [], s2 = [] //用栈存链表的数,这样就能从末位计算
    while (head1) {
        s1.push(head1.val)
        head1 = head1.next
    }
    while (head2) {
        s2.push(head2.val)
        head2 = head2.next
    }
    // 如果有进位,用cnt存储进位
    let cnt = 0
    let res = null // 结果指针
    while (s1.length !== 0 || s2.length !== 0) {
        let x1 = s1.length === 0 ? 0 :s1.pop()
        let x2 = s2.length === 0 ? 0 :s2.pop()
        let sum = x1 + x2 + cnt // 当前这位相加
        cnt = Math.floor(sum / 10) // 更新当前加和是否有进位
        // 进行节点插入!!!!!!!!!!
        let tempNode = new ListNode(Math.floor(sum % 10))
        tempNode.next = res // 暂时放到前面
        res = tempNode // ????
        
    }
    if (cnt > 0) {
        let tempNode = new ListNode(cnt)
        tempNode.next = res
        res = tempNode
    }
    return res
}

9.NC 单链表的排序

给定一个节点数为n的无序单链表,对其按升序排序。

  • 1.思路 把链表拿出来放到一个数组里排好序,再放回链表里。

  • 2.方法 1.把链表的值放到数组里 2.用sort进行升序排序 3.node回到head,对数组遍历,将数组的值放回链表

  • 3.代码

function sortInList( head ) {
    // write code here
    if (!head || !head.next) return head
    
    // 定义一个新数组
    let arr = []
    let node = head
    while(node) {
        arr.push(node.val)
        node = node.next
    }
    arr.sort((a,b) => a-b) // 升序排序
    node = head
    for (let item of arr) { // 对数组进行遍历
        node.val = item
        node = node.next
    }
    return head
}

image.png