关于链表的算法(JavaScript版本)

翻转链表

1. 反转链表

反转链表
输入:{1,2,3}
输出:{3,2,1}

方法:改变链表指向,即反着指

function ReverseList(Head)
{
    let pre = null, cur = Head
    while(cur){
        let temp = cur.next
        cur.next = pre
        pre = cur
        cur = temp
    }
    return pre
}

2. 链表内指定区间反转

链表内指定区间反转
输入:{1,2,3,4,5},2,4
输出:{1,4,3,2,5}

方法①:沿用题目1的方法

先将待翻转的链表区域截取出来,对其进行翻转后再进行拼接

function reverseBetween(head, m, n) {
    const newHead = new ListNode(0)
    newHead.next = head
    let start = newHead, end = newHead
    // 指向start的前一个节点
    while (m > 1) {
        start = start.next
        m--
    }
    // 指向end节点
    while (n > 0) {
        end = end.next
        n--
    }
    const left = start.next, right = end.next
    // 截断
    start.next = null
    end.next = null
    // 使用基础版反转链表
    reverseList(left)
    // 再拼接
    start.next = end
    left.next = right
    return newHead.next
    // 需要遍历链表两次
}
function reverseList(head) {
    let pre = null, cur = head
    while (cur) {
        const next = cur.next
        cur.next = pre
        pre = cur
        cur = next
    }
}

方法②:头插法

链表1.png

function reverseBetween(head, m, n) {
    // 一次遍历,头插法
    let newHead = new ListNode(0)
    newHead.next = head
    let pre = newHead
    // 指向start的前一个节点
    for (let i = 1; i < m; i++) {
        pre = pre.next
    }
    let cur = pre.next
    for (let j = 0; j < n - m; j++) {
        let next = cur.next
        cur.next = next.next
        next.next = pre.next
        pre.next = next
    }
    return newHead.next
}

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

链表中的节点每k个一组翻转
输入:{1,2,3,4,5},2
输出:{2,1,4,3,5}
方法:对每一组执行题目1的反转

function reverseKGroup(head, k) {
    let newHead = new ListNode(0)
    newHead.next = head
    let pre = newHead,
        end = newHead
    while (end.next) {
        // end移动到待翻转的末尾节点
        for (let i = 0; i < k && end !== null; i++) {
            end = end.next
        }
        if (end === null) {
            break
        }
        // start:翻转的起始节点,next:翻转的最后一个节点的下一个节点
        let start = pre.next,
            next = end.next
        // 截断
        end.next = null
        // 翻转
        pre.next = reverseList(start)
        // 拼接
        start.next = next
        // 重新定位
        pre = start
        end = pre
    }
    return newHead.next
}
function reverseList(head) {
    let pre = null,
        cur = head
    while (cur) {
        let next = cur.next
        cur.next = pre
        pre = cur
        cur = next
    }
    return pre
}

环形链表

1. 判断链表中是否有环

判断链表中是否有环
输出:true or false
方法:快慢指针

function hasCycle(head) {
    if (head === null) {
        return false
    }
    // 快慢指针
    let slow = head, fast = head
    // 循环条件:快指针有下个和下下个节点
    while (fast.next && fast.next.next) {
        slow = slow.next
        fast = fast.next.next
        // 快慢指针相遇,则代表有环
        if (slow === fast) return true
    }
    return false
}

2. 链表中环的入口结点

链表中环的入口结点
方法:先通过题目1的方法确定相遇的节点,然后一个节点从头节点发出,另一个节点从相遇节点出发,再次相遇的节点即为入环点

function EntryNodeOfLoop(pHead) {
    // 快慢指针
    // 先确定相遇的节点
    if (pHead === null) {
        return null
    }
    let slow = pHead, fast = pHead
    while (fast.next && fast.next.next) {
        slow = slow.next
        fast = fast.next.next
        // 相遇之后,一个节点从head触发,还有一个从相遇节点出发,他俩相遇的节点就是入环点
        if (slow === fast) {
            let start = pHead
            while (start !== slow) {
                start = start.next
                slow = slow.next
            }
            return start
        }
    }
    return null
}

合并链表

