JS:链表题目(1)——两数相加、合并链表(小顶堆)

2.两数相加
19.删除链表的倒数第 N 个结点
21.合并两个有序链表
23.合并 K 个升序链表
141.环形链表
142.环形链表 II

160.相交链表
876.链表的中间结点
25.K 个⼀组翻转链表
83.删除排序链表中的重复元素
92.反转链表 II
234.回⽂链表


2. 两数相加 (中等)

在这里插入图片描述
一次遍历计算完成,需注意:

  1. 构建空的头结点,不用处理一开始指针指向null的情况
  2. 执行加法,考虑进位的情况。新链表要记得移动。
  3. 两个链表不等长
  4. 读完两个链表后,最后一次进位仍要处理

21. 合并两个有序链表 (简单)

在这里插入图片描述
比上一题简单,不用创建新节点。
while循环的条件是 &&关系

23. 合并K个升序链表 (困难)

在这里插入图片描述

难题:JavaScript没有 小顶堆


数组reduce()方法详解

arr.reduce(callback,[initialValue])
reduce 为数组中的每一个元素依次执行回调函数,不包括数组中被删除或从未被赋值的元素,接受四个参数:初始值(或者上一次回调函数的返回值),当前元素值,当前索引,调用 reduce 的数组。

var arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
    console.log(prev, cur, index);
    return prev + cur;
})
console.log(arr, sum);
打印结果:
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

index是从1开始的,第一次的prev的值是数组的第一个值。数组长度是4,但是reduce函数循环3次。

var  arr = [1, 2, 3, 4];
var sum = arr.reduce(function(prev, cur, index, arr) {
    console.log(prev, cur, index);
    return prev + cur;
}0) //注意这里设置了初始值
console.log(arr, sum);
打印结果:
0 1 0
1 2 1
3 3 2
6 4 3
[1, 2, 3, 4] 10

例子index是从0开始的,第一次的prev的值是我们设置的初始值0,数组长度是4,reduce函数循环4次。

23.合并K个排序链表,哈希表排序,击败100%!
在这里插入图片描述


什么是二叉堆

应用有两个,首先是一种排序方法「堆排序」,第二是一种很有用的数据结构「优先级队列」。

二叉堆在逻辑上其实是一种特殊的二叉树(完全二叉树),只不过存储在数组里。一般的链表二叉树,我们操作节点的指针,而在数组里,我们把数组索引作为指针。

优先级队列是基于二叉堆实现的,主要操作是插入和删除。插入是先插到最后,然后上浮到正确位置;删除是调换位置后再删除,然后下沉到正确位置。核心代码也就十行。

小顶堆代码:
代码来源于leetcode

class MinHeap {
    constructor() {
        this.heap = [];
    }
    // 交换节点位置
    swap(i1, i2) {
        [this.heap[i1], this.heap[i2]] = [this.heap[i2], this.heap[i1]];
    }
    // 获得父节点
    getParentIndex(i) {
        return Math.floor((i - 1) / 2);
        // return (i - 1) >> 1;
    }
    // 获得左节点
    getleftIndex(i) {
        return 2 * i + 1;
    }
    // 获得右节点
    getrightIndex(i) {
        return 2 * i + 2;
    }
    // 上移
    shiftUp(index) {
        if (index == 0) return;

        const parentIndex = this.getParentIndex(index);
        if (this.heap[parentIndex] && this.heap[parentIndex].val > this.heap[index].val) {
            this.swap(parentIndex, index);
            this.shiftUp(parentIndex);
        }
    }
    // 下移
    shiftDown(index) {
        let leftIndex = this.getleftIndex(index);
        let rightIndex = this.getrightIndex(index);
        if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[leftIndex].val) {
            leftIndex = rightIndex
        }
        if (this.heap[leftIndex] && this.heap[leftIndex].val < this.heap[index].val) {
            this.swap(leftIndex, index);
            this.shiftDown(leftIndex);
        }
        // if (this.heap[rightIndex] && this.heap[rightIndex].val < this.heap[index].val) {
        //     this.swap(rightIndex, index);
        //     this.shiftDown(rightIndex);
        // }
    }
    // 插入
    insert(value) {
        this.heap.push(value);
        this.shiftUp(this.heap.length - 1);
    }
    // 删除堆顶
    // 用数组尾部元素替换堆顶(直接删除堆顶会破坏堆结构)
    pop() {
        if(this.size() == 1) return this.heap.shift()
        const top = this.heap[0]
        // pop()方法删除数组最后一个元素并返回,赋值给堆顶
        this.heap[0] = this.heap.pop();
        // 对堆顶重新排序
        this.shiftDown(0);
        return top
    }
    // 获取堆顶
    peek() {
        return this.heap[0];
    }
    // 获取堆的大小
    size() {
        return this.heap.length;
    }
}

在这里插入图片描述
最后该题代码:

var mergeKLists = function(lists) {
    let head = p = new ListNode(0)
    const heap = new MinHeap()
    // 插入k个升序链表的头部节点
    for (let k of lists) {
        if(k) heap.insert(k)
    }
    // 不断的地比较最小堆中k个节点的大小
    while (heap.size()) {
        const val = heap.pop()
        p.next = val
        p = p.next
        if (val.next) heap.insert(val.next)
    }
    return head.next;
};

160. 相交链表 (简单)

在这里插入图片描述
这题看一幅图就明白了,重点在于怎么简洁实现:一个while循环可解决,条件是p1 == p2
在这里插入图片描述

复习:剑指 Offer 52. 两个链表的第一个公共节点

细节: 这里把最后None也当成节点

不相交的情况设A链表长度是a B链表长度是b 最后a+b = b+a 所以一定是一起走到None这个节点。可以理解为两条链表最后都指向了同一个 null (None)节点,代替了不相交的特殊情况。 非常的巧妙。

var getIntersectionNode = function(headA, headB) {
    let p1 = headA, p2 = headB
    while (p1 != p2) {
        if(p1) p1 = p1.next;
        else p1 = headB;

        if(p2) p2 = p2.next
        else p2 = headA
    }
    return p1;
};

方法二:类似找倒数第K个节点,因为如果有相交节点,最后肯定是一起走的(相同长度)。比较A和B的长度,使两条链表走相同的路程。
既然「寻找两条链表的交点」的核心在于让 p1 和 p2 两个指针能够同时到达相交节点 c1,那么可以通过预先计算两条链表的长度来做到这一点,

var getIntersectionNode = function(headA, headB) {
    let lenA = 0, lenB = 0;
    for (let p1 = headA; p1 != null; p1 = p1.next) lenA++;
    for (let p2 = headB; p2 != null; p2 = p2.next) lenB++;

    p1 = headA, p2 = headB;
    if (lenA > lenB) {
        for (let i = 0; i < lenA - lenB; i++) p1 = p1.next;
    } else {
        for (let i = 0; i < lenB - lenA; i++) p2 = p2.next;
    }

    // 看两个指针是否会相同,p1 == p2 时有两种情况:
    // 1、要么是两条链表不相交,他俩同时走到尾部空指针
    // 2、要么是两条链表相交,他俩走到两条链表的相交点
    while (p1 != p2) {
        p1 = p1.next;
        p2 = p2.next;
    }
    return p1;
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值