探索链表的奥秘:删除重复元素的艺术

821 阅读4分钟

引言:

链表是一种线性数据结构,由一系列节点组成,每个节点包含数据和一个或多个指向其他节点的引用(指针)。链表的主要类型包括单向链表、双向链表和循环链表。

简单看一个图表来理解链表:

image.png

链表

对于链表内部是如何的?看看代码的实现:

let lb = {
    val: 1,
    next: {
        val: 2,
        next: {
            val: 3,
            next: {
                val: 4
            }
        }
    }
}

存值和节点的引入,依次下去组成链表。

链表的属性和功能:

  1. 定义链表的节点:
function ListNode(val) {
  this.val = val;
  this.next = null;
}

每个ListNode实例都有一个val属性,用于存储节点的值,以及一个next属性,用于指向链表中的下一个节点。

  1. 创建链表:
function createLinkedList(arr) {
    if (arr.length === 0) return null;
    let head = new ListNode(arr[0]);
    let current = head;
    for (let i = 1; i < arr.length; i++) {
        current.next = new ListNode(arr[i]);
        current = current.next;
    }
    return head;
}
  1. 插入节点:
function insertAtHead(head, value) {
    let newNode = new ListNode(value);
    newNode.next = head;
    return newNode;
}
  1. 删除节点:
function deleteNode(head, value) {
    if (head.val === value) {
        return head.next;
    }
    let current = head;
    while (current.next !== null) {
        if (current.next.val === value) {
            current.next = current.next.next;
            return head;
        }
        current = current.next;
    }
    return head;
}

其实对于插入节点来说,打破1和2的节点,将3插入:

function ListNode(val) {
    this.val = val
    this.node = null
}

const node = new ListNode(1)
const node2 = new ListNode(2)
const node3 = new ListNode(3)

node.next = node2
node2.next = node3
node.next = node3

代码将node2设置为node的下一个节点,将node3设置为node2的下一个节点。这样,nodenode2node3就形成了一个链表,链表的顺序是node -> node2 -> node3

最后,代码执行了node.next = node3,这导致node的直接下一个节点变成了node3,从而跳过了node2。此时,链表的结构变为node -> node3node2不再与nodenode3直接相连,因此在当前的链表中,node2成为了孤立的节点,不再属于链表的一部分。

这段代码创建了一个链表,然后修改了链表的链接,使得原始链表中的第二个节点被移除,链表的头节点直接指向了第三个节点。

所以链表的属性功能总结一下几点:

  1. 有序的列表

  2. 线性的结构

  3. 所有的节点都是离散分布的

  4. 访问链表中任何一个节点,都要从头结点逐个查找

  5. 相比数组,数组中增加或者删除一个元素,会导致需要移动n个元素,时间复杂度为O(n), 链表的增加和删除元素不需要移动其他元素,时间复杂度为O(1)

  6. 链表查找元素的时间复杂度为O(n),而数组查找元素的时间复杂度为O(1)

实践链表

  1. 合并两个有序链表

image.png

思路:判断节点中值的大小,寻找下一个节点,根据剩下的list链表直接连接,这里要注意返回的不是头部节点,而是头部的next,防止头部是空节点。

代码实现:

var mergeTwoLists = function (list1, list2) {
    let head = new ListNode()
    let cur = head

    while (list1 && list2) {
        if (list1.val <= list2.val) {
            cur.next = list1
            list1 = list1.next
        } else {
            cur.next = list2
            list2 = list2.next
        }
        cur = cur.next
    }


    cur.next = list1 !== null ? list1 : list2

    return head.next
};

2.删除链表的节点

image.png

分析:

val = 5


head = {
    val: 4,
    next: {
        val: 1,
        next: {
            val: 5,
            next: {
                val: 9,
                next: null
            }
        }
    }
}

也就是在这个链表给你val=5 的值,删除此节点。

判断之前若在头部就要记得移除头结点,移除的值需要头结点的next来代替,如果发现cur.next.val等于val,那么cur.next就是要被删除的节点。

为了删除这个节点,将cur.next更新为cur.next.next,这样cur就跳过了要删除的节点,直接指向了要删除节点的下一个节点,从而实现了删除操作。

最后,函数返回头节点head,这可能是一个新的头节点(如果原来的头节点被删除了),或者是原来的头节点(如果头节点没有被删除)。

附上完整代码:

var deleteNode = function (head, val) {
    let cur = head
    if(cur.val == val) {// 移除头结点
        head = head.next
    }
    while (cur && cur.next){
        if(cur.next.val == val) {
            // cur.next 就是要被移除的值
            cur.next = cur.next.next  
        }
        cur = cur.next
    }


    return head
};

总结:

链表作为数据结构的一部分不可或缺,跟着文章了解基础内容,大家差不多就可以来上实战了!