与链表相关的OJ题(LeetCode)

1.反转链表.

在这里插入图片描述
输入:head=[1,2,3,4,5]
输出:[5,4,3,2,1]

思路:我们用1,2,3节点来做个简单的示例:

先将2节点的前一个节点1用prev保存起来,2节点的下一个节点3用newNode节点来保存起来,不然进行后面操作时就找不到链表了.
然后将2.next指向前一个结点(1号节点prev).
在这里插入图片描述

最后先让prev右移到cur的位置,再将cur移动到newNode的位置.
在这里插入图片描述

然后重复步骤就可以实现链表的逆置.

第一次在循环中图形展示:
newNode=cur.next;
cur.next=prev;
在这里插入图片描述

第一次循环结束后图形展示:
prev=cur;
cur=newNode.
在这里插入图片描述

第二次在循环中图形展示:
newNode=cur.next;
cur.next=prev;

在这里插入图片描述

第二次循环结束后图形展示:
prev=cur;
cur=newNode.

在这里插入图片描述

然后再进入下次循环.重复上面步骤,等到cur到第五个节点的位置进入循环时:
在这里插入图片描述
至此循环结束,链表反转完成.返回prev.

具体代码展示:

public class ListNode{
    
    
	int val;
	ListNode next;
	ListNode(int val) {
    
     this.val = val; }
}
class Solution {
    
    
    public ListNode reverseList(ListNode head) {
    
    
        ListNode cur=head;
        //用来保存cur的前一个节点.
        ListNode prev=null;
        //用来保存cur的后一个节点
        ListNode newNode=null;
        while(cur!=null){
    
    
        	//将newNode右移,放在cur的下一个节点位置.
            newNode=cur.next;
            //将cur的next指向前一个节点prev的位置
            cur.next=prev;
            //将prev右移放到cur的位置
            prev=cur;
            //将cur右移放到newNode的位置.
            cur=newNode;
        }
        //返回prev.
        return prev;
    }
}

2.链表的中间节点.

给定一个头结点为 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个中间结点。
示例:输入[1,2,3,4,5]
输出:此列表中的结点 3 (序列化形式:[3,4,5]) 返回的结点值为 3 .
因为我们返回的是一个ListNode对象.所以会打印出3以后的序列链表.

思路: 采用快慢指针的思想,定义两个指针fast,slow开始都放在head的位置.让fast每次走两步,slow每次走一步:
fast=fast.next.next;
slow=slow.next;

当链表中元素个数为奇数时:fast走到最后,fast.next刚好为null跳出循环,slow此时刚好走到中间位置.此时返回slow.(fast1为第一步,fast2为第二步.下面slow一样.)
在这里插入图片描述
当链表中元素个数为偶数时,fast走到最后时刚好为null跳出循环,slow刚好走到中间两个节点中第二个节点的位置.此时返回slow.
图形展示:
在这里插入图片描述
具体代码展示:

public class ListNode{
    
    
	int val;
	ListNode next;
	ListNode(int val) {
    
     this.val = val; }
}
class Solution {
    
    
    public ListNode middleNode(ListNode head) {
    
    
    	//定义快慢两个指针,刚开始都在头节点位置.
        ListNode fast=head;
        ListNode slow=head;
        //fast不能为空,fast.next也不能为空,否则会报空指针异常.
        while(fast!=null&&fast.next!=null){
    
    
        	//fast每次走两步.
            fast=fast.next.next;
            //slow每次走一步.
            slow=slow.next;
        }
        return slow;
    }
}

3.输入一个链表,输出该链表中倒数第k个节点。

方法1:两次遍历.
思路: 假设链表中元素的个数用count表示,那么倒数第k个节点就是从第一个节点往后移(count-k)步.

如下图,假设链表中元素总共有5个,k是2,即输出倒数第二个节点.从cur开始走三步刚好到倒数第k个位置.
我们让(count-k)- -,当(count-k)=0时,cur刚好走到k的位置,返回cur就行.
在这里插入图片描述

方法2:前后双指针.
思路:此方法不需要知道元素个数.先让前指针front走k步,然后指针back和front同时开始走,这时他们之间刚好隔了k个位置,当front走到null时,back刚好走到倒数第k个节点.

