链表中环的问题
题目:LeetCode141,LeetCode142
思路:
- 第一种思路是利用集合 和map方法,如果发现了包含节点,说明是环形链表,并且环形链表的入口为包含节点
- 第二种思路是利用双指针,先让快慢指针走,当两个指针相遇时则 说明时环形链表,之后快指针回到头节点,让快指针和慢指针同步走相同步数,如果相遇,则说明相遇的节点为入口节点
解法:
-
解法一 :集合
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 { /** * 有两种方法可以确定环形入口的位置 * 1.通过快慢双指针确定相遇位置之后,让一个指针从头开始和另一指针已相同的方式走,等相遇就说明是入口节点 * 2.确定环的长度和末尾节点,这样我们就可以通过寻找链表倒数环的长度的节点就是我们要寻找的入口节点(问题退化) * 这里实现第2种方法 * @param head * @return */ public ListNode detectCycle(ListNode head) { //1.确定环的长度, ListNode fast = head; ListNode slow = head; boolean isNotCycle = true; while (fast!=null&&fast.next!=null){ slow = slow.next; fast = fast.next.next; if (slow==fast){ //相等,退出循环 isNotCycle = false; break; } } //并未相遇,说明不是环形链表 if (isNotCycle){ return null; } //slow固定不动,fast从头开始走向slow,则该走的长度就是环形长度 //len为环形的长度 int len = 0; fast = head; //一定能相等,因为前面已经确定是环形链表 while (fast!=slow){ fast = fast.next; len++; } //简化成寻找链表的倒数第k个节点 fast = head; //先让fast走len部 while (len>0){ fast = fast.next; len--; } ListNode fast2 = head; //同时移动,直到fast1和fast2相等,其实本质上就是方法一, while (fast!=fast2){ fast2 = fast2.next; fast = fast.next; } return fast2; } }
public class Solution { public ListNode detectCycle(ListNode head) { if (head == null) { return null; } ListNode slow = head, fast = head; while (fast != null) { slow = slow.next; if (fast.next != null) { fast = fast.next.next; } else { return null; } if (fast == slow) { ListNode ptr = head; while (ptr != slow) { ptr = ptr.next; slow = slow.next; } return ptr; } } return null; } }
双向链表设计
题目:主要解决的是双向链表的增删改查
定义:每个节点都包含指向前驱节点和后驱节点的指针
结构:
class ListNode {
int val;
ListNode next;
ListNode prev;
ListNode() {
}
ListNode(int val) {
this.val = val;
}
ListNode(int val, ListNode next,ListNode prev) {
this.val = val;
this.next = next;
this.prev = prev;
}
}
思路:
- 不考虑查询,因为双向链表的优点就是查询方便,具体查询看前面的单向链表
- 主要 考虑插入和删除,而且头节点,尾节点的插入删除和中间节点的差别都会有点大
查询:略
插入:
-
头部,尾部插入
// 在头部插入一个节点的方法 public void insertAtHead(int data) { Node newNode = new Node(data); // 创建一个新节点 if (head == null) { // 如果链表为空 head = newNode; // 新节点成为头节点 } else { // 如果链表不为空 head.prev = newNode; // 头节点的前指针指向新节点 newNode.next = head; // 新节点的后指针指向头节点 } head = newNode; // 新节点成为头节点 } // 在尾部插入一个节点的方法 public void insertAtTail(int data) { Node newNode = new Node(data); // 创建一个新节点 if (head == null) { // 如果链表为空 head = newNode; // 新节点成为头节点 } else { // 如果链表不为空 tail.next = newNode; // 尾节点的后指针指向新节点 newNode.prev = tail; // 新节点的前指针指向尾节点 } tail = newNode; // 新节点成为尾节点 }
-
中间插入
// 在中间插入一个节点的方法,假设给定了要插入的位置(从0开始计数) public void insertAtMiddle(int data, int position) { Node newNode = new Node(data); // 创建一个新节点 if (head == null) { // 如果链表为空 head = newNode; // 新节点成为头节点 tail = newNode; // 新节点也成为尾节点 } else if (position == 0) { // 如果要插入的位置是头部 insertAtHead(data); // 调用在头部插入的方法 } else { Node current = head; // 从头开始遍历链表 int count = 0; // 记录当前遍历到的位置 while (current != null && count < position - 1) { current = current.next; // 移动到下一个节点 count++; // 增加位置计数器 } if (current == null) { // 如果遍历到了链表末尾,说明要插入的位置超出了范围 System.out.println("Invalid position"); // 打印无效位置信息 } else if (current.next == null) { // 如果遍历到了最后一个节点,说明要插入的位置是尾部 insertAtTail(data); // 调用在尾部插入的方法 } else { Node nextNode = current.next; // 记录当前节点的后一个节点 current.next = newNode; // 当前节点的后指针指向新节点 newNode.prev = current; // 新节点的前指针指向当前节点 newNode.next = nextNode; // 新节点的后指针指向原来的后一个节点 nextNode.prev = newNode; // 原来的后一个节点的前指针指向新节点 } } }
删除:
-
首尾元素的删除
// 删除头节点的方法 public void deleteAtHead() { if (head == null) { // 如果链表为空 System.out.println("The list is empty"); // 打印空链表信息 } else if (head == tail) { // 如果链表只有一个节点 head = null; // 头节点置空 tail = null; // 尾节点也置空 } else { // 如果链表有多个节点 Node nextNode = head.next; // 记录头节点的后一个节点 head.next = null; // 头节点的后指针置空 nextNode.prev = null; // 后一个节点的前指针置空 head = nextNode; // 后一个节点成为头节点 } } // 删除尾节点的方法 public void deleteAtTail() { if (head == null) { // 如果链表为空 System.out.println("The list is empty"); // 打印空链表信息 } else if (head == tail) { // 如果链表只有一个节点 head = null; // 头节点置空 tail = null; // 尾节点也置空 } else { // 如果链表有多个节点 Node prevNode = tail.prev; // 记录尾节点的前一个节点 tail.prev = null; // 尾节点的前指针置空 prevNode.next = null; // 前一个节点的后指针置空 tail = prevNode; // 前一个节点成为尾节点 } }
-
删除中间元素
// 删除中间节点的方法,假设给定了要删除的位置(从0开始计数) public void deleteAtMiddle(int position) { if (head == null) { // 如果链表为空 System.out.println("The list is empty"); // 打印空链表信息 } else if (position == 0) { // 如果要删除的位置是头部 deleteAtHead(); // 调用删除头节点的方法 } else { Node current = head; // 从头开始遍历链表 int count = 0; // 记录当前遍历到的位置 while (current != null && count < position) { current = current.next; // 移动到下一个节点 count++; // 增加位置计数器 } if (current == null) { // 如果遍历到了链表末尾,说明要删除的位置超出了范围 System.out.println("Invalid position"); // 打印无效位置信息 } else if (current.next == null) { // 如果遍历到了最后一个节点,说明要删除的位置是尾部 deleteAtTail(); // 调用删除尾节点的方法 } else { Node prevNode = current.prev; // 记录当前节点的前一个节点 Node nextNode = current.next; // 记录当前节点的后一个节点 current.prev = null; // 当前节点的前指针置空 current.next = null; // 当前节点的后指针置空 prevNode.next = nextNode; // 前一个节点的后指针指向后一个节点 nextNode.prev = prevNode; // 后一个节点的前指针指向前一个节点 } } }