前端常见算法题(链表篇)--上

218 阅读11分钟

一、反转问题

2021.02.11

No.25 K个一组翻转链表

给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。

k 是一个正整数,它的值小于或等于链表的长度。

如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。

 

示例:

给你这个链表:1->2->3->4->5

当 k = 2 时,应当返回: 2->1->4->3->5

当 k = 3 时,应当返回: 3->2->1->4->5

 

说明:

你的算法只能使用常数的额外空间。 你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/re… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=25 lang=javascript
 *
 * [25] K 个一组翻转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
    // 链表转数组
    const  list2Arr = head => {
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 对k个节点进行数组切割即可
    const kReverse = (a,k) => {
        const r = [];
        let cnt = 0;
        while(a.length >= k + cnt)
        {
            let tmp = a.slice(cnt, cnt+k);
            tmp.reverse();
            tmp.map( (x)=> r.push(x));
            cnt += k;
        }
        a.slice(cnt).map( (x)=> r.push(x));
        return r;
    }
    
    return arr2List(kReverse(list2Arr(head), k));
};

方案二:

/*
 * @lc app=leetcode.cn id=25 lang=javascript
 *
 * [25] K 个一组翻转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
    let cur = head;
    let count = 0;
    // 求k个待反转元素的首节点和尾节点
    while(cur != null && count != k){
        cur = cur.next;
        count++;
    }
    // 足够k个节点,去反转
    if(count == k){
        // 递归链接后续k个反转的链表头节点
        cur = reverseKGroup(cur,k);
        while(count != 0){
            count--;
            // 反转链表
            let tmp = head.next;
            head.next = cur;
            cur = head;
            head = tmp;
        }
        head = cur;
    }
    return head;
};

方案三:

/*
 * @lc app=leetcode.cn id=25 lang=javascript
 *
 * [25] K 个一组翻转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
    if(!head) return null;
    // 反转链表
    let reverse = (a,b) => {
        let pre;
        let cur = a;
        let next = b;
        while(cur != b){
            next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
        return pre;
    }
    // 反转区间a-b的k个待反转的元素
    let a = head;
    let b = head;
    for(let i = 0;i < k;i++){
        // 不足k个,不需要反转
        if(!b) return head;
        b = b.next;
    }
    // 反转前k个元素
    let newHead = reverse(a,b);
    // 递归链接后续反转链表
    a.next = reverseKGroup(b,k);
    return newHead;
};

方案四:

/*
 * @lc app=leetcode.cn id=25 lang=javascript
 *
 * [25] K 个一组翻转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
    let stack = [];
    let preHead = new ListNode(0);
    let pre = preHead;
    // 循环链接后续反转链表
    while(true){
        let count = 0;
        let tmp = head;
        while(tmp && count < k){
            stack.push(tmp);
            tmp = tmp.next;
            count++;
        }
        // 不够k个,直接链接剩下链表返回
        if(count != k){
            pre.next = head;
            break;
        }
        // 出栈即是反转
        while(stack.length > 0){
            pre.next = stack.pop();
            pre = pre.next;
        }
        pre.next = tmp;
        head = tmp;
    }
    return preHead.next;
};

方案五:

/*
 * @lc app=leetcode.cn id=25 lang=javascript
 *
 * [25] K 个一组翻转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var reverseKGroup = function(head, k) {
  	const myReverse = (head, tail) => {
        let prev = tail.next;
        let p = head;
        while (prev !== tail) {
            const nex = p.next;
            p.next = prev;
            prev = p;
            p = nex;
        }
        return [tail, head];
    }
    const hair = new ListNode(0);
    hair.next = head;
    let pre = hair;

    while (head) {
        let tail = pre;
        // 查看剩余部分长度是否大于等于 k
        for (let i = 0; i < k; ++i) {
            tail = tail.next;
            if (!tail) {
                return hair.next;
            }
        }
        const nex = tail.next;
        [head, tail] = myReverse(head, tail);
        // 把子链表重新接回原链表
        pre.next = head;
        tail.next = nex;
        pre = tail;
        head = tail.next;
    }
    return hair.next;
};

有五种解法:1、利用数组求解,比较笨重,需要切换成数组再切回来;2、递归相关方案,利用栈、迭代等进行解析;3、利用虚置前节点,将复杂度降到常数级别,很巧妙



2021.02.12

No.61 旋转链表

给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。

示例 1:

输入: 1->2->3->4->5->NULL, k = 2 输出: 4->5->1->2->3->NULL 解释: 向右旋转 1 步: 5->1->2->3->4->NULL 向右旋转 2 步: 4->5->1->2->3->NULL 示例 2:

输入: 0->1->2->NULL, k = 4 输出: 2->0->1->NULL 解释: 向右旋转 1 步: 2->0->1->NULL 向右旋转 2 步: 1->2->0->NULL 向右旋转 3 步: 0->1->2->NULL 向右旋转 4 步: 2->0->1->NULL

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/ro… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=61 lang=javascript
 *
 * [61] 旋转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function(head, k) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 数组位置变化
    const arrRotate = (a, k) => {
        const len = a.length;
        const hashTable = {};
        for(let i=0; i< a.length; i++) {
            hashTable[(i+k) % len] = a[i]
        };
        return Object.values(hashTable) 
    };
    return arr2List(arrRotate(list2Arr(head), k));
};

方案二:

/*
 * @lc app=leetcode.cn id=61 lang=javascript
 *
 * [61] 旋转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function (head, k) {
    if (!head || k === 0) return head;  // 特判
    let p = head, len = 1;
    while (p.next) {
        p = p.next;
        len++;
    }
    p.next = head; // 首尾相接
    k = len - k % len; // 处理要移动的距离
    while (k--) p = p.next;
    head = p.next;  // head 指向第 len - k + 1 个节点,即答案
    p.next = null;  // 切断 len - k 与 len - k + 1 的关系
    return head;
}

方案三:

/*
 * @lc app=leetcode.cn id=61 lang=javascript
 *
 * [61] 旋转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function(head, k) {
    if(head===null||head.next===null)return head;
    let len = 0;//链表长度
    let p = head;
    let first = head;//第一个结点
    let stack = [];//辅助栈
    while(p){
        stack.push(p);
        p = p.next;
        len++;
    }
    p = stack.pop();
    for(let i = 1;i<=k%len;i++){
        p.next = first;
        stack.unshift(p);
        first = p;
        p = stack.pop();
        p.next = null;
    }
    return first;
};

方案四:

/*
 * @lc app=leetcode.cn id=61 lang=javascript
 *
 * [61] 旋转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
var rotateRight = function(head, k) {
       var order = new ListNode(0,head); // head链表的copy
       var fast = new ListNode(0,head); // 快指针
       var slow = new ListNode(0,head); // 慢指针
       var count = 0; // 链表长度
   
       while(order && order.next){ // 循环求链表长度
           order = order.next;
           count ++;
       }
       var n = k % count; // 因为 k > 链表长度 时,出现重复操作。对 k 求余,去除重复
   
       for (var i = 0; i < n; i++){ // 快指针先走 n 步
           fast = fast.next;
       }
       while(fast && fast.next){ // 链表的快指针、慢指针操作
           fast = fast.next;
           slow = slow.next;
       }
       
       // 两步操作
       // 第一、将倒数第 k%count 元素 与 倒数第 (k%count-1) 元素 断开
       var resultPre = slow.next; // 序号2  返回结果集的 起始位置
       var result = resultPre; // 序号3  最后return的结果集
       slow.next = null; // 将链表断开
       // 第二、再讲链表的尾部与头部链接
       while(resultPre && resultPre.next) {
           resultPre = resultPre.next
       }
       if (resultPre){ 
           resultPre.next = head; // 情况1:旋转链表后有改变,将链表拼接起来 
       }
       else {
           result = head;  // 情况2: 旋转链表后没有改变,返回原始链表
       }
       return result;
   };

方案五:

/*
 * @lc app=leetcode.cn id=61 lang=javascript
 *
 * [61] 旋转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} k
 * @return {ListNode}
 */
