链表算法题

一)常用技巧:

1)画图:非常直观+形象最终便于我们理解

2)引入虚拟头节点:做链表算法题的时候,所做的都是不带头结点的,就是从第一个节点开始已经存储有效数据了,像这种链表需要考虑很多边界情况,我们可以创建一个新的头节点,在这个头节点里面并不会存储有效的数据,只是起到一个哨兵的作用

优点:便于处理边界情况,方便针对链表进行操作

3)不要吝啬空间,大胆去定义变量:

链表算法题_第1张图片

链表算法题_第2张图片

链表算法题_第3张图片

4)快慢双指针:链表中判断环,找链表中环的入口,以及找链表中倒数第N个节点

二)常见习题:

一)反转链表:

剑指 Offer 24. 反转链表 - 力扣(LeetCode)

算法原理:只需要定义一个指针current遍历整个链表,创建一个虚拟的头节点然后进行头插操作,最后返回newHead.next即可

链表算法题_第4张图片

class Solution {
    public ListNode reverseList(ListNode head) {
          ListNode newHead=new ListNode(-1);
          ListNode current=head;
          while(current!=null){
              ListNode CurNext=current.next;
              if(newHead.next==null){
                  newHead.next=current;
                  current.next=null;//防止链表的最后一个元素成环
              } 
              else{
                  //使用链表头插法来解决此问题
                  ListNode next=newHead.next;
                  newHead.next=current;
                  current.next=next;
              }
              current=CurNext;
          }
    return newHead.next;
    }
}

二)两数相加:

2. 两数相加 - 力扣(LeetCode)

算法原理:模拟两数相加的过程即可,并使用虚拟头节点来连接我们最终的结果

class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        ListNode newHead=new ListNode(-1);
        ListNode current=newHead;
        int carry=0,sum=0;
        while(l1!=null&&l2!=null){
             sum=(l1.val+l2.val+carry)%10;
             carry=(l1.val+l2.val+carry)/10;
             ListNode temp=new ListNode(sum);
             current.next=temp;
             current=current.next;
             l1=l1.next;
             l2=l2.next;
        }
        //1.处理空余的第一个链表
        while(l1!=null){
            sum=(l1.val+carry)%10;
            carry=(l1.val+carry)/10;
            ListNode temp=new ListNode(sum);
            current.next=temp;
            current=current.next;
            l1=l1.next;
        }
        //2.处理空余的第二个链表
         while(l2!=null){
            sum=(l2.val+carry)%10;
            carry=(l2.val+carry)/10;
            ListNode temp=new ListNode(sum);
            current.next=temp;
            current=current.next;
            l2=l2.next;
        }
        //3.两个链表都处理完成之后处理最后一个carry也就是最后一个进位数
        if(carry!=0){
            ListNode temp=new ListNode(carry);
            current.next=temp;
            current=current.next;
        }
    return newHead.next;
    }
}

三)两两交换链表中的节点:

24. Swap Nodes in Pairs - 力扣(LeetCode)

解法1:递归解法:找到重复子问题

链表算法题_第5张图片

解法2:迭代:交换两个指针的指向,需要两个节点指针,还需要被前面的指针连起来,还需要连接后面的指针,所有是需要四个节点的

1)直接引入虚拟头节点

2)定义四个变量即可,当我们定义四个变量以后,这些节点的交换工作直接看着图就可以完成,不需要担心顺序问题和链表断开的问题

3)循环结束的条件:current和next有一个为空的时候,直接返回

3.1)如下图所示,当节点个数是偶数个的时候,current==null,恰好所有节点都已经交换成功了,此时应该退出循环

链表算法题_第6张图片

3.2)或者说当链表中有奇数个节点,current!=null,但是next等于空了,此时不需要进行交换,直接返回即可

链表算法题_第7张图片

链表算法题_第8张图片

class Solution {
    public ListNode swapPairs(ListNode head) {
        if(head==null||head.next==null) return head;
        ListNode newHead=new ListNode(-1);
        ListNode prev=newHead;
        ListNode current=head;
        ListNode curNext=current.next;
        ListNode tailNext=curNext.next;
        newHead.next=curNext;
        while(current!=null&&curNext!=null){
//1.如果全部交换完成current==null,或者只剩下了一个节点curNext==null
            prev.next=curNext;
            curNext.next=current;
            current.next=tailNext;
//2.交换完成之后重置节点
            prev=current;
            current=prev.next;
//3.剩下的两个指针指向为空,所以需要额外判断一下
            if(current==null) break;
                curNext=current.next;
            if(curNext==null) break;
                tailNext=curNext.next;
        }
    return newHead.next;
    }
}

