链表9大练习

1:删所有key

删所有key
注意:头节点得最后删;因为删头和删除其它是不一样的;就怕删了第一个是头;第二个要删的还是头。这不多了一个判断的事情吗;你每次还得去判断这个
注意:你一定是找删除节点的前一个;这样子你的链表直接跳过要删除的节点即可。因为找的是删除的前一个cur.next就不可能是null;不会有这个空指针异常。

public class LinkOj1 {
//删除所有的key
    public static void main(String[] args) {

    }
}


 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){
            return null;
        }
        ListNode cur=head;
//        遍历一遍;遇到值就处理
        while (cur.next!=null){
            if(cur.next.val==val) {
                cur.next = cur.next.next;
                //删掉就相当于往后走了;所以要else
            }
            else {
                cur=cur.next;
            }
        }
 if(head.val==val){
            head=head.next;
        }

        return head;
    }
}

2:反转链表

反转链表

在这里插入图片描述

class Solution {
    public ListNode reverseList(ListNode head) {
     
        ListNode cur = head;
         head=null;//为了在第一次的时候;cur.next为空;最后节点为null
        while (cur != null) {
            //更新cur往后走;得记住后面节点的地址;不能直接在后面cur=cur.next;因为cur.next已经被改成head了
            ListNode Node=cur.next;
         //替换的语句;把当前节点的next换成上一个的地址。走到最后;刚好head到新头的位置
         cur.next=head;
         
         //这是更新的语句;head往后走;cur也忘后走
         head=cur;
            cur=Node;
          

        }
        return head;
    }
}

总结:这题需要用到三个节点;head;cur;Node;head是头节点;cur是用来遍历的节点(我们之前写的都是cur=cur.next;但是现在问题在于我们遍历到节点的时候需要把当前节点的next修改成前一个的地址;也就是起到掉头反转的效果。。但是引入问题我们把当前节点的next修改了;我们就没法继续往下遍历;就需要Node在修改前先把这个当前节点的next记录下来;用来代替后面cur=cur.next——>cur=Node )
在这里插入图片描述
往下走的问题解决了;但是上一个节点的地址我们又丢了;可以使用新创建一个节点保存这个上一个节点的位置。但是head也是需要往后走;或者是走到最后时把最后节点的地址赋给head。所以可以一举两得;用head记录上一个的位置;还能往下走更新。但是这里的开始是head和cur是保持在同一个位置;所以我们走慢一步;在cur更新前;将cur赋值给head。

3:返回中间节点

返回中间节点
这种粗暴的方法;虽然多少有点丑陋;但是还是管用的。遍历一遍找


class Solution {
    public ListNode middleNode(ListNode head) {
       ListNode cur=head;
       int count=0;
       while(cur!=null){
        
            count++;
            cur=cur.next;
        
       }
       cur=head;
        // 因为cur是从head开始;所以要走count-1步;或者我们干脆条件还是小于0;然后中间节点就不加1;因为开始的位置是从head开始。我们上面算是走多了一步

       if(count%2==1){
            int a=count/2+1;
       while(a>1){
            cur=cur.next;
                a--;

       }
     return cur;

       }
       else{
        int b= count/2+1;
    while(b>1){
                cur=cur.next;
                    b--;

        }
        return cur;



       }

    }
}

快慢指针:一个指针走一步;一个指针走两步;当走两步的指针找到终点的时候;另一个就是刚好到中间节点。而且这种情况也能避免是偶数个节点的冲突情况;但是我们需要注意特殊情况;这种是否能适用呢?
比如:只有一个节点;或者没有节点。

class Solution {
    public ListNode middleNode(ListNode head) {
         if(head==null){
             return null;

         }
            if(head.next==null){
             return head;

         }
         ListNode Node1=head;
         ListNode Node2=head;
         while(Node2.next!=null){
         //使用Node2.next而不使用;Node2;是为了避免;cur如果在最后一个节点进入时;往下走两步又空指针异常
           
           Node1=Node1.next;
        //走两步没问题;但是说走到最后一步;后面没东西;next一次是空;再next一次;null的next肯定就空指针异常    
           Node2=Node2.next.next;
        //但是如果不加以下判断;当走到这里为空;继续循环判定;Node2.next就空指针异常了
             if( Node2==null){
             //这个语句不一定都能执行到;当恰好走到尾部;循环是进不来的
               return Node1;
             }
         }
        return Node1;

    }
}

4:倒数第k个节点

倒数第k个节点
方法1:走len-k下就是倒数第k个节点;先求len;再遍历找到。

public class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
        ListNode cur=head;
        if(head==null){
            return null;
        }
        int len=0;
            while(cur!=null){
               len++;
               cur=cur.next; 
            }
            cur=head;
            int count=len-k;
            //非法情况;k比长度节点个数还大。因为我们的代码结构;所以这里必须处理;如果不处理返回cur不是null;返回的是head了。
            if(count<0){
           
                return null;
            }
            while(count >0){

             cur=cur.next;
             count--;
            }


       return cur;

    }
}

方法2:快慢指针;a先走k下;然后b和a一起走;b还在开头;直到a到末尾;b就是倒数第k个节点。当有思路;还是得考虑特殊情况能不能适合你这个思路

class Solution {
    public ListNode FindKthToTail(ListNode head,int k) {
           ListNode cur1=head;
           ListNode cur2=head;
            if(head==null){
// null如果进去循环就空指针异常
                return null;
            }
            if(k<=0){
					return null;
					}

           //等于0要不要进去?简单画个图举例分析一下即可;走k步不需要。假设k=1;是否真的走了一步。
           while(k>0){
            cur1=cur1.next;
            // 这里的情况是k比节点个数还大;但是我们如果光是cur1==null是不够这个条件;如果k和节点个数是一样大的 cur1==null就能符合。所以得加个判定;剔除这种情况。这种情况这里k是等于1的;下一轮循环就进不来
            if(cur1==null&&k!=1){

                return null;
            }
            k--;
           }
           while(cur1!=null){

           cur1=cur1.next;
           cur2=cur2.next;

           }
           return cur2;       
    }
}

你得注意异常情况;当你的快指针已经null;k还没到1;这种情况可能是找不到;你都没k个节点怎么找倒数第k个节点。k是异常的情况;head==null情况。

5:合并两个有序链表

合并两个有序链表在这里插入图片描述
到底在拼在原来的某一个链表上;还是创建一个新的依次插入即可了???题目是说新链表


class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
            // 创建一个新链表;头节点还不能移动。。还得注意;我们交换的是节点;而不是节点的值;最后拼接链表;只需要原来的最后一个节点的下一个接成要拼接链表的头节点
            ListNode newlist=new ListNode(0);
            ListNode newhead=newlist;
             ListNode head=newhead;
            // list1和list2就是头节点。 
            //这里的结束条件都有一个链表的元素全部放人后就结束;另一种直接插入后面即可
            while(list1!=null&&list2!=null){
                if(list1.val>=list2.val){
                   head.next=list2;
                   head=head.next;
                   list2=list2.next;
                }
                else{
                   head.next=list1;
                   head=head.next;
                   list1=list1.next;


                }
                 }
                //  走到这里1:一开始两个链表都为空;2:其中一个一开始就为空;3:进行插入新链表后的位置走到空的节点......都是一样把不空的直接拼接到后面去
                
       if(list1==null){
            head.next=list2;

       }
       else{

             head.next=list1;
           
       }
           




           
         return newhead.next;



    }
}

在这里插入图片描述
因为这里的头节点是不知道的;头节点可不能引用别人的地址;不能修改;一改头就变了;链表也变成别人的。
只能修改头的next;头一修改;就没了;哪怕你搞个新节点赋值给头;然后再修改;一改就变成别的链表了。就找不到这个我们新的刚创建的链表。

6:链表分割

给一个x值;把小于的放x前面;大于的放x后面。顺序不能改变
链表分割

