链表的一些oj题

链表练习题汇总及解题思路

一 关于链表的一些练习题

1.删除链表中等于给定值 val 的所有节点。

题目的链接:. - 力扣(LeetCode)

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        if (head == null) {
            return null;
        }

        // 使用虚拟节点来处理头节点可能需要被删除的情况
        ListNode dummy = new ListNode(-1);
        dummy.next = head;

        ListNode cur = head;
        ListNode pre = dummy;
        while (cur != null) {
            if (cur.val == val) {
                pre.next = cur.next;
            } else {
                pre = cur;
            }
            cur = cur.next;
        }
        return dummy.next;
    }
}

 93a8b03660024bb4bdb36dc8d9adf6ca.png

2.反转一个单链表

题目的链接:. - 力扣(LeetCode) 

class Solution {
    public ListNode reverseList(ListNode head) {
           
        if(head==null){
            return head;
        }
        ListNode cur=head.next;
        head.next=null;
        while (cur!=null){
            ListNode curN= cur.next;
            cur.next=head;
            head=cur;
            cur=curN;
        }
        return head;
    }
    }

e6f819701a324632b71f29e2e3bceca5.png 95053303ee7f489bbff39416c53d2838.png

cda181669d05463aaa9469475d17e16b.png

3f75bb6efc274323890cc8808e601260.png 3.给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。

题目链接:. - 力扣(LeetCode)

class Solution {
    public ListNode middleNode(ListNode head) {
        if(head==null){
        return head;
    }
        ListNode fast=head;
        ListNode slow=head;
        while (fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        return slow;
    }
    }

这道题运用了小学数学概念相同的路程,fast的速度时slow速度的两倍,所以slow用的时间为fast的两倍,一个已经走完,一个正好在中间。

aeaa7e95248d4add942251d53d6a2fd1.png

4.. 输入一个链表,输出该链表中倒数第k个结点。

让fast前往前走k个节点,然后fast和slow同时走,当fast走到最后一个节点时,slow走的节点就是倒数第K个节点。

public int returnNode(int key) {
        ListCode fast=head;
        ListCode slow=head;
        if (head==null){
            return -1;
        }
        int count=0;
        while (count!=key-1){
            fast=fast.next;
            if(fast==null){
                return -1;
            }
            count++;
        }
        while (fast.next!=null){
            fast=fast.next;
            slow=slow.next;
        }
        return slow.val;
    }
}

5. 将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

题目链接:21. 合并两个有序链表 - 力扣(LeetCode)

class Solution {
    public ListNode mergeTwoLists(ListNode headA, ListNode headB) {
        ListNode newHead=new ListNode(-1);
        ListNode tem=newHead;
        while (headA!=null && headB!=null){
            if(headA.val<headB.val){
                tem.next=headA;
                headA=headA.next;
            }else {
                tem.next=headB;
                headB=headB.next;
            }
            tem=tem.next;
        }
        if(headA!=null){
            tem.next=headA;
        }else {
            tem.next=headB;
        }
        return newHead.next;
    }
}
public static void main(String[] args) {
        MySingleList list1=new MySingleList();
        MySingleList list2=new MySingleList();
        MySingleList.ListCode newHead=mergeTwoLists(list1.head,list2.head);

    }
}

4d46bf1ec4a944429ecb03ba868d0693.png

2a15958f1afb4fe081079357822bbe0d.png

6. . 编写代码,以给定值x为基准将链表分割成两部分,所有小于x的结点排在大于或等于x的结点之前 。

题目链接:链表分割_牛客题霸_牛客网

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        ListNode be=null;
        ListNode bs=null;
        ListNode ae=null;
        ListNode as=null;
        ListNode cur=pHead;
        while (cur != null) {
            if(cur.val<x){
                if (bs == null) {
                    be=bs=cur;
                }else {
                    be.next=cur;
                    be=be.next;
                }
            }else {
                if (as==null){
                    ae=as=cur;
                }else {
                    ae.next=cur;
                    ae=ae.next;
                }
            }
            cur=cur.next;
        }
        if(bs==null){
            return as;
        }
        be.next=as;
        if(as!=null){
            ae.next=null;
        }
        return bs;
    }
    }

