链表9大练习

1:删所有key

删所有key

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 {
    
    
                cur=cur.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。

第三题:返回中间节点

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


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;

    }
}

第四题:倒数第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;       
    }
}

第五题:合并两个有序链表

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


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;头一修改;就没了;哪怕你搞个新节点赋值给头;然后再修改;一改就变成别的链表了。就找不到这个我们新的刚创建的链表。

第六题:链表分割

链表分割

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;
    }
    
}

第七题:链表回文结构

链表回文结构
在这里插入图片描述
什么是回文结构? 回文结构序列是 指顺读和反读都一样的序列。
如果不给定时间复杂度和空间复杂度要求就有很大的发挥空间。
{ { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { { {
想法1:把链表遍历一遍;把val值存在数组里;再判定数组里的数据是不是回文。因为数组对下标操作能操作随心所欲的操作。但是显然不符合题意。

想法2:定义两个指针;一个往前走;一个从尾走。但是这里要找到尾也不容易。但是这里是单链表;咋往前走???这种行不通的。

想法3:逆序后和原来比较;但是逆序后就是新链表;空间复杂度就O(n)

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

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

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

        ListNode cur1=A;
        ListNode cur2=A;
        while(cur2.next!=null){
    
    
            cur1=cur1.next;
            cur2=cur2.next.next;
            if(cur2==null){
    
    

                break;
            }

//循环的条件必须用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){
    
    
            cur1=cur1.next;
            cur2=cur2.next.next;
            if(cur2==null){
    
    

                break;
            }
         


        }

        // 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){
    
    
                return true;
            }
            //不加这个条件;如果是偶数个节点;没法结束。
            A=A.next;
            cur1=cur1.next;
        }
        return true;




    }
}

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

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

在这里插入图片描述
相交肯定就是这种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;

第九题:判断链表是否有环

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

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

public class Solution {
    
    
    public boolean hasCycle(ListNode head) {
    
    
    ListNode cur1=head;
    ListNode cur2=head;
    
    while(cur2!=null&&cur2.next!=null){
    
    
       
        cur1=cur1.next;
     cur2=cur2.next.next;
      if(cur1==cur2){
    
    
            return true;
        }
     
    }
return false;


    }
}

猜你喜欢

转载自blog.csdn.net/m0_64254651/article/details/130188389