五种常见的链表操作(Java实现)

277 阅读5分钟

单链表反转

个人博客地址

递归法:递归法是从最后一个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

  1. 程序到达Node newHead = reverse(head.next);时进入递归。
  2. 我们假设此时递归到了3结点,此时head=3结点,temp=3结点.next(实际上是4结点)。
  3. 执行Node newHead = reverse(head.next);传入的head.next是4结点,返回的newHead是4结点。 接下来就是弹栈过程了
  4. 程序继续执行 temp.next = head就相当于4->3
  5. 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内的地址换成前一个节点的,但为了防止后面链表的丢失,在每次换之前需要先创建个指针指向下一个节点。

以上是关于链表常见的操作,如有错误欢迎指正。


主题列表:juejin, github, smartblue, cyanosis, channing-cyan, fancy, hydrogen, condensed-night-purple, greenwillow

贡献主题:github.com/xitu/juejin…

theme: juejin highlight: