合并两个有序链表(变种,字节原题)

141 阅读4分钟

题目描述

合并两个有序链表,去重相同元素
eg:
1 -> 2 -> 4
1 -> 3 -> 5
合并后得到 1 -> 2 -> 3 -> 4 -> 5

解法一:暴力法

直接先合并,再遍历一遍结果链表,消除重复节点

func main() {
    l1 := &ListNode{
       Val: 1,
       Next: &ListNode{
          Val: 2,
          Next: &ListNode{
             Val: 4,
          },
       },
    }
    printList(l1)
    l2 := &ListNode{
       Val: 1,
       Next: &ListNode{
          Val: 3,
          Next: &ListNode{
             Val: 5,
          },
       },
    }
    printList(l2)
    res := mergeTwoList(l1, l2) // 先合并
    res = removeDuplicates(res) // 再去重
    printList(res)
}

func printList(head *ListNode) {
    for head != nil {
       if head.Next != nil {
          fmt.Printf("%d ->", head.Val)
       } else {
          fmt.Printf("%d\n", head.Val)
       }
       head = head.Next
    }
}

func mergeTwoList(l1, l2 *ListNode) *ListNode {
    dummy := &ListNode{}
    cur := dummy
    p1, p2 := l1, l2
    for p1 != nil && p2 != nil {
       if p1.Val <= p2.Val {
          cur.Next = p1
          p1 = p1.Next
       } else {
          cur.Next = p2
          p2 = p2.Next
       }
       cur = cur.Next
       cur.Next = nil
    }

    if p1 != nil {
       cur.Next = p1
    } else if p2 != nil {
       cur.Next = p2
    }
    return dummy.Next
}

func removeDuplicates(head *ListNode) *ListNode {
    cur := head
    for cur != nil && cur.Next != nil {
       if cur.Val == cur.Next.Val {
          cur.Next = cur.Next.Next // 跳过重复节点
       } else {
          cur = cur.Next
       }
    }
    return head
}

解法二:时间复杂度优化

  • 如果两条待合并链表长度相近,那么可以在一边判断大小合并的过程中,消除重复元素,整体时间复杂度更优
  • 如果两条待合并链表长度相差较大,那么一个链表遍历完了,另一个链表的剩余部分,还是得用上述方式,再消除重复节点
func main() {
    l1 := &ListNode{
       Val: 1,
       Next: &ListNode{
          Val: 2,
          Next: &ListNode{
             Val: 4,
          },
       },
    }
    printList(l1)
    l2 := &ListNode{
       Val: 1,
       Next: &ListNode{
          Val: 3,
          Next: &ListNode{
             Val: 5,
          },
       },
    }
    printList(l2)
    res := mergeTwoListUniq(l1, l2)
    printList(res)
}

func printList(head *ListNode) {
    for head != nil {
       if head.Next != nil {
          fmt.Printf("%d ->", head.Val)
       } else {
          fmt.Printf("%d\n", head.Val)
       }
       head = head.Next
    }
}

func mergeTwoListUniq(l1, l2 *ListNode) *ListNode {
    dummy := &ListNode{}
    cur := dummy
    p1, p2 := l1, l2
    for p1 != nil && p2 != nil {
       if p1.Val < p2.Val {
          cur.Next = p1
          p1 = p1.Next
       } else if p1.Val > p2.Val {
          cur.Next = p2
          p2 = p2.Next
       } else { // 值相同,任选一个,两个指针都前进
          cur.Next = p1
          p1 = p1.Next
          p2 = p2.Next
       }
       cur = cur.Next
    }

    if p1 != nil {
       cur.Next = removeDuplicates(p1)
    }
    if p2 != nil {
       cur.Next = removeDuplicates(p2)
    }
    return dummy.Next
}

func removeDuplicates(head *ListNode) *ListNode {
    cur := head
    for cur != nil && cur.Next != nil {
       if cur.Val == cur.Next.Val {
          cur.Next = cur.Next.Next // 跳过重复节点
       } else {
          cur = cur.Next
       }
    }
    return head
}

扩展1:删除排序链表中的重复元素

image.png 前面采用的是最直观的遍历思路,去跳过链表上重复的节点(链表删除操作),如下所示

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func deleteDuplicates(head *ListNode) *ListNode {
    cur := head
    for cur != nil && cur.Next != nil{
        if cur.Next.Val == cur.Val{
            cur.Next = cur.Next.Next
        }else{
            cur = cur.Next
        }
    }
    return head
}

实际上,本题解法,还可以采用双指针法

由于链表已经排好序,所以重复的元素一定连在一起,我们让快指针 fast 走在前面探路,慢指针 slow 走在后面,fast找到一个不重复的节点就给 slow串联起来,并让 slow 前进一步。这样就保证了slow指针串联的节点都是不重复的,当fast 走到链表末尾时,断开slow与最后面重复节点的连接即可。

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func deleteDuplicates(head *ListNode) *ListNode {
    if head == nil || head.Next == nil{ // 空链表或只有一个节点,不会重复
        return head
    }
    slow, fast := head, head
    for fast != nil{
        if fast.Val != slow.Val{ // slow指向所有不重复的节点
            slow.Next = fast
            slow = slow.Next
        }
        fast = fast.Next
    }
    // 断开`slow`与最后面重复节点的连接
    slow.Next = nil
    return head
}

扩展2:删除排序链表中的重复元素 II

image.png

解法一:双指针法

上一题是让你把多余的重复节点去掉,这道题要求你把所有重复的元素全都去掉,改动一下上一题双指针写法

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func deleteDuplicates(head *ListNode) *ListNode {
    // 有可能头节点就重复了,因此构造虚拟节点,方便操作
    // 题目说了 -100 <= node.val <= 100,所以用 -101 作为虚拟头节点值
    dummy := &ListNode{
        Val: -101,
        Next: head,
    }
    slow, fast := dummy, head
    for fast != nil{
        if fast.Next != nil && fast.Next.Val == fast.Val{
            // 发现有重复元素,跳过这一段相同的节点
            for fast.Next != nil && fast.Next.Val == fast.Val{
                fast = fast.Next
            }
            // 此时fast走到了这一段重复元素的最后一个节点,需要全部删除,所以再走一步
            fast = fast.Next
            // slow.Next此时仍指向fast一开始的位置,即这一段重复元素的第一个节点
            // 直接断开与这一段的连接
            slow.Next = nil
        }else{ 
            // 不重复节点,接到slow后面
            slow.Next = fast
            fast = fast.Next
            slow = slow.Next
        }
    }
    return dummy.Next
}

解法二:分解链表

将原链表分解为两条链表,一条链表存放不重复的节点,另一条链表存放重复的节点,返回不重复的这条链表即为答案

/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */
func deleteDuplicates(head *ListNode) *ListNode {
    // 题目说了 -100 <= node.val <= 100,所以用 -101 作为虚拟头节点值
    dummyDup := &ListNode{
        Val: -101,
        Next: head,
    }
    dummyUniq := &ListNode{
        Val: -101,
        Next: head,
    }
    pDup, pUniq := dummyDup, dummyUniq
    p := head
    for p != nil{
        if (p.Next != nil && p.Next.Val == p.Val) || (p.Val == pDup.Val){
            pDup.Next = p
            pDup = pDup.Next
        }else{
            pUniq.Next = p
            pUniq = pUniq.Next
        }
        p = p.Next
    }
    // 切断新链表末尾与原链表剩余节点的连接
    pDup.Next = nil
    pUniq.Next = nil
    return dummyUniq.Next
}