LeetCode解题笔记:链表主题


链表是空节点,或者有一个值和一个指向下一个链表的指针,因此很多链表问题可以用递归来处理。 相对来说,链表的难度不大,题目的花样不多,掌握以下这几个问题,就可以同类退出其他问题的解法。

LeetCode提供的链表结构

/**
 * Definition for singly-linked list.
 * public class ListNode {
 *     int val;
 *     ListNode next;
 *     ListNode(int x) {
 *         val = x;
 *         next = null;
 *     }
 * }
 */

1. 找出两个链表的交点

160. Intersection of Two Linked Lists (Easy)

例如以下示例中 A 和 B 两个链表相交于 c1:


A:          a1 → a2
                    ↘
                      c1 → c2 → c3
                    ↗
B:    b1 → b2 → b3


但是不会出现以下相交的情况,因为每个节点只有一个 next 指针,也就只能有一个后继节点,而以下示例中节点 c 有两个后继节点。


A:          a1 → a2       d1 → d2
                    ↘  ↗
                      c
                    ↗  ↘
B:    b1 → b2 → b3        e1 → e2


要求时间复杂度为 O(N),空间复杂度为 O(1)。如果不存在交点则返回 null。

解题思路:

对于以下两个链表,

A:          a1 → a2
                    ↘
                      c1 → c2 → c3
                    ↗
B:    b1 → b2 → b3

首先获取A、B两个链表各自的长度,A=5, B=6. 计算出长度差值distance=1,删除(移动)链表B前distance个节点,也就是删除b1节点。此时两个链表如下:


A:          a1 → a2
                    ↘
                      c1 → c2 → c3
                    ↗
B:          b2 → b3

对于处理后的链表,挨个比较链表A和链表B的各个节点,当节点相等时,即为相交节点。

代码

public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
	int lenA = getNodeLength(headA);
	int lenB = getNodeLength(headB);

  	// 得到链表后,长的链表,要移动distance到指定位置。
	while (lenA - lenB > 0) {
		headA = headA.next;
	} else {
		headB = headB.next;
	}
	
	// 得到处理后的链表A、B,然后挨个比较,判断是否相等
	while (headA != null && headB != null) {
		if (headA.val == headB.val) {
			return headA;
		}
		headA = headA.next;
		headB = headB.next;
	}

}
// 获取链表的长度
private void getNodeLength(ListNode head) {
	int l = 0;
	while (head != null) {
		l++;
		head = head.next;
	}
	return l;
}

链表相遇

2. 链表反转

206. Reverse Linked List (Easy)

Example:

Input: 1->2->3->4->5->NULL
Output: 5->4->3->2->1->NULL

解题思路:

对于每一个节点,记录下翻转前的当前节点和下一个节点,然后进行翻转。

代码:

public ListNode reverseList(ListNode head) {
	if (head == null) {
		return null;
	}

	ListNode preNode = null;
	ListNode currentNode = head;
	ListNode nextNode = head.next;

	while (currentNode != null) {
		nextNode = currentNode.next;
		currentNode.next = preNode;
		preNode = currentNode;
		currentNode = nextNode;
	}
	return preNode;
}

链表翻转

3. 归并两个有序的链表

21. Merge Two Sorted Lists (Easy)

Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists.

Example:

Input: 1->2->4, 1->3->4
Output: 1->1->2->3->4->4

解题思路:

使用递归,每个元素进行比较,然后.next的值通过递归确定。

代码:

// 归并两个有序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
	if (l1 == null && l2 == null) {
		return null;
	}
	if (l1 == null) {
		return l2;
	}
	if (l2 == null) {
		return l1;
	}

	ListNode head = null;
	if (l1.val < l2.val) {
		head = l1;
		head.next = mergeTwoLists(l1.next, l2);
	}  else {
		head = l2;
		head.next = mergeTwoLists(l1, l2.next);
	}
	return head;        
}

链表合并

4. 从有序链表中删除重复节点

83. Remove Duplicates from Sorted List (Easy)

Given 1->1->2, return 1->2.
Given 1->1->2->3->3, return 1->2->3.

解题思路:

使用递归,比较head.val == head.next.val, 相等的话,则head指向head.next

代码:

public ListNode deleteDuplicates(ListNode head) {
 	if (head == null || head.next == null) {
 		return head;
 	}

 	head.next = deleteDuplicates(head.next);
 	// 如果相等,指向下一个node
 	return head.val == head.next.val ? head.next : head;
}

删除重复节点

5. 删除链表的倒数第 n 个节点

19. Remove Nth Node From End of List (Medium)

