24. 两两交换链表中的节点
两两交换
首先设置虚拟头结点->指向真正头结点,设置两个指针分别指向两个头结点,而交换分为三步
首先处理两两交换结点的头与尾的链接:
将pre指针指向结点2(cru.next),将cru.next(即结点1)指向结点三。
最后将二者进行交换,由二结点指向一节点。一个循环结束,移动指针。
class Solution {
public ListNode swapPairs(ListNode head) {
ListNode dhead=new ListNode(-1);
dhead.next=head;
ListNode cru=head;
ListNode pre=dhead;
while(cru!=null&&cru.next!=null) {
pre.next=cru.next;
cru.next=cru.next.next;
pre.next.next=cru;
pre=cru;;
cru=cru.next;
}
return dhead.next;
}
}
19.删除链表的倒数第N个节点
思路:计算链表长度,需要两次循环,首先遍历一遍计算长度,再遍历一遍进行删除
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) {
return head;
}
ListNode dhead = new ListNode();
dhead.next = head;
ListNode pre = dhead;
ListNode cru = head;
int j = 0;
while (cru != null) {
j++;
cru = cru.next;
}
cru = head;
if (j - n >= j || j - n < 0)
return head;
for (int i = 0; i < j - n; i++) {
cru = cru.next;
pre = pre.next;
}
pre.next = cru.next;
return dhead.next;
}
}
官方解法一:双指针法:
代码如下:
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
ListNode first = head;
ListNode second = dummy;
for (int i = 0; i < n; ++i) {
first = first.next;
}
while (first != null) {
first = first.next;
second = second.next;
}
second.next = second.next.next;
ListNode ans = dummy.next;
return ans;
}
}
官方解法二:栈
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
Deque<ListNode> stack = new LinkedList<ListNode>();
ListNode cur = dummy;
while (cur != null) {
stack.push(cur);
cur = cur.next;
}
for (int i = 0; i < n; ++i) {
stack.pop();
}
ListNode prev = stack.peek();
prev.next = prev.next.next;
ListNode ans = dummy.next;
return ans;
}
}
、
02.07. 链表相交
文章讲解:代码随想录
题目:力扣题目链接
暴力解法:
两层循环,找到a链与b链相等起始节点:
class Solution {
public Node2 getIntersectionNode(Node2 headA, Node2 headB) {
Node2 a=headA;
Node2 b=headB;
while(a!=null) {
while(b!=null){
if(a==b) {
return a;
}
b=b.next;
}
a=a.next;
b=headB;
}
return null;
}
当a与b结点相等时,直接返回结点。
官方解法一:
求出两个链表的长度,并求出两个链表长度的差值,然后让curA移动到,和curB 末尾对齐的位置,如图:
此时我们就可以比较curA和curB是否相同,如果不相同,同时向后移动curA和curB,如果遇到curA == curB,则找到交点。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode curA = headA;
ListNode curB = headB;
int lenA = 0, lenB = 0;
while (curA != null) { // 求链表A的长度
lenA++;
curA = curA.next;
}
while (curB != null) { // 求链表B的长度
lenB++;
curB = curB.next;
}
curA = headA;
curB = headB;
// 让curA为最长链表的头,lenA为其长度
if (lenB > lenA) {
//1. swap (lenA, lenB);
int tmpLen = lenA;
lenA = lenB;
lenB = tmpLen;
//2. swap (curA, curB);
ListNode tmpNode = curA;
curA = curB;
curB = tmpNode;
}
// 求长度差
int gap = lenA - lenB;
// 让curA和curB在同一起点上(末尾位置对齐)
while (gap-- > 0) {
curA = curA.next;
}
// 遍历curA 和 curB,遇到相同则直接返回
while (curA != null) {
if (curA == curB) {
return curA;
}
curA = curA.next;
curB = curB.next;
}
return null;
}
}
官方解法二:栈
用两个栈来解决
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Deque<ListNode> st1 = new LinkedList<>();
Deque<ListNode> st2 = new LinkedList<>();
ListNode tmp1 = headA;
ListNode tmp2 = headB;
while(tmp1 != null){
st1.push(tmp1);
tmp1 = tmp1.next;
}
while(tmp2 != null){
st2.push(tmp2);
tmp2 = tmp2.next;
}
ListNode ans = null;
while(!st1.isEmpty() && !st2.isEmpty()){
ListNode a = st1.pop();
ListNode b = st2.pop();
if(a == b){
ans = a;
}
}
return ans;
}
}
官方解法三:双指针(数学解法)
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if (headA == null || headB == null) {
return null;
}
ListNode pA = headA, pB = headB;
while (pA != pB) {
pA = pA == null ? headB : pA.next;
pB = pB == null ? headA : pB.next;
}
return pA;
}
}
原理:
若相交:
不想交:
官方解法四:哈希集合
判断两个链表是否相交,可以使用哈希集合存储链表节点。
首先遍历链表 headA,并将链表 headA 中的每个节点加入哈希集合中。然后遍历链表 headB,对于遍历到的每个节点,判断该节点是否在哈希集合中:
如果当前节点不在哈希集合中,则继续遍历下一个节点;
如果当前节点在哈希集合中,则后面的节点都在哈希集合中,即从当前节点开始的所有节点都在两个链表的相交部分,因此在链表 headB 中遍历到的第一个在哈希集合中的节点就是两个链表相交的节点,返回该节点。
如果链表 headB中的所有节点都不在哈希集合中,则两个链表不相交,返回 null。
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
Set<ListNode> visited = new HashSet<ListNode>();
ListNode temp = headA;
while (temp != null) {
visited.add(temp);
temp = temp.next;
}
temp = headB;
while (temp != null) {
if (visited.contains(temp)) {
return temp;
}
temp = temp.next;
}
return null;
}
}
142.环形链表II
我的思路:哈希集合
建立循环判断当前节点是否已在哈希集合中,若已经存在,则返回该节点。
class Solution {
public Node3 detectCycle(Node3 head) {
if(head==null||head.next==null)
{
return null;
}
Set<Node3> list=new HashSet<Node3>();
Node3 cru=head;
while(cru.next!=null) {
list.add(cru);
if(list.contains(cru.next)) {
return cru.next;
}
else {
cru=cru.next;
}
}
return null;
}
}
官方思路:快慢指针
具体思路过于冗长,参照 文章讲解:代码随想录
快慢指针运动过程有如下图,核心思路:当快慢指针重逢时有等式:(x + y) * 2 = x + y + n (y + z)
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode slow = head;
ListNode fast = head;
while (fast != null && fast.next != null) {
slow = slow.next;
fast = fast.next.next;
if (slow == fast) {// 有环
ListNode index1 = fast;
ListNode index2 = head;
// 两个指针,从头结点和相遇结点,各走一步,直到相遇,相遇点即为环入口
while (index1 != index2) {
index1 = index1.next;
index2 = index2.next;
}
return index1;
}
}
return null;
}
}
小技巧:
链表的功能实现通常用到
1.虚拟头结点:
链表的一大问题就是操作当前节点必须要找前一个节点才能操作。这就造成了,头结点的尴尬,因为头结点没有前一个节点了。每次对应头结点的情况都要单独处理,所以使用虚拟头结点的技巧,就可以解决这个问题。
2.双指针(进阶版:快慢指针)【巨好用!】
3.栈(引用的话)
1)声明:
import java.util.Stack;
2)创建:
Stack<ListNode> s = new Stack<ListNode>();
3)函数:
表 Stack类的常用方法
方法 | 描述 |
---|---|
push(value) | 将给定的值压入栈的顶端 |
pop() | 删除并返回栈顶的值 |
peek() | 返回栈顶端的值,但是不将其从栈中删除 |
isEmpty() | 如果栈为空则返回true |
size() | 返回栈中元素的个数 |
4.哈希集合
1)声明:
import java.util.HashSet;
import java.util.Set;
2)创建:
Set<ListNode> list=new HashSet<ListNode>();
3)函数:
方法名 | 方法说明 |
add() | 往集合中添加元素 |
contains() | 判断集合中是否存在某元素 |
remove() | 从集合中删除指定的元素 |
clear() | 清除集合中所有元素 |
isEmpty() | 判断集合元素是否为空 |
Iterator() | 返回元素的迭代器 |
size() | 获取集合中元素个数 |