LeetCode(链表:只能通过操纵节点以及节点的next指针完成,不能修改节点的值)

在这里插入图片描述
(1)需要一个指针Cur遍历链表,一旦遍历到了待删除结点的前一个结点,delNode=cur.next;,接下来将cur的指针直接指向待删除结点的下一个结点
在这里插入图片描述
在这里插入图片描述
(2)该逻辑对删除最后一个元素依然适用,如果3是最后一个结点,它的next指向null,逻辑适用
(3)该逻辑对删除第一个元素不适用,如删除2这个元素,前面没有任何元素,因此找不到头结点之前的元素,需要对头结点进行单独的操作
(4)如果没有引入虚拟头结点,需要判断链表是否为空,head=null,会在判断head.next!=null时,对head.next的访问会造成程序崩溃,需要判断if(head==null) return head;
(5)cur初始指向dummyHead,此时判断cur.next!=null时不会有问题
在这里插入图片描述

在这里插入图片描述

public class ListNode {
    int val;
    ListNode next;
    public ListNode(int x){
        val=x;
    }
    //链表节点的构造函数,使用arr作为参数,创建一个链表,当前构造的ListNode为链表的头结点
    public ListNode(int[] arr){
         if(arr==null||arr.length==0){
             throw new IllegalArgumentException("arr can not be empty");
         }
         this.val=arr[0];
         ListNode cur=this;
         for(int i=1;i<arr.length;i++){
             cur.next=new ListNode(arr[i]);
             cur=cur.next;
         }
         //this是循环创建的链表所对应的头结点
    }
    //以当前节点为头结点的链表信息字符串
    @Override
    public String toString() {
        StringBuilder res=new StringBuilder();
        ListNode cur=this;
        while(cur!=null){
            res.append(cur.val+"->");
            cur=cur.next;}

        res.append("null");
        return res.toString();
    }
}

递归实现链表删除元素

在这里插入图片描述
(1)可以将链表理解成一个个结点的挂接,也可以理解成一个头结点后面挂接了一个更短的链表
(2)0->(1…) 1是新的链表的头结点
(3)0->1->(2…) 2是新的链表的头结点

(4)直到最后可以将空也理解成一个链表,空就是最基础最频繁的链表

问题:传给函数一个头结点和一个元素v,函数要将以传来的结点为头结点的链表中所有元素为v的结点删除

递归问题只有两部分:一是可将当前问题继续分解成更小的同一问题;二是最基本情况的解;最后需要考虑在使用了解决了更小规模的问题相应的解之后结合当前的调用组织逻辑成当前问题的解
(1)步骤一:假设已有一个递归解决删除更小链表中相应的元素的函数,得到了更小问题的解
(2)步骤二:构建原问题的解,只需要判断头结点是否需要删除。如果头结点不需要删除,头结点后面挂接求出来的更小问题的解,需要删除,原问题的解就是更小问题的解

在这里插入图片描述

注意递归算法的宏观语意

public class Solution {
    public ListNode removeElements(ListNode head,int val) {
       
        if (head == null) {
            return head;
        }
        removeElements的宏观语意就是删除链表中值为value的结点,把它想象成一个子过程
       
        res的结果就是将头结点后面跟的链表中所有值为value的结点删除后剩下的链表
        ListNode res=removeElements(head.next,val);
        if (head.val == val)
            return res;
        else {
        否则head不要删除,head后面接上将head后面的链表中值为value的结点删除后剩下的链表
            head.next = res;
            return head;
        }
    }
    public static void main(String[] args) {
        int[] nums={1,2,6,3,4,5,6};
        ListNode head=new ListNode(nums);
        System.out.println(head);

        ListNode res=(new Solution()).removeElements(head,6);
        System.out.println(res);
    }



}

递归的微观理解

递归的调用过程与子函数的调用过程没有区别,只不过递归调用的还是这个函数本身而已,将程序调用的系统栈过程与递归调用的过程进行比较
在这里插入图片描述
递归函数的调用,本质就是函数调用,只不过调用的函数是自己而已,每次调用函数传入的参数都不相同

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

反转链表

在这里插入图片描述

递归

在这里插入图片描述

在这里插入图片描述

对于递归算法,最重要的就是明确递归函数的定义。具体来说,我们的 reverse 函数定义是这样的:输入一个节点 head,将「以 head 为起点」的链表反转,并返回反转之后的头结点。

