链表

面试顺序:
链表 树
深度优先搜索 宽度优先搜索
动态规划
链表要注意每一次.next .val都要判断是否为空 比如m-n反转链表就只要遍历就要判断

把dummy当作指向头结点的指针
使用它的场景:
1.头结点在变换(被删除或者拼接代替等) 返回头结点不适合就返回dummy.next
注意第一条中正因为头结点在变换使用dummy不用判断如何变换 合并两个有序链表就是不需要设置dummy.next是那个headdummy.next
2.适合有前驱关系让整个题目更加简单 有时甚至创建dummy为了有多个前驱进行对应操作 prev和cur
3.适合走路问题 就是题目涉及了要走到第几个结点 。比如反转m到n 我们首先要到m-1结点 哪需要走多少步多少次next呢? m-2次
dummy.next=head; head=dummy;使用这句话我们就可以移动head去第几个结点就移动几步因为Dummy和head之间的这步让我们不用减2了
在这里插入图片描述

  • 关于dummy.next的返回值问题 node1=node2 并不会改变链表的结构 不会删除点也不会重复点 只是指针指向不同了 所以我们返回dummy.next即使删除结点 也没有破环链表的结构 指针也在跟着不停的移动 那么返回的一定是最新的head

技巧:当头结点会变化(更新/删除)时候用dummy Node 哑结点

  • 为什么使用dummy node

链表题中经常会遇到这样的问题:链表的第一个node,因为没有前驱节点,所以该node需要特殊处理,会导致额外的代码量。如果创建一个dummy,将其作为第一个node的前驱节点,这样链表中所有的node都可以也能够同样的逻辑来处理了。

  • 对比
    以前删除头结点 需更新头结点的值
    1.额外判断是否已经删除到链表已经是空的了不能删除 等等有很多条if条件
    2.而且没有指向头结点的指针,总要单独处理头结点,
    如果有了dummy node就让头结点和之后的结点处于相同的模式,而且直接返回dummy.next没有那么多if语句
  • 模板node1=node2的意思是node1指向node2
ListNode dummy=new ListNode(0);
dummy.next=head;
ListNode prev=dummy;指向dummy 从dummy开始遍历
ListNode curt=head;
while(cur!=null)
{
 prev=cur;
cur=cur.next;
}
return dummy.next; dummy不变的
  • 一个例子更深的理解dummy node的使用场景之 --头结点在变化
    删除链表中指定值的结点
  • 题目分析 给定的值有可能是头结点的值 删除头结点造成冗余的if和更新
    果断选择用dummy.next指向head
    在删除的时候使用prev.next=cur.next 直接删除cur本身
  • 下图是正常不删除头结点而只是中间的 采用复制后 删除node3 不用dummy
    在这里插入图片描述

使用dummy便于利用前驱结点pre和当前结点cur的关系

从dummy.next=head prev=dummy; cur=head;中获取pre和cur的关系
所以当因为头结点是变化的而使用dummy 就要熟悉的根据题目使用prev和cur

  • 举例 善于利用因为设置dummy赠送的prev和cur关系
    根据题意设置prev为空 或者dummy
    跳转题目—反转链表
    下为必背答案
1->5->7->null    
null<-1<-5<-7    
7->5->1->null

public ListNode reverse(ListNode head)
{
  if(head==null||head.next==null){return head;}
  ListNode prev=null;
  ListNode cur=head;//cur指向head
  while(cur!=null)
  {
     ListNode temp=cur.next;
     cur.next=prev;
     prev=cur;
     cur=temp;
 }
 return prev;
}

反转m-n的部分链表
首先要想到反转我们还是需要prev 和 cur 并且反转的head也变了所以肯定需要用dummy.next

  • 在这里就是要反转m到n的 拿在m-n的这段链表里 我们定义谁为prev呢?
    也要新创建一个dummy么 那肯定不能因为新建dummy就增添结点改变链表结构了 就选择m结点为prev选择m.next为cur
  • 因为是部分反转链表 部分反转链表之后还要重新拼接回来 m-1.next是n m.next是n+1 所以我们也要提前把m-1结点和 n+1结点声明出来 m-1 很好说遍历链表就可以 但是n+1却不需要再次遍历了 因为n+1可以在反转的时候获得
  • 总之我们要使用5个结点 mNode m-1Node nNode m-1Node n+1Node
    其中的n+1Node 可以用cur获得 因为从前反转Cur为null了就截止了 函数返回prev
    现在我们可以For循环,cur指向n+1的时候跳出循环 然后nNode就是prev随着cur走 现在就只需要设置mNode(prev/nNode) m-1Node(拼接需要) cur(mNode.next开始/n+1Node截至)这三个结点
    在这里插入图片描述
