两个有序集合的合并

117 阅读3分钟

双指针法的解决思路:

  1. 初始化两个指针:分别指向两个有序集合的起始位置。
  2. 比较当前指针指向的元素
    • 如果集合1的元素小于集合2的元素,将集合1的元素放入结果集中,并将集合1的指针向后移动。
    • 如果集合2的元素小于集合1的元素,将集合2的元素放入结果集中,并将集合2的指针向后移动。
    • 如果两个集合的元素相等,则可以选择将其中一个元素加入结果集,然后两个指针都向后移动。
  3. 处理剩余元素:当其中一个集合的所有元素都已合并到结果集中,另一集合可能还有剩余元素。此时直接将剩余元素加入结果集中。
  4. 返回结果集:所有元素被合并后返回结果。

代码:

package main 

import "fmt"

func mergeSet(set1, set2 []int) []int {
    result := []int{}
    
    i, j := 0, 0

    for i < len(set1) && j < len(set2) {
        // 1. 比较大小,小的先走
        if set1[i] < set2[j] {
            result = append(result, set1[i])
            i++
        } else if set1[i] > set2[j] {
            // 2. 大的后走
            result = append(result, set2[j])
            j++
        } else {
            // 3. 相等时候插入一个,指针都走一步
            result = append(result, set1[i])
            result = append(result, set2[j])
            i++
            j++
        }
    }

    // 如果有剩下的
    for i < len(set1) {
        result = append(result, set1[i])
        i++
    }

    for j < len(set2) {
        result = append(result, set2[j])
        j++
    }


    return result
}


func main() {
    // 集合1
    a := []int{1,2,3,4,5}
    
    // 集合2
    b := []int{3,4,5,6,7}

    result := mergeSet(a, b)

    fmt.Println(result)
    
}

时间复杂度

  • 双指针法的时间复杂度为O(n+m),其中n和m分别为两个集合的长度。因为每个集合的每个元素只会被遍历一次,所以整体时间复杂度是线性级别的。

合并逻辑调整:一轮for循环实现方式

我们可以将双指针逻辑合并为一个更简洁的单个for循环,进一步简化代码。通过调整循环条件,可以避免使用两个单独的for循环处理剩余元素的逻辑。

优化后的代码:

package main 

import "fmt"

func mergeSetOneLoop(set1, set2 []int) []int {
    result := []int{}
    
    i, j := 0, 0

    // 一个循环内处理合并
    for i < len(set1) || j < len(set2) {
        if i < len(set1) && (j >= len(set2) || set1[i] < set2[j]) {
            result = append(result, set1[i])
            i++
        } else if j < len(set2) && (i >= len(set1) || set1[i] > set2[j]) {
            result = append(result, set2[j])
            j++
        } else {
            result = append(result, set1[i])
            i++
            j++
        }
    }

    return result
}

func main() {
    // 集合1
    a := []int{1,2,3,4,5}
    
    // 集合2
    b := []int{3,4,5,6,7}

    result := mergeSetOneLoop(a, b)

    fmt.Println(result) // [1 2 3 3 4 4 5 5 6 7]
}

代码解释:

  1. 使用一个for循环控制流程,条件为i < len(set1) || j < len(set2),即当任意一个集合没有遍历完时,继续合并。
  2. 在循环中判断:
    • i没有超出set1的长度且j已经超出set2的长度,或者set1[i]小于set2[j]时,将set1[i]添加到结果中,并移动i
    • 同理,处理set2中的元素。
    • set1[i]set2[j]相等时,将set1[i]set2[j](二者相等)添加到结果集中,并同时移动两个指针。

时间复杂度:

  • 单循环法的时间复杂度依然是O(n+m),因为每个元素只被处理一次,整体算法的时间复杂度是线性的。

两种实现方式的对比

解决方法时间复杂度优点缺点
双指针法(两个for循环)O(n + m)逻辑清晰、代码结构分明,容易理解使用多个for循环,代码稍微冗长
单循环法O(n + m)代码简洁,合并剩余元素时不需要额外的循环条件判断稍显复杂,理解门槛较高