【JAVA】链表常见题型练习——牛客练习

目录

1.反转链表

2.将节点数为 size 链表完成 m 位置到 n 位置的区间反转

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

4.合并两个递增的链表

5.合并 k 个升序的链表

6. 判断链表中是否有环

7.找出链表中环的入口结点

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

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

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

11.判断一个链表是否为回文结

1.反转链表

给定一个单链表的头结点pHead,长度为n,反转该链表后,返回新链表的表头。如当输入链表{1,2,3}时,经反转后,原链表变为{3,2,1},所以对应的输出为{3,2,1}。

解析:关键在改变next指向

public class Solution {
    public ListNode ReverseList(ListNode head) {  //反转链表核心:改变next指向
//         if(head == null) {  //若为空链表,则返回null
//             return null;
//         }
//         if(head.next == null) { //若链表只有一个节点,则无需翻转,翻转也为自身,返回head即可
//             return head;
//         }
        ListNode prev = null;
        ListNode cur = head;
        while(cur != null) {
            ListNode curNext = cur.next; //先保存反转前,当前cur的下一个值
            cur.next = prev;  //改变指向 ,此时完成第一个节点的指向
            prev = cur;   //继续向后移动改变后面节点的指向
            cur = curNext;     
        }
        return prev;
    }
}

2.(m,n) 区间链表反转

给出的链表:1→2→3→4→5→NULL, m=2,n=4;
返回:1→4→3→2→5→NULL.
 

数据范围: 链表长度 :0<size≤1000,0<m≤n≤size,链表中每个节点的值满足∣val∣≤1000

时间复杂度 O(n),空间复杂度 O(1)。

解析:范围外节点指向保持不变,范围内的完成节点反转

 public ListNode reverseBetween (ListNode head, int m, int n) {
        ListNode dummyNode = new ListNode(-1);  //定义一个虚拟头结点
        dummyNode.next = head;
         ListNode prev = dummyNode; // 定义一个前驱,指向虚拟头节点
        
        for(int i=0; i<m-1; i++) { 
            prev = prev.next;  //进入循环,prev始终指向m的前一个位置
        }
        ListNode cur = prev.next; //cur始终是prev的后面一个
         ListNode curNext;  //定义一个变量curNext 用于后面保存cur的下一个位置
        for(int i=0; i < n-m; i++){  //当进入区间范围内,开始反转
            curNext = cur.next;
            cur.next = curNext.next; //cur的下一个节点指向 curNext下一个节点(因为curNext已经移走)
             curNext.next = prev.next;  //curNext移动到cur前面,也即是prev后面---》到此完成一个数的反转
            prev.next = curNext;    //prev继续向后移动,也即是当前的curNext位置
        }
        return dummyNode.next;
    }

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

链表中的节点每 k 个一组翻转,返回翻转后的链表,如果链表中的节点数不是 k 的倍数,将剩下的节点保持原样

数据范围: 0≤n≤2000 ,1≤k≤2000 ,链表中每个元素都满足:0≤val≤1000
空间复杂度 O(1),时间复杂度 O(n)

如:给定的链表:1→2→3→4→5

对于 k = 2k=2 ,返回 :2→1→4→3→5

对于 k = 3k=3 , 返回:3→2→1→4→5

解析:取出k个节点进行翻转,因此首先是找到k个结点的集合,然后对每个结点集合进行翻转操作

 public ListNode reverseKGroup (ListNode head, int k) {
        if(head == null || head.next == null) {
            return head;  //若为空或只有一个节点,不需反转,返回头结点即可
        }
        ListNode end = head;  //定义一个下标end,初始位置在head处
        for(int i=0; i<k; i++) {
            if(end == null) { 
                return head;
            }
            end = end.next;
        }
        ListNode newHead = reverse(head,end); //开始反转
        head.next = reverseKGroup(end,k); //
        return newHead;
    }
    
    private ListNode reverse(ListNode head,ListNode end) {
        ListNode prev = null;
        ListNode cur = head;
        while(cur != end) {  //当进入组内,执行反转
           ListNode curNext = cur.next; //先记录下cur的下一个位置
            cur.next = prev;  //改变指向,也及时完成反转
            prev = cur; //然后继续完成组内其余节点的反转
             cur = curNext;
        }
        return prev;
    }

