【牛客网-面试必刷TOP 101】01链表

BM1 反转链表
解题思路
第一种方法:借助栈
1. 栈的特点是先进后出,用stack来存储链表,之后新建一个头节点,按出栈顺序拼接形成新的链表。
2. 注意,最后一个节点的next要赋值null
3. 空间复杂度O(N), 时间复杂度O(N)

JAVA代码实现

import java.util.*;
public class Solution {
    public ListNode ReverseList (ListNode head) {
        // 用栈,空间复杂度O(N),时间复杂度O(N)
        Stack<ListNode> stack = new Stack<>();
        while(head != null){
            stack.push(head);
            head = head.next;
        }
        //出栈,重新连接
        if(stack.isEmpty()){
            return null;
        }
        ListNode node = stack.pop();
        ListNode dummy = node;
        while(!stack.isEmpty()){
            ListNode tempNode = stack.pop();
            node.next = tempNode;
            node = node.next;
        }
        //最后一个下一位为空
        node.next = null;
        return dummy;
    }
}

在这里插入图片描述

第二种方法,修改链表指针指向;
指针cur表示当前要处理的节点
指针tmp用于暂存cur.next,避免后面修改指针后,断链无法继续遍历
指针pre表示当前处理节点的上一个节点,进行翻转的目的是将cur.next修改成pre
初始化时,我们将pre设为null,因为第一个节点最后变成最后一个节点,其next为null
import java.util.*;
public class Solution {
    public ListNode ReverseList (ListNode head) {
        //双指针迭代,空间复杂度O(1),时间复杂度O(N)
        //前继节点为pre,当前操作节点为cur
        //初始化cur为head,pre=null
        ListNode cur = head, pre = null;
        while(cur != null){
            ListNode nextNode = cur.next;//存储下一个节点,不然局部反转的时候会断链
            cur.next = pre;
            pre = cur;
            cur = nextNode;
        }
        return pre;
    }
}
BM2 链表内指定区间反转
解题思路
在上一题的基础上进行修改
首先,要处理的cur节点先走m步走到第m个节点
反转结束的条件不是链表遍历到末尾null时候,而是遍历到第n个节点就结束
没有结束额外空间,空间复杂度O(1), 时间复杂度O(N)
import java.util.*;
public class Solution {
    public ListNode reverseBetween (ListNode head, int m, int n) {
        // write code here
        //可以用BM1 反转链表的思想,
        ListNode  dummy = new ListNode(-1);
        dummy.next = head;
        ListNode pre = dummy;
        ListNode cur = head;
        //找m
        for (int i = 1; i < m; ++i) {
            pre = cur;
            cur = cur.next;
        }
        //反转m到n
        for (int i = m; i < n; ++i) {
            ListNode temp = cur.next;
            cur.next = temp.next;
            temp.next = pre.next;
            pre.next = temp;
        }
        return dummy.next;
    }
}
BM3 链表中的节点每k个一组翻转
解题思路
参考了官方解题,递归翻转
设置一个遍历的尾指针tail,每次反转之前找到翻转区间的最后一个节点
如果tail在k步内变成了null,则直接返回,不进行翻转(最后一段不足k个,不翻转)
之后与BM1一样,定义pre,cur,翻转结束的条件是当走到tail,停止翻转
递归的返回值是当前翻转这一分组的头结点
这道题还不是很理解,后面有新的理解再重新整理笔记
import java.util.*;
public class Solution {
    public ListNode reverseKGroup (ListNode head, int k) {
        // write code here
        ListNode tail = head;//反转的尾部
        //遍历k次到尾部
        for (int i = 0; i < k; ++i) {
            //如果不足k到了尾部,则不翻转
            if (tail == null) {
                return head;
            }
            tail = tail.next;
        }
        ListNode pre = null;
        ListNode cur = head;
        while (cur != tail) {
            //到达尾部前
            ListNode temp = cur.next;
            cur.next = pre;
            pre = cur;
            cur = temp;
        }
        //当前尾指向下一段要翻转的链表
        head.next = reverseKGroup(tail,k);
        return pre;
    }
}
BM4 合并两个排序的链表
解题思路
比较简单,新建一个链表,同时遍历两个链表,比较val的大小,遇到较小的就加入;
import java.util.*;
public class Solution {
    public ListNode Merge (ListNode pHead1, ListNode pHead2) {
        // write code here
        ListNode res = new ListNode(-1);
        ListNode dummy = res;
        while(pHead1 != null && pHead2 != null){
            if(pHead1.val <= pHead2.val){
                dummy.next = pHead1;
                pHead1 = pHead1.next;
            }else{
                dummy.next = pHead2;
                pHead2 = pHead2.next;
            }
            dummy = dummy.next;
        }
        if(pHead1 != null){
            dummy.next = pHead1;
        }
        if(pHead2 != null){
            dummy.next = pHead2;
        }
        return res.next;
    }
}
BM6 判断链表中是否有环
解题思路
是的,没有BM5,因为它是难题,菜的一批的我看答案也还没理解透
利用双指针,slow每次往前走一步,fast每次往前走两步,如果链表中有环,则两个指针最终会在非终点的地方相遇
因为地球是圆的,slow和fast不是平行走,总会在一个时间点遇到的,时长的问题而已     
双指针在后面很多题目中都会遇到过。
import java.util.*;
public class Solution {
    public boolean hasCycle(ListNode head) {
        //快慢指针
        ListNode fast = head;
        ListNode slow = head;
        if(head == null){
            return false;
        }
        // fast.next = head;
        // slow.next = head;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            if(slow == fast){
                return true;
            }
        }
        return false;
    }
}
BM7 链表中环的入口结点
解题思路

