用100道题拿下你的算法面试(链表篇-5):删除链表的倒数第 N 个节点

一、面试问题

给定一个链表,任务是删除链表的倒数第 N 个节点

示例 1:

输入:LinkedList = 1 ->2 ->3 ->4 ->5 , N = 2

输出:1 -> 2 -> 3 -> 5

解释:删除倒数第 2 个节点(节点值为 4)后的链表为:1 -> 2 -> 3 -> 5

示例 2:

输入:LinkedList = 7 ->8 ->4 ->3 ->2 , N = 1

输出:7 ->8 ->4 ->3

解释:删除倒数第 1 个节点(节点值为 2)后的链表为:7 -> 8 -> 4 -> 3。

二、【推荐方法-1】找到正向的等价节点 ------ 时间复杂度 O (n),空间复杂度 O (1)

(一) 解法思路

要删除倒数第 N 个节点,首先求出链表的长度 。然后,删除正向的第 (长度 - N + 1) 个节点

按照以下步骤解题:

  1. 遍历链表,计算链表长度。
  2. 计算需要从正向删除的位置:待删节点位置 = 链表长度 − N + 1。
  3. 若待删位置为第 1 个节点,将头节点更新为原头节点的下一个节点。
  4. 遍历到目标位置的前一个节点,修改其 next 指针,跳过目标节点。
  5. 返回修改后的链表。

(二) 使用 6 种语言实现

1. C++

cpp 复制代码
// C++ 程序:删除链表的倒数第 N 个节点
#include <iostream>
using namespace std;

// 链表节点结构
class Node {
public:
    int data;
    Node* next;

    Node(int new_data) {
        data = new_data;
        next = nullptr;
    }
};

// 函数:删除链表的倒数第 N 个节点
Node* removeNthFromEnd(Node* head, int N) {
  
    // 第一步:计算链表的总长度
    int length = 0;
    Node* curr = head;
    while (curr != nullptr) {
        length++;
        curr = curr->next;
    }

    // 第二步:计算正向需要删除的节点位置
    int target = length - N + 1;

    // 情况 1:要删除的是头节点
    if (target == 1) {
        Node* newHead = head->next;
      
        // 释放被删除节点的内存
        delete head; 
        return newHead;
    }

    // 情况 2:找到待删除节点的**前一个节点**
    curr = head;
    for (int i = 1; i < target - 1; i++) {
        curr = curr->next;
    }

    // 删除目标节点,修改指针
    Node* nodeToDelete = curr->next;
    curr->next = curr->next->next;
    delete nodeToDelete;  

    return head;
}

// 打印链表
void printList(Node* node) {
    Node* curr = node;
    while (curr != nullptr) {
        cout << " " << curr->data;
        curr = curr->next;
    }
}

// 主函数测试
int main() {
  
    // 构建链表: 1 -> 2 -> 3 -> 4 -> 5
    Node* head = new Node(1);
    head->next = new Node(2);
    head->next->next = new Node(3);
    head->next->next->next = new Node(4);
    head->next->next->next->next = new Node(5);

    int N = 2; // 删除倒数第 2 个节点
    head = removeNthFromEnd(head, N);

    printList(head);  // 输出结果

    return 0;
}

2. C

cpp 复制代码
// C 程序:删除链表的倒数第 N 个节点
#include <stdio.h>
#include <stdlib.h>

// 定义链表节点结构
struct Node {
    int data;
    struct Node* next;
};

// 函数:删除链表的倒数第 N 个节点
struct Node* removeNthFromEnd(struct Node* head, int N) {
  
    // 第一步:计算链表的总长度
    int length = 0;
    struct Node* curr = head;
    while (curr != NULL) {
        length++;
        curr = curr->next;
    }

    // 第二步:计算正向需要删除的节点位置
    int target = length - N + 1;

    // 情况 1:需要删除的是头节点
    if (target == 1) {
        struct Node* newHead = head->next;
      
        // 释放被删除节点的内存
        free(head);  
        return newHead;
    }

    // 情况 2:遍历到待删除节点的**前一个节点**
    curr = head;
    for (int i = 1; i < target - 1; i++) {
        curr = curr->next;
    }

    // 删除目标节点,修改指针跳过该节点
    struct Node* nodeToDelete = curr->next;
    curr->next = curr->next->next;
  
    // 释放内存
    free(nodeToDelete);  

    return head;
}

// 打印链表
void printList(struct Node* node) {
    struct Node* curr = node;
    while (curr != NULL) {
        printf(" %d", curr->data);
        curr = curr->next;
    }
}

