链表
1.导论
- 链表分类:单链表、双向链表、循环链表
- 链表考查点:
链表的增删改查
虚拟头结点
链表反转
链表相交
是否有环
如果有环,环的入口
2.编程题
2.1 203.移除链表元素
给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点 。
列表中的节点数目在范围 [0, 104] 内
1 <= Node.val <= 50
0 <= val <= 50
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//递归
class Solution {
public ListNode removeElements(ListNode head, int val) {
if (head == null) {
return head;
}
head.next = removeElements(head.next, val);
return head.val == val ? head.next : head;
}
}
//迭代
class Solution {
public ListNode removeElements(ListNode head, int val) {
/*
思路:
1.创建一个前置头节点
2.循环遍历链表,如果节点值和val相等,进行删除,否则继续遍历
3.返回新链表的头节点
*/
ListNode preHead = new ListNode(-1);
preHead.next = head;
ListNode cur = preHead;
while(cur.next != null){
if(cur.next.val == val){
cur.next = cur.next.next;
}else{
cur = cur.next;
}
}
return preHead.next;
}
}
2.2 707.设计链表
设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。
在链表类中实现这些功能:
get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val 的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。
所有val值都在 [1, 1000] 之内。
操作次数将在 [1, 1000] 之内。
请不要使用内置的 LinkedList 库
//节点类
public class ListNode {
int val;
ListNode next;
ListNode(int x) { val = x; }
}
class MyLinkedList {
int size;//记录链表的长度
ListNode head;
public MyLinkedList() {
this.size = 0;
this.head = new ListNode(0);
}
public int get(int index) {
if(index < 0 || index>=size) return -1;
ListNode cur = head;
for(int i=0;i<=index;++i){
cur = cur.next;
}
return cur.val;
}
public void addAtHead(int val) {
addAtIndex(0,val);
}
public void addAtTail(int val) {
addAtIndex(size,val);
}
public void addAtIndex(int index, int val) {
if(index > size) return;
if(index < 0) index = 0;
++size;
ListNode dummy = new ListNode(-1);
dummy = head;
ListNode cur = dummy;
for(int i=0;i<index;++i){
cur = cur.next;
}
ListNode temp = cur.next;
ListNode newNode = new ListNode(val);
cur.next = newNode;
newNode.next = temp;
}
public void deleteAtIndex(int index) {
if(index <0 || index >=size) return;
size--;
ListNode dummy = new ListNode(-1);
dummy = head;
ListNode cur = dummy;
for(int i=0;i<index;++i){
cur = cur.next;
}
cur.next = cur.next.next;
}
}
/**
* Your MyLinkedList object will be instantiated and called as such:
* MyLinkedList obj = new MyLinkedList();
* int param_1 = obj.get(index);
* obj.addAtHead(val);
* obj.addAtTail(val);
* obj.addAtIndex(index,val);
* obj.deleteAtIndex(index);
*/
2.3 203.反转链表
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。
链表中节点的数目范围是 [0, 5000]
-5000 <= Node.val <= 5000
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//递归
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
//迭代
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null){
return head;
}
ListNode cur = head;
ListNode next = null;
ListNode reverseNode = new ListNode(-1);
while(cur != null){
next = cur.next; //先保存当前结点的下一个结点,后面需要使用
cur.next = reverseNode.next;//将cur的下一个结点指向新的链表的最前端
reverseNode.next = cur;//将cur链接到新的链表上
cur = next;//让cur后移
}
return reverseNode.next;//返回头指针0
}
}
2.4 19.删除链表的倒数第N个结点
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//使用栈
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;
}
while(n-- > 0){
stack.pop();
}
ListNode pre = stack.peek();
pre.next = pre.next.next;
return dummy.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;
return dummy.next;
}
}
//计算链表长度
class Solution {
public ListNode removeNthFromEnd(ListNode head, int n) {
ListNode dummy = new ListNode(0, head);
int length = getLength(head);
ListNode cur = dummy;
for (int i = 1; i < length - n + 1; ++i) {
cur = cur.next;
}
cur.next = cur.next.next;
ListNode ans = dummy.next;
return ans;
}
public int getLength(ListNode head) {
int length = 0;
while (head != null) {
++length;
head = head.next;
}
return length;
}
}
2.5 160.相交链表
给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表没有交点,返回 null 。
listA 中节点数目为 m
listB 中节点数目为 n
0 <= m, n <= 3 * 104
1 <= Node.val <= 105
0 <= skipA <= m
0 <= skipB <= n
如果 listA 和 listB 没有交点,intersectVal 为 0
如果 listA 和 listB 有交点,intersectVal == listA[skipA + 1] == listB[skipB + 1]
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//哈希集合
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode cur = headA;
Set<ListNode> set = new HashSet();
while(cur != null){
set.add(cur);
cur= cur.next;
}
cur = headB;
while(cur != null){
if(!set.add(cur)){
return cur;
}
cur=cur.next;
}
return null;
}
}
//双指针
public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
if(headA == null || headB == null){
return null;
}
ListNode curA = headA;
ListNode curB = headB;
while(curA != curB){
curA = curA==null ? headB : curA.next;
curB = curB == null ? headA : curB.next;
}
return curA;
}
}
2.6 141.环形链表
给定一个链表,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。
如果链表中存在环,则返回 true 。 否则,返回 false
链表中节点的数目范围是 [0, 104]
-105 <= Node.val <= 105
pos 为 -1 或者链表中的一个 有效索引
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//哈希集合
public class Solution {
public boolean hasCycle(ListNode head) {
Set<ListNode> visited = new HashSet<ListNode>();
while(head != null){
if(!visited.add(head)){
return true;
}
head = head.next;
}
return false;
}
}
//快慢指针
public class Solution {
public boolean hasCycle(ListNode head) {
if(head == null || head.next == null){
return false;
}
ListNode slow = head;
ListNode fast = head.next;
while(slow != fast){
if(fast == null || fast.next == null){
return false;
}
slow = slow.next;
fast = fast.next.next;
}
return true;
}
}
2.7 142.环形链表II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:你是否可以使用 O(1) 空间解决此题?
链表中节点的数目范围在范围 [0, 104] 内
-105 <= Node.val <= 105
pos 的值为 -1 或者链表中的一个有效索引
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
//哈希集合
public class Solution {
public ListNode detectCycle(ListNode head) {
ListNode pos = head;
Set<ListNode> visited = new HashSet<ListNode>();
while (pos != null) {
if (visited.contains(pos)) {
return pos;
} else {
visited.add(pos);
}
pos = pos.next;
}
return null;
}
}
//快慢指针
public class Solution {
public ListNode detectCycle(ListNode head) {
if(head == null || head.next == null){
return null;
}
ListNode slow = head;
ListNode fast = head;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
if(slow == fast){//说明有环
ListNode temp = head;
while(slow != temp){//temp和slow相遇的结点就是环的入口
slow = slow.next;
temp = temp.next;
}
return temp;
}
}
return null;
}
}
2.8 2095. 删除链表的中间节点
给你一个链表的头节点 head 。删除 链表的 中间节点 ,并返回修改后的链表的头节点 head 。
长度为 n 链表的中间节点是从头数起第 ⌊n / 2⌋ 个节点(下标从 0 开始),其中 ⌊x⌋ 表示小于或等于 x 的最大整数。
对于 n = 1、2、3、4 和 5 的情况,中间节点的下标分别是 0、1、1、2 和 2 。
链表中节点的数目在范围 [1, 105] 内
1 <= Node.val <= 105
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
//shuangzhizhen
class Solution {
public ListNode deleteMiddle(ListNode head) {
if(head == null || head.next == null) return null;
ListNode slow = head;
ListNode fast = head.next.next;
while(fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
slow.next = slow.next.next;
return head;
}
}
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode deleteMiddle(ListNode head) {
ListNode dummy = new ListNode(-1);//设置前驱结点
dummy.next = head;
ListNode cur = dummy;
int length = getLength(head);
for(int i=0;i<length/2;++i){
cur = cur.next;//查找中间结点的前一个结点
}
cur.next = cur.next.next;
return dummy.next;
}
//计算链表长度
private int getLength(ListNode head){
int length = 0;
while(head != null){
head = head.next;
++length;
}
return length;
}
}