在这里插入图片描述

还有借助双指针slow和fast,之后要寻找规律
假设双指针在结点C相遇,且slow是第一次走到C,A->B的距离为X,B->C的距离为Y
则slow走了X+Y,fast每次走的是slow的两倍,所以fast走了2*(X+Y)
fast走的2*(X+Y)实际路径是A->B B->C C->B B->C,我们不知道的是C->B的距离,根据上述条件,可以知道
C->B的距离= 2*(X+Y)-(A->B)-(B->C) -(B->C)= 2*(X+Y)-X-Y-Y=X
噢,原理规律就是,相遇点C->B(环入口)的距离,竟然等于从头结点到环入口的距离
那好办了,slow还是在C开始走,fast回到头结点,和slow一起一步一步走,走到他俩遇见的结点,就是环的入口结点。破案。
import java.util.*;
public class Solution {
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        //双指针 空间O(1) 时间O(N)
        if(pHead == null || pHead.next == null){
            return null;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;
        while(fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
            //有环,双指针会在环中相遇
            if(slow == fast){
                break;
            }
        }
        if(fast == null || slow == null){
            return null;
        }
        fast = pHead;
        //与第一次相遇的节点相同速度出发,相遇节点为入口结点
        while(fast != slow){
            fast = fast.next;
            slow = slow.next;
        }
        return fast;

    }
}
还有一种办法是,不断的遍历链表,借助集合,存储遍历到的结点;
每遍历一个结点判断当前集合中是否已经有这个结点,有的话直接return。
    public ListNode EntryNodeOfLoop(ListNode pHead) {
        //hash 空间O(N) 时间O(N)
        HashSet<ListNode> set = new HashSet<>();
        while(pHead != null){
            if(set.contains(pHead)){
                return pHead;
            }
            set.add(pHead);
            pHead = pHead.next;
        }
        return null;
    }
BM8 链表中倒数最后k个结点
解题思路
两次循环,先统计长度,之后遍历到count-k处,return
 public ListNode FindKthToTail (ListNode pHead, int k) {
        // 最简单的想法,但是需要两次遍历
        //先遍历,看链表长度,然后遍历count-k次,return
        ListNode dummy = pHead;
        if(pHead == null){
            return pHead;
        }
        int count = 1;
        while(dummy.next != null){
            count++;
            dummy = dummy.next;
        }
        if(count < k){
            return null;
        }
        dummy = pHead;
        while(count > k){
            dummy = dummy.next;
            count --;
        }

        return dummy;

    }