75c83847271f4e7b92203f8fcfcdfe66.png

dae311edb6154dc0ad314cad49c50376.png

 7.链表的回文结构

题目链接:链表的回文结构_牛客题霸_牛客网

public boolean chkPalindrome() {
        if (head==null){
            return true;
        }
        ListCode fast=head;
        ListCode slow=head;
        while (fast!=null && fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
        ListCode cur=slow.next;
        while (cur!=null){
            ListCode curN=cur.next;
            cur.next=slow;
            slow=cur;
            cur=curN;
        }
        while (head.next!=slow) {
            if (head.val != slow.val) {
                return false;
            }
            if(head.next==slow){
                return true;
            }
            head=head.next;
            slow=slow.next;

        }
        return true;

    }
}

注:这个题的主要难点是如果它有奇数个或者偶数个时,它的情况是不同的。 

第一步:找到中间的位置

2fd07a1773054982aa7a97c9e006de7e.png

第二步:对slow后进行反转

04d360c195c24c4baf0717a96c9bd877.png

第三步:判断值是否相同04036a2b683640c9a68b71754064cd9a.png 

第四步:判断奇偶f44e0fa28c3e40d4ae5502b701c6e610.png 

 

25ecf767b1b54fd9b1f023c8e5d417d2.png

 

8.输入两个链表,找出它们的第一个公共结点。

题目链接:. - 力扣(LeetCode)

public class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pl=headA;
        ListNode ps=headB;
        int lenA=0;
        int lenB=0;
        while(pl!=null){
            pl=pl.next;
            lenA++;
        }
        while(ps!=null){
            ps=ps.next;
            lenB++;
        }
        int len=lenA-lenB;
        pl=headA;
        ps=headB;
        if(len<0){
            pl=headB;
            ps=headA;
            len=lenB-lenA;
        }
        while(len!=0){
            pl=pl.next;
            len--;
        }
        while(pl!=ps){
            pl=pl.next;
            ps=ps.next;
        }
        if(pl==null){
            return null;
        }
        return pl;
    }
}

9.求它们分别的长度,判断长度差,这个长度差就是出去公共部分的长度差,所以让长的链表先走长度差的大小,然后两个再同时走。 

 b2e07d747fa04db29e23127c2ebd6f63.png

07f4e4d0cfb745c78998338b4ee873fe.png

10.给定一个链表,判断链表中是否有环

题目链接:. - 力扣(LeetCode)

public boolean hasCycle(ListCode head) {

        ListCode fast=head;
        ListCode slow=head;
        while (fast!=null &&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if (fast==slow) {
                return true;
            }

        }
        return false;

    }

3143ca7fb5b74705b666393a82586420.png

b3febc680814441980198f2f0f106d15.png

10.给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULL

题目链接:. - 力扣(LeetCode)

public ListCode detectCycle() {
        ListCode fast=head;
        ListCode slow=head;
        while (fast!=null && fast.next!=null) {
            fast = fast.next.next;
            slow = slow.next;
            if (fast == slow) {
                break;
            }
        }
        if(fast==null || fast.next==null){
            //没有环
            return null;
        }
        slow=head;
        while (fast!=slow){
            fast=fast.next;
            slow=slow.next;
        }
        return slow;
    }
}

54da8e5b56474516b192596af3bf1eee.png

14098bf746dd4088a67899640fea722f.png

希望能对大家有所帮助!!!

 39e78bbea3d94ce88fd63940f6acd176.jpeg

 

 

