链表同数组是一种线性的数据结构,与数组不同,其内存空间非连续,可以合理地利用内存碎片。
由于链表中的每一项都有指针(用来指向链表中其他项)
使得同等的数据量,链表比数组占用内存多,但增删操作比数组迅速,不必浪费时间来移动增删项后面的数据。
单指针的称为单链表,指针指向后项
而双指针则可以同时指向前后项
Add Operation
初始化新节点
新节点连接
旧节点断开
Delete Operation
创建新连接,cur被孤立无法到达
Linked List Cycle
判断链表内是否存在环
分析:
1、若存在环,则指针一步一步的向后移动不会遇到重点,否则不是环。
2、若存在环,通过快慢点的方式来退出循环。
public boolean hasCycle(ListNode head) {
ListNode slow=head;
ListNode fast=head;
while(fast!=null && fast.next!=null){//遇到终点说明不是环
slow=slow.next;
fast=fast.next.next;
if(fast==slow){//同起点,直线中快点不会与慢点相遇,相遇则证明有环
return true;
}
}
return false;
}
Linked List Cycle II
如果链表存在环,求出环的起点。
分析:见LeetCode中二分查找章节中Find the Duplicate Number的追逐法
private ListNode detectCycle(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {//遇到终点说明不是环
fast = fast.next.next;
slow = slow.next;
if (fast == slow) {//同起点,直线中快点不会与慢点相遇,相遇则证明有环
fast = head;//相遇后让快点从起点开始按慢点的速度跑
while (fast != slow) {
fast = fast.next;
slow = slow.next;
}
return fast;
// 通过画图得出
// 相遇点距环起点的距离
// 等于
// 起点距环起点距离加n倍的环周长
}
}
return null;
}
Intersection of Two Linked Lists
求出两链表的交叉点
分析:两链表在交叉点前的长度是解决问题的关键
0、设两指针同时同速移动,每移动一次将指针置空断开连接,当长链表走到终点时,便是交叉点,但此解法不符合不改变链表结构的要求。
1、设两指针同时同速移动,短的链表到终点时,可通过长链表的指针移动,计算出两链表的长度差,将差值补到起始位,让两指针继续同速移动,节点相同时即交叉点。
2、设两指针同时同速移动,在短链表的指针到终点时,让其转到长链表移动,在长链表的指针到终点时,让其转到短链表移动,节点相同时即交叉点。这种算法通过转移起点,也达到了消除长度因素的影响。
private ListNode getIntersectionNode(ListNode headA, ListNode headB) {
ListNode a = headA;
ListNode b = headB;
while (a != b) {
a = a == null ? headB : a.next;
b = b == null ? headA : b.next;
}
return a;
}
Remove Nth Node From End of List
移除链表中的倒数第n项
分析:通过快慢指针,使两指针间距离为n,当快指针到终点时,慢指针在倒数第n项。
private ListNode removeNthFromEnd(ListNode head, int n) {
if (head == null) return null;
ListNode lastNode = head;
for (int i = 1; i < n; i++) {
lastNode = lastNode.next;
if (lastNode == null) return null;//不存在倒数第n项
}
ListNode preNode = null;//倒数第n+1项
ListNode curNode = head;//倒数第n项
while (lastNode.next != null) {//快指针到终点了,各点就位
lastNode = lastNode.next;
preNode = curNode;
curNode = curNode.next;
}
if (preNode==null){//移除操作
head=curNode.next;
}else {
preNode.next = curNode.next;
}
return head;
}
Reverse Linked List
翻转链表
分析:
node指向head
将node的next赋给head,node向后移动
直到node为空
//方法一
private ListNode reverseList(ListNode head) {
ListNode pre = null;
while (head != null) {
ListNode temp = head.next;//存
head.next = pre;//连
pre = head;//移
head = temp;//换
}
return pre;
}
//方法二
private ListNode reverseList(ListNode head) {
return reverse(head, null);
}
private ListNode reverse(ListNode curNode, ListNode preNode) {
if (curNode == null) {
return preNode;
}
ListNode temp = curNode.next;
curNode.next = preNode;
return reverse(temp, curNode);
}
Remove Linked List Elements
移除指定值的项
分析:遍历比较移除
private ListNode removeElements(ListNode head, int val) {
ListNode node = head;
ListNode pre = null;
while (node != null) {
if (node.val == val) {
if (pre != null) {//删除操作
pre.next = node.next;
} else {
head = node.next;
}
node = node.next;
} else {
pre = node;
node = node.next;
}
}
return head;
}
Odd Even Linked List
将链表中偶数位的项移到奇数位项的后面
分析:定义两指针,一个走奇数位一个走偶数位,最后将两指针连接
private ListNode oddEvenList(ListNode head) {
if (head == null) return null;
ListNode odd = head;//奇
ListNode even = head.next;//偶
ListNode evenHead = head.next;
while (even != null && even.next != null) {
odd.next = odd.next.next;//奇偶位重新连接
even.next = even.next.next;
odd = odd.next;//指针移到下一位
even = even.next;
}
odd.next = evenHead;
return head;
}
Palindrome Linked List
判断链表是否回文
分析:将链表从中间截断,若后半部与前半部相同则回文成立
private boolean isPalindrome(ListNode head) {
ListNode fast = head;
ListNode slow = head;
while (fast != null && fast.next != null) {//慢走1快走2,当快到终点时,慢在中点
fast = fast.next.next;
slow = slow.next;
}
if (fast != null) {//长度为奇数
slow = slow.next;
}
slow = reverse(slow);//将后半部翻转
fast = head;
while (slow != null) {
if (fast.val != slow.val) {
return false;
}
fast = fast.next;
slow = slow.next;
}
return true;
}
private ListNode reverse(ListNode head) {
ListNode pre = null;
while (head != null) {
ListNode temp = head.next;
head.next = pre;
pre = head;
head = temp;
}
return pre;
}
Add Operation - Doubly Linked List
新建节点,指向其前后
将其前后节点,指向新建节点
Delete Operation - Doubly Linked List
将要删除节点的前后节点,相互连接,切断删除点的路径
Merge Two Sorted Lists
将两排序的链表合并
分析:两指针分别遍历两链表,判断项大小后连接
private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
ListNode head = null;
ListNode node = null;
ListNode temp;//较小项
while (l1 != null || l2 != null) {
if (l1 != null && l2 != null) {
if (l1.val < l2.val) {
temp = l1;
l1 = l1.next;
} else {
temp = l2;
l2 = l2.next;
}
} else if (l1 == null) {
temp = l2;
l2 = l2.next;
} else {
temp = l1;
l1 = l1.next;
}
if (head == null) {
head=temp;
node=temp;
}else {
node.next = temp;
node = node.next;
}
}
return head;
}
Add Two Numbers
将两链表各项相加,前项代表低位
分析:遍历两链表,求两项和满十进1,进入下两项和
private ListNode addTwoNumbers(ListNode l1, ListNode l2) {
ListNode head = null;
if (l1 == null || l2 == null) {//其中一链表为空,结果为非空链表
if (l1 == null) {
head = l2;
} else {
head = l1;
}
return head;
}
ListNode node = null;
int sum = 0;
while (l1 != null || l2 != null || sum != 0) {
if (l1 != null) {//预防两链表长度不同
sum += l1.val;
l1 = l1.next;
}
if (l2 != null) {
sum += l2.val;
l2 = l2.next;
}
if (head == null) {
head = new ListNode(sum % 10);
node = head;
} else {
node.next = new ListNode(sum % 10);
node = node.next;
}
sum = sum / 10;
}
return head;
}
Flatten a Multilevel Doubly Linked List
节点除previous 、next指针外,存在child指针。将给出的链表抹平。
分析:对存在child指针的节点,先存储next,将child赋next,再将child终点的next赋之前存储的next
private Node flatten(Node head) {
if (head == null) {
return null;
}
Node node = head;
while (node != null) {
if (node.child != null) {
Node temp = node.next;//存next
node.next = flatten(node.child);//将child抹平返回头节点
node.child = null;//删除child
node.next.prev = node;//连接抹平的点
while (node.next != null) {//移动到新连接链表的末尾
node = node.next;
}
node.next = temp;//将之前存储的next连接
if (temp != null) {
temp.prev = node;
}
} else {
node = node.next;
}
}
return head;
}
Copy List with Random Pointer
深度复制具有随机指针的链表。
分析:遍历链表新建节点,新建的节点无法将随机指针复制。通过Map将新旧节点存储,二次遍历时,将随机指针赋值。
public RandomListNode copyRandomList(RandomListNode head) {
HashMap hashMap = new HashMap();
RandomListNode node = head;
while (node != null) {//旧节点做key
hashMap.put(node, new RandomListNode(node.label));
node = node.next;
}
node = head;
while (node != null) {//复制随机指针
hashMap.get(node).next = hashMap.get(node.next);
hashMap.get(node).random = hashMap.get(node.random);
node = node.next;
}
return hashMap.get(head);
}
Rotate List
将链表从倒数第k项旋转
分析:k可能超出链表长度,所以需要取余。
private ListNode rotateRight(ListNode head, int k) {
if (head == null || head.next == null) {
return head;
}
ListNode node = head;
int len = 1;
while (node.next != null) {//求链表长度,node指到结尾
node = node.next;
len++;
}
node.next = head;//将链表连成环
k = k % len;
k = len - k;
while (k-- > 0) {//将倒数第k项,变为正数,移到对应点
node = node.next;
}
head = node.next;
node.next = null;//切断环
return head;
}