// 创建新节点
struct Node* createNode(int new_data) {
    struct Node* new_node
        = (struct Node*)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = NULL;
    return new_node;
}

// 主函数测试
int main() {
  
    // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
    struct Node* head = createNode(1);
    head->next = createNode(2);
    head->next->next = createNode(3);
    head->next->next->next = createNode(4);
    head->next->next->next->next = createNode(5);

    int N = 2;  // 删除倒数第 2 个节点
    head = removeNthFromEnd(head, N);

    printList(head);  // 输出结果

    return 0;
}

3. Java

java 复制代码
// Java 程序:删除链表的倒数第 N 个节点
class Node {
    int data;
    Node next;

    // 构造函数:创建新节点
    Node(int new_data) {
        data = new_data;
        next = null;
    }
}

// 主类:包含删除节点的函数
public class DSA {
  
    // 函数:删除链表的倒数第 N 个节点
    static Node removeNthFromEnd(Node head, int N) {
      
        // 第一步:计算链表的总长度
        int length = 0;
        Node curr = head;
        while (curr != null) {
            length++;
            curr = curr.next;
        }

        // 第二步:计算正向需要删除的节点位置
        int target = length - N + 1;

        // 情况 1:要删除的是头节点,直接返回下一个节点
        if (target == 1) {
            return head.next;
        }

        // 情况 2:遍历到待删除节点的**前一个节点**
        curr = head;
        for (int i = 1; i < target - 1; i++) {
            curr = curr.next;
        }

        // 删除目标节点:将前一个节点的 next 跳过目标节点
        curr.next = curr.next.next;

        return head;
    }

    // 打印链表
    static void printList(Node node) {
        Node curr = node;  
        while (curr != null) {
            System.out.print(" " + curr.data);
            curr = curr.next;
        }
    }

    // 主函数:测试代码
    public static void main(String[] args) {
      
        // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new Node(5);

        int N = 2; // 删除倒数第 2 个节点
        head = removeNthFromEnd(head, N);

        printList(head);  // 输出结果
    }
}

4. Python

python 复制代码
# Python3 程序:删除链表的倒数第 N 个节点
class Node:
    def __init__(self, new_data):
        self.data = new_data
        self.next = None

# 函数:删除链表的倒数第 N 个节点
def remove_nth_from_end(head, N):
  
    # 第一步:计算链表的总长度
    length = 0
    curr = head
    while curr is not None:
        length += 1
        curr = curr.next

    # 第二步:计算正向需要删除的节点位置
    target = length - N + 1

    # 情况 1:要删除的是头节点,直接返回下一个节点
    if target == 1:
        return head.next

    # 情况 2:遍历到待删除节点的**前一个节点**
    curr = head
    for _ in range(target - 2):
        curr = curr.next

    # 删除目标节点:将前一个节点的 next 跳过目标节点
    curr.next = curr.next.next

    return head

# 打印链表
def print_list(node):
    curr = node
    while curr is not None:
        print(f" {curr.data}", end="")
        curr = curr.next
    print()

# 主函数测试
if __name__ == "__main__":
  
    # 创建链表: 1 -> 2 -> 3 -> 4 -> 5
    head = Node(1)
    head.next = Node(2)
    head.next.next = Node(3)
    head.next.next.next = Node(4)
    head.next.next.next.next = Node(5)

    N = 2  # 删除倒数第 2 个节点
    head = remove_nth_from_end(head, N)

    print_list(head)

5. C#

cs 复制代码
// C# 程序:删除链表的倒数第 N 个节点
using System;

class Node {
    public int Data;
    public Node next;

    // 构造函数:创建新节点
    public Node(int newData) {
        Data = newData;
        next = null;
    }
}

// 包含删除函数的类
class GfG {
    static Node RemoveNthFromEnd(Node head, int N) {

        // 第一步:计算链表的长度
        int length = 0;
        Node curr = head;
        while (curr != null) {
            length++;
            curr = curr.next;
        }

        // 第二步:计算正向需要删除的节点位置
        int target = length - N + 1;

        // 情况 1:要删除的是头节点,直接返回下一个节点
        if (target == 1) {
            return head.next;
        }

        // 情况 2:遍历到目标节点的**前一个节点**
        curr = head;
        for (int i = 1; i < target - 1; i++) {
            curr = curr.next;
        }

        // 删除目标节点:跳过当前节点的下一个节点
        curr.next = curr.next.next;

        return head;
    }

