链表问题-简单
一、判断单链表是否有环
1.哈希表
遍历链表,把每个结点记录到哈希表中
- 若最后能够遍历完链表,说明不存在环
- 若遍历时出现了已经存在于哈希表中的结点,说明存在环,且该节点为环的起点。
注意:不可能存在既有环还能闭合的单链表,若存在则有一个节点必有两个next节点。
public class Solution {
public boolean hasCycle(ListNode head) {
HashSet<ListNode> set = new HashSet<>();
while(head!=null){
if(set.contains(head)) return true;
set.add(head);head = head.next;
}
return false;
}
}
2.快慢指针
设置一个快指针,一个慢指针,快指针每次走两步,慢指针每次走一步
- 若快指针走到了终点null,说明不存在环
- 若快指针和慢指针在某一处相遇,说明存在环,但该处不一定是环的起点。
- 相遇后将快指针置为头结点,并且之后快指针和慢指针同时一步一步地遍历,相遇之处即为环的起点。
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode l1 = head,l2 = head;
while(l2!=null&&l2.next!=null){
l1 = l1.next;l2 = l2.next.next;
if(l1==l2) return true;
}
return false;
}
}
二、判断两个链表是否相交
1.不存在环的情况
注意到以下事实:
- 若两个链表相交,他们必然有一段重复的区域
- 两个链表相交则终点必然相同
所以判断步骤如下:
- 首先分别遍历两个链表,记录终点和长度
- 若终点节点不相同则直接返回null
- 若终点节点相同,再将长的链表从头开始移动两个链表长度的差值。
- 然后依次同时遍历两个链表,第一次出现相同的结点时该结点即为相交的起点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
int deltaLen = 0;
ListNode A = headA,B = headB;
while(A!=null&&A.next!=null){
deltaLen++;A= A.next;
}
while(B!=null&&B.next!=null){
deltaLen--;B=B.next;
}
if(A!=B) return null;
if(deltaLen>0){
while(deltaLen--!=0) headA=headA.next;
}else{
while(deltaLen++!=0) headB=headB.next;
}
while(headA!=null){
if(headA==headB) return headB;
headA=headA.next;
headB=headB.next;
}
return null;
}
}
三、反转链表
1.引入额外数组
思路很简单,记录链表的所有值,最后反向赋值回链表。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode h = head;
int len = 0,i=0;
while(head!=null){
len++;head=head.next;
}
int[] help = new int[len];
head=h;
while(head!=null){
help[i++]=head.val;head=head.next;
}
head=h;
while(head!=null){
head.val=help[--len];
head=head.next;
}
return h;
}
}
2.直接反转
遍历链表,每次遍历使当前结点指向前一个节点,由于正常遍历只会记录当前结点和下一个节点,因此需要引入一个变量记录上一个节点。同时,改变当前结点的指向后,为防止下一个结点丢失,也应该记录。
class Solution {
public ListNode reverseList(ListNode head) {
ListNode pre = null,cur = head;
while(cur!=null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
3.相关题目:回文链表
很容易想到空间复杂度为o(n)的方法,构造一个辅助数组或者辅助栈然后再判断是否回文,但利用快慢指针+反转链表的方法可以达到o(1)的空间复杂度。具体方法如下:
- 慢指针走一步,快指针走两步,快指针走到头时慢指针必然走到链表的中间位置。
- 将链表的前半部分反转。
- 同时遍历链表的前后部分,判断是否回文。
class Solution {
public boolean isPalindrome(ListNode head) {
if(head.next==null) return true;
ListNode l1 = head,l2 = head,preL1 = null;
while(l2!=null&&l2.next!=null){
preL1 = l1;
l1 = l1.next;
l2 = l2.next.next;
}
if(l2!=null){
preL1.next = null;
l1 = l1.next;
l2 = reverse(head);
}else{
preL1.next = null;
l2 = reverse(head);
}
while(l1!=null){
if(l1.val!=l2.val) return false;
l1 = l1.next;l2 = l2.next;
}
return true;
}
public ListNode reverse(ListNode head){
ListNode pre = null,cur = head;
while(cur!=null){
ListNode next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
}
补充:链表中快慢指针的两种写法:
- 写法一
这种方法遍历结束后,若链表长度为奇数,慢指针会来到链表的中点位置;若链表长度为偶数,慢指针会到链表中点向后取整的位置。
ListNode fast = head,slow = head;
while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
}
- 方法二
这种方法遍历结束后,若链表长度为奇数,慢指针也会来到链表的中点位置;若链表长度为偶数,慢指针会到链表中点向前取整的位置。
ListNode fast = head,slow = head;
while(fast.next!=null&&fast.next.next!=null){
slow = slow.next;
fast = fast.next.next;
}
四、两两交换链表中的结点
不允许使用值交换,必须交换结点对象!
1.遍历方法
每次交换都必须知道四个值,一是需要交换的两个节点,二是需要交换的两个节点的前后两个节点。因此需要保证第二个节点,第二个节点的下一个结点和下下个结点都不为空。
最后再注意一下边界条件。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null) return head;
ListNode help = new ListNode();help.next = head;
ListNode cur1 = head,cur2 = head.next,pre = help,next = cur2.next;
while(cur2!=null&&cur2.next!=null&&cur2.next.next!=null){
pre.next = cur2;
cur2.next = cur1;
cur1.next = next;
pre = cur1;
cur1 = pre.next;
cur2 = cur1.next;
next = cur2.next;
}
pre.next = cur2;
cur2.next = cur1;
cur1.next = next;
return help.next;
}
}
2.递归法
我们只需要考虑头结点和第二个节点的交换,剩下的结点交给递归处理。
class Solution {
public ListNode swapPairs(ListNode head) {
if(head==null||head.next==null) return head;
ListNode newHead = head.next;
head.next = swapPairs(head.next.next);
newHead.next = head;
return newHead;
}
}