使用dummy因为有前驱结点再删除的时候不用cur.next=cur.next.next了 而是pre.next=cur.next

删除链表中的重复结点 (涉及删除头结点)
删除重复结点保留一个 虽然这个不涉及头结点的变化 但是当你在思考删除除第一个结点之外的重复结点的时候 你就会发现很难定义如何删除这些重复结点 如果你很难删除一个结点就可以采用prev

partation list

根据给定的val 把小于val的放左边 大于Val的放右边
设置两个dummy和相应的两个prev=dummy
leftprev.next=head leftprev=head; head=head.next;只添加结点值小于val的
rightdummy就添加大于的 最后把
leftprev.next=rightdummy.next;rightprev.next=null;

把链表重新排序

归并排序和快速排序联系和区别

  • 快排:先整体有序再局部有序
    归并:先局部有序再整体有序
  • 排序的有序性: 有重复的数字 快排后原来再前面的重复数字还在前面 是不稳定排序 归并是稳定排序
  • nlogn 归并是最好最坏都是nlogn 快排的平均才是nlogn 最坏n^2
  • 归并时间复杂度 O(n) 快排O(1)
    在这里插入图片描述)在这里插入图片描述

归并
在这里插入图片描述
在这里插入图片描述

合并K个链表

~堆
  1. 使用数据结构 堆 必须要new一个comparator比较器
  • 堆结构是封装好的priorityQueue 队列,构造函数要有堆容量和比较器
    public PriorityQueue(int initialCapacity,Comparator<? super E> comparator)
    4条链表就堆就4个结点
  • 先把有序链表的头放入堆 注意判断
  • 只要堆不空就一致poll 出最小的结点添加到链表中 每poll一个就补上一个poll出来的结点的next
    在这里插入图片描述
import java.util.*;
class Solution {
      private  Comparator<ListNode> comparator=new Comparator<ListNode>() {
        @Override
        public int compare(ListNode o1, ListNode o2) {
            return o1.val-o2.val;
        }
    };
    public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null||lists.length==0){return null;}
        Queue<ListNode> heap=new PriorityQueue<>(lists.length,comparator);
        ListNode dummy=new ListNode(0);
        ListNode prev=dummy;

        for (int i = 0; i < lists.length; i++) {
            if(lists[i]!=null){
            heap.add(lists[i]);}
        }
        while(!heap.isEmpty())
        {
         ListNode cur=heap.poll();
         prev.next=cur;
         prev=cur;
         if(cur.next!=null)
         {
             heap.add(cur.next);
         }
        }
        return dummy.next;
    }

}

~归并排序的合并

和数组不同的点在于 数组是在deivideTomerge带着两个范围去merge
而链表的merge核心需要是两个链表 在deivideTomerge就要确定链表在这里插入图片描述在这里插入图片描述

class Solution {
   public ListNode mergeKLists(ListNode[] lists) {
        if(lists==null||lists.length==0){return null;}
        return divideToMerge(lists,0,lists.length-1);
        }
         public ListNode divideToMerge(ListNode[] lists,int start,int end)
         {
             if(start==end){return lists[start];}
             int mid=start+(end-start)/2;
             ListNode left=divideToMerge(lists,start,mid);
             ListNode right=divideToMerge(lists,mid+1,end);
             return mergeTwo(left,right);
         }
         public ListNode mergeTwo(ListNode left,ListNode right)
         {
             ListNode dummy=new ListNode(0);
             ListNode prev=dummy;
             while(left!=null&&right!=null)
             {
                 if(left.val<right.val)
                 {
                     prev.next=left;
                     left=left.next;
                 }
                 else
                 {
                     prev.next=right;
                     right=right.next;
                 }
                 prev=prev.next;
             }
             if(left!=null){prev.next=left;}
             if(right!=null){prev.next=right;}
             return dummy.next;
         }

    
}

在这里插入图片描述


在这里插入图片描述
在这里插入图片描述

链表是否有环


在求环入口的时候通常都初始化fast=head
而不是fast.next;

双指针和DummyNode的结合

在这里插入图片描述
删除倒数第K个结点
在这里插入图片描述
快满指针之间是步数关系 不是结点差数 只要结点之间出发有差距从head还是Dummy出发最后都是再末尾挺 不会改变p2的位置 所以如果想删除倒数第K 你就会发现确实停在倒数K+1的位置上
在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值