LeetCode链表题经典题目(一)

147. 对链表进行插入排序

(https://leetcode-cn.com/problems/insertion-sort-list/)
image-20220316151215046

  1. 当前节点和前面的节点构成有序:

image-20220316151624479

  1. 当前节点和前面的节点构成无序,需要移动,从链表头部往后寻找(数组一般是从后往前寻找):

image-20220316152253152

class Solution {
    public ListNode insertionSortList(ListNode head) {
        if(head==null)
            return null;
        ListNode  dummy=new ListNode(-1);
        dummy.next=head;
        ListNode lastSorted=head,cur=head.next;
      
        while(cur!=null)
        {
          if(lastSorted.val<=cur.val)//当前节点和前面的节点已经构成有序
          {
              lastSorted=lastSorted.next;
          }
          else
          {
              ListNode pre=dummy;
              while(pre.next.val<=cur.val)
                pre=pre.next;//pre指向第一个大于cur的节点的前一个节点
            lastSorted.next=cur.next;
            cur.next=pre.next;
            pre.next=cur;
          }
          cur=lastSorted.next;//cur指向下个未排序的节点


        }
        return dummy.next;

    }
}
//O(n^2)
//O(1)

2. 两数相加

https://leetcode-cn.com/problems/add-two-numbers/
image-20220320092705138

思路:对链表进行加法操作,从两条链表的头结点开始,将对应的节点值相加并求进位,由于两条链表的长度可能不一样,所以当遍历完某条链表时,可以用0值来代替不存在的节点,方便计算,注意的一点时,如果最后的进位不为0,说明最高位有进位,需要额外添加1个值为1的节点,比如1+9 返回01

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode dummy=new ListNode();
        ListNode cur=dummy;
        int sum=0,carry=0;
        while(l1!=null||l2!=null)
        {
           int num1=l1==null?0:l1.val;
           int num2=l2==null?0:l2.val;
           sum=num1+num2+carry;
           carry=sum/10;
           sum%=10;
           cur.next=new ListNode(sum);
           cur=cur.next;
           if(l1!=null)
                l1=l1.next;
            if(l2!=null)
                l2=l2.next;

        }
        if(carry!=0)//最后的进位不要忘记
            cur.next=new ListNode(1);
        return dummy.next;

    }
   
}
//O(max(m,n))  m n分别为两个链表的长度
//O(1)

21. 合并两个有序链表

(https://leetcode-cn.com/problems/merge-two-sorted-lists/)
image-20220321085708539

思路:向合并两个有序数组一样,同时从头遍历两条链表,比较当前的两个节点的值,取较小的值,并将较小的值所在链表的对应指针往后移动一次;当有某条链表提前遍历完时,只需要将剩余的节点直接连到答案节点上即可,无需再次遍历

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy=new ListNode();
        ListNode cur=dummy;
        while(list1!=null&&list2!=null)
        {
           if(list1.val<=list2.val)
           {
               cur.next=list1;
               list1=list1.next;
           }
           else 
           {
               cur.next=list2;
               list2=list2.next;
           }
           cur=cur.next;
            
        }
        if(list1!=null)
            cur.next=list1;
        if(list2!=null)
            cur.next=list2;
        return dummy.next;

    }
}
//O(n)
//O(1)

19. 删除链表的倒数第 N 个结点

