一、C语言实现

1. 先获取链表长度,再从正向遍历

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
// 比较朴素的算法
// 先求出链表长度,然后根据长度求出要删除的元素正向的位置
// 最后遍历到该元素前一元素进行删除操作

struct ListNode *removeNthFromEnd(struct ListNode *head, int n)
{
  int len = 0;
  struct ListNode *origin = head; 
  // 计算链表的长度
  while (head)
  {
    len++;
    head = head -> next;
  }
  // 将head重新指向头结点
  head = origin;
  // 计算是正向第几个节点
  int count = len - n;
  // 如果是正向第一个结点,则头结点被删除,只需要直接返回头结点的next即可
  if (count == 0) {
    return head -> next;
  } 
  // 如果不是正向第一个结点(即结点),则直接遍历到该结点的前一个结点
  for (int i = 0; i < count - 1; i++) {
    head = head -> next;
  }
  // 将该节点删除,然后返回头结点的指针
  head -> next = head -> next -> next;
  return origin;
}

2. 双指针法,只需要遍历一次

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     struct ListNode *next;
 * };
 */
//  双指针法,移动first指针使其到的链表末端,同时移动second指针
//  使其指向最终要删除的节点的前一个节点,此时即可删除节点并返回头结点
//  只需一轮循环即可完成
struct ListNode *removeNthFromEnd(struct ListNode *head, int n)
{
  // 创建一个哑结点,并将其指向头结点
  struct ListNode dumpy = {0, head};
  // 将first指针指向head头指针
  struct ListNode *first = head;
  // 将second指针指向哑结点,此处dumpy是结构体,因此需要取地址
  struct ListNode *second = &dumpy;
  for (int i = 0; i < n; i++)
  {
    first = first->next;
  }
  while (first)
  {
    first = first->next;
    second = second->next;
  }
  second->next = second->next->next;
  // first second和head都是结构体指针,因此可以使用 -> 访问成员
  // dumpy是结构体,可以直接用点号访问
  return dumpy.next;
}

二、JS实现

1. 先获取链表长度,再从正向遍历

/**
 * 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} n
 * @return {ListNode}
 */
// 比较朴素的算法
// 先求出链表长度,然后根据长度求出要删除的元素正向的位置
// 最后遍历到该元素前一元素进行删除操作
const removeNthFromEnd = function (head, n) {
  const origin = head
  if (!head) {
    return null
  }
  let count = 1
  while (head.next) {
    count++
    head = head.next
  }
  head = origin
  if (count <= n) {
    return origin.next
  } else {
    // 倒数第n个数,就是正数第count - n + 1个数,那么,只需要遍历到此数的前一个数,即可
    // 前一个数则是第count - n个数,从第一个数开始遍历,只需遍历count - n - 1次就可到达此数
    const index = count - n - 1
    for (let i = 0; i < index; i++) {
      head = head.next
    }
    head.next = head.next.next
  }
  return origin
}

2. 双指针法,只需要遍历一次

/**
 * 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} n
 * @return {ListNode}
 */

//  双指针法,移动first指针使其到的链表末端,同时移动second指针
//  使其指向最终要删除的节点的前一个节点,此时即可删除节点并返回头结点
//  只需一轮循环即可完成
const removeNthFromEnd = function (head, n) {
  // 创建一个哑结点,并将它指向头结点
  const dumpy = new ListNode(0, head)
  // 将第二个指针指向哑结点,第一个指针指向头结点
  let second = dumpy
  let first = head
  // 将第一个指针往前移动n个位置
  for (let i = 0; i < n; i++) {
    first = first.next
  }
  // 移动第一个指针直到链表结尾,同时移动第二个指针
  while (first) {
    first = first.next
    second = second.next
  }
  // 此时第二个指针指向的是倒数第n个节点的前一个节点
  // 将倒数第n个节点删除即可
  second.next = second.next.next
  // 返回头结点,使用head会出错,为什么呢?因为如果要删除的节点是头结点,那么头结点被删除后,
  // 哑结点直接指向第二个结点,而head还是指向头结点,因此会报错。
  // 所以此时要么返回head.next,要么返回dumpy.next。
  // 而head.next的返回需要判断second.next是不是指向头结点
  // 而dumpy.next则直接使用
  return dumpy.next
}

三、算法时间及空间复杂度

1. 链表长度法的时间及空间复杂度

  • 时间复杂度:O(L),其中L是链表的长度。
  • 空间复杂度:O(1)。

2. 双指针法

  • 时间复杂度:O(L),其中L是链表的长度。
  • 空间复杂度:O(1)。

阿料
1 声望4 粉丝

一个平凡的coder