代码随想录31期第三天 | 203.移除链表元素 |707.设计链表 |206.反转链表

代码随想录31期第三天 | 203.移除链表元素707.设计链表206.反转链表

移除链表元素

题目链接

思路

删除链表中的元素主要就是需要找到被删除的节点的前一个节点以及后一个节点 所以通常用一个pre保存前节点 判断pre.next.val是否是要删除的节点

而针对头节点需要额外注意一下 我们可以采取两种方法解决 第一种方法是我们采用一个虚拟头节点 这样就可以统一处理 head与其他节点一样 第二种方法是我们可以找到第一个不为要删除的节点作为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) {
        if (head == null) { //如果传入的是空链表 直接返回null
            return null;
        }
        
        ListNode headpointer = new ListNode(-1, head); //虚拟头节点 因为题目说val均大于0 因此赋值-1
        ListNode pre = headpointer; //让pre等于虚拟头节点 这样可以统一处理
        
        while (pre.next != null) { //注意递归的是pre 因为我们要保证虚拟头节点一直指向的是头部 
            if (pre.next.val == val) {
                pre.next = pre.next.next;
            } else {
                pre = prev.next;
            }
        }
        
        return headpointer.next; //返回虚拟头节点的后一个节点
    }
}

第一次写时 用的是for循环 不管下一个是否是要删除的节点 我都让pre递归下去了 实际上这是很错误的 因为当下一个是要删除的节点是 我们的next变成了新的 这个新的还没进行判断 因此用while循环 只有当下一个节点不是要删除的节点的时候 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 removeElements(ListNode head, int val) {
        
        while(head!=null&&head.val == val){
          head = head.next;
        }
        if (head == null) { //结合了上面的情况和本身head就为null 进行判断
            return null;
        }
				ListNode prev = head;
        
        while (prev.next != null) {
            if (prev.next.val == val) {
                prev.next = prev.next.next;
            } else {
                prev = prev.next;
            }
        }
        
        return head;
    }
}

其实主要的区别就是这段代码:

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

这段代码就是为了去寻找第一个val不与要找的val相同的头节点 并且要判断是否head为null(因为有可能整个链表都是同一个元素 这样就会移动到null)

设计链表

这道题的可扩展性很高 可以设计很多函数 来丰富自己的链表的功能

题目链接

同时这个题需要自己把LinkNode给写出来 供链表使用 相当于我们把功能封装了一下

思路

这道题要设计四个函数 分别是int get(int index)、void addAtHead(int val)、void addAtTail(int val)、void addAtIndex(int index, int val)、void deleteAtIndex(int index) 由于这道题涉及到很多index 我们需要判断该index是否有效 可以在我们的链表中加入size属性存放链表大小 方便进行判断

每个函数判断index的标准不一定一样 例如get方法和deleteAiIndex的index一定是要小于size的 而比如addAtIndex中 index可以是等于size代表在末尾加元素

代码

class LinkNode{
    int value;
    LinkNode next;
    public LinkNode(){

    }
    public LinkNode(int value){
        this.value = value;
        this.next=null;
    }
}
class MyLinkedList {
    int size = 0;
    LinkNode dummyhead = new LinkNode(0);//设置一个虚拟头节点


    public MyLinkedList() {

    }
    
    public int get(int index) {
        if(index >= size){
            return -1;
        }
        LinkNode pre = this.dummyhead;
        for(int i=0;i<=index;i++){
            pre = pre.next;
        }
        return pre.value;
    }
    
    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;
        }
        LinkNode pre = this.dummyhead;
        for(int i=0;i<index;i++){
            pre = pre.next;
        }
        LinkNode newNode = new LinkNode(val);
        LinkNode tmp = pre.next; //设置临时节点保存一下
        pre.next=newNode;
        newNode.next = tmp;
        size++;
    }
    
    public void deleteAtIndex(int index) {
        if(index<size){
        LinkNode pre = this.dummyhead;
        for(int i=0;i<index;i++){
            pre = pre.next;
        }
        
        pre.next=pre.next.next;
        size--;
        }
        
    }
}

/**
 * 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 一个是cur

img

首先定义一个cur指针,指向头结点,再定义一个pre指针,初始化为null,因为反转后的最后一个的next就是null。

然后就要开始反转了,首先要把 cur->next 节点用tmp指针保存一下,也就是保存一下这个节点。

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

接下来,就是循环走如下代码逻辑了,继续移动pre和cur指针 令pre=cur,cur=tmp。

最后,cur 指针已经指向了null,循环结束,链表也反转完毕了。 此时我们return pre指针就可以了,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 cur = head;
        ListNode pre = null;
        ListNode tmp;
        while(cur != null){
            tmp = cur.next;
            cur.next=pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
}
// 递归法
class Solution {
    public ListNode reverse(ListNode pre,ListNode cur){
      if(cur==null){
        return pre;
      }
      ListNode temp = cur.next;// 先保存下一个节点
      cur.next = prev;// 反转
        // 更新prev、cur位置
        // prev = cur;
        // cur = temp;
        return reverse(cur, temp);
    }
    public ListNode reverseList(ListNode head) {
        return reverse(null,head);
    }
}

递归法的底层实现逻辑和双指针法其实是一样的 区别在于递归需要自己写一个方法 也就是这里的reverse 用作将两个节点翻转 传入一个pre和一个cur 如果cur为null证明到了最后 直接返回pre即可 否则正常执行反转的逻辑(用temp存储cur.next 然后更新位置 再return reverse(cur,temp)

  • 20
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值