第六节 链表
1.什么时候使用dummy node:
当所返回的链表的头不确定的时候使用。
2.链表基本功(链表的题型通常都是分步骤地对链表进行基本操作):
a.链表中插入一个节点
b.链表中删除一个节点
c.链表reverse,代码如下:
public ListNode reverse(ListNode head) { if (head == null) { return null; } ListNode pre = head; ListNode cur = head.next; while (cur != null) { ListNode temp = cur; cur = cur.next; temp.next = pre; pre = temp; } head.next = null; return pre; }
d.merge两个排序链表
public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode cur = dummy; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { cur.next = l1; cur = cur.next; l1 = l1.next; } else { cur.next = l2; cur = cur.next; l2 = l2.next; } } if (l1 != null) { cur.next = l1; } if (l2 != null) { cur.next = l2; } return dummy.next; }
e.找链表的中点(快慢指针的方法)
public ListNode findMid(ListNode head) { if (head == null || head.next == null) { return head; } ListNode slow = head; ListNode fast = head.next; while (fast != null && fast.next != null) { slow = slow.next; fast = fast.next.next; } return slow; }
3.题目剖析
(1)merge k sorted lists
方法一:分治
时间复杂度分析:
T(N,k) = T(N1, k/2) + T(N2, k/2) + N1 + N2
= T(N1, k/2) + T(N2, k/2) + N
= T(N11, k/4) + T(N12, k/4) + N11 + N12 + T(N21, k/4) + T(N22, k/4) + N21 + N22 + N
= T(N11, k/4) + T(N12, k/4) + N1 + T(N21, k/4) + T(N22, k/4) + N2 + N
= T(N11, k/4) + T(N12, k/4) + T(N21, k/4) + T(N22, k/4) + 2N
......
= T(n1, 1) + T(n2, 1) +...+ T(nk, 1) + Nlgk = O(Nlgk)
代码:
public class Solution { /** * @param lists: a list of ListNode * @return: The head of one sorted list. */ public ListNode mergeKLists(List<ListNode> lists) { if (lists.size() == 0) { return null; } return mergeHelper(lists, 0, lists.size() - 1); } private ListNode mergeHelper(List<ListNode> lists, int start, int end) { if (start == end) { return lists.get(start); } int mid = start + (end - start) / 2; ListNode left = mergeHelper(lists, start, mid); ListNode right = mergeHelper(lists, mid + 1, end); return mergeTwoLists(left, right); } private ListNode mergeTwoLists(ListNode list1, ListNode list2) { ListNode dummy = new ListNode(0); ListNode tail = dummy; while (list1 != null && list2 != null) { if (list1.val < list2.val) { tail.next = list1; tail = list1; list1 = list1.next; } else { tail.next = list2; tail = list2; list2 = list2.next; } } if (list1 != null) { tail.next = list1; } else { tail.next = list2; } return dummy.next; } }
方法二:最小堆
时间复杂度分析:
T(N, k) = N * lgk = O(Nlgk)
代码:
public class Solution { /** * @param lists: a list of ListNode * @return: The head of one sorted list. */ public ListNode mergeKLists(List<ListNode> lists) { // write your code here if (lists == null || lists.size()==0) { return null; } Comparator<ListNode> listNodeComparator = new Comparator<ListNode>() { public int compare(ListNode l1, ListNode l2) { if (l1 == null && l2 == null) { return 1; } else if (l1 == null) { return 1; } else if (l2 == null) { return -1; } else { return l1.val - l2.val; } } }; ListNode dummy = new ListNode(0); ListNode cur = dummy; PriorityQueue<ListNode> minHeap = new PriorityQueue<ListNode>(lists.size(), listNodeComparator); for (ListNode l : lists) { if (l != null) { minHeap.offer(l); } } while (minHeap.peek() != null) { cur.next = minHeap.poll(); cur = cur.next; if (cur.next != null) { minHeap.offer(cur.next); } } cur.next = null; return dummy.next; } }
方法三:两两合并
时间复杂度分析:
T(N, k) = N + T(N, k/2)
= 2N + T(N, k/4)
= 3N + T(N, k/8)
......
= Nlgk + T(N, 1) = O(Nlgk)
代码:
public class Solution { /** * @param lists: a list of ListNode * @return: The head of one sorted list. */ public ListNode mergeKLists(List<ListNode> lists) { // write your code here if (lists.size() == 0) { return null; } if (lists.size() == 1) { return lists.get(0); } int size = lists.size(); List<ListNode> nextLists = new ArrayList<ListNode>(); for (int i = 0; i<=size-1; i+=2) { if (i == size-1) { nextLists.add(lists.get(i)); break; } ListNode afterMerge = mergeTwoLists(lists.get(i), lists.get(i+1)); nextLists.add(afterMerge); } return mergeKLists(nextLists); } public ListNode mergeTwoLists(ListNode l1, ListNode l2) { ListNode dummy = new ListNode(0); ListNode cur = dummy; while (l1 != null && l2 != null) { if (l1.val <= l2.val) { cur.next = l1; cur = cur.next; l1 = l1.next; } else { cur.next = l2; cur = cur.next; l2 = l2.next; } } if (l1 != null) { cur.next = l1; } if (l2 != null) { cur.next = l2; } return dummy.next; } }
(2)Copy List with Random Pointers
方法一:使用哈希表,需要消耗O(N)的额外空间
代码:
public class Solution { /** * @param head: The head of linked list with a random pointer. * @return: A new head of a deep copy of the list. */ public RandomListNode copyRandomList(RandomListNode head) { // write your code here Map<RandomListNode, RandomListNode> map = new HashMap<RandomListNode, RandomListNode>(); RandomListNode cur = head; RandomListNode copyDummy = new RandomListNode(0); RandomListNode copyCur = copyDummy; while (cur != null) { copyCur.next = new RandomListNode(cur.label); copyCur = copyCur.next; map.put(cur, copyCur); cur = cur.next; } cur = head; while (cur != null) { copyCur = map.get(cur); copyCur.random = map.get(cur.random); cur = cur.next; } return copyDummy.next; } }
方法二:第一遍扫的时候巧妙运用next指针, 开始数组是1->2->3->4 。 然后扫描过程中 先建立copy节点 1->1`->2->2`->3->3`->4->4`, 然后第二遍copy的时候去建立边的copy, 拆分节点, 一边扫描一边拆成两个链表,这里用到两个dummy node。第一个链表变回 1->2->3 , 然后第二变成 1`->2`->3` 。消耗额外空间O(1)。
代码:
public class Solution { /** * @param head: The head of linked list with a random pointer. * @return: A new head of a deep copy of the list. */ public RandomListNode copyRandomList(RandomListNode head) { // write your code here copyNext(head); copyRandom(head); return split(head); } public RandomListNode split(RandomListNode head) { RandomListNode dummy = new RandomListNode(0); RandomListNode cur = dummy; while (head != null) { cur.next = head.next; cur = cur.next; head.next = head.next.next; head = head.next; } return dummy.next; } public void copyRandom(RandomListNode head) { while (head != null) { RandomListNode copy = head.next; if (copy.random != null) { copy.random = copy.random.next; } head = head.next.next; } } public void copyNext(RandomListNode head) { while (head != null) { RandomListNode copy = new RandomListNode(head.label); copy.next = head.next; copy.random = head.random; head.next = copy; head = head.next.next; } } }
(3) Linked List Cycle I and II
使用快慢指针,代码略,见lintcode。