青铜三人行之删除链表的倒数第N个节点

先说一个消息,为了方便互相交流学习,青铜三人行建了个微信群,感兴趣的伙伴可以扫码加下面的小助手抱你入群哦!
青铜三人行之删除链表的倒数第N个节点_第1张图片

每周一题,代码无敌。这周,「青铜三人行」为你带来了一道关于“链表的题目”。

删除链表的倒数第N个节点

青铜三人行——每周一题@删除链表的倒数第N个节点

力扣题目

给定一个链表,删除链表的倒数第 n 个节点,并且返回链表的头结点。

示例:
给定一个链表: 1->2->3->4->5, 和 n = 2.
当删除了倒数第二个节点后,链表变为 1->2->3->5.
说明:
给定的 n 保证是有效的。
进阶:
你能尝试使用一趟扫描实现吗?

啥是链表

要完成这道题,首先就得了解一下啥是链表?简单来说,链表是一种数据结构,它又一系列离散的节点组成。其特点是,每个节点上除了自己的数据以外,还会有一个或两个指针指向下一个或者上一个节点,使得这些节点可以链起来。

其中,只有指向下一个节点的链表称为单向链表,它只能从前一个节点到下一个节点一个方向来查找其中的节点数据:

在这里插入图片描述
而双向链表则拥有两个指针,分别指向之前和之后的节点:
在这里插入图片描述

而在 JS 中,这道题目里给我们设定了链表的结构,很明显,是一个单向列表:

  function ListNode(val) {
      this.val = val;
      this.next = null;
  }

解法一,两次遍历找到对应的节点:

了解了链表的数据结构以后,这道题就不难解决了。不过题目里有个小小的花招,即要求寻找「倒数第 n 个节点」。因为是单向链表,我们没法倒着寻找节点,因此我们很容易想到先找到整个链表的长度,计算出要找的元素的正向位置,然后再从头遍历,进行删除:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let node = head;
    let length = 0;
    while(true) {
        if(node === null) {
            break;
        }       
        node = node.next;
        length++;
    }
    node = head;
    if(n === length) return head.next;
    for(let i = 0; i< length - n -1; i++) {
        node=node.next;
    }
    node.next=node.next.next
    return head;
};

解法二,转离散节点为连续节点

这道题数据量较小,因此运行的速度都比较快。于是向着题目中「只扫描一次」这个进阶目标前进。书香提出了一种方法,既然题目的难点在于链表不容易反向查找,那么把它映射成一个连续的数据结构不就可以解决了吗?于是很自然想到了应用数组,完成了题目:

/**
 * Definition for singly-linked list.
 * function ListNode(val) {
 *     this.val = val;
 *     this.next = null;
 * }
 */
/**
 * @param {ListNode} head
 * @param {number} n
 * @return {ListNode}
 */
var removeNthFromEnd = function(head, n) {
    let node = head;
    const arrNodes = [];
    while(true) {
        if(node === null) {
            const length = arrNodes.length;
            if(length === n ) 
                head = head.next;
            else 
                arrNodes[length-n-1].next=arrNodes[length-n+1];
            break;
        }       
        arrNodes.push(node);
        node = node.next;
    }
    return head;
};

值的注意的是,当需要删除的元素是第一个元素的时候,容易造成数组的越界,需要特殊处理:

 if(length === n ) head = head.next;

解法三,双指针法

上面书香的解法虽然在一次扫描中完成了任务,却额外引入了一个数组的外部结构。有没有更好的办法呢?Helen 和 曾大师对于这个问题,采用了新的办法:题目要求删除倒数第 n 个节点,那么我只需要在我当前扫描到的节点指针之后相隔 n 的节点再设置一个指针,到后一个指针越界的时候,当前节点就是需要删除的节点了:

代码如下:

var removeNthFromEnd = function(head, n) {

  const dummy = new ListNode(0); 
  dummy.next = head;

  let l = dummy;
  let r = dummy;

  let offset = n;

  while (offset--) {
    r = r.next;
  }

  while (r.next) {
    l = l.next;
    r = r.next;
  }

  l.next = l.next.next;

  return dummy.next;
}

ertra

最后,曾大师 go 语言的福利时间又到啦,同样是双指针,你能看出有什么不同吗?

func removeNthFromEnd(head *ListNode, n int) *ListNode {

    store := head
    i := 0
    p := head
    for p != nil {

        if(i>n) {
            store = store.Next
        }

		p = p.Next
        i++
	}
    // 删除头节点
    if(i == n){
        return store.Next
    }
   
    store.Next=store.Next.Next
   
    return head

}

而在时间和空间上,相当惊人 ,嗯嗯…

青铜三人行之删除链表的倒数第N个节点_第2张图片
结尾
这周的题目相对来说比较简单,主要是说明了链表的数据结构。链表相对于数组来说,更容易插入、删除其中的节点,而数组比起来则更容易查找到某个节点(想想为什么?)。两个数据结构相辅相成,在不同的应用场景选择合适的数据结构,可以让你的程序运行起来事半功倍哦!这次的题目就这样了,欢迎通过[email protected]邮箱联系我们,下周见!


三人行

  • 知乎专栏
  • 视频专辑

你可能感兴趣的:(算法,leetcode,青铜三人行)