1. 合并两个排序的链表

合并两个排序的链表
输入:
{1,3,5},{2,4,6}
输出:
{1,2,3,4,5,6}
方法:递归

function Merge(pHead1, pHead2) {
    // 如果有空链表,则直接返回另一个非空链表
    if (pHead1 === null) {
        return pHead2
    }
    if (pHead2 === null) {
        return pHead1
    }
    if (pHead1.val <= pHead2.val) {
        pHead1.next = Merge(pHead1.next, pHead2)
        return pHead1
    } else {
        pHead2.next = Merge(pHead1, pHead2.next)
        return pHead2
    }
}

2. 合并k个已排序的链表

合并k个已排序的链表
输入:
[{1,2,3},{4,5,6,7}]
返回值:
{1,2,3,4,5,6,7}
方法:分治,两两合并

function mergeKLists(lists) {
    // 分治
    let len = lists.length
    if (len === 0) return null
    function mergeNlist(start, end) {
        if (start === end) return lists[start]
        let mid = (start + end) >> 1
        let l1 = mergeNlist(start, mid)
        let l2 = mergeNlist(mid + 1, end)
        return Merge(l1, l2)
    }
    return mergeNlist(0, len - 1)
}
// 合并两个有序链表
function Merge(l1, l2) {
    if (l1 === null) {
        return l2
    }
    if (l2 === null) {
        return l1
    }
    if (l1.val <= l2.val) {
        l1.next = Merge(l1.next, l2)
        return l1
    } else {
        l2.next = Merge(l1, l2.next)
        return l2
    }
}

单链表的排序

单链表的排序
输入:{1,3,2,4,5}
返回值:{1,2,3,4,5}
方法:分成两个链表,然后使用两个链表合并的方法

function sortInList( head ) {
//     分成两个链表,然后使用两个链表合并的方法
    if(!head || !head.next) return head
    let slow = head, fast = head
//     preSlow始终是slow的前一个节点
    let preSlow = null
    while (fast && fast.next) {
        preSlow = slow
        slow = slow.next
        fast = fast.next.next
    }
//     截断,head到preslow,slow到末尾
    preSlow.next = null
//     递归调用,因为不是有序的
    let l1 = sortInList(head)
    let l2 = sortInList(slow)
    return mergeList(l1, l2)
    
//     合并【有序】链表
    function mergeList(l1, l2){
        if(l1 === null){
            return l2
        }
        if(l2 === null){
            return l1
        }
        if(l1.val <= l2.val){
            l1.next = mergeList(l1.next, l2)
            return l1
        } else {
            l2.next = mergeList(l1, l2.next)
            return l2
        }
    }
}

倒数第N个节点

1. 链表中倒数最后k个结点

链表中倒数最后k个结点
输入:{1,2,3,4,5},2
返回值:{4,5}

方法:快慢指针,注意特判

function FindKthToTail(pHead, k) {
    // 快慢指针
    let slow = pHead, fast = pHead
    for (let i = 0; i < k; i++) {
        // 如果快指针为空,则表示链表长度小于k,返回null
        if (fast === null) {
            return null
        }
        fast = fast.next
    }
    while (fast) {
        fast = fast.next
        slow = slow.next
    }
    return slow
}

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

删除链表的倒数第n个节点
输入:{1,2},2
返回值:{2}
方法:快慢指针,特判删除头节点的情况

function removeNthFromEnd(head, n) {
    let fast = head, slow = head
    for (let i = 0; i < n; i++) {
        fast = fast.next
    }
    // 因为题目保证n一定是有效的,特判:删除头节点的情况
    if (fast === null) {
        slow = slow.next
        return slow
    }
    while (fast.next) {
        slow = slow.next
        fast = fast.next
    }
    slow.next = slow.next.next
    // 返回头节点
    return head
}

两个链表的第一个公共结点

两个链表的第一个公共结点
在这里插入图片描述
方法:双指针,依次往后移,到达尾节点后重新回到头节点,根据到达公共节点走过的路程一致找到公共节点

