单链表反转
递归法:递归法是从最后一个Node开始,在弹栈的过程中将指针顺序置换的
定义Node节点:
public static class Node {
public int date;
public Node next;
}
反转方法如下:
public Node reverse(Node head) {
if (head == null || head.next == null)
return head;
Node temp = head.next;
Node newHead = reverse(head.next);
temp.next = head;
head.next = null;
return newHead;
}
递归过程: 例如链表为1->2->3->4。反转后为 4->3->2->1
- 程序到达Node newHead = reverse(head.next);时进入递归。
- 我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)。
- 执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。 接下来就是弹栈过程了
- 程序继续执行 temp.next = head就相当于4->3
- head.next = null 即把3结点指向4结点的指针断掉。 返回新链表的头结点newHead
遍历法:
//如何实现链表的反转
public static Node reverseList(Node node) {
Node pre = null;
Node temp = null;
while (node != null) {
temp = node.next;
node.next = pre;
pre = node;
node = temp;
}
return pre;
}
- 准备两个空结点, pre用来保存先前结点,temp用来做临时变量
- 在头结点node遍历的时候此时为1结点
- next =1结点.next(2结点)
- 1结点.next=pre(null)
- pre = 1结点, node = 2结点
- 进入下一次循环
- node=2结点
- next = 2结点.next(3结点)
- 2结点.next=pre(1结点) 就是即完成2->1
- pre = 2结点 node = 3结点 进行循环...
链表中环的检测
1.快慢指针法
- 有两个指针fast和slow,同时从头结点开始往下遍历链表中的所有节点。
- slow是慢指针,一次遍历一个节点。
- fast是快指针,一次遍历两个节点。
- 如果链表中没有环,fast和slow会先后遍历完所有的节点。
- 如果链表中有环,fast和slow则会先后进入环中,一直循环,并一定会在在某一次遍历中相遇。
- 因此,只要发现fast和slow相遇了,就可以判定链表中存在环。 如下图所示:slow为慢指针,fast为快指针
实现代码如下:
public static boolean checkCircle(Node head) {
if (head == null)
return false;
Node fast = head.next;
Node slow = head;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
if (slow == fast)
return true;
}
return false;
}
2.哈希表法
遍历链表中所有的节点,并将所有遍历过的节点信息保存下来。如果某个节点的信息出现了两次,则存在环。可以利用HashSet,利用HashSet值不能重复的特点,我们遍历所有结点并在哈希表中存储每个结点的引用。如果当前结点为空结点 null(即已检测到链表尾部的下一个结点),那么我们已经遍历完整个链表,并且该链表不是环形链表。如果当前结点的引用已经存在于哈希表中,那么返回 true(即该链表为环形链表)。
public boolean checkCircle(Node head) {
//创建一个set集合
Set<Node> nodes = new HashSet<>();
while (head != null) {
if (nodes.contains(head)) {
return true;
} else {
nodes.add(head);
}
head = head.next;
}
return false;
}
两个有序的链表合并
因为两个链表都是有序链表(递增),因此可以找出两个链表中的最小元素,即比较两个链表表头的元素。我们可以利用两个指针——指向两个链表的最小节点,每次比较两个指针所指向节点的值,将值比较小的节点加到新的链表中,然后更新指针,如此循环往复直到到达一个链表的尾部。最后,还需要将另一个链表的剩余部分(如果存在的话)添加到新的链表的尾部。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public ListNode mergeTwoLists(ListNode l1,ListNode l2){
ListNode list=new ListNode(0);
ListNode p=list;
while(l1!=null && l2!= null){
if(l1.val<l2.val){
p.next=l1;
l1=l1.next;//l1向后移
}
else{
p.next=l2;
l2=l2.next;//l2向后移
}
p=p.next;//p后移,进入下一次循环
}
//将另一个链表的剩余部分(如果存在的话)添加到新的链表的尾部。
p.next=l1!=null ? l1 : l2;
return list.next;;
}
删除链表倒数第 n个结点
给定一个链表: 1->2->3->4->5, 和 n = 3. 当删除了倒数第二个节点后,链表变为 1->2->4->5. 为了只遍历一次就能找到链表的倒数第n个节点,可以定义两个指针。第一个指针我们可以使用两个指针,一个指针fast先走n步,然后另一个指针slow从头结点开始,找到要删除结点的前一个结点,这样就可以完成结点的删除了。
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode(int x) { val = x; }
* }
*/
public ListNode deleteLastNth(ListNode head, int n) {
ListNode fast = head;
ListNode slow = head;
//fast移n步,
for (int i = 0; i < n; i++) {
fast = fast.next;
}
//如果fast为空,表示删除的是头结点
if (fast == null)
return head.next;
while (fast.next != null) {
fast = fast.next;
slow = slow.next;
}
//这里最终slow不是倒数第n个节点,他是倒数第n+1个节点,
//他的下一个结点是倒数第n个节点,所以删除的是他的下一个结点
slow.next = slow.next.next;
return head;
}
求链表的中间结点
定义两个指针fast和slow。slow一次遍历一个节点,fast一次遍历两个节点,由于fast的速度是slow的两倍,所以当fast遍历完链表时,slow所处的节点就是链表的中间节点。
public static Node findMiddleNode(Node node) {
if (node == null)
return null;
Node fast = node;
Node slow = node;
while (fast != null && fast.next != null) {
fast = fast.next.next;
slow = slow.next;
}
return slow;
}
总结
练习链表的一些体会:
1、 函数中需要移动链表时,最好新建一个指针来移动,以免更改原始指针位置。
2、 单链表有带头节点和不带头结点的链表之分,一般做题默认头结点是有值的。
3、 链表的内存时不连续的,一个节点占一块内存,每块内存中有一块位置(next)存放下一节点的地址(这是单链表为例)。
3、 链表中找环的思想:创建两个指针一个快指针一次走两步一个慢指针一次走一步,若相遇则有环。
4、 链表找倒数第n个节点思想:创建两个指针,第一个先走n步然后两个在一起走。第一个走到最后,第二个指针指向倒数第n位置。
5、 反向链表思想:从前往后将每个节点的指针反向,即.next内的地址换成前一个节点的,但为了防止后面链表的丢失,在每次换之前需要先创建个指针指向下一个节点。
以上是关于链表常见的操作,如有错误欢迎指正。