牛客TOP101之链表篇(高效刷题,带你手撕算法)

大家好,我是小曾哥,今天这篇内容作为牛客101道必刷题的开篇,是我亲自刷题的一些方法,以及一些心得,高效刷题,告别题海战术,举一反三。
现在分享给大家,后续还会继续更新牛客系列,小曾哥带大家一起刷牛客编程题!
在这里插入图片描述

1、反转链表

1、给定一个单链表的头结点pHead(该头节点是有值的,比如在下图,它的val是1),长度为n,反转该链表后,返回新链表的表头。

如当输入链表{1,2,3}时, 经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。 以上转换过程如下图所示:
输入:{1,2,3}
返回值:{3,2,1}
输入:{}
返回值:{}
说明:空链表则输出空

在这里插入图片描述
常规思路:迭代
具体细节:在遍历链表时,将当前节点的next 指针改为指向前一个节点。由于节点没有引用其前一个节点,因此必须事先存储其前一个节点。在更改引用之前,还需要存储后一个节点。最后返回新的头引用。
在这里插入图片描述
拆解步骤

1、因为链表结尾是 null,所以让 pre 的值是 null, p 就表示我们的头部
在这里插入图片描述
2、因为 p 的 next 成员马上就要指向 pre, 如果不保存 p 的下一个节点就会使其丢失,所以通过临时变量 t 保存它
在这里插入图片描述
3、让 P 的 next 成员指向 pre
在这里插入图片描述
4、pre 移动到 p 的位置,p 移动到 t 的位置,此时我们就回到了第一步中的情况
在这里插入图片描述
5、保持这个循环不变式直到 p 移动到原链表结尾我们就获得了翻转之后的链表。

/*
public class ListNode {
    int val;
    ListNode next = null;

    ListNode(int val) {
        this.val = val;
    }
}*/
public class Solution {
    
    
    public ListNode ReverseList(ListNode head) {
    
    
        //pre指针:用来指向反转后的节点,初始化为null
        ListNode pre = null;
         //当前节点指针
        ListNode cur = head;
        //循环迭代
        while(cur!=null){
    
    
            //Cur_next 节点,永远指向当前节点cur的下一个节点
            ListNode Cur_next = cur.next;
            //反转的关键:当前的节点指向其前一个节点(注意这不是双向链表,没有前驱指针)
            cur.next = pre;
            //更新pre
            pre = cur;
            //更新当前节点指针
            cur = Cur_next ;
        }
        //为什么返回pre?因为pre是反转之后的头节点
        return pre;
    }
}

2、链表内指定区间反转

将一个节点数为 size 链表 m 位置到 n 位置之间的区间反转,要求时间复杂度 O(n)O(n),空间复杂度 O(1)O(1)。

输入:{1,2,3,4,5},2,4
返回值:{1,4,3,2,5}

输入:{5},1,1
返回值:{5}

思路步骤:
1、固定子区间外的节点
2、在需要反转的区间里,每遍历到一个节点,让这个新节点来到反转部分的起始位置。

curr:指向待反转区域的第一个节点 left;
Cur_next:永远指向 curr 的下一个节点,循环过程中,curr 变化以后 Cur_next 会变化;
pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
在这里插入图片描述

public class Solution {
    
    
    /**
     *
    * cur:指向待反转区域的第一个节点 left;
	* Cur_next:永远指向 cur 的下一个节点,循环过程中,cur 变化以后 Cur_next 会变化;
	* pre:永远指向待反转区域的第一个节点 left 的前一个节点,在循环过程中不变。
     */
 
    public ListNode reverseBetween (ListNode head, int m, int n) {
    
    
        //设置虚拟头节点
        ListNode dummyNode = new ListNode(-1);
        dummyNode.next =head;
        //将pre指向头结点
        ListNode pre = dummyNode;
        // 循环将pre指向m的前一个位置
        for(int i=0;i<m-1;i++){
    
    
            pre = pre.next;
        }
 		// 设置cur指向索引m的结点
        ListNode cur = pre.next;
        ListNode Cur_next ;
        for(int i=0;i<n-m;i++){
    
    
        	// 将cur_next指向cur的下一个
            Cur_next = cur.next;
            // 进行第一步操作,将cur下一个结点指向cur_next的下一个位置
            cur.next = Cur_next.next;
            // 进行第二步操作,将cur_next下一步指向pre.next当前位置
            Cur_next .next = pre.next;
            // 进行第三步操作,将pre.next指向cur_next,以此类推
            pre.next = Cur_next ;
            // 注意:1、2、3步骤不能调换,否则中间变量会失效。
        }
        return dummyNode.next;
    }
}