function FindFirstCommonNode(pHead1, pHead2)
{
    // write code here
    if(!pHead1 || !pHead2) return null
    let l1 = pHead1, l2 = pHead2
    while(l1 !== l2){
        l1 = (l1 === null ? pHead2 : l1.next)
        l2 = (l2 === null ? pHead1 : l2.next)
    }
    return l1
}

链表相加

链表相加
输入:[9,3,7],[6,3]
返回值:{1,0,0,0}
方法:先进行链表反转,再做相加,注意有进位

function addInList( head1 ,  head2 ) {
//     链表反转
    function reverseList(head){
        let pre = null, cur = head
        while(cur){
            let next = cur.next
            cur.next = pre
            pre = cur
            cur = next
        }
        return pre
    }
//     链表求和
    let l1 = reverseList(head1), 
        l2 = reverseList(head2)
    let inNum = 0
//     newHead始终不动
    let newHead = new ListNode(null)
    let p = newHead
    while(true){
//         求和
        let sum = inNum + (l1 ? l1.val : 0) + (l2 ? l2.val: 0)
        inNum = Math.floor(sum / 10)
        p.next = new ListNode(sum % 10)
        p = p.next
        if(l1) l1 = l1.next
        if(l2) l2 = l2.next
        if(!l1 && !l2 && !inNum){
            break
        }
    }
    return reverseList(newHead.next)
}

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

判断一个链表是否为回文结构
输入:{1,2,2,1}
返回值:true
方法:把链表变成数组,然后再比较

function isPail( head ) {
//     转数组
    const newArr = []
    while(head){
        newArr.push(head.val)
        head = head.next
    }
    for(let i = 0, j = newArr.length - 1; i <= j; i ++, j--){
        if(newArr[i] !== newArr[j]){
            return false
        }
    }
    return true
}

删除有序链表中重复的元素

删除有序链表中重复的元素-I

删除有序链表中重复的元素-I
输入:{1,1,2}
返回值:{1,2}
输入:
{1,1,2}
复制
返回值:
{1,2}
方法:有下一个节点且与下一个节点的值相同,有下下个节点的情况下,则指向下下个节点,否则指向null

function deleteDuplicates(head) {
    // write code here
    let cur = head
    while (cur) {
        // 如果有下一个节点且与下一个节点的值相同
        if (cur.next && cur.val === cur.next.val) {
            // 如果有下下个节点,则直接指向下下个节点,否则直接指向null
            if (cur.next.next) {
                cur.next = cur.next.next
            } else {
                cur.next = null
            }
        } else {
            // 不相同则指向下一个节点
            cur = cur.next
        }
    }
    return head
}

删除有序链表中重复的元素-II

删除有序链表中重复的元素-II
输入:{1,2,2}
返回值:{1}
本题和 I 的区别:本题删除所有重复的数字
方法:当当前元素的值与后一节点的值一致时,记录后一节点,移动后一节点直至与当前值不同

function deleteDuplicates(head) {
    // 删除重复的元素
    let dummy = new ListNode(-9999)
    dummy.next = head
    let pre = dummy,
        cur = head
    while (cur && cur.next) {
        if (cur.val === cur.next.val) {
            let temp = cur.next
            // 重复的元素全部删除
            while (temp && temp.val === cur.val) {
                temp = temp.next
            }
            pre.next = temp
            cur = temp
        } else {
            pre = pre.next
            cur = cur.next
        }
    }
    return dummy.next
}

链表的奇偶重排

链表的奇偶重排
输入:{1,2,3,4,5,6}
返回值:{1,3,5,2,4,6}
方法:计数,为奇数时插入到奇数链表

function oddEvenList(head) {
    let p = head
    // 奇偶链表的虚拟头结点
    let l1 = new ListNode(-1),
        l2 = new ListNode(-1)
    let p1 = l1,
        p2 = l2
    // 计数
    let count = 1
    while (p) {
        // 当为奇数时,插入到奇链表,且奇链表节点往后移
        if (count % 2 === 1) {
            p1.next = p
            p1 = p1.next
        } else {
            p2.next = p
            p2 = p2.next
        }
        p = p.next
        count++
    }
    // 切断偶链表
    p2.next = null
    p1.next = l2.next
    return l1.next
}
  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值