面试顺序:
链表 树
深度优先搜索 宽度优先搜索
动态规划
链表要注意每一次.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个链表
~堆
- 使用数据结构 堆 必须要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的位置上