算法打卡day03|203.移除链表元素|707.设计链表|206.反转链表JavaScript

56 阅读4分钟

链表理论基础

  • 链表中的元素在内存在并不是连续存放的,而数组是需要开辟固定大小的内存空间;
  • 所以链表对内存空间的利用率更高;
  • 数组可通过下标访问元素,链表只能从表头开始迭代;
  • 链表在添加或移除元素的时候不需要移动其他元素,而数组牵一发而动全身。

LeetCode 203.移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

image.png

  • 思路:
    • 法1:直接遍历,然后找到val相等的结点。
    • 法2:创建哑结点,也就是带头结点的链表,这样就不用单独考虑原来链表的head.val === val的情况了。
  • 注意点:链表中删除某个元素的操作,要先迭代的条件是 当前结点p不是null ,所以while的条件是p.next存在。

不带头结点(不带哑结点)

/**
 * 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} val
 * @return {ListNode}
 */
var removeElements = function(head, val) {
    if(!head){  // 空链表
        return head;
    }
    let p = head;
    while(p.next){
        if(p.next.val === val){
            p.next = p.next.next;  // 移除
        }else{
            p = p.next;
        }
    }
    if(head.val === val){  // 删除头结点
        head = head.next;
    }
    return head;
};
带头结点链表(带哑结点)
var removeElements = function(head, val) {
    let dummyNode = new ListNode();  // 创建哑结点
    dummyNode.next = head;
    let p = dummyNode;
    while(p.next){
        if(p.next.val === val){
            p.next = p.next.next;
        }else{
            p = p.next;
        }
    }
    return dummyNode.next;  // 第一个结点是空的,所以返回的是下一个
};

LeetCode 707.设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 nextval 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。 在链表类中实现这些功能:

  • get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1
  • addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
  • addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
  • addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
  • deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

-- 思路:任何一种数据结构的基本操作都是增删改查,这里主要是增删查,本题难点在。不带表头结点的做法会比带表头结点的多出一个操作:单独考虑对头结点的操作,比如删除头结点。
-- 注意点:增删操作,每次需要更新长度size属性。

其实这题带不带哑结点都无所谓~~

单链表(不带头结点)

// 结点
var ListNode = function(val, next){
    this.val = (val === undefined ? 0 : val);
    this.next = (next === undefined ? null : next);
}
// 链表
var MyLinkedList = function() {
    this.size = 0;
    this.head = new ListNode(0);
};
// 不带表头结点版本
/** 
 * @param {number} index
 * @return {number}
 */
MyLinkedList.prototype.get = function(index) {
    if(index < 0 || index > this.size - 1){
        // 下标越界
        return -1;
    }
    let cur = this.head;
    for(let i = 0; i < index; i ++){
        // 遍历,直到index下标
        cur = cur.next
    }
    return cur.val;
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtHead = function(val) {
    this.addAtIndex(0, val);
};

/** 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtTail = function(val) {
    this.addAtIndex(this.size, val);
};

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
MyLinkedList.prototype.addAtIndex = function(index, val) {
    if(index <= this.size){  // 检查下标是否合法
        let cur = this.head;
        let q = new ListNode(val);
        if(index <= 0){  // 插入头结点之前
            q.next = this.head;
            this.head = q;
        }else{  // 其他地方插入新结点
            for(let i = 0; i < index - 1; i ++){
                cur = cur.next;
            }
            // 插入结点
            q.next = cur.next;
            cur.next = q;
        }
        this.size += 1;  
    }
};

/** 
 * @param {number} index
 * @return {void}
 */
MyLinkedList.prototype.deleteAtIndex = function(index) {
    if(index >= 0 && index < this.size){  // 检查下标合法性
        if(index === 0){  // 删除头结点
            this.head = this.head.next;
        }else{  // 其他结点的删除
            let count = 0;
            let prev ;  // 记录一个前驱结点
            let cur = this.head;
            while(count < index){
                prev = cur;
                cur = cur.next;
                count += 1;
            }
            prev.next = cur.next;
        }
        this.size -= 1;
    }
};

LeetCode 206.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
image.png

  • 思路:
    • 新设置一个空链表,任何倒置插入元素?
    • 这样太耗空间,建议原地反转。需要记录前置结点。
  • 注意点:最后返回的是prev,不是head,经过反转,prev已经是原链表的最后一个非null结点,head还在原来链表的第一个元素,也不是返回cur,此时的cur已经指向最后的null了。
  • 代码流程图示(图中数字和代码注释中数字对应): 空白-第 9 页.png
/**
 * 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 || !head.next){  // 可以先排除掉空链表和只有一个元素的链表
        return head;
    }
    let cur = head;  // cur用来遍历链表 ①
    let prev = null;  // ②
    while(cur){
        let temp = cur.next;  //③ 先保存这个结点,否则后续改变指向后找不到 cur.next,将断链
        cur.next = prev;  // cur.next指向前一个元素④
        prev = cur;  // prev继续往后⑤
        cur = temp;  // cur继续往后⑥
    }
    return prev;  // 返回的是prev,这时候prev已经到最后的头了,head还在一开始的位置,也就是新的尾部
};