Day3数据结构与算法(Java)----链表(虚拟头节点)

一,203.移除链表元素

力扣题目链接

  • 直接使用原来的链表来进行删除操作

移除头结点和移除其他节点的操作是不一样的,因为链表的其他节点都是通过前一个节点来移除当前节点,而头结点没有前一个节点。

所以头结点如何移除呢,其实只要将头结点向后移动一位就可以,这样就从链表中移除了一个头结点。所以要分成头节点和非头节点来讨论,对于非头节点,通过 cur=cur.next;的形式来不断更新和遍历。

(1)分成头节点和非头节点

第一种写法:用两个指针来指向非头节点。一个指向前一节点,一个指向当前节点。双指针的思想

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
     while(head!=null&&head.val==val)
     head=head.next;
     if(head==null)
     return head;

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

第二种写法:用一个指针来指向非头节点。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
     while(head!=null&&head.val==val)
     head=head.next;

     ListNode cur=head;
     while(cur!=null)
     {
      while(cur.next!=null&&cur.next.val==val)
      {
          cur.next=cur.next.next;
      }
      cur=cur.next;
     }
   return head;
    }
}

此时需要用while循环,而不能直接用if,否则会报错

可以改成如下代码

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

(2)加上一个虚节点

第一种写法:添加虚节点(dummy,虚的)只是加了一个虚节点,令pre指向虚节点,cur指向当前节点,后面的操作和第一钟写法是一模一样的。注意返回头节点是return dummy.next;不是return dummy.

ListNode dummy = new ListNode(-1, head);
/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
     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;
    }
}

第二种写法:添加一个虚节点,但是只用一个指针

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode removeElements(ListNode head, int val) {
     ListNode dummy=new ListNode(-1,head);
     ListNode cur=dummy;
     while(cur.next!=null)
     {
         if(cur.next.val==val)
         {
         cur.next=cur.next.next;
         }
        else
        {
         cur=cur.next;
        }
     }
     return dummy.next;
    }
}

二, 707.设计链表(虚拟头节点)

//设置虚结点,同时采用头插法加入结点
class ListNode{
    int val;
    ListNode next;
    ListNode(){}
    ListNode(int val)
    {
        this.val=val;
    }
}

class MyLinkedList {
   int size;
   ListNode dummy;
    public MyLinkedList() {
    size=0;
    dummy=new ListNode(0);
    }
    
    public int get(int index) {
    if(index<0||index>=size)
   {return -1;}
    ListNode cur=dummy;
    for(int i=0;i<=index;i++)
    {
        cur=cur.next;
    }
    return cur.val;
    }
    
    public void addAtHead(int val) {
     addAtIndex(0,val);
    }
    
    public void addAtTail(int val) {
    addAtIndex(size,val);
    }
    
    public void addAtIndex(int index, int val) {
    if(index>size)
    return;
   if(index<0)
    index=0;
    size++;
    ListNode pre=dummy;
    for(int i=0;i<index;i++)
    {
        pre=pre.next;
    }
    ListNode addNode=new ListNode(val);
    addNode.next=pre.next;
    pre.next=addNode;
    }
    
    public void deleteAtIndex(int index) {
    if(index>=size||index<0)
    return;
    size--;
    if(index==0)
    {
        dummy=dummy.next;
        return;
    }
    ListNode pre=dummy;
    for(int i=0;i<index;i++)
    {
        pre=pre.next;
    }
    pre.next=pre.next.next;
    }
}

/**
 * Your MyLinkedList object will be instantiated and called as such:
 * MyLinkedList obj = new MyLinkedList();
 * int param_1 = obj.get(index);
 * obj.addAtHead(val);
 * obj.addAtTail(val);
 * obj.addAtIndex(index,val);
 * obj.deleteAtIndex(index);
 */

代码采用了头插法来实现(所以进行插入和删除操作时,要设置一个结点pre指向前一结点),以下是链表的打印函数,可以看到:用头插法建立的单链表的逻辑顺序和输入元素的顺序相反。

public void printList()
    {
        ListNode cur=dummy.next;
        for(int i=0;i<size;i++)
        {
            System.out.print(cur.val+"-->");
            cur=cur.next;
        }
        System.out.print("null");
    }

其中插入操作里面的代码

 if(index>size)
    return;
   if(index<0)
    index=0;

可以改写为:

 if(index>size||index<0)
    return;

同时应该注意return的用法:方法是void,不满足条件直接return; 

当索引是0,删除结点,只能是虚结点,删除(dummy=dummy.next;)后,直接return;不然会报错

 if(index>=size||index<0)
    return;
    size--;
    if(index==0)
    {
        dummy=dummy.next;
        return;
    }

 代码也可以改成如下:

if(index>=size||index<0)
    return;

 注意边界条件:对于size;

 public int get(int index) {
        //如果index非法,返回-1
        if (index < 0 || index >= size) {
            return -1;
        }
    public void addAtIndex(int index, int val) {
 if(index>size||index<0)
    return;

  public void deleteAtIndex(int index) {
        if (index < 0 || index >= size) {
            return;
        }

三,206.反转链表

力扣题目链接

1.采用双指针写法:(注意先要保存下一结点再改变指针方向,注意顺序

为什么要保存一下这个节点呢,因为接下来要改变 cur->next 的指向了,将cur->next 指向pre ,此时已经反转了第一个节点了。

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
    ListNode pre=null;
    ListNode cur=head;
    ListNode temp=null;
    while(cur!=null)
    {
    temp=cur.next;//保存下一结点
    cur.next=pre;//改变指针指向
    pre=cur;//先移动pre指针
    cur=temp;//再移动cur指针
    }
    return pre;
    }
}

2.采用递归写法(在双指针的基础上改变)注意

 pre=cur; 

cur=temp;

对应到递归代码 是

 public ListNode reverse(ListNode pre,ListNode cur)
    {
    return reverse(cur,temp);// pre=cur;;cur=temp;
    }

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode() {}
 *     ListNode(int val) { this.val = val; }
 *     ListNode(int val, ListNode next) { this.val = val; this.next = next; }
 * }
 */
class Solution {
    public ListNode reverseList(ListNode head) {
    return reverse(null,head);//给pre和cur赋初始值
    }
    public ListNode reverse(ListNode pre,ListNode cur)
    {
    if(cur==null)
     return pre;//递归结束条件
    ListNode temp=null;
    temp=cur.next;//保存下一结点
    cur.next=pre;//改变指针指向
    return reverse(cur,temp);// pre=cur;//先移动pre指针;cur=temp;//再移动cur指针
    }
}

3.上面的递归写法和双指针法实质上都是从前往后翻转指针指向,其实还有另外一种与双指针法不同思路的递归写法:从后往前翻转指针指向。 (还没想明白,有时间再看看)

// 从后向前递归
class Solution {
    ListNode reverseList(ListNode head) {
        // 边缘条件判断
        if(head == null) return null;
        if (head.next == null) return head;
        
        // 递归调用,翻转第二个节点开始往后的链表
        ListNode last = reverseList(head.next);
        // 翻转头节点与第二个节点的指向
        head.next.next = head;
        // 此时的 head 节点为尾节点,next 需要指向 NULL
        head.next = null;
        return last;
    } 
}

 总结:链表的边界条件十分重要,同时要注意是设置当前结点指针,还是前一结点的指针,两者对于判断什么时候循环退出是不一样的

  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值