public class Partition {
    public ListNode partition(ListNode pHead, int x) {
        // write code here
        // 两个哨兵节点
        if(pHead==null){
        return null;
        }
        ListNode small=new ListNode(0);
        ListNode head1=small;
        ListNode big=new ListNode(0);
        ListNode head2=big;
        ListNode cur=pHead;
        // 这一步没必要;因为我们已经不需要用到原来链表;所以干脆用它的头结点去跑也可以
        //这里使用的方法;两个哨兵节点;借鉴上面的合并有序链表;第一个空出来;都放下一个里;最后返回头的next。
        while(cur!=null){
            if(cur.val<x){
              small.next=cur;
              small=small.next;
              cur=cur.next;
            }
            else{
              big.next=cur;
              big=big.next;
              cur=cur.next;
            } 
        }
        small.next=head2.next;
        //一定要注意;把链表的尾部的next设置为null
        big.next=null;
        return head1.next;
    }
}

7:链表回文结构

链表回文结构
在这里插入图片描述
什么是回文结构? 回文结构序列是 指顺读和反读都一样的序列。
如果不给定时间复杂度和空间复杂度要求就有很大的发挥空间。

解法:两个指针遍历走到中间;然后一个往前走;一个往后走;分别看它们的值是否相等。
怎么走到中间这也是一个问题;快慢指针?一个走两步;一个走一步;然后两个都赋值于中间节点一个往前一个往后。
(没法往前走是问题;那没关系;我把其中一个中间节点重新赋予头节点地址;那就从头往中间走;但是比较的顺序却反了)
但是这样子也有解决办法;假如12 21.我们就把后面的反转过来。然后一边从头开始;一边从中间开始;

总结:快慢指针;找到中间节点;然后把后半段进行反转。然后前半段从头开始遍历;后半段从尾开始遍历;两两比较。

分三部分:
1:快慢指针找到中间节点;
快慢指针的两种写法

        ListNode cur1=A;
        ListNode cur2=A;
        while(cur2.next!=null){
          if(cur2==null){
                break;
            }
            cur1=cur1.next;
            cur2=cur2.next.next;
//循环的条件必须用cur2.next;不然就会出现空指针异常。假设使用的是cur!=null;当走到cur.next=null;即最后一个节点;cur2=cur2.next.next;就空指针异常。
//一定要加这个判断;假设走到倒数第而个节点;然后cur2=cur2.next.next;这时cur2是不是就为null;而我们的循环判断条件是cur.next!=null;这里又会发送空指针异常
//知道这里中间节点是哪一个;如果是奇数节点;那不必都说;如果是偶数节点个数。画个图就知道是在上半部分的最后一个节点。
        }

另一种写法:这里利用一个语法&&只要第一个不符合条件;然后后面的代码就无效;所以这里cur2=null时;cur2.next是已经无效了;不会发生空指针异常;两种本质上是一样的
在这里插入图片描述

2:反转链表:就和第二题思路一样即可;但是这里可以省略一步;把开始头的next置空;因为我们都使用不到这个链表

在这里插入图片描述

3:链表遍历:遍历链表;如果都相等就是回文;如果不相等就不是回文。因为反转过后中间节点就跑到最后面去了;后面我们需要后面往前遍历。值不相等就直接返回不是回文;.结束条件就是前面的节点和后面的节点相遇就说明是回文。


class PalindromeList {
    public boolean chkPalindrome(ListNode A) {
        // write code here
        if(A==null||A.next==null){
            return false;
        }
        ListNode cur1=A;
        ListNode cur2=A;
        while(cur2.next!=null){
          if(cur2==null){
                break;
            }
            cur1=cur1.next;
            cur2=cur2.next.next;
          
        }

        // while(cur2!=null&&cur2.next!=null){
        //       cur2=cur2.next.next;
        //       cur1=cur1.next;


        // }

        // 反转后面的链表
        ListNode cur=cur1;
        cur1=null;
        while(cur!=null){
            ListNode Node=cur.next;
            cur.next=cur1;
            cur1=cur;
            cur=Node;
        }
   
//走到这里我们需要记住A是前部分链表头节点;cur1是反转后的后半部分头节点。
        while(A!=cur1){
        //这里外面的A!=cur1是奇数个节点的情况;里面的A.next!=cur1是偶数个节点的情况
            if(A.val!=cur1.val){
                return false;
            }
            if(A.next==cur1){
               break;
            }
            //不加这个条件;如果是偶数个节点;没法结束。
            A=A.next;
            cur1=cur1.next;
        }
        return true;
    }
}