Given linked list: 1->2->3->4->5, and n = 2.
After removing the second node from the end, the linked list becomes 1->2->3->5.
public ListNode removeNthFromEnd(ListNode head, int n) {
	if (head == null || n < 0) {
		return null;
	}

	ListNode fast = head;
	ListNode slow = head;
	while (n > 0) {
		fast = fast.next;
		n--;
	}
	// 解决n指向了最前面
	// 
	// Input: [1], n=1
	// Output: [1]
	// Expected: []
	if (fast == null) {
		return head.next;
	}
	while (fast.next != null) {
		fast = fast.next;
		slow = slow.next;
	}
	slow.next = slow.next.next;
	return head; 
}

删除倒数第N个节点

方法二:

思路:

计算链表长度,减去n,即可获得正向的第i个节点,然后删除即可

代码:

public ListNode removeNthFromEnd(ListNode head, int n) {
	if (head == null || n < 0) {
		return head;
	}
	// 得到链表的长度
	int len = getNodeLength(head);
	ListNode newHead = head;
	// 计算链表的正向第d个
	int distance = len - n;
	// 解决删除第一个head的问题
	if (distance == 0) {
		return head.next;
	}
	while (distance > 1) {
		newHead = newHead.next;
		distance--;
	}
	newHead.next = newHead.next.next;
	return head;
}

private int getNodeLength(ListNode head) {
	if (head == null) {
		return 0;
	}
	int len = 0;
	while (head != null) {
		head = head.next;
		len++;
	}
	return len;
} 

删除倒数第n个节点

6. 交换链表中的相邻结点

24. Swap Nodes in Pairs (Medium)

Given 1->2->3->4, you should return the list as 2->1->4->3.

题目要求:不能修改结点的 val 值,O(1) 空间复杂度。

解题思路:

使用头指针法,虚拟出一个头指针,指向需要交换的两个几点前面

public ListNode swapPairs(ListNode head) {
	ListNode node = new ListNode(-1);
	node.next = head;
	ListNode pre = node;

	while (pre.next != null && pre.next.next != null) {
		ListNode l1 = pre.next;
		ListNode l2 = l1.next;
		ListNode next = l2.next;

		// 这两行的代码位置不能改变,否则会变成双向链表
		l1.next = next;
		l2.next = l1;
		
		pre.next = l2;

		pre = l1;
	}

	return node.next;
}

链表节点交换

7. 链表求和

445. Add Two Numbers II (Medium)

Input: (7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)
Output: 7 -> 8 -> 0 -> 7


题目要求:不能修改原始链表。

解题思路:

通过stack存储链表元素,然后相加,结果存储到stack中,最终把stack转换成ListNode

代码:

public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Stack<Integer> s1 = new Stack<>();
        Stack<Integer> s2 = new Stack<>();
        Stack<Integer> result = new Stack<>();

        // 使用栈存储,逆序取出
        while (l1 != null) {
            s1.push(l1.val);
            l1 = l1.next;
        }

        while (l2 != null) {
            s2.push(l2.val);
            l2 = l2.next;
        }

        int carry = 0;
        // 此处的判断条件很重要
        while (!s1.isEmpty() || !s2.isEmpty() || carry != 0) {
            int v1 = s1.isEmpty() ? 0 : s1.pop();
            int v2 = s2.isEmpty() ? 0 : s2.pop();

            int temp = v1 + v2 + carry;
            carry = temp / 10;
            int val = temp % 10;
            result.push(val);
        }

        // 将Stack转换为链表
        ListNode head = new ListNode(-1);
        ListNode root = head;
        while (!result.isEmpty()) {
            ListNode temp = new ListNode(result.pop());
            root.next = temp;
            root = temp;
        }

        return head.next;
    }

两个链表相加

8. 回文链表

234. Palindrome Linked List (Easy)

题目要求:以 O(1) 的空间复杂度来求解。

解题思路:

先获得链表长度,然后/2得到中间位置,从中间位置一分为二,后半部分存储为栈 然后前半部分与栈进行比较

代码:

public boolean isPalindrome(ListNode head) {
        // 偶数的话,直接就是后半部分
        // 奇数的话,是中间-1。如[1, 2, 3, 2, 1]
        int n = getLen(head) / 2;
        ListNode fast = head;
        while (n > 0) {
            fast = fast.next;
            n--;
        }

        Stack<Integer> right = new Stack<>();
        while (fast != null) {
            right.push(fast.val);
            fast = fast.next;
        }

        while (!right.isEmpty()) {
            if (right.pop() != head.val) {
                return false;
            }

            head = head.next;
        }
        return true;
    }

    private int getLen(ListNode head) {
        int res = 0;
        while (head != null) {
            head = head.next;
            res++;
        }

        return res;
    }

回文链表