4.合并两个递增的链表

两个递增的链表,单个链表的长度为n,合并这两个链表并使新链表中的节点仍然是递增排序。

数据范围:0≤n≤1000,−1000≤节点值≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)

如输入{1,3,5},{2,4,6}时,合并后的链表为{1,2,3,4,5,6}

解析:合并两个单链表,返回两个单链表头结点值小的那个节点(递归方法)

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

 新建一个数组用于接收两个链表,再对数组中元素排序,最后再将数组转化为链表

public class Solution {    
    public ListNode Merge(ListNode list1,ListNode list2) {  //递归实现
       
        if(list1==null) return list2;  // list1 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中所有元素排序Collections.sorts(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 个升序的链表并将结果作为一个升序的链表返回其头节点。

数据范围:节点总数满足 0≤n≤10^5; 链表个数满足1≤k≤10^5 ;

每个链表的长度满足1≤len≤200 ;     每个节点的值满足∣val∣<=1000

要求:时间复杂度 O(nlogk)

如输入:[{1,2},{1,4,5},{6}]------->{1,1,2,4,5,6}

解析:使用两两合并的方法

public class Solution {
    public ListNode mergeKLists(ArrayList<ListNode> lists) {
        if(lists == null || lists.size() == 0)  return null;
        return mergeSort(lists,0,lists.size()-1);
    }   
        //将链表数组二分,left,right知道的是下标
        public ListNode mergeSort(ArrayList<ListNode> lists,int left,int right) { 
            if(left >= right) {
                return lists.get(left);
            }
            int mid = left + ((right-left) >> 1);
            ListNode list1 = mergeSort(lists,left,mid);
            ListNode list2 = mergeSort(lists,mid+1,right);
            return merge(list1,list2);
        }
    
        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。

数据范围:链表长度0≤n≤10000,链表中任意节点的值满足∣val∣<=100000

要求:空间复杂度 O(1),时间复杂度 O(n)

解析:定义快慢指针,从head开始出发,当二者相遇,则说明fast走完2圈,slow走完1圈

public class Solution {
    public boolean hasCycle(ListNode head) {
        if(head == null) return false;
        
        ListNode fast = head; //定义一个快慢指针,开始都位于head处
        ListNode slow = head;
        
        while(fast !=null && fast.next !=null) { //循环条件:至少2各节点才能构成环
            fast = fast.next.next;
            slow = slow.next;  //快慢指针分别走2步和1步
            if(fast == slow) {  //2个指针相遇
                return true;
            }
        }
        return false;
    }
}

7.找出链表中环的入口结点

给一个长度为n链表,若其中包含环,请找出该链表的环的入口结点,否则,返回null。

数据范围:n≤10000,1<=结点值<=10000

空间复杂度 O(1),时间复杂度 O(n)


    public ListNode EntryNodeOfLoop(ListNode pHead) {
        ListNode fast,slow;
        fast = pHead;
        slow = pHead; 
        while(fast != null && fast.next != null) {  //先找到快慢指针相遇的位置
            fast = fast.next.next;
            slow = slow.next;
            if(fast == slow)  //此时说明有环存在
               break;
        }
        if(fast == null || fast.next == null) return null;  //若fast指向空,则不存在环
        fast = pHead; //指针指向链表头部
        while(fast != slow) {
            fast = fast.next;  //与第一次相遇的节点 相同速度出发,相遇节点为入口节点
            slow = slow.next;
        }
        return fast;
    }

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

输入一个长度为 n 的链表,设链表中的元素的值为 ai ,返回该链表中倒数第k个节点。

如果该链表长度小于k,请返回一个长度为 0 的链表。

数据范围: 0≤n≤10^5,0≤ai​≤10^9,0≤k≤10^9

空间复杂度 O(n),时间复杂度 O(n)

解析:用一个数组存放链表中所有元素,然后再根据下标寻找倒数第k个节点

    public ListNode FindKthToTail (ListNode pHead, int k) {
  //=====================方法1:================================//      
         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); //根据下标找到倒数第k个节点

    }

解析:快指针先出发走K个节点,然后快慢指针在此基础上同时出发,当fast=null时,表明走完了链表,此时,slow所在的位置也就是倒数第几个位置

    public ListNode FindKthToTail (ListNode pHead, int k) {
  
        if(pHead == null) return pHead;     
        ListNode fast = pHead;
        ListNode slow = pHead;
        
        while(k != 0) {  //快指针先走k步
            if(fast == null) return null;  //若fast为空返回null,不为空则继续向后移动
            fast = fast.next;
            k--;
        }
        while(fast != null) {  //快慢指针一起移动
            fast = fast.next;
            slow = slow.next; 
        }
        return slow;   
    }

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

给定一个链表,删除链表的倒数第 n 个节点,返回链表的头指针
 

给出的链表为:1→2→3→4→5, n= 2
删除了链表的倒数第 n个节点后:1→2→3→5.

空间复杂度 O(1),时间复杂度O(n)

解析:同上,用快慢指针,考虑head为null的情况,和(n为链表)的情况

public ListNode removeNthFromEnd (ListNode head, int n) {
        if(head == null) return null;
        ListNode fast = head;
        ListNode slow = head;
        
        for(int i=0; i<n; i++) {  //快指针先走n步
             fast = fast.next;
        }
        //fast走到了最后一个null处
        if(fast == null) {  //fast == null说明fast以及走完了链表,也即是删除倒数第n(n是链表的长度)
            return head.next; //如果删除的是第一个数,则需要返回head.next
        }
        while(fast.next != null) { //快慢指针一起向后走
            fast = fast.next;
            slow = slow.next;
        }
        slow.next = slow.next.next;  //删除倒数第n个节点 slow.next,此时更新 slow.next
        return head;
    }

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

输入两个无环的单向链表,找出它们的第一个公共结点,若无则返回空。

数据范围:n≤1000
要求:空间复杂度 O(1),时间复杂度 O(n)

解析方法1:用集合完成,将链表1放入set里,然后看集合set是否包含链表2中的元素,若包含则是二者的公共节点,不包含则返回null

    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        HashSet set = new HashSet<>();  //创建一个set集合
        
        while(pHead1 != null) {
            set.add(pHead1);  //将链表1中的元素放入set集合
            pHead1 = pHead1.next;
        }
        while(pHead2 != null) {
            if(set.contains(pHead2)) {  //查看集合set里是否包含链表2里的元素
                return pHead2;  //包含则返回钙元素,也即是两个链表的公共节点
            }
            pHead2 = pHead2.next;
        }
        return null;
    }

解析方法2:快慢指针:分别从两个链表同时遍历,走完自己再走另一个链表,当二者相遇时,此时节点即是公共节点

  public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {

        ListNode list1 = pHead1;
        ListNode list2 = pHead2;
        
        while(list1 != list2) {
            list1 = (list1 == null)? pHead2: list1.next;
            list2 = (list2 == null)? pHead1: list2.next;
        }
        return list1;
    }

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

回文是指该字符串正序逆序完全一致。

数据范围: 链表节点数0≤n≤10^5,链表中每个节点的值满 ∣val∣≤10^7

如:

输入:{1,2,2,1}  ------>  1->2->2->1  返回值:true       

输入:{2,1}    ------> 2->1                 返回值:false     

解析:创建一个列表list存放链表中的元素,通过比较链表和列表  从0下标位置和最后一个位置是否相等(链表头头往后,列表从后往前)比较对应元素是否相等,若都等则返回true

 public boolean isPail (ListNode head) {
        List<Integer> list = new ArrayList<>(); //创建一个list列表用于存放链表中的元素
        
        if(head == null || head.next == null) return true;
        
        while(head != null) {  //把链表转为list列表
            list.add(head.val);
            head = head.next;
        }
        
        int j = list.size()-1;
        for(int i =0; i<j; i++) {
            if(!list.get(i).equals(list.get(j))) {
                return false;
            }
               j--;
        }
        return true;
    }

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值