我们还是求倒数第2个节点,即k=2.让front先走两步,然后,front,back同时开始走,每次走一步,front始终在back前面两个位置处,当front走到null时,back刚好在倒数第2个节点位置处.直接图形展示:
front先走两步:
在这里插入图片描述然后两个指针同时开始走,直到front走到null的位置,此时back刚好走到倒数第2个位置.返回back.
在这里插入图片描述
具体代码展示:

public class ListNode{
    
    
    int val;
    ListNode next;
    ListNode(int val){
    
    
        this.val=val;
    }
    public ListNode getKFromEnd1(ListNode head, int k){
    
    
       	ListNode cur=head;
        int count=0;
        while (cur!=null){
    
    
        	//当cur不等于null时,count++,
        	//当刚好cur=null时,count刚好是链表中元素的个数		
            count++;
            //让cur每次往后走一步,直至为null跳出循环.
            cur=cur.next;
        }
        //进行合法性检测,当k的值小于0或者大于链表中元素个数时直接返回null.
        if (k>count||k<0){
    
    
            return null;
        }else{
    
    
            cur=head;
            //用num来保存(count-k)的值
            int num=(count-k);
            while(num!=0){
    
    
            	//cur每次右移一位.
                cur=cur.next;
                num--;
            }
        }
        //返回cur节点.
        return cur;
        
     public ListNode getKFromEnd2(ListNode head, int k){
    
    
        //此方法只需要遍历一次.
        //刚开始让front和back都在头节点位置.
        ListNode front = head;
        ListNode back  = head;
        //
        while (k!=0){
    
    
        	//如果front节点为空,直接返回null.
            if (front==null){
    
    
                return null;
            }else{
    
    
            	//让front每次右移一位.
                front=front.next;
                //k的值减一.
                k--;
            }
        }
        while(front!=null){
    
    
        	//front和back同时开始走,每次右移一位.
            front=front.next;
            back=back.next;
        }
        //返回back节点.
        return back;
    }
}

4.分割链表.

给你一个链表的头节点head和一个特定值x请你对链表进行分隔,使得所有小于x的节点都出现在大于或等于x的节点之前。

示例:令 x=3 ,将小于3的节点放在前面,大于等于3的节点放在后面.
在这里插入图片描述

思路:
先创建两个带头的链表lessHead和greatHead

ListNode lessHead=new ListNode (0);
ListNode greatHead=new ListNode (0);

再定义两个指针tailL和tailG,作用为记录链表末尾节点位置.

 ListNode tailL=lessHead;
 ListNode tailG=greatHead;

在这里插入图片描述
将小于x的节点放在lessHead链表中,将大于等于x的节点放在greatHead链表中,
将tailL和tailG每次插入节点后都往后移一位,保证它们在末尾位置.
最后将两个链表连接起来,并且让tailG的next指向null,构成的新链表就是所求的链表.

连接链表操作:
tailL.next=greatHead.next;
tailG.next=null;

图形展示:
在这里插入图片描述

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;
    ListNode (int val){
    
    
        this.val=val;
    }
    public ListNode partition1(ListNode head, int x) {
    
    
        if (head==null){
    
    
            return null;
        }
        //构建两个新的链表
        ListNode lessHead=new ListNode (0);
        ListNode tailL=lessHead;
        ListNode greatHead=new ListNode (0);
        ListNode tailG=greatHead;
        //cur记录初始链表的头节点位置.
        ListNode cur=head;
        //遍历原始链表,找节点往新链表里面插入.
        while (cur!=null){
    
    
            if (cur.val<x){
    
    
            	//如果cur.val的值小于x,则将此节点放到lessHead链表中
            	//tailL指向这个节点,并且将tailL右移一位,保证tailL在末尾位置.
                tailL.next=cur;
                tailL=cur;
            }else {
    
    
            	//如果cur.val的值大于等于x,则将此节点放到greatHead链表中
            	//tailG指向这个节点,并且将tailG右移一位,保证tailG在末尾位置.
                tailG.next=cur;
                tailG=cur;
            }
            //cur每次右移一位.
            cur=cur.next;
        }
        //连接链表操作.
        tailL.next=greatHead.next;
        tailG.next=null;
        //返回lessHead.next,因为lessHead的头节点为0,
        //取lessHead.next刚好是所求链表,从上面最后一个图可以看出来.
        return lessHead.next;
    }
}

5.链表的回文

判断链表的回文.

思路:我们可以使用数组来做,将链表中的元素都存入数组中,再从数组的首尾位置同时开始作比较.

如下图所示就是一个回文链表:
在这里插入图片描述