    // 打印链表
    static void PrintList(Node node) {
        Node curr = node;
        while (curr != null) {
            Console.Write(" " + curr.Data);
            curr = curr.next;
        }
        Console.WriteLine();
    }

    // 主函数:测试
    static void Main() {

        // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new Node(5);

        int N = 2; // 删除倒数第 2 个节点
        head = RemoveNthFromEnd(head, N);

        PrintList(head);
    }
}

6. JavaScript

javascript 复制代码
// JavaScript 程序:删除链表的倒数第 N 个节点
class Node {
    constructor(newData) {
        this.data = newData;
        this.next = null;
    }
}

// 函数:删除链表的倒数第 N 个节点
function removeNthFromEnd(head, N) {
    
    // 第一步:计算链表的总长度
    let length = 0;
    let curr = head;
    while (curr !== null) {
        length++;
        curr = curr.next;
    }

    // 第二步:计算正向需要删除的节点位置
    let target = length - N + 1;

    // 情况 1:要删除的是头节点,直接返回下一个节点
    if (target === 1) {
        return head.next;
    }

    // 情况 2:遍历到目标节点的**前一个节点**
    curr = head;
    for (let i = 1; i < target - 1; i++) {
        curr = curr.next;
    }

    // 删除目标节点:将前一个节点的 next 跳过目标节点
    curr.next = curr.next.next;

    return head;
}

// 打印链表
function printList(node) {
    let curr = node;
    while (curr !== null) {
        process.stdout.write(" " + curr.data);
        curr = curr.next;
    }
    console.log();
}

// 主函数测试
// 创建链表: 1 -> 2 -> 3 -> 4 -> 5
let head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(4);
head.next.next.next.next = new Node(5);

let N = 2; // 删除倒数第 2 个节点
head = removeNthFromEnd(head, N);

printList(head);

(三) 代码输出和算法复杂度

输出:

复制代码
 1 2 3 5

时间复杂度:O(n)。

空间复杂度:O(1)。

三、【推荐解法-2】使用快慢指针 ------ 时间复杂度 O (n),空间复杂度 O (1)

(一) 解法思路

核心思路是:先将快指针向前移动 N 步 ,然后同时移动快指针和慢指针,直到快指针到达链表末尾。此时慢指针恰好位于待删除节点的前一个节点,我们只需修改它的 next 指针,跳过目标节点即可完成删除。

(二) 使用 6 种语言实现

1. C++

cpp 复制代码
// C++ 程序:使用快慢指针删除链表的倒数第 N 个节点
#include <iostream>
using namespace std;

// 链表节点类
class Node {
public:
    int data;
    Node* next;

    Node(int new_data) {
        data = new_data;
        next = nullptr;
    }
};

// 函数:删除倒数第 N 个节点(快慢指针法)
Node* removeNthFromEnd(Node* head, int N) {
  
    // 初始化快慢指针,都指向头节点
    Node* fast = head;
    Node* slow = head;

    // 第一步:让快指针先走 N 步
    for (int i = 0; i < N; i++) {
        if (fast == nullptr) return head;  // 边界处理
        fast = fast->next;
    }

    // 第二步:如果快指针为空,说明要删除的是头节点
    if (fast == nullptr) {
        Node* newHead = head->next;
        delete head;          // 释放内存
        return newHead;
    }

    // 第三步:快慢指针一起移动,直到快指针到达最后一个节点
    while (fast->next != nullptr) {
        fast = fast->next;
        slow = slow->next;
    }

    // 第四步:删除慢指针的下一个节点(即目标节点)
    Node* nodeToDelete = slow->next;
    slow->next = slow->next->next;
    delete nodeToDelete;

    return head;
}

// 打印链表
void printList(Node* node) {
    Node* curr = node;
    while (curr != nullptr) {
        cout << " " << curr->data;
        curr = curr->next;
    }
}

// 主函数测试
int main() {
  
    // 构建链表: 1 -> 2 -> 3 -> 4 -> 5
    Node* head = new Node(1);
    head->next = new Node(2);
    head->next->next = new Node(3);
    head->next->next->next = new Node(4);
    head->next->next->next->next = new Node(5);

    int N = 2;  // 删除倒数第 2 个节点
    head = removeNthFromEnd(head, N);

    printList(head);  // 输出结果

    return 0;
}

2. C

cpp 复制代码
// C 程序:使用快慢指针删除链表的倒数第 N 个节点
#include <stdio.h>
#include <stdlib.h>

struct Node {
    int data;
    struct Node* next;
};

