0. 链表题目注意
清楚链表在内存中的存储方式
注意每个节点中指针的指向
写链表相关题目时首先在纸上画出相关操作,然后再写代码
注意对非法情况进行判断
- 一些高频的链表问题
- 翻转链表
- K个节点为一组进行翻转
- 判断链表是否有环
- 返回链表中间(1/2)节点(扩展返回链表1/k 节点)
- 求链表的交
- 旋转链表
1. 翻转链表
不断通过头插法进行翻转链表的构建,需要注意其中的指针指向防止断链。
/**
* 链表翻转非递归实现
* @param head:头指针
* @return
*/
public static Node reverseList(Node head){
if(head == null || head.next == null){
return head;
}
Node cur = head.next;
Node pre = head;
pre.next = null;
Node tmp ;
while(cur != null){
tmp = cur.next;
cur.next = pre;
pre = cur;
cur = tmp;
}
return pre;
}
2. K个节点为一组进行翻转
经常有两个节点一组翻转,三个节点一组翻转,考虑到扩展性,可以直接实现K个节点一组进行翻转,如果在面试时候可以这样实现,肯定会得到面试官的青睐。
思路:采用递归实现,先找到前K个节点,然后对前K个节点进行翻转。
/**
* K个节点一组进行翻转
* @param head:头指针
* @param k:需要进行翻转的组数
* @return
*/
public static Node reverseKList(Node head, int k){
if(head == null || head .next == null){
return head;
}
//不直接对head操作,这样可以保留一个头指针
Node cur = head;
// 记录节点个数
int count = 0;
// 找到需要进行翻转的K个节点
while(cur != null && count != k){
cur = cur.next;
count ++;
}
// 进行特殊情况处理
if(count == k){
cur = reverseKList(cur, k);
while(count -- > 0){
Node tmp = head.next;
head.next = cur; // 与后面翻转好的连接
cur = head;
head = tmp ;
}
}
return head;
}
3. 判断链表是否有环
思路:1使用快慢指针的方式,一个一次走一步,一个一次走两步,如果相遇则说明有环;2使用set或者map进行记录,如果重复出现则说明有环;
/**
* 使用快慢指针判断链表是否有环
* @param head:头指针
* @return
*/
public static boolean isCircleByFastSlow(Node head){
if(head == null || head.next == null){
return false;
}
Node fast = head;
Node slow = head;
while(fast != null && fast.next != null){
fast = fast.next.next;
slow = slow.next;
if(fast == slow){
return true;
}
}
return false;
}
/**
* 使用set判断是否含有环
* @param head:头指针
* @return
*/
public static boolean isCircleBySet(Node head){
if(head == null || head.next == null){
return false;
}
Set<Node> set = new HashSet<>();
Node cur = head;
while(cur != null){
// 如果set中不包含当前节点则加入
// 否则直接返回true
if(!set.contains(cur)){
set.add(cur);
cur = cur.next;
}else{
return true;
}
}
return false;
}
4. 返回链表的中间(1/2)节点(扩展返回链表1/k 节点)
这个题目一个可以扩展的题目,例如返回中间节点,返回1/3节点等,可以实现返回1/k节点的方法。
思路:两个指针,一个一次走一步,一个一次走K步;
/**
* 当前节点走K步之后是否为空
* @param head:头结点
* @param k:K处
* @return
*/
public static boolean isNull(Node head, int k){
if(head == null){
return true;
}
Node cur = head;
while(k -- > 0){
if(cur.next != null){
cur = cur.next;
}else{
return true;
}
}
return false;
}
/**
* 返回1/k处的节点,例如中间节点为1/2处节点
* @param head:头结点
* @param k:K处
* @return
*/
public static Node getNodeOfK(Node head, int k){
if(head == null){
return null;
}
Node fast = head;
Node slow = head;
while(!isNull(fast, k)){
int count = k;
while(count -- > 0){
fast = fast.next;
}
slow = slow.next;
}
return slow;
}
5.求链表的交
相交链表特点:从后往前总是相同的;
注意:在进行链表相交问题判断的时候要先判断两个链表是否有环,有环时处理方式稍有差别,下面的方法不考虑环。
思路:
(1)两边扫描,因为两个链表的长度可能不同,扫描两边可以保证两个指针扫描的长度相同,扫描过程中如果两个指针相遇则相交;这个方法需要提前判断两个链表有交,判断链表有交有多种方法:例如:判断最后一个节点是否相同;将一个链表的首位相连从另外一个链表的头开始扫描;
(2)使用栈进行存储,然后再不断的出栈;
(3)先对一个链表使用set进行存储,然后再遍历另外一个链表;
/**
* 两边扫描求链表的交,此处保证链表有交
* 可以提前判断链表是否有交,判断链表有交的方法很多
* @param head1:头指针
* @param head2:头指针
* @return
*/
public static Node mixedListByFastSlow(Node head1, Node head2){
if(head1 == null || head2 == null){
return null;
}
Node p1 = head1;
Node p2 = head2;
while(p1 != p2){
p1 = p1 == null ? head2 : p1.next;
p2 = p2 == null ? head1 : p2.next;
}
return p1;
}
/**
* 使用栈求交
* @param head1
* @param head2
* @return
*/
public static Node mixedListByStack(Node head1, Node head2){
if(head1 == null || head2 == null){
return null;
}
Stack<Node> stack1 = new Stack<>();
Stack<Node> stack2 = new Stack<>();
Node result = new Node(0);
while(head1 != null){
stack1.push(head1);
head1 = head1.next;
}
while(head2 != null){
stack2.push(head2);
head2 = head2.next;
}
if(stack1.peek() != stack2.peek()){
return null;
}else{
while(stack1.peek() == stack2.peek()){
result = stack1.pop();
stack2.pop();
}
}
return result;
}
/**
* 使用set集合判断链表的交
* @param head1
* @param head2
* @return
*/
public static Node mixedListBySet(Node head1, Node head2){
if(head1 == null || head2 == null){
return null;
}
Set<Node> set = new HashSet<>();
while(head1 != null){
set.add(head1);
head1 = head1.next;
}
while(head2 != null){
if(set.contains(head2)){
return head2;
}else{
head2 = head2.next;
}
}
return null;
}
/**
* 将链表向右移动K个位置
* @param head:头结点
* @param k:位置
* @return
*/
public static Node rotateList(Node head, int k){
if(head == null){
return null;
}
Node cur = head;
int count = 0;
while(cur != null){
count ++;
cur = cur.next;
}
k %= count;
// 指向后面部分
Node fast = head;
while(k -- > 0){
fast = fast.next;
}
// 指向前面部分
Node slow = head;
// 这个地方要想明白,两个指针同时向前走
// 最终可以走到分界点
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
Node newHead = slow.next;
fast.next = head;
slow.next = null;
return newHead;
}
6.旋转链表
问题:将链表向右移动K个位置,例如 1 2 3 4 5 在K = 2时旋转为 4 5 1 2 3。
思路:使用两个指针
/**
* 将链表向右移动K个位置
* @param head:头结点
* @param k:位置
* @return
*/
public static Node rotateList(Node head, int k){
if(head == null){
return null;
}
Node cur = head;
int count = 0;
while(cur != null){
count ++;
cur = cur.next;
}
k %= count;
// 指向后面部分
Node fast = head;
while(k -- > 0){
fast = fast.next;
}
// 指向前面部分
Node slow = head;
// 这个地方要想明白,两个指针同时向前走
// 最终可以走到分界点
while(fast.next != null){
fast = fast.next;
slow = slow.next;
}
Node newHead = slow.next;
fast.next = head;
slow.next = null;
return newHead;
}