const rotateRight = (head, k) => {
    if (!head || !head.next) return head
    let curr = head, n = 0
    // 遍历链表计算其长度
    while (++n && curr.next) curr = curr.next
    k = k % n	// 去重
    // 链表右移
    while (k--) {
        curr = head
        while (curr.next.next) curr = curr.next
        // 这里curr是链表的打断位置,即倒数第二项
        curr.next.next = head // 链表最后一项指向头部形成环
        head = curr.next // 定位新的头节点
        curr.next = null // 打断链表环
    }
    return head
}

本题有五种思路:1、hash表存储,利用数组的位置排序进行hash敛散生成新的链表;2、链表成环,对应位置剪开;3、堆栈辅助处理循环剪断位置;4、快慢指针,根据双指针的位置间距进行处理,对新剪开位置使用另外两个指针记录处理;5、常规穷举,一步一步进行



2021.02.13

No.92 翻转链表-ii

反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。

说明: 1 ≤ m ≤ n ≤ 链表长度。

示例:

输入: 1->2->3->4->5->NULL, m = 2, n = 4 输出: 1->4->3->2->5->NULL

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/re… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=92 lang=javascript
 *
 * [92] 反转链表 II
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} m
 * @param {number} n
 * @return {ListNode}
 */
var reverseBetween = function(head, m, n) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 数组 m => n 翻转
    const reversePosition = (a, m, n) => {
        return [...a.slice(0,m-1), ...a.slice(m-1,n).reverse(), ...a.slice(n)]
    };
    return arr2List(reversePosition(list2Arr(head),m,n));
};