// 函数:删除链表倒数第 N 个节点(快慢指针法)
struct Node* removeNthFromEnd(struct Node* head, int N) {

    // 定义快慢指针,初始都指向头节点
    struct Node* fast = head;
    struct Node* slow = head;

    // 快指针先向前走 N 步
    for (int i = 0; i < N; i++) {
        if (fast == NULL) return head; 
        fast = fast->next;
    }

    // 如果快指针为空,说明要删除的是头节点
    if (fast == NULL) {
        struct Node* newHead = head->next;
        free(head);
        return newHead;
    }

    // 快慢指针一起移动,直到快指针到达链表末尾
    while (fast->next != NULL) {
        fast = fast->next;
        slow = slow->next;
    }

    // 删除慢指针的下一个节点(即倒数第 N 个节点)
    struct Node* nodeToDelete = slow->next;
    slow->next = slow->next->next;
    free(nodeToDelete);

    return head;
}

// 打印链表
void printList(struct Node* node) {
    struct Node* curr = node;
    while (curr != NULL) {
        printf(" %d", curr->data);
        curr = curr->next;
    }
}

// 创建新节点
struct Node* createNode(int new_data) {
    struct Node* new_node
        = (struct Node*)malloc(sizeof(struct Node));
    new_node->data = new_data;
    new_node->next = NULL;
    return new_node;
}

// 主函数测试
int main() {

    // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
    struct Node* head = createNode(1);
    head->next = createNode(2);
    head->next->next = createNode(3);
    head->next->next->next = createNode(4);
    head->next->next->next->next = createNode(5);

    int N = 2;  
    head = removeNthFromEnd(head, N);

    printList(head); 

    return 0;
}

3. Java

java 复制代码
// Java 程序:使用快慢指针删除链表的倒数第 N 个节点
class Node {
    int data;
    Node next;

    Node(int new_data) {
        data = new_data;
        next = null;
    }
}

// 主类:包含删除方法
public class DSA {

    // 函数:删除链表的倒数第 N 个节点(快慢指针法)
    static Node removeNthFromEnd(Node head, int N) {
      
        // 初始化快慢指针,都指向头节点
        Node fast = head;
        Node slow = head;

        // 第一步:让快指针先走 N 步
        for (int i = 0; i < N; i++) {
            if (fast == null)
                return head;  // 边界处理:N 超出链表长度
            fast = fast.next;
        }

        // 第二步:如果快指针为空,说明要删除的是头节点
        if (fast == null) {
            return head.next;
        }

        // 第三步:快慢指针一起移动,直到快指针到达最后一个节点
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 第四步:删除目标节点(慢指针的下一个节点)
        slow.next = slow.next.next;

        return head;
    }

    // 打印链表
    static void printList(Node node) {
        Node curr = node;
        while (curr != null) {
            System.out.print(" " + curr.data);
            curr = curr.next;
        }
    }

    // 主函数测试
    public static void main(String[] args) {

        // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new Node(5);

        int N = 2; // 删除倒数第 2 个节点
        head = removeNthFromEnd(head, N);

        printList(head);
    }
}

4. Python

python 复制代码
# Python3 程序:使用快慢指针删除链表的倒数第 N 个节点
class Node:
    def __init__(self, new_data):
        self.data = new_data
        self.next = None

# 函数:删除链表的倒数第 N 个节点(快慢指针法)
def remove_nth_from_end(head, N):
  
    # 初始化快慢指针,都指向头节点
    fast = head
    slow = head

    # 第一步:让快指针先走 N 步
    for _ in range(N):
        if fast is None:
            return head  # 边界处理
        fast = fast.next

    # 第二步:如果快指针为空,说明要删除的是头节点
    if fast is None:
        return head.next

    # 第三步:快慢指针一起移动,直到快指针到达最后一个节点
    while fast.next is not None:
        fast = fast.next
        slow = slow.next

    # 第四步:删除慢指针的下一个节点(目标节点)
    slow.next = slow.next.next

    return head

# 打印链表
def print_list(node):
    curr = node
    while curr is not None:
        print(f" {curr.data}", end="")
        curr = curr.next
    print()

# 主函数测试
if __name__ == "__main__":
  
    # 创建链表: 1 -> 2 -> 3 -> 4 -> 5
    head = Node(1)
    head.next = Node(2)
    head.next.next = Node(3)
    head.next.next.next = Node(4)
    head.next.next.next.next = Node(5)

    N = 2  # 删除倒数第 2 个节点
    head = remove_nth_from_end(head, N)

    print_list(head)