这么写也是可以的:

链表算法题_第9张图片

四)重排链表:

143. 重排链表 - 力扣(LeetCode)

算法原理:

1)先找到链表的中间节点:快慢双指针

2)将中间节点后面的链表进行逆序操作:反转链表,双指针+头插法+递归

让第一个链表的最后一个位置指向null,让第二个链表的最后一个位置指向null

可以把包括中间节点的后面的链表翻转,也可以不包括中间节点后面的链表翻转

3)合并两个链表:合并两个有序链表,不需要进行比较,只是需要按照一定的顺序来进行合并

class Solution {
    public void reorderList(ListNode head) {
if(head==null||head.next==null||head.next.next==null) return;
//1.先找到链表的中间节点,一定要画图分析slow的落点
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
//2.翻转中间节点后面的链表
        ListNode prev=null;
        ListNode current=slow.next;
        if(current==null) return;
        ListNode CurNext=current.next;
        while(current!=null){
            CurNext=current.next;
            current.next=prev;
            prev=current;
            current=CurNext;
        }
//3.合并两个链表
   ListNode newHead=new ListNode(-1);
   current=newHead;
   ListNode l1=head;
   ListNode l2=prev;
   while(l1!=null&&l2!=null){
        current.next=l1;
        l1=l1.next;
        current=current.next;
        current.next=l2;
        l2=l2.next;
        current=current.next;
      }
    if(l1!=null) current.next=l1;
    if(l2!=null) current.next=l2;
 //4.特殊处理一下遗漏的节点,如果是奇数个节点,我们还是需要让这个奇数节点的下一个节点指向空,将两个链表分离
    slow.next=null;
    }
}

class Solution {
    public void reorderList(ListNode head) {
//1.先找到链表的中间节点
        ListNode fast=head;
        ListNode slow=head;
        while(fast!=null&&fast.next!=null){
            fast=fast.next.next;
            slow=slow.next;
        }
//2.翻转中间节点后面的链表,采用头插法
      ListNode tempHead=new ListNode(-1);
      ListNode current=slow.next;
      while(current!=null){
          ListNode CurNext=current.next;
          current.next=tempHead.next;
          tempHead.next=current;
          current=CurNext;
      }
//3.合并两个链表
   ListNode newHead=new ListNode(-1);
   current=newHead;
   ListNode l1=head;
   ListNode l2=tempHead.next;
   while(l1!=null&&l2!=null){
        current.next=l1;
        l1=l1.next;
        current=current.next;
        current.next=l2;
        l2=l2.next;
        current=current.next;
      }
    if(l1!=null) current.next=l1;
    if(l2!=null) current.next=l2;
 //4.特殊处理一下遗漏的节点,如果是奇数个节点,我们还是需要让这个奇数节点的下一个节点指向空
    slow.next=null;
    }
}

五)合并k个升序链表:

23. 合并 K 个升序链表 - 力扣(LeetCode)

解法1:暴力破解

依次合并两个有序链表即可,假设每一个链表的平均长度是N,时间复杂度是K^2*N

N*(k-1)+N*(k-2)+N*(k-3)+N(k-4)+N

第一个链表需要向下合并(k-1)次,第二个链表需要向下合并(k-2)次,第三个链表需要向下合并(k-3)次,所以将这些表达式进行求和就可以得出时间复杂度是N*(k*(k-1)/2)

class Solution {
     public ListNode merge(ListNode head1,ListNode head2){
        //这里面是合并两个有序链表的逻辑
        if(head1==null) return head2;
        if(head2==null) return head1;
        ListNode newHead=new ListNode(-1);
        ListNode current=newHead;
        while(head1!=null&&head2!=null){
            if(head1.val>head2.val){
                current.next=head2;
                head2=head2.next;
                current=current.next;
            }else{
                current.next=head1;
                head1=head1.next;
                current=current.next;
            }
        }
        if(head1!=null){
            current.next=head1;
        }else{
            current.next=head2;
        }
    return newHead.next;
    }
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode newHead=null;
        for(int i=0;i
解法2:利用优先级队列来做优化(推荐):

1)可以先定义指针k1和k2和k3指向每一个链表的第一个节点,并且使用一个优先级队列创建一个小根堆,再来创建一个虚拟头节点来保存最终的结果,将每一个链表的头节点的最小的那个部分先存放到头节点,堆顶元素就是我们要最终要拼接在newHead后面的结果

2)这个逻辑和合并两个有序链表的逻辑是相同的,先找到各个链表头结点的最小值然后拼接到最后的结果里面,那么这里面的时间复杂度就是O(N*K),仅需要让指针遍历一遍即可

