算法学习系列(四):链表

61 阅读4分钟

链表结构

下面文章中的算法都会基于这个链表结构

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;
	}

	@Override
	public String toString() {
		return "ListNode{" + "val=" + val + '}';
	}
}

工具类

用于生成链表结构,头插、尾插

public class LinkedListUtils {

	ListNode head;
	ListNode tail;
	/**
	 * 头插
	 * @param val
	 */
	public void addFirst(int val){
		if(head == null){
			head = new ListNode(val);
			return;
		}
		//新建一个node
		ListNode node = new ListNode(val);
		//指向头节点
		node.next = head;
		//换头
		head = node;
	}

	/**
	 * 尾插
	 * @param val
	 */
	public void addLast(int val){
		if(head == null && tail == null){
			head = new ListNode(val);
			tail = head;
			return;
		}
		tail.next = new ListNode(val);
		tail = tail.next;
	}

	public void addLast(ListNode node){
		if(head == null && tail == null){
			head = node;
			tail = node;
			return;
		}
		tail.next = node;
		tail = tail.next;
	}

	/**
	 * 打印
	 */
	public void printList(){
		ListNode tmp = head;
		while (tmp != null){
			System.out.print(tmp.val);
			tmp = tmp.next;
		}
	}
}

判断一个链表是否有环

判断一个链表是否有环,如果有环,返回入环节点,如果没有返回null
方法一:使用HashSet结构,利用HashSet不可重复的特性
方法二:使用快慢指针

使用HashSet判断是否有环

import org.junit.Test;
import java.util.HashSet;

public class LinkListTest {

	@Test
	public void test(){
		LinkedListUtils ringLinkedList = new LinkedListUtils();
		ringLinkedList.addLast(1);
		ringLinkedList.addLast(2);
		ringLinkedList.addLast(3);
		ListNode ringNode = new ListNode(4);
		ringLinkedList.addLast(5);
		ringLinkedList.addLast(ringNode);
		ringLinkedList.addLast(6);
        //放开注释为有环链表
		//ringLinkedList.addLast(ringNode);
		ListNode result = judgeLoopLinkedList(ringLinkedList.head);
		System.out.println(result == null? "null" : result.toString());
	}

	private ListNode judgeLoopLinkedList(ListNode head){
		HashSet<ListNode> nodeSet = new HashSet<>();
		ListNode node = head;
		while (node != null){
			if(!nodeSet.add(node)){
				return node;
			}
			node = node.next;
		}
		return null;
	}
}

使用快慢指针判断是否有环

1)、快指针走两步,慢指针走一步
2)、相遇后,快指针回到头节点
3)、然后快指针走一步,慢指针也走一步,相遇节点为第一个相交节点

import org.junit.Test;

public class LinkListTest {

	@Test
	public void test(){
		LinkedListUtils ringLinkedList = new LinkedListUtils();
		ringLinkedList.addLast(1);
		ringLinkedList.addLast(2);
		ringLinkedList.addLast(3);
		ListNode ringNode = new ListNode(4);
		ringLinkedList.addLast(5);
		ringLinkedList.addLast(ringNode);
		ringLinkedList.addLast(6);
		//放开注释为有环链表
        //ringLinkedList.addLast(ringNode);
		ListNode result = judgeLoopLinkedList(ringLinkedList.head);
		System.out.println(result == null? "null" : result.toString());
	}

	private ListNode judgeLoopLinkedList(ListNode head){
		if(head == null || head.next == null || head.next.next == null){
			return null;
		}
		ListNode S = head.next;//慢指针走一步
		ListNode F = head.next.next;//快指针走两步
		while (S != F){
			if(F.next == null || F.next.next == null){
				return null;
			}
			F = F.next.next;
			S = S.next;
		}
		//到这里表示成环,快指针回到头节点
		F = head;
		while (F != S){
			//快慢指针都走一步
			F = F.next;
			S = S.next;
		}
		//快慢指针相遇
		return F;
	}
}

找相交节点

现有两个链表,不知道是否是带环链表,判断两个链表是否相交,如果相交返回相交节点,如果不相交返回null

相交情况分析

不管是有环还是无环,两个链表相交之可能是以下几种情况:

  • 无环相交
  • 有环相交,且入环节点相等
  • 有环相交,但入环节点不相等

