leetcode 448. 找到所有数组中消失的数字

264 阅读2分钟

我觉得这道题的名字应该改为“找到数组中缺失的所有数”。“所有数组”,看着以为是输入会有多个数组。另外,“数”和“数字”含义不一样。

力扣题目

这道题的难度是“简单”,但是满足进阶要求 “不使用额外空间且时间复杂度为 O(n)。你可以假定返回的数组不算在额外空间内” 的解法并不容易想到。

以下给出4种解法

解法一,排序+二分查找

先把原数组就地排序,然后遍历[1,n](n就是数组长度)并在数组中找缺少的数。

import "sort"

func findDisappearedNumbers(nums []int) []int {
    l := len(nums)
    if l == 0 {
        return []int{1}
    }
    if l > 1 {
        sort.Sort(sort.IntSlice(nums))
    }
    var res []int
    for x := 1; x <= l; x++ {
        var found bool
        if l > 50 {
            found = binarySearch(nums, x)
        } else {
            for _, m := range nums {
                if m == x {
                    found = true
                    break
                }
            }
        }
        if !found {
            res = append(res, x)
        }
    }
    return res
}

// 如果x存在于a中,返回true。否则返回false。
func binarySearch(a []int, x int) bool {
    var (
        low int
        high = len(a)-1
    )
    for low <= high {
        mid := low + (high-low)>>1
        v := a[mid]
        if v == x {
            return true
        }
        if v < x {
            low = mid+1
        } else {
            high = mid-1
        }
    }
    return false
}

排序(假设具体实现是基于快速排序的)的时间复杂度是O(nlogn)O(nlogn),空间复杂度是O(logn)O(logn)(系统栈的开销)。
找缺少的数的for循环,时间复杂度是O(nlogn)O(nlogn)(用了二分查找),空间复杂度O(1)O(1)
所以,整体上,时间复杂度是O(nlogn)O(nlogn),空间复杂度是O(logn)O(logn)

解法二,哈希表

先将数组中的数都放入一个哈希表(map),然后再遍历[1,n]去找缺少的数。

func findDisappearedNumbers(nums []int) []int {
    l := len(nums)
    if l == 0 {
        return []int{1}
    }
    m := make(map[int]struct{})
    for _, x := range nums {
        m[x] = struct{}{}
    }
    var res []int
    for x := 1; x <= l; x++ {
        _, ok := m[x]
        if !ok {
            res = append(res, x)
        }        
    }
    return res
}

数组中的数插入哈希表,时间复杂度O(n)O(n),空间复杂度O(n)O(n)
找缺少的数的for循环,时间复杂度O(n)O(n),空间复杂度O(1)O(1)
所以,整体上,时间复杂度O(n)O(n),空间复杂度O(n)O(n)

解法三,桶

利用桶排序的思想,将数组中的数放在n个桶里。然后看哪个桶是空的,空的桶中该有的数就是缺少的数之一。
该解法和解法一、解法二都有相通之处。

func findDisappearedNumbers(nums []int) []int {
    l := len(nums)
    if l == 0 {
        return []int{1}
    }
    buckets := make([]bool, l)
    for _, x := range nums {
        buckets[x-1] = true
    }
    var res []int
    for i, found := range buckets {
        if !found {
            res = append(res, i+1)
        }
    }
    return res
}

数组中的数放到桶里,时间复杂度O(n)O(n),空间复杂度O(n)O(n)
遍历所有桶找缺少的数的for循环,时间复杂度O(n)O(n),空间复杂度O(1)O(1)
所以,整体上,时间复杂度O(n)O(n),空间复杂度O(n)O(n)

解法四,原地调整

这个解法比较难想到,只有它符合进阶解法的时间和空间复杂度要求。
先就地调整数组,再找缺少的数。具体细节看代码和注释。

func findDisappearedNumbers(nums []int) []int {
    l := len(nums)
    if l == 0 {
        return []int{1}
    }
    for i := 0; i < l; {
        x := nums[i]
        j := x-1
        // 如果某数组元素x在[1,n]范围内,
        // 且x不在x-1索引上,
        // 且x-1索引上的数不等于x,
        // 将索引i和索引j上的元素交换。
        if x >= 1 && x <= l && i != j && nums[j] != x {
            nums[j], nums[i] = x, nums[j]
        } else {
            i++
        }
    }
    // 上面调整后,数组中存在的[1,n]范围内的数都有一个在它“该在”的位置。
    // 假设x属于[1,n],且存在于数组中,那么现在必定有一个x在索引x-1上。
    var res []int
    for i, x := range nums {
        // i+1位置上的数不是i+1
        if i+1 != x {
            // i+1就是缺少的数之一
            res = append(res, i+1)
        }
    }
    return res
}

第一个for循环中,对于某个数组元素,要么不调整,直接跳过;要么调整,做一次交换,它就放到了该在的索引上,不会再改变位置了。所以第一个for循环的时间复杂度是O(n)O(n)。第二个for循环的时间复杂度也是O(n)O(n)
就地调整,空间复杂度O(1)O(1)
所以,整体上,时间复杂度O(n)O(n),空间复杂度O(1)O(1)