方案二:

/*
 * @lc app=leetcode.cn id=92 lang=javascript
 *
 * [92] 反转链表 II
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} m
 * @param {number} n
 * @return {ListNode}
 */
var reverseBetween = function(head, m, n) {
    // step1 => 双指针p1,p2保持固定间距
    const dummyHead = new ListNode(0, head);
    let p1 = p2 = head;
    let step2 = n - m;
    while (step2-- > 0) {
        p2 = p2.next;
    }
    let step1 = m - 1;
    let tmpHead = p1;
    while (step1-- > 0) {
        tmpHead = p1;
        p1 = p1.next;
        p2 = p2.next;
    }
    // step2 => 极其重要的测试case:p1指针压根没动【dummyHead就起作用了】
    if (p1 == head) {
        tmpHead = dummyHead;
    }
    // step3 => 尾插法
    let tmp = p1;
    while (tmp != p2) {
        tmp = p1.next;
        p1.next = tmp.next;
        tmp.next = tmpHead.next;
        tmpHead.next = tmp;
    }
    return dummyHead.next;
};

两种解法:1、转成数组,利用数组的slice及reverse拼接;2、双指针+尾插法,利用头尾指针的交换



2021.02.14

No.206 反转链表

反转一个单链表。 示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

方案一:

/*
 * @lc app=leetcode.cn id=206 lang=javascript
 *
 * [206] 反转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 数组 m => n 翻转
    const reverseList = (a) => {
        return a.reverse()
    };
    return arr2List(reverseList(list2Arr(head)));
};

方案二:

/*
 * @lc app=leetcode.cn id=206 lang=javascript
 *
 * [206] 反转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    let prev = null;
    let curr = head;
    while (curr) {
        const next = curr.next;
        curr.next = prev;
        prev = curr;
        curr = next;
    }
    return prev;
};

方案三:

/*
 * @lc app=leetcode.cn id=206 lang=javascript
 *
 * [206] 反转链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @return {ListNode}
 */
var reverseList = function(head) {
    if (head == null || head.next == null) {
        return head;
    }
    const newHead = reverseList(head.next);
    head.next.next = head;
    head.next = null;
    return newHead;
};

三种方法:1、转成数组,利用数组的reverse;2、迭代;3.递归