双指针
fast先走k步
之后fast和slow一起走,当fast走到链表尾部时,slow所在的结点刚好是倒数第k个
public class Solution {
     public ListNode FindKthToTail (ListNode pHead, int k) {
        //快慢指针
        if(pHead == null){
            return pHead;
        }
        ListNode fast = pHead;
        ListNode slow = pHead;
        while(k-- > 0){
            if(fast != null){
                fast = fast.next;
            }else{
                return null;
            }
        }
        while(fast != null){
            fast = fast.next;
            slow = slow.next;
        }
        return slow;
     }
}
BM9 删除链表的倒数第n个节点
解题思路
双指针
注意,有可能删除第1个结点,所以定义一个虚拟头结点res
fast从res开始,先走n步
实际上就是从head开始走了n-1步
当fast走到链表尾部时,slow就走到了倒数第n-1个结点,跳过第n个结点即可:slow.next = slow.next.next
import java.util.*;
public class Solution {
    public ListNode removeNthFromEnd (ListNode head, int n) {
        // 双指针
        //注意有可能删除第一个,所以使用一个头节点
        if(head == null){
            return head;
        }
        ListNode res = new ListNode(-1);
        res.next = head;

        ListNode slow = res;
        ListNode fast = res;

        //fast先走n步
        for(int i=0;i<n;i++){
            fast = fast.next;
        }
        while(fast.next != null){
            fast = fast.next;
            slow = slow.next;   
        }
        slow.next = slow.next.next;
        return res.next;
    }
}
BM10 两个链表的第一个公共结点
解题思路
第一种方法是先遍历两个链表的长度len1和len2;
如果len1>len2, 链表1先走len1-len2步,因为公共部分在尾部,要尾部对齐;
之后同时遍历链表1和链表2,如果遇到两者相等,直接return(有公共结点会返回,没有会一起走到尾部,返回null)
代码较为繁琐,也可能是我太菜了,不会优化
 public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
            //1.先统计两个链表的长度len1,len2
            ListNode dummy1 = pHead1;
            ListNode dummy2 = pHead2;
            int len1 = 0;
            while(dummy1 != null){
                dummy1 = dummy1.next;
                len1++;
            }
            int len2 = 0;
            while(dummy2 != null){
                dummy2 = dummy2.next;
                len2++;
            }
            //2,较长的链表,指针先走|len1-len2|,因为链表的公共部分在尾部,尾部对齐
            dummy1 = pHead1;
            dummy2 = pHead2;
            if(len1 > len2){
                while(len1 - len2 > 0){
                    dummy1 = dummy1.next;
                    len1--;
                }
            }
            if(len1 < len2){
                while(len2 - len1 > 0){
                    dummy2 = dummy2.next;
                    len2--;
                }
            }
            //3.两个链表同时移动,如果遇到节点相同,return,如果有一方已经=null,直接return null
            while(dummy1 != null && dummy2 != null){
                if(dummy1 == dummy2){
                   return dummy1;
                }else{
                    dummy1 = dummy1.next;
                    dummy2 = dummy2.next;
                }
            }
            return null;
        }