1.将链表中节点的值赋值给数组:

//size初始为0,遍历链表,当cur!=null时,将cur.val的值赋给数组.然后size++.
//直到cur为null时,跳出循环.size刚好是链表中元素的个数
while (cur != null){
    
    
	array[size]=cur.val;
	cur=cur.next;
	size++;
}

2.letf设置为0,为数组第一个元素的下标,
right设置为 size-1,表示数组中最后一个元素的下标,
因为数组下标是由0开始的,所以要使用size-1.

int left=0;
int right=size-1;

在这里我只给出一个链表元素为奇数时的图形:
在这里插入图片描述

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;
    ListNode (int val) {
    
     
    	this.val = val;
    }
    public boolean isPalindrome(ListNode head) {
    
    
        int[] array=new int[100000];
        ListNode cur=head;
        //初始元素个数设置为0
        int size=0;
        //遍历链表,将链表中节点的值都存放进数组当中.
        while (cur != null){
    
    
            array[size]=cur.val;
            cur=cur.next;
            //size自增
            size++;
        }
        //left是数组第一个元素下标
        int left=0;
        //right是数组最后一个元素下标.
        int right=size-1;
        while(left<right){
    
    
        	//如果左边的值不等于右边的值,直接返回false
        	//否则的话就继续向下比较.直到跳出循环.
            if (array[left]!=array[right]){
    
    
                return false;
            }
            //left逐步右移,right逐步左移.
            left++;
            right--;
        }
        //跳出循环,说明满足回文条件,直接返回true就行.
        return true;
    }
}

6.返回两个单链表相交的起始节点.

给你两个单链表的头节点headA和headB,请你找出并返回两个单链表相交的起始节点. 如果没有交点,返回null.(两个链表都没有环).
在这里插入图片描述

思路:遍历两个链表A,B,若两个链表相交,那么它最后一个节点必定相等.
求出各链表的长度sizeA,sizeB,即元素个数.两个size相减值设为gap,如果gap小于0,说明A链表比较短,此时让B链表从头先走gap步,此时AB链表与相交节点的距离相等.两个链表同时开始遍历,节点相同时直接返回任意一个就行.

图形展示:在这里插入图片描述

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;

    public ListNode (int val) {
    
    
        this.val = val;
    }
    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
    
    
        ListNode curA=headA;
        ListNode curB=headB;
        int sizeA=1;
        int sizeB=1;
        //如果两个链表任意一个为null时,永不可能相交,直接返回null.
        if (headA==null || headB==null){
    
    
            return null;
        }
        //遍历链表A,求出最后一个节点,并且得到链表长度.
        while(curA.next != null){
    
    
            curA=curA.next;
            sizeA++;
        }
        //遍历链表B,求出最后一个节点,并且得到链表长度.
        while(curB.next != null){
    
    
            curB=curB.next;
            sizeB++;
        }
        //如果最后一个节点不相等,说明两个链表并没有相交.返回null.
        if (curA != curB){
    
    
            return null;
        }
        //走到这一步说明两个链表肯定相交.
        //重新把curA,curB设为链表头节点位置.
        curA=headA;
        curB=headB;
        //计算差值.
        int gap=sizeA-sizeB;
        if (gap>=0) {
    
    
        	//大于等于0说明A链表可能较长.让curA链表先走gap步.与curB同步.
            while (gap != 0) {
    
    
                curA = curA.next;
                //大于等于0用gap--.
                gap--;
            }
        }else{
    
    
	        //小于0说明A链表较长.让curB链表先走gap步.与curA同步.
            while (gap != 0){
    
    
                curB=curB.next;
                //小于等于0用gap++
                gap++;
            }
        }
        //通过遍历,当节点相同时为相交节点,返回该节点.
        while(curA!=curB){
    
    
            curA=curA.next;
            curB=curB.next;
        }
        return curA;
    }
}

7.删除非尾节点.

给一个不带头节点的单链表,删除非尾节点

思路:让后面一个节点覆盖前一个结点就行.

假设要删除的节点pos是3号位节点,将pos的后一个节点设置为nextNode.
用nextNode.val覆盖pos.val,然后将pos.next直接指向nextNode.next.
就实现了4号位节点直接覆盖3号位节点的操作.
如图所示:
在这里插入图片描述

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;
    public ListNode (int val) {
    
    
        this.val = val;
    }
    public void deleteNode(ListNode pos){
    
    
    	//要删除的节点为null或者pos是尾节点时,什么都不返回.
        if (pos==null||pos.next==null){
    
    
            return;
        }
        //实现覆盖操作.
        ListNode nextNode=pos.next;
        pos.val=nextNode.val;
        pos.next=nextNode.next;
    }
}