那么输入 reverse(head) 后,会在这里进行递归:
ListNode last = reverse(head.next);
不要跳进递归,而是要根据刚才的函数定义,来弄清楚这段代码会产生什么结果:
在这里插入图片描述
这个 reverse(head.next) 执行完成后,整个链表就成了这样:
在这里插入图片描述
并且根据函数定义,reverse 函数会返回反转之后的头结点,我们用变量 last 接收了。
现在再来看:head.next.next = head;
在这里插入图片描述

head.next = null;
return last;
在这里插入图片描述

有两个地方需要注意:
在这里插入图片描述

迭代

public ListNode reverse(ListNode head) {
    ListNode prev = null;
    while (head != null) {//保证head.next有效
        ListNode next = head.next;
        head.next = prev;
        prev = head;
        head = next;
    }
    //head为空,prev指向以前链表的最后一个结点,即新的链表的头结点
    return prev;
}

在这里插入图片描述

head.next=null后,找不到第二个结点了

在这里插入图片描述

需要在1和2还连接时,先保存1的下一个结点(保存考察结点的下一个结点)

在这里插入图片描述
在这里插入图片描述

完成一次更新

在这里插入图片描述
在这里插入图片描述

将cur指向上一个结点,需要一个指向前一个结点的指针

在这里插入图片描述

完整步骤(逆序使用头插法)

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

反转链表前 N 个节点:链表考察哨兵节点+虚拟节点+链表指针的移动

在这里插入图片描述
在这里插入图片描述在这里插入图片描述

在这里插入图片描述

逆序处理使用栈:虚拟头结点ListNode node=new Node(…)是一个结点,头结点ListNode head=null是一个为空的引用

在这里插入图片描述
在这里插入图片描述

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> stack1=new Stack<>();
        Stack<Integer> stack2=new Stack<>();
        ListNode resCur=null;
        while(l1!=null){
            stack1.push(l1.val);
            l1=l1.next;
        }
        while(l2!=null){
            stack2.push(l2.val);
            l2=l2.next;
        }
        int carry=0;
        while(!stack1.isEmpty()&&!stack2.isEmpty()){
            int sum=stack1.pop()+stack2.pop()+carry;
            int ans=sum%10;
            ListNode newNode= new ListNode(ans);
            newNode.next=resCur;
            resCur=newNode;
            carry=sum/10;
            
        }

        while(!stack1.isEmpty()){
            int sum=stack1.pop()+carry;
            int ans=sum%10;
            ListNode newNode=new ListNode(ans);
            newNode.next=resCur;
            resCur=newNode;
            carry=sum/10;
        }

         while(!stack2.isEmpty()){
            int sum=stack2.pop()+carry;
            int ans=sum%10;
            ListNode newNode=new ListNode(ans);
            newNode.next=resCur;
            resCur=newNode;
            carry=sum/10;
           
        }
        if(carry!=0){
          ListNode newNode=new ListNode(carry);
             newNode.next=resCur;
            resCur=newNode;}
        return resCur;

    }
}

在这里插入图片描述

思路:删除所有头部的重复节点,返回不重复的第一个节点。

1.特殊情况,头节点为null或头节点下一节点为null,直接返回头节点,这时不存在重复节点
2.如果头节点与,头节点的下一节点值相等,跳过所有相等节点。递归调用函数判断最后一个跳过节点的后一节点。
3.如果头节点与,头节点的下一节点值不等,递归调用函数判断头节点的后一节点。

注意删除所有重复节点时,head=head.next,跳过重复节点,在调用递归时,判断的是head.next,因为此时的head与已删除的节点重复,需要用这种方法跳过

在些当前问题的递归逻辑时,不仅要考虑下一次递归的结果对当前递归的影响(子过程),也要考虑当前的head结点是否需要进行处理,如题,head需要更新成非重复结点,注意while的条件

class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        if (head == null || head.next == null) {
            return head;
        }
        if (head.val == head.next.val) {
            while (head != null && head.next != null && head.val == head.next.val) {
                head = head.next;
            }
            return deleteDuplicates(head.next);
        } else {
            head.next = deleteDuplicates(head.next);
            return head;
        }
    }
}
class Solution {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;
        ListNode cur = dummy;
        while (cur.next != null && cur.next.next != null) {
            if (cur.next.val == cur.next.next.val) {
                ListNode temp = cur.next;
                while (temp != null && temp.next != null && temp.val == temp.next.val ) {
                    temp = temp.next;
                }
                cur.next = temp.next;
            } 
            else
                cur = cur.next;
        }
        return dummy.next;
    }
}