(https://leetcode-cn.com/problems/remove-nth-node-from-end-of-list/)
image-20220323095923417

思路:使用快慢指针,先让快指针走n步,然后快慢指针一起走,这样当快指针走到最后一个节点时,慢指针刚好走到待删除节点的前一个节点; 注意:为了避免删除的节点是头结点这种情况,建议使用dummy节点,避免发生空指针异常

class Solution {
    public ListNode removeNthFromEnd(ListNode head, int n) {
        ListNode dummy=new ListNode();
        dummy.next=head;
        ListNode slow=dummy,fast=dummy;
        for(int i=1;i<=n;i++)
            fast=fast.next;
        while(fast.next!=null)//快指针是否到达最后一个节点?
        {
            slow=slow.next;
            fast=fast.next;
        }
        slow.next=slow.next.next;//删除节点
        return dummy.next;//返回 注意返回的不是head  因为待删除的节点就是head节点
    }
}
//O(n)
//O(1)

141. 环形链表

(https://leetcode-cn.com/problems/linked-list-cycle/)
image-20220325101929430

思路:假设链表中有环,可以将这个环想象成1个环形跑道,如果有两个人在跑道上跑步,一个跑的快,一个跑的慢,假设跑的快的速度是跑的慢的速度的两倍,那么快的人一定可以在一圈内追上慢的人

简单来说:如果有环的话,快慢指针可以相遇

public class Solution {
    public boolean hasCycle(ListNode head) {
        ListNode slow=head,fast=head;
        while(fast!=null&&fast.next!=null)
        {
            fast=fast.next.next;
            slow=slow.next;
            if(fast==null||slow==null)//到达null 说明没有环
                return false;
            if(fast==slow)//fast追上slow说明有环
                return true;
        }
        return false;//无环
    }
}
//O(n)
//O(1)

142. 环形链表 II

(https://leetcode-cn.com/problems/linked-list-cycle-ii/)

在这里插入图片描述

思路:先根据141题的思路判断链表是否存在环,即使用快慢指针,快指针的速度是慢指针的2倍,当相遇时,假设慢指针走了k步,则快指针走了2k步,由于二者最后位置相同,所以快指针多走的k步实际上就环的长度的整数倍(类似于操场跑步,可能多跑了1圈,也可能多跑了2圈);假设入环点到相遇点的距离是m,则起点到入环点的距离是k-m, 而从相遇点再走到入环点的距离也是k-m, 因此相遇后,让一个节点从起点开始走,一个节点从相遇点开始走,此时速度一样,则再次相遇时,相遇点就是入环点
在这里插入图片描述

public class Solution {
    public ListNode detectCycle(ListNode head) {
        if(head==null)
            return null;
        ListNode fast=head,slow=head;
        boolean hasCircle=false;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
            if(fast==null||slow==null){//等于null说明没有环
                return null;
            }
            if(fast==slow){//存在环 退出循环开始后面找环的入口
                hasCircle=true;
                break;
            }
        }
        if(!hasCircle)
            return null;
        fast=head;//一个指向头节点
        while(fast!=slow){//再次相遇的地方就是入环口
            fast=fast.next;
            slow=slow.next;   
        }
        return fast;
    }
}
//O(n)
//O(1)

206. 反转链表

(https://leetcode-cn.com/problems/reverse-linked-list/)
image-20220330083000695

思路1:迭代

class Solution {
    public ListNode reverseList(ListNode head) {
        ListNode pre=null,cur=head;
        while(cur!=null)
        {
            //顶针式写法
            ListNode tmp=cur.next;
            cur.next=pre;
            pre=cur;
            cur=tmp;
        }
        return pre;//最后返回pre
    }
}
//O(n)
//O(1)

思路2:递归

class Solution {
    public ListNode reverseList(ListNode head) {
       if(head==null||head.next==null)
            return head;
        ListNode newHead=reverseList(head.next);//反转后n-1个节点得到的头结点
        head.next.next=head;//比如1->2->3->4->5  后面4个节点已经反转得到1->2<-3<-4<-5
        //现在需要2步操作  2.next=1  1.next=null  其中2=1.next  1是当前的旧head 
        head.next=null;
        return newHead;
    }
}
//O(n)
//O(n)

23. 合并K个升序链表

(https://leetcode-cn.com/problems/merge-k-sorted-lists/)
在这里插入图片描述

思路1:优先级队列,先将每个链表的头节点加入优先级队列中,然后取出队首的节点就是最小的,假设取出的节点是node,然后判断node后面还有没有节点,如果有则将node的下一个节点也加入队列,开始下一轮的选取,直到最后队列为空

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        PriorityQueue<ListNode> pq=new PriorityQueue<>((a,b)->a.val-b.val);
        for(ListNode head:lists){
            if(head!=null){
                pq.offer(head);
            }
        }
        ListNode dummy=new ListNode(),p=dummy;
        while(!pq.isEmpty()){
            ListNode node=pq.poll();
            p.next=node;
            p=p.next;
            if(node.next!=null){
                pq.offer(node.next);
            }
        }
        return dummy.next;
    }
}
//(kn*logk)  最多有kn个节点 每个节点入队出队的时间logk
//O(k) 队列中的元素最多不会超过k个

思路2:根据合并两条有序链表的基础,将k条链表分治处理

在这里插入图片描述

如上图所示,划分到最小的子问题后,6条链表两两合并变成3条,然后在合并变成2条,最后变成1条,即最终的答案;假设每条链表的长度为n

第一轮合并: k 2 \frac{k}{2} 2k组 每组的节点数: n × 2 n\times 2 n×2 ---->nk

第二轮合并: k 4 \frac{k}{4} 4k组 每组的节点数: n × 4 n\times 4 n×4—>nk

第三轮合并: k 8 \frac{k}{8} 8k组 每组的节点数: n × 8 n \times 8 n×8—>nk

即每一层都需要nk次操作,一共有 l o g k log k logk层,故时间复杂度为: n k × l o g k nk\times logk nk×logk

空间复杂度为递归消耗的空间: l o g k log k logk

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        return merge(lists,0,lists.length-1);
    }
    public ListNode merge(ListNode[] lists,int left,int right){
        if(left==right){
            return lists[left];
        }
        if(left>right){
            return null;
        }
        int mid=(left+right)>>1;
        return mergeTwoLists(merge(lists,left,mid),merge(lists,mid+1,right));
    }
    //合并两条有序的链表的模板方法
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        ListNode dummy=new ListNode();
        ListNode cur=dummy;
        while(list1!=null&&list2!=null)
        {
           if(list1.val<=list2.val)
           {
               cur.next=list1;
               list1=list1.next;
           }
           else 
           {
               cur.next=list2;
               list2=list2.next;
           }
           cur=cur.next;
            
        }
        if(list1!=null)
            cur.next=list1;
        if(list2!=null)
            cur.next=list2;
        return dummy.next;

    }
}