<think>嗯,用户的问是关于如何在编写链表算法时避免空指针解引用错误。首先,我需要回忆一下链表操作中常见的空指针情况。比如访问头节点或尾节点的时候,如果链表为空,直接访问next或者prev就会出错。还有在遍历链表时,循环条件设置不当也可能导致访问空指针。 记得引用中提到过带尾指针的单循环链表实现,这可能涉及到如何处理尾节点的next指针,避免指向空的情况。另外,判断链表是否有环的快慢指针算法也需要处理指针的移动,确保在移动前指针不为空。比如引用[3]提到快慢指针每次移动前都要检查是否存在空指针,这样可以避免解引用空指针。 然后,用户提到的合并两个有序链表的例子中,代码里用了条件判断来处理cur1和cur2是否为空,这可能是一个关键点。比如在合并过程中,如果其中一个链表已经遍历完,就直接将尾指针指向另一个链表的剩余部分。这时候需要确保剩下的链表节点不为空,或者正确处理空的情况。 我还需要思考有哪些通用的最佳实践。比如,在访问节点属性之前先检查指针是否为空,使用卫语句提前处理边界条件,初始化指针时赋予安全值,比如头节点的next初始化为null。此外,防御性编程,比如在函数入口检查输入链表是否为null,可以避免很多问。 还有,循环链表的处理可能需要注意终止条件,避免无限循环。例如,在判断双链表是否对称的算法中,引用[4]中的代码用p != q和q != p->next作为循环条件,这可能在处理奇偶节点时防止越界。 另外,测试用例也很重要。比如空链表、单节点链表、头尾节点处理等情况都需要覆盖,确保代码在各种情况下都不会解引用空指针。 可能用户在实际编码中遇到了一些空指针崩溃的问,所以需要具体的解决方案。我应该把这些点整理成结构化的建议,包括输入验证、指针检查、边界处理、防御性编程和测试方法,并结合引用中的例子来说明。</think>### 链表操作中避免空指针解引用错误的最佳实践 #### 一、输入验证阶段 1. **函数入口检查** - 对所有传入的链表参数进行非空判断:`if (head == NULL) return NULL` - 示例:在合并有序链表算法中,需先判断输入链表是否为NULL[^5] 2. **特殊节点检查** - 处理头节点/尾节点时,验证`L->next`或`L->prev`是否存在 - 如判断循环双链表对称性时,需先确保链表不为空[^4] #### 二、指针移动与访问规范 1. **访问前必验证** - 在访问`p->next`或`p->data`前,必须保证`p != NULL` - 示例:快慢指针算法中每次移动前需判断指针有效性[^3] ```c while (fast && fast->next) { // 确保fast和fast->next非空 slow = slow->next; fast = fast->next->next; } ``` 2. **尾指针安全处理** - 带尾指针的循环链表更新时,需维护`tail->next`指向头节点 - 如带尾指针的单循环链表实现中需保证循环闭合性[^1] #### 三、边界条件处理 | 场景 | 解决方案 | 示例代码片段 | |-----------------|----------------------------|--------------------------------| | 空链表 | 提前返回或初始化保护 | `if (L->next == L) return true` | | 单节点链表 | 单独处理头尾指针关系 | `tail->next = head` | | 删除头/尾节点 | 更新相邻节点指针后再释放内存 | `prev->next = curr->next` | #### 四、防御性编程技巧 1. **卫语句(Guard Clauses)** ```c void insertNode(Node* prev, Node* newNode) { if (prev == NULL || newNode == NULL) return; newNode->next = prev->next; prev->next = newNode; } ``` 2. **初始化安全值** - 创建新节点时立即初始化指针:`newNode->next = NULL` 3. **循环终止条件增强** - 遍历时使用`while (curr && curr->next)`替代简单条件 #### 五、测试验证方法 1. **必测场景清单** - ✅ 空链表操作 - ✅ 单节点链表 - ✅ 头/尾节点边界操作 - ✅ 循环链表闭环/开环测试 2. **自动化检测工具** - 使用Valgrind检测内存错误 - 开启编译器警告选项:`-Wall -Wextra` ### 典型案例分析 **合并有序链表时的安全操作[^5]** ```c struct ListNode* mergeTwoLists(struct ListNode* list1, struct ListNode* list2){ Node head; // 虚拟头节点 Node* tail = &head; // 始终指向有效节点 while (list1 && list2) { // 循环条件确保两个指针有效 if (list1->val <= list2->val) { tail->next = list1; list1 = list1->next; // 移动前指针已非空 } else { tail->next = list2; list2 = list2->next; } tail = tail->next; // 移动尾指针 } tail->next = list1 ? list1 : list2; // 三元运算符避免空指针 return head.next; } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值