3 链表中的节点每k个一组翻转

描述:将给出的链表中的节点每 k 个一组翻转,返回翻转后的链表 如果链表中的节点数不是 k 的倍数,将最后剩下的节点保持原样你不能更改节点中的值,只能更改节点本身。

示例
输入:{1,2,3,4,5},2
返回值:{2,1,4,3,5}

题目分析:跟题目1类相似,区别在于取出k个节点进行翻转,因此首先是找到k个结点的集合,然后对每个结点集合进行翻转操作。

具体步骤
1、找到待翻转的k个节点(注意:若剩余数量小于 k 的话,则不需要反转,因此直接返回待翻转部分的头结点即可)。
2、对其进行翻转。并返回翻转后的头结点(注意:翻转为左闭右开区间,所以本作的尾结点其实就是下一作的头结点)。
3、对下一轮 k 个节点也进行翻转操作。
4、将上一轮翻转后的尾结点指向下一轮翻转后的头节点,即将每一轮翻转的k的节点连接起来。
在这里插入图片描述

方法一
public class Solution {
    
    
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    public ListNode reverseKGroup (ListNode head, int k) {
    
    
        // write code here
        ListNode cur = head;
        int count = 0;
        //找到k个节点集合
        while(cur !=null && count !=k){
    
    
            cur = cur.next;
            count ++;
        }
        if(count == k){
    
    
            cur = reverseKGroup(cur,k);
            // 反转列表
            while(count !=0){
    
    
                count --;
                ListNode tmp = head.next;
                // 将头节点连接下一个翻转的节点
                head.next = cur;
                cur  = head;
                head = tmp;
            }
            // 拼接后续的链表
            head = cur; 
        }
        return head;
        
        
    }
}


