链表part01:203.移除链表元素, 707.设计链表,206.反转链表

本文介绍了如何在链表中移除指定元素,使用顺序遍历并设置虚拟头结点的方法。接着讨论了设计链表类,包括添加、删除和获取元素等操作。最后,讲解了两种方法(双指针和递归)来翻转链表。

前言

常用链表操作复习

203. 移除链表元素

题目链接:https://leetcode.cn/problems/binary-search/
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。

示例:
在这里插入图片描述

  • 顺序遍历即可,遍历的时候保存前指针以及后指针,
  • 在单链表中移除头结点 和 移除其他节点的操作方式是不一样,需要单独写一段逻辑来处理移除头结点的情况。
  • 可以设置一个虚拟头结点,这样原链表的所有节点就都可以按照统一的方式进行移除了。代码如下:
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)
class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if(head == null) {
            return head;
        }
        ListNode dummy = new ListNode(-1, head);
        ListNode pre = dummy;
        ListNode cur = head;
        while(cur != null) {
            if(cur.val == val) {
                pre.next = cur.next;
            }else {
                pre = cur;
            }
            cur = cur.next;
        }
        return dummy.next;
    }
}

707. 设计链表

题目链接:https://leetcode.cn/problems/remove-element/
题意:
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

//单链表
class ListNode {
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val) {
        this.val=val;
    }
}
class MyLinkedList {
    //size存储链表元素的个数
    int size;
    //虚拟头结点
    ListNode head;

    //初始化链表
    public MyLinkedList() {
        size = 0;
        head = new ListNode(0);
    }

    //获取第index个节点的数值,注意index是从0开始的,第0个节点就是头结点
    public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
        ListNode currentNode = head;
        //包含一个虚拟头节点,所以查找第 index+1 个节点
        for (int i = 0; i <= index; i++) {
            currentNode = currentNode.next;
        }
        return currentNode.val;
    }

    //在链表最前面插入一个节点,等价于在第0个元素前添加
    public void addAtHead(int val) {
        addAtIndex(0, val);
    }

    //在链表的最后插入一个节点,等价于在(末尾+1)个元素前添加
    public void addAtTail(int val) {
        addAtIndex(size, val);
    }

    // 在第 index 个节点之前插入一个新节点,例如index为0,那么新插入的节点为链表的新头节点。
    // 如果 index 等于链表的长度,则说明是新插入的节点为链表的尾结点
    // 如果 index 大于链表的长度,则返回空
    public void addAtIndex(int index, int val) {
        if (index > size) {
            return;
        }
        if (index < 0) {
            index = 0;
        }
        size++;
        //找到要插入节点的前驱
        ListNode pred = head;
        for (int i = 0; i < index; i++) {
            pred = pred.next;
        }
        ListNode toAdd = new ListNode(val);
        toAdd.next = pred.next;
        pred.next = toAdd;
    }

    //删除第index个节点
    public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }
        size--;
        if (index == 0) {
            head = head.next;
	    return;
        }
        ListNode pred = head;
        for (int i = 0; i < index ; i++) {
            pred = pred.next;
        }
        pred.next = pred.next.next;
    }
}

206.翻转链表

题意:反转一个单链表。
示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null。
然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。
为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。
接下来,就是循环走,继续移动pre和cur指针。
最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时return pre指针就可以了,pre指针就指向了新的头结点。

// 双指针法
//时间复杂度: O(n)
//空间复杂度: O(1)
public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while(cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }

递归法相对抽象一些,但是其实和双指针法是一样的逻辑,同样是当cur为空的时候循环结束,不断将cur指向pre的过程。
关键是初始化的地方,双指针法中初始化 cur = head,pre = NULL,在递归法中可以从如下代码看出初始化的逻辑也是一样的。