(1)与链表的其他题目类似,为了防止删除头结点的极端情况发生,先创建空结点dummy,使dummy指向传入的head结点。
(2)然后创建cur的指针,指向链表的头部(即dummy)。
(3)接着对cur指针迭代,因为要对比cur(cur最初始的定义指向空结点)指针的下一个结点与下下一个结点的值是否相等,为了防止产生空指针异常,故退出迭代的条件为:cur.next != null && cur.next.next != null(注意灵活设置跳出迭代的条件)。
(4)在迭代过程中,如果cur.next.val == cur.next.next.val说明此时有重复元素,此时创建一个临时指针temp,指向cur的下一个节点,即temp指向的第一个重复元素所在的位置。通过while循环去重,去重后,temp指向的是重复元素中的最后一个位置。最后cur.next = temp.next就实现了消除重复元素。(不要在遍历过程中直接删除结点,代码冗余,创建临时指针temp完成操作)
(5)当然,如果未发现重复元素,则直接向后迭代即可。迭代完成后,返回dummy的next。

创建多个指针预先保留需要操作的结点

在这里插入图片描述

(1)交换一对结点的位置,要让1的前一个结点指向2,2指向1,1指向2的后一个结点,需要指针p记录1的前一个结点,为了防止对头结点的特殊处理,设定一个虚拟头结点,需要两个指针指向待交换的结点,一个指针next指向待交换结点的下一个结点
在这里插入图片描述
在这里插入图片描述

 public ListNode swapPairs(ListNode head) {        
        ListNode dummyHead=new ListNode();        
        dummyHead.next=head;        
        ListNode cur=dummyHead;        
        while(cur.next!=null&&cur.next.next!=null){        
        ListNode cur1=cur.next;        
        ListNode cur2=cur1.next;       
        ListNode next=cur2.next;        
        cur2.next=cur1;        
        cur1.next=next;       
        cur.next=cur2;        
        cur=cur1;
        }       
         return dummyHead.next;        
    }

while循环中移动end指针,划分出满足条件的链表范围

在这里插入图片描述
复杂度:O(n),其中 n 为链表的长度。start 指针会在 O(n/k)个节点上停留,每次停留需要进行一次 O(k)的翻转操作
在这里插入图片描述

 public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy=new ListNode(0);
        dummy.next=head;
        //初始化pre和end都指向dummy。pre指每次要翻转的链表的头结点的上一个节点。end指每次要翻转的链表的尾节点
        ListNode pre=dummy;
        ListNode end=dummy;

        while(end.next!=null){
            //循环k次,找到需要翻转的链表的结尾,这里每次循环要判断end是否等于空,因为如果为空,end.next会报空指针异常。
            //dummy->1->2->3->4->5 若k为2,循环2次,end指向2
            for(int i=0;i<k&&end != null;i++){
                end=end.next;
            }
            //如果end==null,即需要翻转的链表的节点数小于k,不执行翻转。
            if(end==null){
                break;
            }
            //先记录下end.next,方便后面链接链表
            ListNode next=end.next;
            //然后断开链表
            end.next=null;
            //记录下要翻转链表的头节点
            ListNode start=pre.next;
            //翻转链表,pre.next指向翻转后的链表。1->2 变成2->1。 dummy->2->1
            pre.next=reverse(start);
            //翻转后头节点变到最后。通过.next把断开的链表重新链接。
            start.next=next;
            //将pre换成下次要翻转的链表的头结点的上一个节点。即start
            pre=start;
            //翻转结束,将end置为下次要翻转的链表的头结点的上一个节点。即start
            end=start;
        }
        return dummy.next;


    }

扩展:操纵节点已经节点的next指针完成,且可以修改节点的值

在这里插入图片描述
之前的删除是通过待删除节点的前一个结点进行删除操作,对于这个题目来说,不可能拿到待删除结点的前一个结点,只能改变结点的值
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

双指针技术:明确两个指针的定义,指针对撞,缩小范围进而求解,或形成一个滑动的窗口,向前滑动解决问题(指针无法向前移动)

在这里插入图片描述
在这里插入图片描述

p最终的位置指向待删除结点的前一个结点
在这里插入图片描述
q指向链表最后的空结点,p和q的距离是固定的
在这里插入图片描述
将p和q都向前移动,直到p指向虚拟头结点,由于p和q的距离是固定的,很容易找到q应该指向的位置
在这里插入图片描述
让p和q同时向前移动,直到q指向null
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在两个链表中依次取结点,构成新的链表:

1->2->3->5
2->4->7->8
1->2->2->4->3->7->8
在这里插入图片描述

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值