总结:

  1. 反转链表问题常见的利用链表的三指针进行相关的反转,常见的为头指针、尾指针及替换指针的相关变形,可以利用栈等数据结构进行迭代或递归;
  2. 对于反转问题有一个取巧的办法就是将其转成数组后利用数组的相关api进行处理,再将数组转为链表

二、分隔合并

2021.02.18

No.21 合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

merge_ex1.jpg  

示例 1:

输入:l1 = [1,2,4], l2 = [1,3,4] 输出:[1,1,2,3,4,4] 示例 2:

输入:l1 = [], l2 = [] 输出:[] 示例 3:

输入:l1 = [], l2 = [0] 输出:[0]  

提示:

两个链表的节点数目范围是 [0, 50] -100 <= Node.val <= 100 l1 和 l2 均按 非递减顺序 排列

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/me… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=21 lang=javascript
 *
 * [21] 合并两个有序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    if(!l1) return l2;
    if(!l2) return l1;

    while(l1 != null && l2 != null) {
        if(l1.val <= l2.val) {
            l1.next = mergeTwoLists(l1.next, l2);
            return l1;
        } else {
            l2.next = mergeTwoLists(l1, l2.next);
            return l2;
        }
    }
};

方案二:

/*
 * @lc app=leetcode.cn id=21 lang=javascript
 *
 * [21] 合并两个有序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    let prev = new ListNode(-1),
        head = prev;
    while(l1 != null && l2 != null) {
        if(l1.val <= l2.val) {
            head.next = l1;
            l1 = l1.next;
        } else {
            head.next = l2;
            l2 = l2.next;
        }
        head = head.next;
    }

    head.next = l1 === null ? l2 : l1;

    return prev.next;
};

方案三:

/*
 * @lc app=leetcode.cn id=21 lang=javascript
 *
 * [21] 合并两个有序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} l1
 * @param {ListNode} l2
 * @return {ListNode}
 */
var mergeTwoLists = function(l1, l2) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 两个数组合并排序
    const mergeArr = (a1,a2) => {
        return [...a1,...a2].sort((a,b) => a-b);
    }
    
    return arr2List(mergeArr(list2Arr(l1), list2Arr(l2)));
};

有三种方案:1、递归,利用隐式栈进行链表的合并;2、迭代,使用双指针进行迭代判断;3、转化成数组合并升序排列



2021.02.21

No.23 合并k个升序链表

给你一个链表数组,每个链表都已经按升序排列。

请你将所有链表合并到一个升序链表中,返回合并后的链表。

示例 1:

输入:lists = [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下: [  1->4->5,  1->3->4,  2->6 ] 将它们合并到一个有序链表中得到。 1->1->2->3->4->4->5->6 示例 2:

输入:lists = [] 输出:[] 示例 3:

输入:lists = [[]] 输出:[]  

提示:

k == lists.length 0 <= k <= 10^4 0 <= lists[i].length <= 500 -10^4 <= lists[i][j] <= 10^4 lists[i] 按 升序 排列 lists[i].length 的总和不超过 10^4

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/me… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=23 lang=javascript
 *
 * [23] 合并K个升序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 对k个节点进行数组切割即可
    const mergeArr = lists => {
        const r = [];
        lists.forEach(list => r.push(...list2Arr(list)))
        return r.sort((a,b) => a-b);
    }
    return arr2List(mergeArr(lists));
};

方案二:

/*
 * @lc app=leetcode.cn id=23 lang=javascript
 *
 * [23] 合并K个升序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    // 合并两个链表
    const mergeTwoLists = function(l1, l2) {
        let prev = new ListNode(-1),
            head = prev;
        while(l1 != null && l2 != null) {
            if(l1.val <= l2.val) {
                head.next = l1;
                l1 = l1.next;
            } else {
                head.next = l2;
                l2 = l2.next;
            }
            head = head.next;
        }
    
        head.next = l1 === null ? l2 : l1;
    
        return prev.next;
    };
    // 分治
    const merge = (lists, l, r) => {
        if (l == r) return lists[l];
        if (l > r) return null;
        const mid = (l + r) >> 1;
        return mergeTwoLists(merge(lists, l, mid), merge(lists, mid + 1, r));
    };

    return merge(lists, 0, lists.length - 1);
};

方案三:

/*
 * @lc app=leetcode.cn id=23 lang=javascript
 *
 * [23] 合并K个升序链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode[]} lists
 * @return {ListNode}
 */