// 递归法
//时间复杂度: O(n), 要递归处理链表的每个节点
//空间复杂度: O(n), 递归调用了 n 层栈空间
public ListNode reverseList(ListNode head) {
        ListNode pre = null;
        ListNode cur = head;
        while(cur != null) {
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        return pre;
    }
<think>We are given a code template for merging two sorted linked lists with dummy head nodes. The requirement: - Both input lists have a dummy head node (which means the actual data starts from list->next). - We are to merge them into a new sorted linked list (with a dummy head node as well). - We do not remove duplicate elements. - We guarantee that the linked lists can be destroyed (meaning we can manipulate the pointers arbitrarily). Steps: 1. We create a new dummy head node for the merged list. 2. We traverse both lists (starting from the first actual node, i.e., list1->next and list2->next) and compare the values. 3. We append the node with the smaller value to the merged list and move the pointer of that list forward. 4. When one list is exhausted, we append the rest of the other list. 5. We return the new dummy head node. Important: Since we are allowed to destroy the original lists, we are just rearranging pointers. However, note: The original lists have dummy head nodes. We must delete the dummy head nodes of the input lists? But the problem says "guarantee that the linked lists can be destroyed". It doesn't require us to delete the dummy heads of the input lists? But if we are creating a new dummy head for the result, then the input lists' dummy heads are not used in the result. Actually, the problem says: "output result linked list's head pointer" (which is the dummy head of the merged list). Also note: We are to merge the two lists without removing duplicates. Implementation: Let's denote: list1 and list2 are the dummy head nodes of the two input lists. We start the merging from: list1->next and list2->next. We create: dummy = (NODE*)malloc(sizeof(NODE)); // or use new in C++? But the code is in C-style? The template uses typedef struct. Since the function signature is in C-style (using struct and typedef), we assume we are writing in C. Steps in code: NODE dummy; // We can create a dummy node on the stack? But we must return a pointer. So we allocate? But the return type is NODE*, and we are to return the head of the merged list (which is a dummy node). Alternatively, we can create a dummy node and then return it. But we must manage memory. Since the requirement says "guarantee that the linked lists can be destroyed", it implies we are allowed to free the original dummy nodes? However, note that the problem does not specify whether we should free the original dummy nodes. But we are only to merge the lists. We are going to leave the original dummy nodes? Actually, we are not going to use them in the merged list. We'll create a new dummy node for the result. Steps: NODE* dummy_result = (NODE*)malloc(sizeof(NODE)); dummy_result->next = NULL; NODE* tail = dummy_result; // tail pointer to build the merged list. NODE* p1 = list1->next; // first actual node of list1 NODE* p2 = list2->next; // first actual node of list2 while (p1 != NULL && p2 != NULL) { if (p1->value <= p2->value) { tail->next = p1; p1 = p1->next; } else { tail->next = p2; p2 = p2->next; } tail = tail->next; } // If one list is not empty, append the rest. if (p1 != NULL) { tail->next = p1; } else { tail->next = p2; } // Now, we have merged the lists. But note: the original dummy nodes of list1 and list2 are still there. // The problem says "guarantee that the linked lists can be destroyed", meaning we can deallocate them? // However, the problem does not require us to deallocate the original dummy nodes in this function? // But if we leave them, then the caller might not know to free them? // But note: the problem says "three linked lists" (the two input and the output) should be destroyable. // We are returning the merged list (which has a new dummy node and then the merged nodes). The caller can then free the two original dummy nodes? // Since we are only required to write the function, we don't deallocate the original dummy nodes here. return dummy_result; However, wait: what about the original dummy nodes of list1 and list2? They are now pointing to parts of the merged list? Actually, we set p1 = list1->next. Then we move p1. The list1 node itself is not touched. Similarly for list2. So the original lists become: list1: dummy_node1 -> ... (but we took away the chain from dummy_node1->next and merged it) So now list1->next is still set but we moved the nodes away. So if the caller frees list1 and list2, they should be careful because the next pointers of the dummy nodes are now pointing to the merged list? To avoid any issues, we should set the next pointers of the original dummy nodes to NULL? But the problem does not specify. Alternatively, note the requirement: "guarantee that the linked lists can be destroyed". We must ensure that when the caller destroys the original lists (without the merged list), they can safely free the memory. But if we leave the dummy nodes of list1 and list2 pointing to the merged list, then if the caller frees the dummy nodes and then uses the merged list, that might be okay because the merged list is independent? However, the problem says "three linked lists" (so the two input lists and the output list) are separate and destroyable? Actually, after we merge, the nodes from list1 and list2 are now in the merged list. So the two input lists (including the dummy nodes) should be destroyed without affecting the merged list. Therefore, we must detach the original dummy nodes so that they become independent (only the dummy node). Then the caller can free the dummy nodes and the merged list remains. How to do that? We can set the list1->next and list2->next to NULL after we take the nodes? But the problem does not require us to change the original lists? It only says we can destroy the linked lists (meaning the entire data structure including the dummy nodes). Actually, if the caller destroys the two input lists, they should free the dummy nodes and then the rest? But we have taken the rest nodes to the merged list. So if they free the dummy nodes without freeing the rest, that's okay? But if they try to free the entire list (dummy and then the chain) that would be a problem because the chain is now in the merged list. Therefore, to be safe, we should set the original dummy nodes to point to NULL so that when the caller frees them, they don't free the merged list. However, the problem says: "in the given code template", and the template function does not take the lists by reference (it takes NODE*). So we are passing the pointers by value. We cannot change the original list1 and list2 pointers (to set them to NULL) because they are passed by value. But we can change the content of the node? We have the list1 and list2 pointers. We can do: list1->next = NULL; list2->next = NULL; But is that acceptable? The problem says "guarantee that the linked lists can be destroyed". This will break the chain from the dummy nodes, so the original lists now become just the dummy nodes. Then the caller can free the dummy nodes without affecting the merged list. However, note: the problem says "output result linked list's head pointer", and we are creating a new dummy node. Therefore, the merged list has a new dummy node and then the merged chain. The merged chain is the original chains of list1 and list2 (without their dummy nodes). So if we set list1->next and list2->next to NULL, then the original lists are just the dummy nodes and they can be freed independently. Steps added: ... after we extract p1 and p2, we do: list1->next = NULL; list2->next = NULL; But wait: what if the caller wants to use the original lists after merging? Probably not, because we are merging and the requirement says "destroyable". But to be safe, we break the link. Alternatively, it might be the caller's responsibility? However, the problem says "guarantee that the linked lists can be destroyed". Therefore, we must leave the state such that the caller can destroy each linked list independently. Therefore, we will set the next of the original dummy nodes to NULL. Code: NODE* list_merge(NODE* list1, NODE* list2) { // Create a new dummy node for the result. NODE* dummy_result = (NODE*)malloc(sizeof(NODE)); dummy_result->next = NULL; NODE* tail = dummy_result; // Start from the first actual node of list1 and list2. NODE* p1 = list1->next; NODE* p2 = list2->next; // Break the original dummy nodes' links to the lists so that they are isolated (only dummy node). list1->next = NULL; list2->next = NULL; while (p1 && p2) { if (p1->value <= p2->value) { tail->next = p1; p1 = p1->next; } else { tail->next = p2; p2 = p2->next; } tail = tail->next; } if (p1) { tail->next = p1; } else { tail->next = p2; } return dummy_result; } Note: We are not freeing the original dummy nodes here because the caller might want to use them? Or the caller will free them? The problem does not specify. But we broke the link so that the original lists (which now consist of only the dummy node) can be freed without affecting the merged list. Also note: we are not removing duplicates, so we include duplicates. However, the problem says "merge two sorted linked lists". We are doing a standard merge. But what if one of the lists is empty? If list1 is empty (so list1->next is NULL), then p1 is NULL. Similarly for list2. Then we break the link (list1->next is already NULL, we set to NULL again? It's okay). Then the while loop doesn't run, and we set tail->next to the non-empty one? If both are empty, then tail->next becomes NULL. So it works. But note: what if the lists are not NULL-terminated? They are singly linked lists and must be NULL-terminated. We assume the input lists are properly formed. Also note: the problem says the lists have a dummy head node. Even if the list is empty, the dummy head node exists (with next=NULL). Therefore, our code should work. However, we are using malloc. We must remember to free the dummy nodes of the original lists? Not in this function. Let me test with an example: list1: dummy1 -> 1 -> 3 -> NULL list2: dummy2 -> 2 -> 4 -> NULL We create dummy_result. p1 = dummy1->next = 1 p2 = dummy2->next = 2 Then we set dummy1->next = NULL, dummy2->next = NULL. Then we merge: dummy_result -> 1 -> 2 -> 3 -> 4 -> NULL And we return dummy_result. The caller can then free: free(dummy1); // because dummy1->next is NULL, so it's just the dummy node. free(dummy2); // same. And the merged list is independent. Therefore, the code is as below. But note: the problem says the input lists have a dummy head and we output a new dummy head. Also note: the problem says "typedef struct __NODE", so we use that. One more point: what if the lists have duplicate values? We include them so no problem. We are using the standard merge procedure. Let's write the code accordingly. However, note that the problem says "code template", so we fill in the function body. Important: We must not leak memory. By breaking the links, we ensure that the original dummy nodes are isolated and can be freed without affecting the merged list. But what about the nodes in the merged list? They are now owned by the merged list. The caller will have to free the entire merged list (including the dummy node we created) at some point. So the function returns a dummy node for the merged list. The caller is responsible for freeing all three lists: list1 (the dummy node), list2 (the dummy node), and the returned merged list (which includes the dummy node and the chain). We are good. Code: #include <stdlib.h> // for malloc typedef struct __NODE { int value; struct __NODE* next; } NODE; NODE* list_merge(NODE* list1, NODE* list2) { // Create a new dummy node for the merged list. NODE* dummy_result = (NODE*)malloc(sizeof(NODE)); dummy_result->next = NULL; // Start pointers for the first actual nodes of the two lists. NODE* p1 = list1->next; NODE* p2 = list2->next; // Break the original lists to isolate the dummy nodes. list1->next = NULL; list2->next = NULL; // Tail of the merged list. NODE* tail = dummy_result; // Merge the lists. while (p1 != NULL && p2 != NULL) { if (p1->value <= p2->value) { tail->next = p1; p1 = p1->next; } else { tail->next = p2; p2 = p2->next; } tail = tail->next; } // Append the remaining part of either list. tail->next = (p1 != NULL) ? p1 : p2; return dummy_result; } Let me test with one list empty: list1: dummy1 -> NULL list2: dummy2 -> 1->2->NULL p1 = NULL, p2 = 1. After breaking: list1->next=NULL, list2->next=NULL. Then while loop doesn't run, we set tail->next = p2 (which is 1->2->NULL). Then return dummy_result -> 1->2->NULL. This is correct. But note: what if both are empty? Then we return a dummy node with next=NULL. Correct. Therefore, the code should be as above. However, the problem says "with empty head node". Our new list has an empty head node (dummy_result) and then the merged chain. So we meet the requirement. Now, let me write the code accordingly. Important: We are using malloc. We assume that the caller will free the memory. Also note: the problem does not specify the language, but the template is in C. So we write in C. We'll write the function as described. But note: the problem says "given code template", so we must only fill in the function body and not change the signature. We have filled the function body. Let me write it. </think>根据要求,以下实现了合并两个带空头节点有序单链表的函数。该函数会创建一个新的空头节点作为合并后链表的头节点,保持原有节点的内存结构不变,确保所有链表都可安全销毁,并输出结果链表的头节点指针: ```c typedef struct __NODE { int value; struct __NODE* next; } NODE; NODE* list_merge(NODE* list1, NODE* list2) { // 创建新的空头节点 NODE* dummy = (NODE*)malloc(sizeof(NODE)); dummy->next = NULL; NODE* cur = dummy; // 指向第一个实际节点(跳过头节点) NODE* p1 = list1->next; NODE* p2 = list2->next; // 断开原头节点的链接 (确保可安全销毁) list1->next = NULL; list2->next = NULL; // 合并两个有序链表 while (p1 != NULL && p2 != NULL) { if (p1->value <= p2->value) { cur->next = p1; p1 = p1->next; } else { cur->next = p2; p2 = p2->next; } cur = cur->next; } // 连接剩余部分 cur->next = (p1 != NULL) ? p1 : p2; return dummy; } ``` ### 关键实现说明: 1. **空头节点处理**: - 输入链表 `list1` 和 `list2` 都包含空头节点(`list->next` 开始为有效数据) - 创建新的空头节点 `dummy` 作为结果链表的头节点 2. **内存安全保证**: - 通过 `list1->next = NULL` 和 `list2->next = NULL` 断开原链表链接 - 原头节点成为独立节点,可被安全销毁 - 新链表节点使用原有内存,避免二次释放问题 3. **合并逻辑**: - 时间复杂度:$O(n+m)$(n、m 为两链表长度) - 空间复杂度:$O(1)$(仅需常数额外空间) - 保留所有重复元素(包括原链表中的重复值) 4. **边界处理**: - 当某链表为空时,自动连接另一链表剩余部分 - 双空链表输入时返回空头节点 ### 示例调用: ```c // 创建链表1: 头节点->1->3->NULL NODE* list1 = (NODE*)malloc(sizeof(NODE)); NODE* a1 = (NODE*)malloc(sizeof(NODE)); a1->value = 1; NODE* a2 = (NODE*)malloc(sizeof(NODE)); a2->value = 3; list1->next = a1; a1->next = a2; a2->next = NULL; // 创建链表2: 头节点->2->4->NULL NODE* list2 = (NODE*)malloc(sizeof(NODE)); NODE* b1 = (NODE*)malloc(sizeof(NODE)); b1->value = 2; NODE* b2 = (NODE*)malloc(sizeof(NODE)); b2->value = 4; list2->next = b1; b1->next = b2; b2->next = NULL; // 合并并输出结果 (头节点->1->2->3->4->NULL) NODE* merged = list_merge(list1, list2); for (NODE* p = merged->next; p; p = p->next) { printf("%d ", p->value); // 输出: 1 2 3 4 } // 安全销毁所有链表 free(list1); // 释放原链表1头节点 free(list2); // 释放原链表2头节点 // 销毁合并链表 (包括新头节点) while (merged) { NODE* temp = merged; merged = merged->next; free(temp); } ``` ### 相关问题: 1. 如何验证合并后链表的内存安全性? 2. 如果要求不修改原链表结构(保持原链表完整),代码需要如何调整? 3. 如何处理循环链表或含环链表的合并场景? 4. 如何优化该算法在大型链表排序中的性能? 5. 在并发环境下合并链表需要考虑哪些线程安全问题?[^1] [^1]: 引用自用户提供的链表合并实现方案和内存管理要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值