8.判断链表中是否带有环.

给定一个链表,判断链表中是否有环.

思路:快慢指针实现,假设有环时,快指针肯定先进环,当最后遍历时发现快指针所出位置节点与慢指针所处位置节点相同时,说明有环.

让快慢指针fast,slow开始时都处于链表头节点位置.fast每次走两步,slow每次走一步,当fast进入环后,开始做绕环操作,当slow进入环后,因为fast每次走两步,所以它们之间的距离每次都会缩小1,最后如果相遇了,说明链表肯定是带环的.
图形展示:在第七步时,两个指针相遇,说明链表带环.
fast=fast.next.next;//走两步
slow=slow.next; //走一步
在这里插入图片描述

注意:此处只能让fast走两步,slow走一步,最后才能判断链表是否带环.
在这里取一个fast一次走三步,slow一次走一步的反例来进行说明:

假如在某一时刻slow走到2号位置,fast走到3号位置.fast每次走三步,slow每次走一步,两个指针刚好每次都错过,两个指针永远不会相遇.但是链表是带环的
这种情况属于fast始终比slow快一圈,并且每次都在slow前面,所以两个指针不可能相遇,所以无法去判断链表是否带环.
所以只能让fast每次走两步,slow每次走一步去进行判断.
在这里插入图片描述

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;

    public ListNode (int val) {
    
    
        this.val = val;
    }
    public boolean hasCycle(ListNode head) {
    
    
        ListNode fast=head;
        ListNode slow=head;
        //开始时,fast和fast.next都不能为null,否则会报出空指针异常.
        while(fast!=null && fast.next!=null){
    
    
        	//fast每次走两步
            fast=fast.next.next;
            //slow每次走一步
            slow=slow.next;
            //如果相等了,则说明带环.返回true.
            if (fast==slow){
    
    
                return true;
            }
        }
        return false;
    }
}

9.返回入环的第一个节点

给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

思路:定义快慢指针,判断链表是否带环,带环的话才能去找入环的第一个节点.当两指针相遇时,记录下fast的位置,并用新的指针PM来从相遇点开始绕环,PH从链表起始位置遍历链表,当PH=PM时,相遇点就是入环的第一个节点.
图形展示:

8号位置为快慢指针相遇点,标记为PM点
1号位置为起始点,标记为PH点.
两个引用同时开始走,走到第三步时,两指针相遇,4号位置为入环的第一个节点.
在这里插入图片描述

数学证明:

入环的第一个节点设为E.
假设环的长度为r, PH 到 E 的距离为L ,E 到 PM 点的距离为 x ,则PM点到E的距离为(r-x).
在这里插入图片描述

//slow进来之前fast可能已经绕环了n圈
fast=nr+L+x;
//slow入环后第一圈肯定就会被fast追上.
slow=L+x;

fast=2*slow;:nr+l+x=2(L+x);
化简后为:L=nr-x;
取极限,n=1,L=r-x,即 PM->E = PH->E;
n取其他值时,只不过fast绕的圈数多了而已,L依旧等于r-x;

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;

    public ListNode (int val) {
    
    
        this.val = val;
    }

    public ListNode detectCycle(ListNode head) {
    
    
          ListNode fast=head;
          ListNode slow=head;
          //记录链表是否带有环,初始值为fasle.
          boolean IsLoop=false;
          while(fast!=null && fast.next!=null){
    
    
              fast=fast.next.next;
              slow=slow.next;
              if (fast==slow){
    
    
              	  //说明链表带环,将IsLoop的值置为true.
                  IsLoop=true;
                  break;
              }
          }
          //如果IsLoop的值为false时,取!就进入判断语句,输出null.
          if (!IsLoop){
    
    
            return null;
          }
          ListNode PH=head;
          ListNode PM=fast;
          //当PH=PM时,两个指针在入环第一个点相遇.
          while (PH != PM){
    
    
              PH=PH.next;
              PM=PM.next;
          }
          //返回PH或者PM都行.
          return PM;
    }
}

10.合并两个链表

输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
在这里插入图片描述