var mergeKLists = function(lists) {
    let queue = new PriorityQueue();
    lists.forEach(list => {
        if(list) queue.enqueue(list, list.val)
    });

    let res = new ListNode(-1);
    let cur = res;
    while(!queue.isEmpty()) {
        cur.next = queue.dequeue();
        cur = cur.next;
        if(cur.next) queue.enqueue(cur.next, cur.next.val);
    }
    return res.next;
}

class Node {
	constructor(val, priority) {
		this.val = val;
		this.priority = priority;
	}
}

class PriorityQueue {
	constructor() {
		this.values = [];
	}

	enqueue(val, priority) {
		let node = new Node(val, priority);
		this.values.push(node);
		this.bubbleUp();
	}

	dequeue() {
		let max = this.values[0];
		let end = this.values.pop();
		if(this.values.length) {
			this.values[0] = end;
			this.bubbleDown();
		}
		return max.val;
	}
    
    isEmpty() {
        return !this.values.length;
    }
    
    bubbleUp(index = this.values.length - 1) {
		if(index <= 0) return;
		let parentIndex = Math.floor((index - 1) / 2);
		if(this.values[index].priority <= this.values[parentIndex].priority) {
			[this.values[index], this.values[parentIndex]] = [this.values[parentIndex], this.values[index]];
			this.bubbleUp(parentIndex);
		}
	}
	
	bubbleDown(index = 0, swapIndex = null) {
		let leftIndex = index * 2 + 1,
			rightIndex = index * 2 + 2,
			length = this.values.length;

		if(leftIndex < length) {
			if(this.values[leftIndex].priority <= this.values[index].priority) {
				swapIndex = leftIndex;
			}
		}

		if(rightIndex < length) {
			if((swapIndex === null && this.values[rightIndex].priority <= this.values[index].priority) || (swapIndex !== null && this.values[rightIndex].priority <= this.values[leftIndex].priority)) {
				swapIndex = rightIndex;
			}
		}

		if(swapIndex !== null) {
			[this.values[index], this.values[swapIndex]] = [this.values[swapIndex], this.values[index]];
			this.bubbleDown(swapIndex, null);
		}
	}
};

有三种解法:1、利用数组解决,最后将数组转为链表;2、结合合并两个链表方案,利用分治算法优化;3、构造优先队列进行优化,利用空间换时间



2021.02.22

No.86 分隔链表

给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。

你应当 保留 两个分区中每个节点的初始相对位置。

 

示例 1:

partition.jpg

输入:head = [1,4,3,2,5,2], x = 3 输出:[1,2,2,4,3,5] 示例 2:

输入:head = [2,1], x = 2 输出:[1,2]  

提示:

链表中节点的数目在范围 [0, 200] 内 -100 <= Node.val <= 100 -200 <= x <= 200

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/pa… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案一:

/*
 * @lc app=leetcode.cn id=86 lang=javascript
 *
 * [86] 分隔链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} x
 * @return {ListNode}
 */
var partition = function(head, x) {
    let a = new ListNode(0),
        b = new ListNode(0);
    const ahead = a,
          bhead = b;
    while(head) {
        if(head.val < x) {
            a.next = head;
            a = a.next;
        } else {
            b.next = head;
            b = b.next;
        }
        head = head.next;
    }
    a.next = bhead.next;
    b.next = null;
    return ahead.next;
};

方案二:

