链表

87 阅读3分钟

链表

  • 单向链表:链表的链接方向是单向的
  • 双向链表:链表的链接方向是双向的
  • 链表的访问要通过顺序读取从头部开始
  • java中的LinkedList就是双向链表,可以用于队列的实现:Queue queue = new LinkedList<>(); 单向链表的实现:
    public static class Node {
        public int value;
        public Node next;
        
        public Node (int val){
            this.value = val;
        }
    }

双向链表的实现:

    public static class DoubleNode {
	public int value;
        public DoubleNode last;
	public DoubleNode next;
        
	public DoubleNode(int val) {
		this.value = val;
        }
    }

实现反转单向链表:

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        printLinkedList(head1);
        head1 = reverseList(head1);
        printLinkedList(head1);
    }
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }
    //核心代码
    //记住就行了,简记的方法
    //定义两个Node类型,先设为null。
    //先出现next,后出现pre
    //多一个last(表示链接的上一个)
    //方法需要返回值pre
    //五轮,上面没讲逻辑只为记住,形成自己的记忆方法就行
    public static Node reverseList(Node head) {
        Node pre = null;
        Node next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            pre = head;
            head = next;
        }
        return pre;
    }
    public static void printLinkedList(Node head) {
        System.out.print("Linked List: ");
        while (head != null) {
            System.out.print(head.value + " ");
            head = head.next;
        }
        System.out.println();
    }

实现反转双向链表:

    public static void main(String[] args) {
        DoubleNode head2 = new DoubleNode(1);
        head2.next = new DoubleNode(2);
        head2.next.last = head2;
        head2.next.next = new DoubleNode(3);
        head2.next.next.last = head2.next;
        head2.next.next.next = new DoubleNode(4);
        head2.next.next.next.last = head2.next.next;
        printDoubleLinkedList(head2);
        printDoubleLinkedList(reverseList(head2));
    }
    public static class DoubleNode {
        public int value;
        public DoubleNode last;
        public DoubleNode next;

        public DoubleNode(int data) {
            this.value = data;
        }
    }
    //核心代码
    //记住就行了,简记的方法
    //定义两个Node类型,先设为null。
    //先出现next,后出现pre
    //多一个last(表示链接的上一个)
    //方法需要返回值pre
    //五轮,上面没讲逻辑只为记住,形成自己的记忆方法就行
    //需要当前节点,下一个节点,上一个节点,一个中介
    public static DoubleNode reverseList(DoubleNode head) {
        DoubleNode pre = null;
        DoubleNode next = null;
        while (head != null) {
            next = head.next;
            head.next = pre;
            head.last = next;
            pre = head;
            head = next;
        }
        return pre;
    }
    public static void printDoubleLinkedList(DoubleNode head) {
        System.out.print("Double Linked List: ");
        DoubleNode end = null;
        while (head != null) {
            System.out.print(head.value + " ");
            end = head;
            head = head.next;
        }
        System.out.print("| ");
        while (end != null) {
            System.out.print(end.value + " ");
            end = end.last;
        }
        System.out.println();
    }

面试时链表解题的方法论

  • 笔试,不用太在乎空间复杂度,一切为了时间复杂度
  • 面试,时间复杂度依然是第一位,但是一定要找到空间最省的方法

重要技巧

  • 额外数据结构记录(哈希表、队列、栈、数组等容器)
  • 快慢指针

下面举一个例子:判断一个单向链表是不是回文链表

时间复杂度O(N),空间复杂度O(N)的解法:用栈,所有的压入栈,在从头开始和栈的弹出相比较

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(3);
        head1.next.next.next.next= new Node(2);
        head1.next.next.next.next.next = new Node(1);
        System.out.println(isPalindrome(head1));
        printLinkedList(head1);
    }
    
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }
    
    public static boolean isPalindrome(Node head) {
        Stack<Node> stack = new Stack<>();
        Node cur = head;
        while (cur != null) {
            stack.push(cur);
            cur = cur.next;
        }
        while (head != null) {
            if (head.value != stack.pop().value) {
                return false;
            }
            head = head.next;
        }
        return true;
    }