5. C#

cs 复制代码
// C# 程序:使用快慢指针删除链表的倒数第 N 个节点
using System;

class Node {
    public int Data;
    public Node next;

    // 构造函数:创建新节点
    public Node(int newData) {
        Data = newData;
        next = null;
    }
}

// 包含删除函数的类
class DSA {
    // 函数:删除链表倒数第 N 个节点(快慢指针法)
    static Node RemoveNthFromEnd(Node head, int N) {
      
        // 初始化快慢指针,都指向头节点
        Node fast = head;
        Node slow = head;

        // 第一步:快指针先向前移动 N 步
        for (int i = 0; i < N; i++) {
            if (fast == null) return head; 
            fast = fast.next;
        }

        // 第二步:如果快指针为空,说明要删除的是头节点
        if (fast == null) {
            return head.next;
        }

        // 第三步:快慢指针一起移动,直到快指针到达链表末尾
        while (fast.next != null) {
            fast = fast.next;
            slow = slow.next;
        }

        // 第四步:删除慢指针的下一个节点(目标节点)
        slow.next = slow.next.next;

        return head;
    }

    // 打印链表
    static void PrintList(Node node) {
        Node curr = node;
        while (curr != null) {
            Console.Write(" " + curr.Data);
            curr = curr.next;
        }
        Console.WriteLine();
    }

    // 主函数:测试代码
    static void Main() {
      
        // 创建链表: 1 -> 2 -> 3 -> 4 -> 5
        Node head = new Node(1);
        head.next = new Node(2);
        head.next.next = new Node(3);
        head.next.next.next = new Node(4);
        head.next.next.next.next = new

6. JavaScript

javascript 复制代码
// JavaScript 程序:使用快慢指针删除链表的倒数第 N 个节点
class Node {
    constructor(newData) {
        this.data = newData;
        this.next = null;
    }
}

// 函数:删除链表倒数第 N 个节点(快慢指针法)
function removeNthFromEnd(head, N) {
    
    // 初始化快慢指针,都指向头节点
    let fast = head;
    let slow = head;

    // 第一步:快指针先向前移动 N 步
    for (let i = 0; i < N; i++) {
        if (fast === null) return head; 
        fast = fast.next;
    }

    // 第二步:如果快指针为空,说明要删除的是头节点
    if (fast === null) {
        return head.next;
    }

    // 第三步:快慢指针一起移动,直到快指针到达链表末尾
    while (fast.next !== null) {
        fast = fast.next;
        slow = slow.next;
    }

    // 第四步:删除慢指针的下一个节点(目标节点)
    slow.next = slow.next.next;

    return head;
}

// 打印链表
function printList(node) {
    let curr = node;
    while (curr !== null) {
        process.stdout.write(" " + curr.data);
        curr = curr.next;
    }
    console.log();
}

// 主函数测试
// 创建链表: 1 -> 2 -> 3 -> 4 -> 5
let head = new Node(1);
head.next = new Node(2);
head.next.next = new Node(3);
head.next.next.next = new Node(4);
head.next.next.next.next = new Node(5);

let N = 2; // 删除倒数第 2 个节点
head = removeNthFromEnd(head, N);

printList(head);

(三) 代码输出和算法复杂度

输出:

复制代码
 1 2 3 5

时间复杂度:O(n)。

空间复杂度:O(1)。

相关推荐
qq_296553271 小时前
[特殊字符] 数组中的递增三元组:O(n) 时间高效查找,面试必考!
数据结构·算法·面试·职场和发展·组合模式·柔性数组
逻辑驱动的ken1 小时前
Java高频面试考点场景题26
java·开发语言·面试·职场和发展·求职招聘
今儿敲了吗1 小时前
链表篇(一)——合并两个有序链表
数据结构·笔记·算法·链表
fie88891 小时前
基于BBO算法的网络负载均衡优化(MATLAB实现)
网络·算法·负载均衡
y = xⁿ1 小时前
20天速通LeetCodeday11:二叉树进阶
数据结构·算法
星辰_mya1 小时前
领域驱动设计(DDD)“老中医”治理订单
java·后端·面试·架构
400分1 小时前
langchain踩坑调用大模型记录-搭建人工智能机器人
算法
张元清1 小时前
React 浏览器标签页 UX:用标题、Favicon 和通知把用户拉回来
前端·javascript·面试
Lkstar1 小时前
读完红宝书和YDKJS,我终于搞懂了原型链、闭包和this
javascript·面试