第二种方法,还是双指针,找规律
链表1的长度为len1,链表2的长度为len2,
指针n1和指针n2,一个从链表1出发,一个从链表2出发,两者都走len1+len2个长度
如果有公共结点,如样例{1,2,3}{4,5}{6,7}
/n1走:1,2,3,6,7,null,4,5,6
n2走:4,5,6,7,null,1,2,3,6
走到相等的时候就是公共起始点
没有公共节点如{1}{2,3},{},n1:1,2,3,null  n2:2,3,1,null 走到最后相等都为空
public class Solution {
    public ListNode FindFirstCommonNode(ListNode pHead1, ListNode pHead2) {
        ListNode n1 = pHead1;
        ListNode n2 = pHead2;
        while(n1 != n2){
            n1 = (n1 == null)? pHead2 : n1.next;//n1第一次走到空的时候是phead1遍历完
            n2 = (n2 == null)? pHead1 : n2.next;
         }
        return n1;
    }
}
BM11链表相加(二)
解题思路
先把两个链表进行翻转,这样就可以按位加
每个链表结点存储的值在0-9,所以如果计算出来超过9,需要借助变量isTen记录超过9的十位数是多少
链表结点的值取当前位置两个值相加+isTen之后的个位数
import java.util.*;
public class Solution {
    public ListNode addInList (ListNode head1, ListNode head2) {
        // write code here
        head1 = reverse(head1);
        head2 = reverse(head2); 
        ListNode ans = new ListNode(-1);
        ListNode dummy = ans;
        int isTen = 0;
        int val=0;
        while(!(head1 == null && head2 == null)){
            val = isTen;
            if(head1 != null){
                val += head1.val;
                head1 = head1.next;
            }
            if(head2 != null){
                val += head2.val;
                head2 = head2.next;
            }
            isTen = val/10;
            dummy.next = new ListNode(val%10);
            dummy = dummy.next;
        }
        if(isTen > 0){
            dummy.next = new ListNode(isTen);
        }
        return reverse(ans.next);
    }

    public ListNode reverse(ListNode head){
        if(head == null){
            return head;
        }
        ListNode cur = head;
        ListNode per = null;
        while(cur!= null){
            ListNode tmp = cur.next;
            cur.next = per;
            per = cur;
            cur = tmp;
        }
        return per;
    }
}
BM12 单链表排序
解题思路
借助数组,存储当前链表的所有结点值
对数组进行排序
重新建立链表返回
public class Solution {
    public ListNode sortInList (ListNode head) {
        // 借助数组
        ListNode dummy = head;
        List<Integer> list = new ArrayList<>();
        while(dummy != null){
            list.add(dummy.val);
            dummy = dummy.next;
        }
        Collections.sort(list);
        dummy = head;
        for(int i=0;i<list.size();++i){
            dummy.val = list.get(i);
            dummy = dummy.next;
        }
        return head;
    }
}
BM13 判断一个链表是否为回文结构
解题思路
借助栈,先将链表的节点值依次入栈
之后,从头遍历链表,将其值与栈顶元素相比,如果相等则继续遍历,栈顶元素出栈;否则直接return false
    public boolean isPail (ListNode head) {
        // write code here
        //借助一个栈,空间复杂度O(N),时间复杂度O(N)
        Stack<Integer> stack = new Stack();
        if(head == null){
            return true;
        }
        ListNode dummy = head;
        while(dummy != null){
            stack.push(dummy.val);
            dummy = dummy.next;
        }
        dummy = head;
        while(!stack.isEmpty()){
            if(stack.pop() != dummy.val){
                return false;
            }
            dummy  = dummy.next;
        }
        return true;
    }
方法二,快慢指针
slow每次走一步,fast每次走两步
fast走到链表末尾时,slow走到链表中间
将slow到fast的子链表进行翻转,翻转后的第一个结点由slow指向
之后fast从原始链表头结点开始遍历,判断slow与fast是否相等
public class Solution {

   public boolean isPail (ListNode head) {
       // write code here
       //双指针,slow往后一步,fast走两步,当fast走到末尾时,slow走到中间
       //后半段进行反转
       //时间复杂度O(N) 空间复杂度O(1)
       if (head == null || head.next == null) {
           return true;
       }
       ListNode slow = head;
       ListNode fast = head;
       while (fast != null && fast.next != null) {
           fast = fast.next.next;
           slow = slow.next;
       }
       slow = reverse(slow);
       fast = head;
       while (slow != null && fast != null) {
           if (slow.val != fast.val) {
               return false;
           }
           slow = slow.next;
           fast = fast.next;
       }

       return true;
   }
   public ListNode reverse(ListNode head) {
       ListNode per = null;
       ListNode cur = head;
       while (cur != null) {
           ListNode tmp = cur.next;
           cur.next = per;
           per = cur;
           cur = tmp;
       }
       return per;
   }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值