/*
 * @lc app=leetcode.cn id=86 lang=javascript
 *
 * [86] 分隔链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val, next) {
 *     this.val = (val===undefined ? 0 : val)
 *     this.next = (next===undefined ? null : next)
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} x
 * @return {ListNode}
 */
var partition = function(head, x) {
    // 链表转数组
    const  list2Arr = head => {
        if(!head) return [];
        const a = [head.val];
        while(head.next) {a.push(head.next.val);head = head.next;}
        return a;
    }
    // 数组转链表
    const arr2List = arr => {
        if(arr.length == 0) return null;
        let head = new ListNode(arr[0]);
        let cur = head;
        for(let i=1; i < arr.length; i++) {
            cur.next = new ListNode(arr[i]);
            cur = cur.next;
        };
        return head;
    };
    // 数组排序
    const arrSort = (arr, x) => {
        return [...arr.filter(a => a < x), ...arr.filter(a => a >= x)];
    };
    
    return arr2List(arrSort(list2Arr(head), x))
};

有两种解法:1、分成大小链表后合并;2、利用数组api排序



2021.03.02

No.725 分隔链表

给定一个头结点为 root 的链表, 编写一个函数以将链表分隔为 k 个连续的部分。

每部分的长度应该尽可能的相等: 任意两部分的长度差距不能超过 1,也就是说可能有些部分为 null。

这k个部分应该按照在链表中出现的顺序进行输出,并且排在前面的部分的长度应该大于或等于后面的长度。

返回一个符合上述规则的链表的列表。

举例: 1->2->3->4, k = 5 // 5 结果 [ [1], [2], [3], [4], null ]

示例 1:

输入: root = [1, 2, 3], k = 5 输出: [[1],[2],[3],[],[]] 解释: 输入输出各部分都应该是链表,而不是数组。 例如, 输入的结点 root 的 val= 1, root.next.val = 2, oot.next.next.val = 3, 且 root.next.next.next = null。 第一个输出 output[0] 是 output[0].val = 1, output[0].next = null。 最后一个元素 output[4] 为 null, 它代表了最后一个部分为空链表。 示例 2:

输入: root = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], k = 3 输出: [[1, 2, 3, 4], [5, 6, 7], [8, 9, 10]] 解释: 输入被分成了几个连续的部分,并且每部分的长度相差不超过1.前面部分的长度大于等于后面部分的长度。  

提示:

root 的长度范围: [0, 1000]. 输入的每个节点的大小范围:[0, 999]. k 的取值范围: [1, 50].

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/sp… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

方案:

/*
 * @lc app=leetcode.cn id=725 lang=javascript
 *
 * [725] 分隔链表
 */

// @lc code=start
/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} root
 * @param {number} k
 * @return {ListNode[]}
 */
var splitListToParts = function (root, k) {
    // 1. 获取链表的长度
    const getRootLength = root => {
        let n = 0;
        while (root) {
            n++;
            root = root.next;
        };
        return n;
    };
    // 2. 分析链表的分割方式
    // b项是 a+1;k-b项是 a
    const len = getRootLength(root),
        a = ~~(len / k),
        b = len % k;
    // 3. 分割链表
    const r = []; // 返回的链表数组
    // 循环链表 
    for(let m = 1;m<=k;m++) {
        if(!root) {
            r.push(null);
            continue;
        }
        let p1 = root,
            p2 = root,
            num = a;
        if(m<=b) {
            while(num-->0) p2 = p2.next;
        } else {
            num -=1;
            while(num-->0) p2 = p2.next;
        };
        // 处理p2为null
        if(!p2) {
            r.push(p1);
            root = null;
            continue;
        }
            
        
        root = p2.next;
        p2.next = null;
        r.push(p1);

    }
    return r;
};

关键在于对k的分离,判断不同的分割长度,对边界条件处理需要注意



总结:

  1. 链表的合并与分割主要新链表的构造,需要根据要求进行拆分与组合,常见的构造链表有双指针法及堆栈法
  2. 特殊的,由于js没有自己的链表结构,因而可以将链表转为数组,利用相关api去处理