方法二:充分利用题目1的代码,更方便理解
public ListNode reverseKGroup (ListNode head, int k) {
    
    
        // write code here
        if(head == null || head.next == null)
            return head;
        ListNode tail = head;
        for(int i =0;i<k;i++){
    
    
            if(tail == null)
                return head;
            tail = tail.next;
        }
        // 开始反转
        ListNode newHead = reverse(head, tail);
        //下次开始的位置就是tail
        head.next = reverseKGroup(tail,k);
        return newHead;
        
    }
    
    private ListNode reverse(ListNode head, ListNode tail) {
    
    
        ListNode pre = null;
        ListNode next = null;
        while (head != tail) {
    
    
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }

4、合并两个排序的链表

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

此题leetcode上也有一样的题目:21
主要介绍几种方法,方便理解

递归

函数功能:合并两个单链表,返回两个单链表头结点值小的那个节点。
如果知道了这个函数功能,那么接下来需要考虑2个问题:

递归函数结束的条件是什么?
递归函数一定是缩小递归区间的,那么下一步的递归区间是什么?
对于问题1.对于链表就是,如果为空,返回什么
对于问题2,跟迭代方法中的一样,如果list1的所指节点值小于等于list2所指的结点值,那么list1后续节点和list2节点继续递归

递归版本很好理解
public class Solution {
    
    
    public ListNode Merge(ListNode list1,ListNode list2) {
    
    
        if(list1 == null)
            return list2;
        if(list2 == null)
            return list1;
        if(list1.val <= list2.val){
    
    
            list1.next = Merge(list1.next,list2);
            return list1;
        }else{
    
    
            list2.next = Merge(list2.next,list1);
            return list2;
        }
        
    }

迭代版本

其实用迭代方法,也是常规考虑方法
步骤:
1、首选新创建一个链表head
2、如果list1指向的结点值小于等于list2指向的结点值,则将list1指向的结点值链接到head的next指针,然后head指向list1,list1继续指向下一个结点值;
3、否则,则将list2指向的结点值链接到head的next指针,然后head指向list2,让list2指向下一个结点值;继续循环步骤2,3
4、直到list1或者list2为空,将list1或者list2剩下的部分链接到head的后面

public ListNode Merge(ListNode list1,ListNode list2) {
    
    
        // 单链表完成
        ListNode head = new ListNode(-1);
        head.next = null;
        ListNode root = head;
        while(list1 !=null && list2 !=null){
    
    
            if(list1.val <= list2.val){
    
    
                head.next = list1;
                head = list1;
                list1 = list1.next;
            }else{
    
    
                head.next = list2;
                head = list2;
                list2 = list2.next;
            }
        }
        // 把剩余的链表直接连接到合并后的为尾部
        if(list1 !=null){
    
    
            head.next = list1;
        }
        if(list2 !=null){
    
    
            head.next = list2;
        }
        return root.next;
    }

借助额外数组

(1) 创建额外存储数组 nums
(2) 依次循环遍历 pHead1, pHead2,将链表中的元素存储到 nums中,再对nums进行排序
(3) 依次对排序后的数组 nums取数并构建合并后的链表
在这里插入图片描述

public ListNode Merge(ListNode list1,ListNode list2) {
    
    
         // list1 list2为空的情况
        if(list1==null) return list2;
        if(list2==null) return list1;
        if(list1 == null && list2 == null){
    
    
            return null;
        }
        //将两个两个链表存放在list中
        ArrayList<Integer> list = new ArrayList<>();
        // 遍历存储list1
        while(list1 !=null){
    
    
            list.add(list1.val);
            list1 = list1.next;
        }
        // 遍历存储list2
        while(list2 !=null){
    
    
            list.add(list2.val);
            list2 = list2.next;
        }
        Collections.sort(list);
         // 将list转换为 链表
        ListNode newHead = new ListNode(list.get(0));
        ListNode cur = newHead;
        for(int i=1;i<list.size();i++){
    
    
            cur.next = new ListNode(list.get(i));
            cur = cur.next;
        }
        // 输出合并链表
        return newHead;
    }

5 合并k个已排序的链表

合并 k 个升序的链表并将结果作为一个升序的链表返回其头节点。
输入: [{1,2},{1,4,5},{6}]
返回值: {1,1,2,4,5,6}

首当其冲可以用辅助数组方法,思路也很很简单,跟题目4差不多一致

辅助数组

根据第四题写的java,现在写的Python代码
def mergeKLists(self , lists: List[ListNode]) -> ListNode:
        newlist = []
        for li in lists:
            while li:
                newlist.append(li.val)
                li = li.next;
#         print(newlist)
        ## 对新的newlist进行排序
        newlist.sort()
        ## 生成新的链表
        head = ListNode(-1)
        cur = head
        for i in range(len(newlist)):
            cur.next = ListNode(newlist[i])
            cur = cur.next
        return head.next

时间复杂度O(NlogN):N表示列表中所有链表的结点数量,首先遍历所有结点O(N),排序O(NlogN)
空间复杂度O(N):辅助数组空间

分治法

使用两两合并的方法

 public ListNode mergeKLists(ArrayList<ListNode> lists) {
    
    
        if (lists == null || lists.size() == 0) {
    
    
            return null;
        }
        return mergeSort(lists, 0, lists.size() - 1);
    }
 
    // 将链表数组二分
    public ListNode mergeSort(ArrayList<ListNode> lists, int left, int right) {
    
    
        if (left >= right) {
    
    
            return lists.get(left);
        }
        int mid = left + ((right - left) >> 1);
        ListNode l1 = mergeSort(lists, left, mid);
        ListNode l2 = mergeSort(lists, mid + 1, right);
        return Merge(l1, l2);
    }
    
    public ListNode Merge(ListNode list1,ListNode list2) {
    
    
        if(list1 == null)
            return list2;
        if(list2 == null)
            return list1;
        if(list1.val <= list2.val){
    
    
            list1.next = Merge(list1.next,list2);
            return list1;
        }else{
    
    
            list2.next = Merge(list2.next,list1);
            return list2;
        }
    }

6 判断链表中是否有环

题目描述:判断给定的链表中是否有环。如果有环则返回true,否则返回false。
在这里插入图片描述
思路:双指针
在这里插入图片描述

public boolean hasCycle(ListNode head) {
    
    
        if(head == null)
            return false;
        // 定义快慢指针
        ListNode fast = head;
        ListNode slow = head;
        while(fast !=null && fast.next !=null){
    
    
            // 快指针是慢指针的两倍速度
            fast = fast.next.next;
            slow = slow.next;
            // 如果两个指针相遇,那么就可成环
            while(fast == slow){
    
    
                return true;
            }
        }
        return false;
    }

7 链表中环的入口结点

题目描述:给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。
在这里插入图片描述
方法1:hash法,记录第一次重复的结点
通过使用set或者map来存储已经遍历过的结点,当第一次出现重复的结点时,即为入口结点。

import java.util.HashSet;
public class Solution {
    
    

    public ListNode EntryNodeOfLoop(ListNode pHead) {
    
    
        // 使用hash法,记录第一次重复的结点
        HashSet<ListNode> hash = new HashSet<>();
        // 存储的结点只要出现重复的,即为环的入口
        while(pHead !=null){
    
    
            if(hash.contains(pHead)){
    
    
                return pHead;
            }
            // 添加未重复的结点
            hash.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
}

时间复杂度:O(n),需要将链表的所有结点遍历一遍,时间复杂度为O(n);
空间复杂度:O(n),需要额外的n空间的hashset来存储结点。

方法2:快慢指针,这个之前在leetcode快慢指针篇中介绍过
通过定义slow和fast指针,slow每走一步,fast走两步,若是有环,则一定会在环的某个结点处相遇(slow == fast),根据下图分析计算,可知从相遇处到入口结点的距离与头结点与入口结点的距离相同。
在这里插入图片描述

public ListNode EntryNodeOfLoop(ListNode pHead){
    
    
        ListNode fast,slow;
        fast = slow = pHead;
        while(fast !=null && fast.next !=null){
    
    
            slow = slow.next;
            fast = fast.next.next;
            // 记录快慢指针第一次相遇的结点
            if(fast == slow)
               break;
        }
        // 如果快指针指向空,则不存在环
        if(fast == null || fast.next ==null) return null;
        // 重新指向链表头部
        fast = pHead;
        // 与第一次相遇的结点相同速度出发,相遇结点为入口结点
        while(fast != slow){
    
    
            fast = fast.next;
            slow = slow.next;
        }
        return fast;
    }

时间复杂度:O(n),需要将链表的所有结点遍历一遍,时间复杂度为O(n);
空间复杂度:O(1),需要额外两个快慢指针来遍历结点。

8 链表中倒数最后k个结点

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。
如果该链表长度小于k,请返回一个长度为 0 的链表。
在这里插入图片描述

输入:{1,2,3,4,5},2
返回值:{4,5}
说明:返回倒数第2个节点4,系统会打印后面所有的节点来比较。

方法1:使用辅助列表方法,先把所有输入值全保存在列表中,然后根据下标找到倒数第二的节点

public ListNode FindKthToTail (ListNode pHead, int k) {
    
    
        // write code here
       ArrayList<ListNode> list = new ArrayList<>();
       while(pHead !=null){
    
    
            list.add(pHead);
            pHead = pHead.next;
       }
       if(k > list.size() || k == 0)
           return null;
       
       return list.get(list.size()-k);
    }

方法2:快慢指针,让快指针先走k步,然后开始快慢指针同速前进,当快指针⾛到链表末尾 null 时,慢指针所在的位置就是倒数第 k个链表节点。

public ListNode FindKthToTail (ListNode pHead, int k) {
    
    
        // 使用快慢指针
        if(pHead == null)
            return pHead;
        ListNode slow,fast;
        slow = fast = pHead;
        // 将快指针向后移动k个位置
        while(k!=0){
    
    
            //如果k值过大,导致快指针为空,则返回null
            if(fast == null)
                return null;
            fast = fast.next;
            k--;
        }
        //快慢指针开始一起移动
        while(fast != null){
    
    
            slow = slow.next;
            fast = fast.next;
        }
        return slow;
    }

9 删除链表的倒数第n个节点

在第8题的基础上进行修改
方法1:辅助列表

public ListNode removeNthFromEnd (ListNode head, int n) {
    
    
        // write code here
        ArrayList<ListNode> list = new ArrayList<>();
        while(head !=null){
    
    
            list.add(head);
            head = head.next;
        }
        if(n > list.size() || n == 0 || (list.size()==1 && n==1))
           return null;
        list.remove(list.size()-n);
        //将list转换成链表
        ListNode newHead = list.get(0);
        ListNode cur = newHead;
        for(int i=1;i<list.size();i++){
    
    
            cur.next = list.get(i);
            cur = cur.next;
        }
        return newHead;
    }

方法二:快慢指针
(注意这里的slow代表的是倒数k个结点的前一个结点)

public ListNode removeNthFromEnd (ListNode head, int n) {
    
    
        // write code here
        if(head == null)
            return head;
        ListNode slow,fast;
        slow = fast = head;
        // 将快指针向后移动n个位置
        for(int i=0;i<n;i++){
    
    
            fast=fast.next;
        }
        //如果n的值等于链表的长度,直接返回去掉头结点的链表,所以是head.next
        if(fast == null)
            return head.next;
        //快慢指针开始一起移动
        while(fast.next != null){
    
    
            slow = slow.next;
            fast = fast.next;
        }
        // 将结点进行删除即可
        slow.next = slow.next.next;
        return head;
    }

10 两个链表的第一个公共结点

题目描述:输入两个无环的单向链表,找出它们的第一个公共结点,如果没有公共节点则返回空。
在这里插入图片描述
方法一分析:双指针

1、使用两个指针N1,N2,一个从链表1的头节点开始遍历,我们记为N1,一个从链表2的头节点开始遍历,我们记为N2。

2、让N1和N2一起遍历,当N1先走完链表1的尽头(为null)的时候,则从链表2的头节点继续遍历,同样,如果N2先走完了链表2的尽头,则从链表1的头节点继续遍历,也就是说,N1和N2都会遍历链表1和链表2。

3、因为两个指针,同样的速度,走完同样长度(链表1+链表2),不管两条链表有无相同节点,都能够到达同时到达终点。(N1最后肯定能到达链表2的终点,N2肯定能到达链表1的终点)。
(判断条件)如何得到公共节点:
1、有公共节点的时候,N1和N2必会相遇,因为长度一样嘛,速度也一定,必会走到相同的地方的,所以当两者相等的时候,则会第一个公共的节点。
2、无公共节点的时候,此时N1和N2则都会走到终点,那么他们此时都是null,所以也算是相等了。
在这里插入图片描述

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    
    
        ListNode l1 = pHead1, l2 =pHead2;
        while(l1 != l2){
    
    
            l1 =(l1 == null)? pHead2 :l1.next;
            l2 =(l2 == null)? pHead1 :l2.next;
        }
        return l1;
    }

方法二、用集合Set
具体步骤:
1、就是先把第一个链表的节点全部存放到集合set中。
2、然后遍历第二个链表的每一个节点,判断在集合set中是否存在,如果存在就直接返回这个存在的结点。
3、如果遍历完了,在集合set中还没找到,说明他们没有相交,直接返回null即可。

public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
    
    
        // 创建集合set
        HashSet set = new HashSet<>();
        //把链表1的结点放在set中
        while(pHead1 !=null){
    
    
            set.add(pHead1);
            pHead1 = pHead1.next;
        }
        
        //访问链表2
        while(pHead2 !=null){
    
    
            if(set.contains(pHead2))
                return pHead2;
            pHead2 = pHead2.next;
        }
        
        return null;
    }

11 链表相加(二)

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。
在这里插入图片描述

输入:[9,3,7],[6,3]
返回值:{1,0,0,0}

思想:使用辅助栈
一个链表代表一个整数,返回多个链表的相加结果
故此顺位迭代就可以了,链表的问题大多借助栈和队列的结构思想

具体步骤:申请两个栈空间和一个标记位,然后将两个栈中内容依次相加,将结果倒插入新节点中。
在这里插入图片描述

public class Solution {
    
    
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    public ListNode addInList (ListNode head1, ListNode head2) {
    
    
        // 创建两个栈list1和list2
        LinkedList<Integer> list1 = new LinkedList<>(); //list1栈
        LinkedList<Integer> list2 = new LinkedList<>(); //list2栈
        putData(list1, head1); //入栈
        putData(list2, head2);
        
        ListNode newNode = null;
        ListNode head = null;
        int carry = 0; //进位
        while(!list1.isEmpty() || !list2.isEmpty() ||carry !=0){
    
    
            int x = (list1.isEmpty()) ? 0: list1.pop();
            int y = (list2.isEmpty()) ? 0: list2.pop();
            int sum = x + y + carry; // j与进位相加
            carry = sum/10; // 更新进位
            //将计算值放入节点
            newNode = new ListNode(sum %10);
            // 更新节点 
            newNode.next = head;
            head = newNode;
        }
        return head;
        
    }
    private static void putData(LinkedList<Integer> s1,ListNode head){
    
    
        if(s1 == null) s1 = new LinkedList<>();
        
        while(head !=null){
    
    
            s1.push(head.val);
            head = head.next;
        }
    }
}

时间复杂度:O(max(m,n)),由于只遍历了一次,只需要看两个链表更长的
空间复杂度:O(m+n),申请了两个栈来记录元素

12 单链表的排序

辅助数组

主要通过辅助数组实现链表的排序
1、遍历链表并将链表结点存储到数组 tmp 中
2、通过对 tmp 进行排序,实现链表结点的排序
3、构建新链表结点 result,遍历数组 tmp ,拼接新的返回链表
在这里插入图片描述

public ListNode sortInList (ListNode head) {
    
    
        // write code here
         ArrayList<Integer> list = new ArrayList<>();
         while(head !=null){
    
    
            list.add(head.val);
            head = head.next;
        }
        Collections.sort(list);
        // 将list转换为 链表
        ListNode newhead = new ListNode(list.get(0));
        ListNode cur = newhead;
        for(int i=1;i<list.size();i++){
    
    
            cur.next = new ListNode(list.get(i));
            cur = cur.next;
        }
        return newhead;
        
        
    }

直接排序算法

13 判断一个链表是否为回文结构

题目描述:给定一个链表,请判断该链表是否为回文结构。回文是指该字符串正序逆序完全一致。

示例1:输入:{2,1}
返回值:false

示例2: 输入:{1,2,2,1}
返回值:true
说明:1->2->2->1

辅助列表list

1、首先初始化一个list列表;
2、遍历链表,将链表中的值转移至list中;
3、在list中通过比较头尾的值来判断链表是否为回文结构。

public boolean isPail (ListNode head) {
    
    
        // 辅助列表
        if(head.next ==null)
            return true;
        List<Integer> list = new ArrayList<>();
        //将链表转换成list
        while(head !=null){
    
    
            list.add(head.val);
            head = head.next;
        }
        
        int j = list.size()-1;
        
        for(int i = 0; i<j; i++){
    
    
// 不适用equals而使用!=的话,对于有负数的测试用例可能会无法通过
//             if(list.get(i) != list.get(j)){
    
    
//                 return false;
//             }
            if(!list.get(i).equals(list.get(j))){
    
    
                return false;
            }
            j--;
        }
        return true;
        
        
    }

双指针

1、设置左右指针,left和right
2、然后分别开始进行比较左右指针的val值
3、满足要求,返回true,不满足,返回false

14 链表的奇偶重排

辅助列表list

使用列表加快运算。
第一遍遍历链表分拣出奇偶结点的值,然后第二遍遍历把重新排序好的值形成新的链表即可

public ListNode oddEvenList (ListNode head) {
    
    
        // write code here
        if(head == null)
            return null;
        if(head.next == null)
            return head;
        ArrayList<Integer> list1 = new ArrayList<>();
        ArrayList<Integer> list2 = new ArrayList<>();
        
        int count = 0;
        while(head !=null){
    
    
            count ++;
            if(count %2 !=0){
    
    
                list1.add(head.val);
            }else{
    
    
                list2.add(head.val);
            }
            head = head.next;
        }
        // 将列表进行重新连接
        list1.addAll(list2);
        //构建新的链表
        ListNode newHead = new ListNode(list1.get(0));
        ListNode cur = newHead;
        for(int i=1;i<list1.size();i++){
    
    
            cur.next = new ListNode(list1.get(i));
            cur = cur.next;
        }
        return newHead;
        
        
    }

15 删除有序链表中重复的元素-I

题目描述:删除给出链表中的重复元素(链表中元素从小到大有序),使链表中的所有元素都只出现一次
例如:
给出的链表为1→1→2,返回1→2.
给出的链表为1→1→2→3→3,返回1→2→3.

解题思路:使用链表方法进行删除,因为是有序链表,两个重复的元素是相邻的,因此判断相邻元素是否相同,相同即直接删除即可。

public ListNode deleteDuplicates (ListNode head) {
    
    
        // 采用链表的方法
        if(head == null || head.next == null){
    
    
            return head;
        }
        ListNode cur = head;
        while(cur != null){
    
    
            if(cur.next !=null && cur.val == cur.next.val){
    
    
                cur.next = cur.next.next;
            }else{
    
    
                cur = cur.next;
            }
        }
        return head;
    }

方法二:辅助列表

public ListNode deleteDuplicates (ListNode head) {
    
    
        // 采用辅助列表方法
        if(head ==null){
    
    
            return head;
        }
        ArrayList<Integer> list = new ArrayList<>();
        // 判断重复元素
        while(head !=null){
    
    
            while(head.next !=null){
    
    
                if(head.val != head.next.val)
                    list.add(head.val);
                head = head.next;
            }
            list.add(head.val);
            head = head.next;
        }
        
        
        // 转换成链表
        ListNode newHead = new ListNode(list.get(0));
        ListNode cur = newHead;
        for(int i=1;i<list.size();i++){
    
    
            cur.next = new ListNode(list.get(i));
            cur = cur.next;
        }
        return newHead;
    }

16 删除有序链表中重复的元素-II

题目描述:给出一个升序排序的链表,删除链表中的所有重复出现的元素,只保留原链表中只出现一次的元素。
例如:1→2→3→3→4→4→5, 返回1→2→5.
给出的链表为1→1→1→2→3, 返回2→3.

解题思路:这个跟15题很相像,但也有所区别,在这个里面我们需要引入一个pre指针,用来标记重复元素的前一个位置。

注意:头结点的情况也需要考虑到

public ListNode deleteDuplicates (ListNode head) {
    
    
        // 首先定义一个虚拟节点
        ListNode dummy = new ListNode(0);
        // 将虚拟节点的下一个位置指向head
        dummy.next = head;
        ListNode pre = dummy;
        ListNode cur = head;
        while(cur != null && cur.next !=null){
    
    
            if(cur.val != cur.next.val){
    
    
                pre = cur;
                cur = cur.next;
            }else{
    
    
                while(cur.next !=null && cur.val == cur.next.val){
    
    
                	// 找到值相同的最后一个
                    cur = cur.next;
                }
                // 删除值相同的节点
                pre.next = cur.next;
                cur = cur.next;
            }
        }
        return dummy.next;
    }

方法二:递归

public ListNode deleteDuplicates (ListNode head) {
    
    
        // 递归
       if(head == null){
    
    
            return null;
        }
        if(head.next != null && head.val == head.next.val){
    
    //发现有重复值
                while(head.next != null && head.val == head.next.val){
    
    
                    head = head.next;//删除
            }
            return deleteDuplicates(head.next);//把与删除的那个结点相同的结点也进行删除
        }
        head.next = deleteDuplicates(head.next);//当没有发现重复值的情况,就一直进行递归操作
        return head;
    }

猜你喜欢

转载自blog.csdn.net/qq_36317312/article/details/123573215
今日推荐