我的链表老是空指针?

BG:day4.今天本来是想分享些自己做题里遇到的问题,但是想到需要作为博客发出来就写的啰嗦了些,写着写着自己原本想表达的东西就消失了,变成了crtl+cv的无营养品,为了写而写。后续自己会注意这个问题,做到尽量把自己最深的感悟写出来,而不是堆砌

1.移除链表元素

题目用例

描述很简单的一道题目,下意识就写出了下面的代码

class Solution {
    public ListNode removeElements(ListNode head, int val) {
        //头结点可能被删除,所以考虑适用虚拟节点
        ListNode dummyNode=new ListNode(-1);
        dummyNode.next=head;
        ListNode temp=dummyNode;
        while(curr.next!=null){
            if(curr.next.val==val){
                curr.next=curr.next.next;
            }
            curr=curr.next;
        }
        return dummyNode.next;
    }
}
  • 问题1:会出现空指针异常:在代码第9行可能会出现curr指向的下一个节点为null。那么经过第11行再次移动后,while判断则会出现空指针异常。
  • 问题2:当我们删除某个节点后继续移动curr,会使得某些等于val值的指针被跳过。

解决以上问题也很简单,画图,一画图就非常清晰了,所以链表问题一定多画图弄清指向关系。

//改正后的代码
while(curr.next!=null){
      if(curr.next.val==val){
           curr.next=curr.next.next;
      }else{
           curr=curr.next; 
      }
}

2.设计链表

这道题目是让我们设计一个链表,完成新增,删除,查找的功能,leetcode中这样的题目还算比较多:像自己实现一个“LRU[最近最少使用]”,“实现一个字典树”,“实现一个特殊功能的栈等等”。第一遍做的时候可能比较懵,但第二三遍就比较熟练了,今天要提升的是**简洁自己的代码**。

//题目初始的代码框架
class MyLinkedList {

    public MyLinkedList() {
    }
    
    public int get(int index) 
    }
    
    public void addAtHead(int val) {
    }
    
    public void addAtTail(int val) {
    }
    
    public void addAtIndex(int index, int val) {
    }
    
    public void deleteAtIndex(int index) {
    }
}

以下是我完成的代码,能通过。但是看看addAtHead(),addAtTail(),addAtIndex()这三个函数的实现,都有很相似的部分,比较冗余[完全可以进行替换]。这类优化还是比较常见的,即:如果我们实现了一个**一般的操作**,特殊的操作完全可以由一般的操作来完成

class MyLinkedList {
    int size;
    ListNode dummyNode;
    public MyLinkedList() {
        this.size=0;
        this.dummyNode=new ListNode(-1);
    }
    
    public int get(int index) {
    }
    
    //可简化成:addAtIndex(0,val);
    public void addAtHead(int val) {
        ListNode node=new ListNode(val);
        ListNode tmp=dummyNode.next;
        dummyNode.next=node;
        node.next=tmp;
        this.size++;
    }
    
    //可见简化成:addAtIndex(size,val);
    public void addAtTail(int val) {
        ListNode curr=dummyNode;
        while(curr.next!=null){
            curr=curr.next;
        }
        curr.next=new ListNode(val);
        this.size++;
    }
    
    public void addAtIndex(int index, int val) {
        if(index>size){
            return;
        }else if(index==size){
            addAtTail(val);
        }else{
            ListNode curr=dummyNode;
            int count=0;
            while(count<index){
                curr=curr.next;
                //找到插入位置的前一个节点
                count++;
            }
            ListNode tmp=curr.next;
            ListNode node=new ListNode(val);
            curr.next=node;
            node.next=tmp;  
            this.size++;
        }
        
    }
    
    public void deleteAtIndex(int index) {
    }
}

class ListNode{
    int val;
    ListNode next;
    public ListNode(int value){
        this.val=value;
    }
}

另外需要注意的是:使用单链表完成,优点是省空间,操作简单;缺点也很明显:查找,新增,删除元素都需要先遍历到咱们想去的位置再进行下一步的操作。双向链表可以优化此问题,再加上map来存储节点和索引的关系可进一步优化{比如实现LRU}

3.反转链表

反转链表1还是比较简单的,进一步的,反转链表2思路也比较容易,但想写出bug free的代码依然是比较困难的。

class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 因为头节点有可能发生变化,使用虚拟头节点可以避免复杂的分类讨论
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;

        ListNode pre = dummyNode;
        // 第 1 步:从虚拟头节点走 left - 1 步,来到 left 节点的前一个节点
        // 建议写在 for 循环里,语义清晰
        for (int i = 0; i < left - 1; i++) {
            pre = pre.next;
        }

        // 第 2 步:从 pre 再走 right - left + 1 步,来到 right 节点
        ListNode rightNode = pre;
        for (int i = 0; i < right - left + 1; i++) {
            rightNode = rightNode.next;
        }

        // 第 3 步:切断出一个子链表(截取链表)
        ListNode leftNode = pre.next;
        ListNode curr = rightNode.next;

        // 注意:切断链接
        pre.next = null;
        rightNode.next = null;

        // 第 4 步:同第 206 题,反转链表的子区间,注意:反转后头变成了尾巴,尾巴变成了头
        reverseLinkedList(leftNode);

        // 第 5 步:接回到原来的链表中
        pre.next = rightNode;
        leftNode.next = curr;
        return dummyNode.next;
    }

    private void reverseLinkedList(ListNode head) {
        // 也可以使用递归反转一个链表
        ListNode pre = null;
        ListNode cur = head;

        while (cur != null) {
            ListNode next = cur.next;
            cur.next = pre;
            pre = cur;
            cur = next;
        }
    }
}
class Solution {
    public ListNode reverseBetween(ListNode head, int left, int right) {
        // 设置 dummyNode 是这一类问题的一般做法
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next = head;
        ListNode pre = dummyNode;
        for (int i = 0; i < left - 1; i++) {
            pre = pre.next;
        }
        ListNode cur = pre.next;
        ListNode next;
        //pre是待反转区域的前一个节点,cur是待反转节点的前一个节点,next是cur.next,是需要反转的节点
        for (int i = 0; i < right - left; i++) {
            next = cur.next;
            cur.next = next.next;
            next.next = pre.next;//注意不是next.next=cur;
            pre.next = next;
        }
        return dummyNode.next;
    }
}
  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值