解决方案

  • 判断是有环还是无环
  • 如果是无环:分别找到end1、end2,并记录length1、length2,如果end1 == end2,length较大的链表先走差值步,然后同时走,找到相等的节点,就是交点。如果end1 != end2,不相交
import org.junit.Test;

public class LinkedListTest {

    @Test
    public void test(){
        ListNode crossNode = new ListNode(3);

        LinkedListUtils linked1 = new LinkedListUtils();
        linked1.addLast(1);
        linked1.addLast(2);
        linked1.addLast(crossNode);
        linked1.addLast(4);
        linked1.addLast(5);
        linked1.addLast(6);
        //linked1.printList();

        LinkedListUtils linked2 = new LinkedListUtils();
        linked2.addLast(7);
        linked2.addLast(8);
        linked2.addLast(9);
        linked2.addLast(crossNode);
        //不可以再添加节点,会导致linked1结构发生变化
        //linked2.addLast(10);
        //linked1.printList();
        //linked2.printList();
        ListNode node = findCrossNodeWithOutLoop(linked1.head, linked2.head);
        System.out.println(node == null?null:node.toString());
    }

    /**
     * 无环相交情况
     * @param head1
     * @param head2
     * @return
     */
    private ListNode findCrossNodeWithOutLoop(ListNode head1, ListNode head2){
        if(head1 == null || head2 == null){
            return null;
        }
        int n = 0;
        ListNode end1 = head1;
        ListNode end2 = head2;
        while (end1.next != null){
            n++;
            end1 = end1.next;
        }
        while (end2.next != null){
            n--;
            end2 = end2.next;
        }
        //终点不相等,表示没有交点
        if(end1 != end2){
            return null;
        }
        end1 = n > 0 ? head1 : head2;
        end2 = (end1 == head1) ? head2 : head1;
        n = Math.abs(n);
        while (n != 0){
            n--;
            end1 = end1.next;
        }
        while (end1 != end2){
            end1 = end1.next;
            end2 = end2.next;
        }
        return end1;
    }
}

  • 如果是有环:判断入环节点是否相等,如果相等,按无环处理(入环节点作为end1、end2)。 如果入环节点不相等,然后一个节点走一圈,如果相遇表示相交,否则不相交
import org.junit.Test;

public class LinkedListTest {

    @Test
    public void test(){
        ListNode crossNode1 = new ListNode(4);
        ListNode crossNode2 = new ListNode(9);

        LinkedListUtils linked1 = new LinkedListUtils();
        linked1.addLast(1);
        linked1.addLast(2);
        linked1.addLast(3);
        linked1.addLast(crossNode1);
        //注释下面一行,放开下面三行注释,为不相交的情况
        linked1.addLast(crossNode2);
        linked1.addLast(6);
        linked1.addLast(crossNode1);

        LinkedListUtils linked2 = new LinkedListUtils();
        linked2.addLast(7);
        linked2.addLast(8);
        linked2.addLast(crossNode2);
        //linked2.addLast(10);
        //linked2.addLast(11);
        //linked2.addLast(crossNode2);
        ListNode cross1 = judgeLoopNode(linked1.head);
        ListNode cross2 = judgeLoopNode(linked2.head);
        ListNode node = findCrossNodeLoop(linked1.head, cross1, linked2.head, cross2);
        System.out.println(node == null?null:node.toString());
    }

    /**
     * 判断是否有环
     * @param head
     * @return
     */
    private ListNode judgeLoopNode(ListNode head){
        if(head == null || head.next == null || head.next.next == null){
            return null;
        }
        ListNode F = head.next.next;
        ListNode S = head.next;
        while (F != S){
            if(F.next == null || F.next.next == null){
                return null;
            }
            F = F.next.next;
            S = S.next;
        }
        F = head;
        while (F != S){
            F = F.next;
            S = S.next;
        }
        return F;
    }