思路:新建一个链表将头节点值设为0,依次比较两个链表的值往新链表里面插入,最后返回newNode.next就是所求合并后的链表.
用curA,curB,cur分别把l1,l2,newNode的头节点保存起来.
当两个链表都不为null时才能够合并.

curA.val与curB.val进行比较

语句1:
如果curA.val<=curB.val)
将curA插入到newNode中
并且curA右移一位,进行下次比较
在这里插入图片描述
此时的curA.val>curB.val,进入语句2

语句2:
如果curA.val>curB.val
将curB插入到newNode中
curB右移一位,进行下次比较,
在这里插入图片描述
此时curA.val<=curB.val,进入语句1中.

在这里插入图片描述
这个时候curA为null了,跳出循环,但是有一个问题,curB的最后一个节点没有添加进去,所以在循环外要将剩余节点插入进去

//如果是curA=null跳出了循环,说明最后剩的是curB中的节点,将节点插入到newNode中.
cur.next=curB;
//如果是curB=null跳出了循环,说明最后剩的是curA中的节点,将节点插入到newNode中.
cur.next=curA;

上面我们的例子剩余的是curB中的节点,所以要插入curB的节点.
在这里插入图片描述

到此合并链表结束.return newNode.next;刚好从1开始.

具体代码实现:

//这个注释就不写了,在上面的讲解中很详细.
public class ListNode {
    
    
    int val;
    ListNode next;
    public ListNode (int val){
    
    
        this.val=val;
    }
    public ListNode mergeTwoList(ListNode l1,ListNode l2) {
    
    
        ListNode curA = l1;
        ListNode curB = l2;
        //构建新链表,并且将头节点的值赋为0.
        ListNode newNode = new ListNode (0);
        ListNode cur=newNode;
        //curA和curB都不能为空,才能进行链表的合并.
        while (curA!=null && curB!=null){
    
    
            if (cur.val<=curB.val){
    
    
                cur.next=curA;
                curA=curA.next;
            }else{
    
    
                cur.next=curB;
                curB=curB.next;
            }
            cur=cur.next;
        }
        if(curA==null){
    
    
            cur.next=curB;
        }
        if(curB==null){
    
    
            cur.next=curA;
        }
        //
        return newNode.next;
    }
}

11.移除链表中的重复节点.

编写代码,移除未排序链表中的重复节点。保留最开始出现的节点
在这里插入图片描述

思路:使用两重循环,第一层循环从链表的头节点开始,用cur保存这个节点,第二重循环从这个头节点开始,依次往后遍历链表,找和cur保存节点值相同的节点进行删除操作.

cur为链表的头节点,用一个新的指针newNode从头节点开始依次往后遍历链表
用newNode.next.val与cur.val进行比较,如果相等,直接让newNode.next=new.next.next.
如果不相等,newNode往后移动一位,再次进行和cur.val的比较.
等newNode.next=null时,跳出第二层循环,让cur右移一位.
当newNode走到如图所示位置时:
newNode.next.val=cur.val;
在这里插入图片描述让newNode.next=newNode.next.next;可以将节点删除.
在这里插入图片描述
当newNode走到最后一个节点时,newNode.next=null,跳出循环,进入第一层循环
令cur=cur.next,即cur右移一步第一层循环第一次结束,进入下一次循环.
在这里插入图片描述然后重复上面的步骤,当cur=null时,循环结束,最终链表就成为了下图所示样子:
在这里插入图片描述最后返回head就行.

具体代码实现:

public class ListNode {
    
    
    int val;
    ListNode next;
    public ListNode removeDuplicateNodes(ListNode head){
    
    
    	//如果head=null,说明链表为空,就不需要操作了,直接返回null
        if (head==null){
    
    
            return null;
        }
        
        ListNode cur=head;
        //第一层循环,保存cur节点用来和newNode遍历得到的节点进行比较.
        while(cur!=null){
    
    
            ListNode newNode=cur;
           //第二层循环,让newNode遍历链表.寻找和cur.val相等值的节点进行删除操作
            while (newNode.next!=null){
    
    
                if (newNode.next.val==cur.val){
    
    
                	//这一步就是删除重复节点的操作.
                    newNode.next=newNode.next.next;
                }else{
    
    
                	//newNode指针往后走一步.
                    newNode=newNode.next;
                }
            }
            //cur往后走一步.
            cur=cur.next;
        }
        //返回head,上面的图里可以清楚的看到为什么要返回head.
        return head;
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_47278183/article/details/120958941
今日推荐