时间复杂度O(N),空间复杂度O(N/2)的解法:用栈,一半压入栈,在从头开始和栈的弹出相比较

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(3);
        head1.next.next.next.next= new Node(2);
        head1.next.next.next.next.next = new Node(1);
        System.out.println(isPalindrome(head1));
        printLinkedList(head1);
    }
    
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }
    
    public static boolean isPalindrome(Node head) {
	if (head == null || head.next == null) {
		return true;
	}
	Node right = head.next;
	Node cur = head;
	while (cur.next != null && cur.next.next != null) {
		right = right.next;
		cur = cur.next.next;
	}
	Stack<Node> stack = new Stack<Node>();
	while (right != null) {
		stack.push(right);
		right = right.next;
	}
	while (!stack.isEmpty()) {
		if (head.value != stack.pop().value) {
			return false;
		}
		head = head.next;
	}
	return true;
    }

时间复杂度O(N),空间复杂度O(1)的解法:用快慢指针,将中间点之后的反转,一个从头开始,一个从后开始,依次比较,最后在反转回来

    public static void main(String[] args) {
        Node head1 = new Node(1);
        head1.next = new Node(2);
        head1.next.next = new Node(3);
        head1.next.next.next = new Node(3);
        head1.next.next.next.next= new Node(2);
        head1.next.next.next.next.next = new Node(1);
        System.out.println(isPalindrome(head1));
        printLinkedList(head1);
    }
    
    public static class Node {
        public int value;
        public Node next;

        public Node(int data) {
            this.value = data;
        }
    }

    public static boolean isPalindrome(Node head) {
        if(head == null && head.next == null){
            return true;
        }
        Node n1 = head;
        Node n2 = head;
        //快慢指针要以快指针作为循环的跳出点
        while (n2.next != null && n2.next.next != null){
            n1 = n1.next;//如果是奇数,它得到的就是中心的mid点,如果是偶数,它得到的就是中心上一个
            n2 = n2.next.next;
        }
        //n2这个位置尴尬没什么用,而n1代表的是mid属于有用点
        n2 = n1.next;//对n1(中心点)之后的进行翻转,此时n2就是开始点
        n1.next = null;//作为之后前部分是从前往后的链表而后部分是从后往前的链表,交汇的停止点
        //下面进行对mid点之后的部分进行翻转链表
        //需要知道开始点,上一个点,下一个点,一个中介
        //n3就是中介
        Node n3= null;
        //while截止条件是当前点是null
        while (n2 != null){
            n3 = n2.next;
            n2.next = n1;
            n1 = n2;
            n2 = n3;
        }
        //n2的位置也很尴尬,没什么用,所以好复用
        n3 = n1;//此时的n1记录的是最后的结点
        n2 = head;//刚开始的头结点,下面就是从前向后和从后向前比,判断是不是回文数
        while (n1 != null && n2 != null){
            if(n1.value != n2.value){
                return false;
            }
            n1 = n1.next;
            n2 = n2.next;
        }
        //n1 和 n2 位置之后都没什么特殊意义了,都可以直接复用
        //判断完成,将之前反转的链表反转回去
        //当前点是n3之前保存的最后一个结点
        //上一个结点可以自己设置,可以以n3的下一个节点作为当前点,那么n3就是上一个节点
        Node n4 = null;//自己设置的上一个节点
        while (n3 != null){
           n2 = n3.next;
           n3.next = n4;
           n4 = n3;
           n3 = n2;
        }
        //上一个结点可以自己设置,可以以n3的下一个节点作为当前点,那么n3就是上一个节点的写法
//        n1 = n3.next;
//        n3.next = null;
//        while (n1 != null){
//            n2 = n1.next;
//            n1.next = n3;
//            n3 = n1;
//            n1 = n2;
//        }
        return true;
    }
    public static void printLinkedList(Node node) {
        System.out.print("Linked List: ");
        while (node != null) {
            System.out.print(node.value + " ");
            node = node.next;
        }
        System.out.println();
    }