25. K 个一组翻转链表

(https://leetcode-cn.com/problems/reverse-nodes-in-k-group/)

在这里插入图片描述

思路:需要定义两个函数

ListNode getKthNode(ListNode cur,int k): 求以cur开始往后的第k个节点

boolean hasKNodes(ListNode cur,int k): 判断以cur开始的链表是否含有k个节点

cur先指向头节点,判断以cur开始的链表是否含有k个节点,没有直接返回,有的话进行反转

对于某一组(有K个节点),该组的第K个节点反转后会成为该组的头节点,该组的原来的头节点反转后会成为该组的最后一个节点,注意:反转后的最后一个节点应该指向下一组的第一个节点(下一组不足K个 未反转)或者指向下一组的最后一个节点(下一组足k个 可以反转),因此反转某组后,使该组反转后的最后一个节点指向下一组的第一个或最后一个节点

public ListNode reverseKGroup(ListNode head, int k) {
         if(k==0||k==1)//0个1组或1个一组相当于不用反转
            return head;
        ListNode cur=head;
        ListNode newHead=null;
        if(hasKNodes(cur,k))//链表长度>=k
            newHead=getKthNode(cur,k);
        else //链表长度<k 相当于不用反转
            return head;
        ListNode pre=null,tmp;
        while(cur!=null&&hasKNodes(cur, k)){
            int i=0;
            ListNode last=cur;//last保存当前组的第一个节点 反转之后last就是最后1个节点
            while(i<k){//逆置某一组  和单链表的反转代码类似
                tmp=cur.next;
                cur.next=pre;
                pre=cur;
                cur=tmp;
                i++;
            }
            last.next=getKthNode(cur,k);//逆置完后该组原来的第一个节点变成最后一个
            //最后一个节点连接下一组的第k个节点(下一组有k个则和第k个连接  不足就跟第一个节点连接)
            
        }
        return newHead;
    }
    //获取从cur开始往后数的第k个节点
    public ListNode getKthNode(ListNode cur,int k){
        if(cur==null)
            return null;
        int i=1;
        ListNode p=cur,pre=p;
        while(p!=null&&i<k){
            pre=p;
            p=p.next;
            i++;
        }
        return p==null?cur:p;//有k个则返回改组第k个节点  不足k个则返回改组第一个节点
    }
    //以cur节点开始是否还有k个节点?
    public boolean hasKNodes(ListNode cur,int k){
        int i=0;
        while(cur!=null){
            i++;
            if(i==k)
                break;
            cur=cur.next;
        }
        return i==k;
    }
//O(n)
//O(1)

138. 复制带随机指针的链表

https://leetcode-cn.com/problems/copy-list-with-random-pointer/
在这里插入图片描述

先根据已有节点的值创建一个具有相同值的节点,建立原有节点和新节点的映射,先不考虑next和random指针创建完映射之后,对于原始节点X,对应的新创建的节点Y=map.get(X) Y.next就是map.get(X.next) Y.random就是map.get(X.random) X.next和X.random都是指向原有的节点,原有的节点是map中的键,因此都对应一个新创建的节点

class Solution {
    HashMap<Node,Node> map;
    public Node copyRandomList(Node head) {
        map=new HashMap<>();
        Node cur=head;
        while(cur!=null){
            map.put(cur,new Node(cur.val));
            cur=cur.next;
        }
        cur=head;
        while(cur!=null){
            Node cloneNode=map.get(cur);
            cloneNode.next=map.get(cur.next);
            cloneNode.random=map.get(cur.random);
            cur=cur.next;
        }
        return map.get(head);
    }
}
//O(n)
//O(n)

24. 两两交换链表中的节点

https://leetcode-cn.com/problems/swap-nodes-in-pairs/
在这里插入图片描述

//迭代
/**
 * Definition for singly-linked list.
 * public 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 swapPairs(ListNode head) {
       if(head==null||head.next==null)//链表为空或者只有1个节点
            return head;
        ListNode dummy=new ListNode();
        dummy.next=head;
        ListNode tmp=dummy;//创建哑节点 方便操作
        while(tmp.next!=null&&tmp.next.next!=null){//有2个节点
            ListNode node1=tmp.next;
            ListNode node2=tmp.next.next;
            tmp.next=node2;//第2个节点作为头节点
            node1.next=node2.next;//第一个节点指向第3个节点
            node2.next=node1;
            tmp=node1;
        }
        return dummy.next;
    }
    
}
//O(n)
//O(1)
//递归
class Solution {
    public ListNode swapPairs(ListNode head) {
       if(head==null||head.next==null)//链表为空或者只有1个节点
            return head;
        ListNode newHead=head.next;//第2个节点是新的头节点
        head.next=swapPairs(newHead.next);//第1个节点指向第3个节点以后反转后的头节点
        newHead.next=head;//第2个节点指向第1个节点
        return newHead;
    }
    
}
//O(n)
//O(n)

剑指 Offer II 029. 排序的循环链表

https://leetcode.cn/problems/4ueAj6/
在这里插入图片描述

思路:遍历两次链表,第一次遍历找出链表中的最小值节点和最大值节点,第2次遍历找出新加入的节点的待插入位置,然后将新节点插入即可,细节处理见代码注释

/*
// Definition for a Node.
class Node {
    public int val;
    public Node next;

    public Node() {}

    public Node(int _val) {
        val = _val;
    }

    public Node(int _val, Node _next) {
        val = _val;
        next = _next;
    }
};
*/

class Solution {
    public Node insert(Node head, int insertVal) {
        if(head==null){
            Node newNode=new Node(insertVal);
            newNode.next=newNode;
            return newNode;
        }
        Node pre=head,cur=head.next;
        while(pre.val<=cur.val&&cur!=head){//cur!=head 防止1 1 1这种情况出现死循环
            pre=cur;
            cur=cur.next;
        }
        Node first=cur;
        boolean flag=false;
       //cur指向最小的节点(第一个) pre指向最大的节点(最后一个) 下面的while如果不执行说明
       //insertVal是当前最小的值 直接连在pre后面
        while(cur.val<insertVal){
            pre=cur;
            cur=cur.next;
            //下面的判断防止当insertVal是当前一个比较大的值导致死循环 链表中找不到比其小的值
            //此时遍历一次链表就停止
            if(flag&&cur==first){//第2次cur=first说明遍历一次结束
                break;
            }
            if(!flag){//设置第2次访问标记
                flag=true;
            }
        }
        //cur是newNode的下一个节点 pre是newNode的上一个节点
         Node newNode=new Node(insertVal);
         newNode.next=cur;
         pre.next=newNode;
         return head;
    }
}
//O(n)
//O(1)
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

CodePanda@GPF

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值