- 链表是否存在公共子节点
- 链表是否存在回文序列
- 合并有序链表
- 寻找中间结点
- 寻找倒数第K个元素
- 旋转链表
- 删除链表特定节点
- 删除连边倒数第K个节点
- 删除链表中的重复元素
1.链表是否存在公共子节点
解题技巧:常用的数据结构和常用算法思想想一遍。
常用的数据结构有数组、链表、队、栈、Hash、集合、树、堆。常用的算法思想有查找、排序、双指针、递归、迭代、分治、贪心、回溯和动态规划等等。
方法一 HashMap
链表A依次存入Hashset集合,并遍历链表B,看Hashset集合中是否有节点与之匹配,若有则是链表AB的第一个公共字节点,若无则AB链表无公共字节点。
public NodeList findCommomNode(NodeList headA,NodeList headB){
//定义一个hashset:无序且不重复;用来存放A链表
HashSet<NodeList> set = new HashSet<>();
while (headA!=null){
set.add(headA);
headA=headA.next;
}
while (headB!=null){
if (set.contains(headB))
return headB;
headB = headB.next;
}
return null;
}
方法二 栈
链表A和链表B同时推入两个栈中,再用时出栈,如果出栈的元素相等,则继续出栈,直到出栈元素不同,则上一个元素就是第一个相同的节点。
//栈的做法
public NodeList findCommomNodeByTrack(NodeList headA,NodeList headB){
//创建两个栈对象,用于存放链表A和链表B
Stack<NodeList> stackA = new Stack<>();
Stack<NodeList> stackB = new Stack<>();
while (headA!=null){
stackA.push(headA);
headA=headA.next;
}
while (headB!=null){
stackA.push(headB);
headB=headB.next;
}
NodeList preNode = null;
while (stackA.size()>0 && stackB.size() > 0){
//查看栈顶元素是否相同
if (stackA.peek()==stackB.peek()){
preNode=stackA.pop();
stackB.pop();
}else {
break;
}
}
return preNode;
}
方法三 差和双指针
让链表长的L1减去链表短的L2得到一个差值|L1-L2|,让L1先走|L1-L2|的步,此时L1和L2的长度已经是一致了,接下来,L1和L2同时向前走,直到遇到第一个相同的结点
public ListNode findFirstCommonNode(ListNode pHead1, ListNode pHead2) {
if(pHead1==null || pHead2==null){
return null;
}
ListNode current1=pHead1;
ListNode current2=pHead2;
int l1=0,l2=0;
//分别统计两个链表的长度
while(current1!=null){
current1=current1.next;
l1++;
}
while(current2!=null){
current2=current2.next;
l2++;
}
//将current的值变回头节点
current1=pHead1;
current2=pHead2;
int sub=l1>l2?l1-l2:l2-l1;
//长的先走sub步
if(l1>l2){
int a=0;
while(a<sub){
current1=current1.next;
a++;
}
}
if(l1<l2){
int a=0;
while(a<sub){
current2=current2.next;
a++;
}
}
//同时遍历两个链表,当值相当的时候就可以返回节点了
while(current2!=current1){
current2=current2.next;
current1=current1.next;
}
return current1;
}
2.链表是否存在回文序列
方法一 栈
将链表全部推入栈中,一边出栈,一边链表进行遍历,比较二者的元素,如果有一个元素不相同则不存在回文序列
public boolean isReturnNodeList(NodeList head){
NodeList temp = head;
Stack<Integer> stackNodeList = new Stack<>();
//把链表节点的值放至栈中
while (temp!=null){
stackNodeList.push(temp.val);
temp=temp.next;
}
while (head!=null){
if (head.val != stackNodeList.pop()){
return false;
}
head=head.next;
}
return true;
}
方法二 双指针之快慢双指针
我们使用双指针思想里的快慢指针 ,fast一次走两步,slow一次走一步。当fast到达表尾的时候,slow正好到达一半的位置,那么接下来可以从头开始逆序一半的元素,或者从slow开始逆序一半的元素,都可以。
3.1合并有序链表
新建一个链表,然后分别遍历两个链表,每次都选最小的结点接到新链表上,最后排完。
3.2合并K个链表
如果面试遇到,我倾向先将前两个合并,之后再将后面的逐步合并进来,这样的的好处是只要将两个合并的写清楚,合并K个就容易很多,现场写最稳妥:
public NodeList mergeManyNodeList(NodeList[] manyNodeLists){
//定义合并后的新链表
NodeList newNodeList = null;
//遍历链表集合,并递归合并两个有序链表
for (NodeList nodeList:manyNodeLists){
newNodeList = mergeTwoNodeLists(newNodeList, nodeList);
}
return newNodeList;
}
4.双指针专题
4.1寻找中间节点
方法一 快慢指针 走两步,走一步
用两个指针 slow 与 fast 一起遍历链表。slow 一次走一步,fast 一次走两步。那么当 fast 到达链表的末尾时,slow 必然位于中间
public NodeList findMiddleNodeList(NodeList head){
//定义两个指针,快慢指针
NodeList slow= head;
NodeList fast =head;
while (fast!=null && fast.next !=null){
slow=slow.next;
fast=fast.next.next;
}
return slow;
}
4.2寻找倒数第K个元素
经典快慢双指针,区别是同样的速度,一个先走一个后走
快指针先走K+1,慢指针在第1个节点,两个指针同时向后走,快指针指向null的时候,慢指针刚好是倒数第K个节点。注意的点,可能K会大于链表长度
4.3旋转链表
方法一:链表反转
先将链表反转成{5,4,3,2,1},找到分割点分成两个链表{5,4} {3,2,1},再将这两个链表进行反转就行了{4,5}{1,2,3}
方法二:双指针找到(倒数)分割的位置,其实就是正着数Len-K+1 ,成为{1,2,3}{4,5},再将链表拼接{4,5}{1,2,3}
5.删除链表元素专题
5.1删除指定结点
使用虚拟头节点可以不用单独处理头结点,使用cur.next.val去判断
删除倒数第N个结点
方法一:整体遍历一遍获得链表长度Len,再重新遍历L-N+1就是要删除的地方
方法二:快慢双指针,这种方法只需要遍历一遍
5.2删除重复元素
重点是该链表是升序的,所以只要判断cur与cur.next是否相等,相等则将cur.next去除即可
5.3.2 重复元素全部删除
这时,则将cur.next与cur.next.next这两个进行比较就行,需要注意可能cur.next.next为空,而cur.next不为空的情况。