3)刚一开始将所有的链表的头结点都入队,那么出堆顶元素的头节点拼接在结果后面,让指针右移,再将节点入队,时间复杂度就是O(N*logK)

链表算法题_第10张图片

class Solution {
    public ListNode mergeKLists(ListNode[] lists) {
        ListNode newHead=new ListNode(-1);
        ListNode currrent=newHead;
        if(lists==null||lists.length==0) return newHead.next;
//1.创建一个小根堆
 PriorityQueue MinHeap=new PriorityQueue(new Comparator() {
            @Override
            public int compare(ListNode o1, ListNode o2) {
                return o1.val-o2.val;
            }
        });
//2.将所有链表的头节点放入到小根堆里面即可
     for(ListNode node:lists){
          if(node!=null)
           MinHeap.offer(node);
     }
//3.合并链表
    while(!MinHeap.isEmpty()){
         ListNode temp=MinHeap.poll();
         currrent.next=temp;
         currrent=currrent.next;
         temp=temp.next;
         if(temp!=null) MinHeap.offer(temp);
      }
//4.返回最终结果
   return newHead.next;
    }
}
解法3:分治->递归

1)重复子问题:给一个数组,先将左边区间的所有链表合并,再将右边区间的所有链表合并,最后就直接合并两个有序链表,如果实在不理解可以先写一下归并排序

2)设计dfs函数:给定一个链表数组,将给定区间的所有链表进行合并

3)时间复杂度:每一层的链表都会执行logK次合并,所以时间复杂度就是NK*logK

链表算法题_第11张图片

class Solution {
   
    public ListNode mergeSort(ListNode[] lists,int left,int right){
        //这个函数被赋予的使 public ListNode merge(ListNode head1,ListNode head2){
        //这里面是合并两个有序链表的逻辑
        if(head1==null) return head2;
        if(head2==null) return head1;
        ListNode newHead=new ListNode(-1);
        ListNode current=newHead;
        while(head1!=null&&head2!=null){
            if(head1.val>head2.val){
                current.next=head2;
                head2=head2.next;
                current=current.next;
            }else{
                current.next=head1;
                head1=head1.next;
                current=current.next;
            }
        }
        if(head1!=null){
            current.next=head1;
        }else{
            current.next=head2;
        }
    return newHead.next;
    }命就是将指定区间内的链表合并成一个大的链表
        if(left==right) return lists[left];//此时数组中只有一个链表
        if(left>right) return null;//此时数组中没有链表
        int mid=(left+right)/2;[left,mid][mid+1,right];
        //1.先将左边的链表进行合并,递归处理左部分
        ListNode head1=mergeSort(lists,left,mid);
        //2.再将右边的链表进行合并,递归处理右部分
        ListNode head2=mergeSort(lists,mid+1,right);
        //3.最后在合并两个有序链表
        ListNode resultHead=merge(head1,head2);
        return resultHead;
    }
    public ListNode mergeKLists(ListNode[] lists) {
       return mergeSort(lists,0,lists.length-1);
    }
}

六)k个一组反转链表

25. K 个一组翻转链表 - 力扣(LeetCode)

解法1:递归:

链表算法题_第12张图片

解法2:迭代+模拟操作+一定要画图

1)先进行计算出整个链表的长度

2)采取头插法+实现长度为K的链表的逆序即可

3)使用虚拟头节点来进行拼接并采取头插法

class Solution {
    public ListNode reverseKGroup(ListNode head, int k) {
//1.先求出数组中的总长度计算循环次数
        ListNode current=head;
        ListNode newHead=new ListNode(-1);
        int len=0;
        while(current!=null){
            len++;
            current=current.next;
        }
    int count=len/k;
    current=head;
    ListNode newCurrent=newHead;
//2.进行反转链表
    for(int i=0;i

七)删除链表的倒数第N个节点

19. 删除链表的倒数第 N 个结点 - 力扣(LeetCode)

首先先回顾一下找到链表倒数第K个节点这道题,定义一个fast指针和slow种子很一开始都是指向链表

 

你可能感兴趣的:(链表,数据结构)