8:相交链表;找出第一个相交节点

相交链表;找出第一个相交节点

在这里插入图片描述
相交肯定就是这种Y形状;不可能相交后后面还能分开;因为同一个节点它们值和指向都是一样。
解法一:遍历一遍链表A;储存在哈希表;然后遍历链表B看看是否能找到相同的节点
在这里插入图片描述
解法二:两种情况两个链表长度一样;或者不一样。
如果是长度一样;那好办;大家一起走;边走边比较就可以知道。
但是长度不一样就不好办;解决办法求各自链表的长度;这个差值一定就是相遇之前的差值;那不就意味着这个差值是多余的嘛;我们让长链表先把这个差值走完;就能转化为第一种情况。(不可能在这些差值的前面相遇;不然它们的长度就不会差别这么多)

class Solution {
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
            ListNode small=headA;
            ListNode big=headB;

int countA=0;
int countB=0;
while(small!=null){
    countA++;

small=small.next;
}
while(big!=null){
    countB++;

big=big.next;
}
// 到这里我就前面的变量不要就好了;因为已经变成null;或者利用起来;重新赋于头节点的值。
            ListNode curA=headA;
            ListNode curB=headB;
int len=countA-countB;
if(len<0){
    int newlen=0-len;
//说明B比较长;然后这里就执行B走newlen步
while(newlen>0){

curB=curB.next;
newlen--;
}

// 到这里他们是共同起点
while(curB!=null){
    if(curB==curA){

        return curA;
    }
    curB=curB.next;
    curA=curA.next;
}

}
else{
//说明A比较长;直接A先走len步
while(len>0){

curA=curA.next;
len--;
}

// 到这里他们是共同起点
while(curB!=null){
    if(curB==curA){

        return curA;
    }
    curB=curB.next;
    curA=curA.next;
}

}
return null;
}
        
    }

上述代码会发现有点重复了;因为谁长谁短我们并不清楚;所以导致要分if else写两份一样逻辑的代码;
优化:我们定义两个变量;一个永远指向短的链表;一个永远指向长的链表。使用这两个变量我们在遍历完计数长度后。这两个都变成null。然后我们根据len 大于0还是小于0;来分别重新给这两个变量赋值;按照small是放短的;big是放长的。后面的逻辑就是写一份就能通用。
ListNode small=headA;
ListNode big=headB;

9:判断链表是否有环

判断链表是否有环
环是什么呢?如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。
在这里插入图片描述
思路1:追击的数学问题;一个环的情况;如果我一直走是不是就会有走重复的地方。一直在转圈圈。
假设:老师从课室去操场散步;我想去问问题;如果只有我自己一个;老师突然去饭堂了;那光我一个人在操场转圈圈也找不到老师。所以就需要两个指针去跑才能有机会相遇。
那么之间的步数怎么设置好了;是老师走一步我走两步好呢;还是老师走一步我走三步呢?必须是两步。
假设是三步:下面这种情况;操场非常小;小到我一步就是一圈;那么我和老师就一直相遇不了。
在这里插入图片描述
设置两步最坏情况一个环的长度就能相遇。中间间隔大;相遇概率很低

结束条件;fast为空就说明没有环;

public class Solution {
    public boolean hasCycle(ListNode head) {
    ListNode cur1=head;
    ListNode cur2=head;
    
    while(cur2!=null&&cur2.next!=null){//cur2来判定结束的条件也是可以的;因为有环cur2就不会有null。
        cur1=cur1.next;
     cur2=cur2.next.next;
      if(cur1==cur2){
            return true;
        }
    }
return false;
    }
}

使用cur!=null还是cur.next!=null;具体看你需要什么。具体情况具体分析

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

20计科-廖雨旺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值