    /**
     * 有环相交情况
     * @param head1
     * @param head2
     * @return
     */
    private ListNode findCrossNodeLoop(ListNode head1, ListNode cross1, ListNode head2, ListNode cross2){
        ListNode end1 = null;
        ListNode end2 = null;
        if(cross1 == cross2){
            end1 = head1;
            end2 = head2;
            int n = 0;
            while (end1.next != cross1){
                n++;
                end1 = end1.next;
            }
            while (end2.next != cross2){
                n--;
                end2 = end2.next;
            }
            end1 = n > 0 ? head1 : head2;
            end2 = (end1 == head1) ? head2 : head1;
            while (n != 0){
                end1 = end1.next;
                n--;
            }
            while (end1 != end2){
                end1 = end1.next;
                end2 = end2.next;
            }
            return end1;
        }else {
            end1 = cross1.next;
            while (end1.next != cross1){
                if(end1 == cross2){
                    return cross2;
                }
                end1 = end1.next;
            }
            return null;
        }
    }
}

题解

import org.junit.Test;

public class LinkedListTest {

    @Test
    public void test(){
        ListNode crossNode1 = new ListNode(4);
        ListNode crossNode2 = new ListNode(9);

        LinkedListUtils linked1 = new LinkedListUtils();
        linked1.addLast(1);
        linked1.addLast(2);
        linked1.addLast(3);
        linked1.addLast(crossNode1);
        linked1.addLast(crossNode2);
        linked1.addLast(6);
        linked1.addLast(crossNode1);

        LinkedListUtils linked2 = new LinkedListUtils();
        linked2.addLast(7);
        linked2.addLast(8);
        linked2.addLast(crossNode2);
        //获取入环节点,如果不是环返回null
        ListNode cross1 = judgeLoopNode(linked1.head);
        ListNode cross2 = judgeLoopNode(linked2.head);
        ListNode node = null;
        //无环
        if(cross1 == null && cross2 == null){
            node = findCrossNodeWithOutLoop(linked1.head, linked2.head);
        }
        //有环
        if(cross1 != null && cross2 != null){
            node = findCrossNodeLoop(linked1.head, cross1, linked2.head, cross2);
        }
        //一个有环,一个没环,肯定不相交
        System.out.println(node == null?null:node.toString());
    }

    /**
     * 判断是否有环
     * @param head
     * @return
     */
    private ListNode judgeLoopNode(ListNode head){
        if(head == null || head.next == null || head.next.next == null){
            return null;
        }
        ListNode F = head.next.next;
        ListNode S = head.next;
        while (F != S){
            if(F.next == null || F.next.next == null){
                return null;
            }
            F = F.next.next;
            S = S.next;
        }
        F = head;
        while (F != S){
            F = F.next;
            S = S.next;
        }
        return F;
    }

    /**
     * 无环相交情况
     * @param head1
     * @param head2
     * @return
     */
    private ListNode findCrossNodeWithOutLoop(ListNode head1, ListNode head2){
        if(head1 == null || head2 == null){
            return null;
        }
        int n = 0;
        ListNode end1 = head1;
        ListNode end2 = head2;
        while (end1.next != null){
            n++;
            end1 = end1.next;
        }
        while (end2.next != null){
            n--;
            end2 = end2.next;
        }
        //终点不相等,表示没有交点
        if(end1 != end2){
            return null;
        }
        end1 = n > 0 ? head1 : head2;
        end2 = (end1 == head1) ? head2 : head1;
        n = Math.abs(n);
        while (n != 0){
            n--;
            end1 = end1.next;
        }
        while (end1 != end2){
            end1 = end1.next;
            end2 = end2.next;
        }
        return end1;
    }


    /**
     * 有环相交情况
     * @param head1
     * @param cross1
     * @param head2
     * @param cross2
     * @return
     */
    private ListNode findCrossNodeLoop(ListNode head1, ListNode cross1, ListNode head2, ListNode cross2){
        ListNode end1 = null;
        ListNode end2 = null;
        if(cross1 == cross2){
            end1 = head1;
            end2 = head2;
            int n = 0;
            while (end1.next != cross1){
                n++;
                end1 = end1.next;
            }
            while (end2.next != cross2){
                n--;
                end2 = end2.next;
            }
            end1 = n > 0 ? head1 : head2;
            end2 = (end1 == head1) ? head2 : head1;
            while (n != 0){
                end1 = end1.next;
                n--;
            }
            while (end1 != end2){
                end1 = end1.next;
                end2 = end2.next;
            }
            return end1;
        }else {
            end1 = cross1.next;
            while (end1.next != cross1){
                if(end1 == cross2){
                    return cross2;
                }
                end1 = end1.next;
            }
            return null;
        }
    }
}