我觉得这道题的名字应该改为“找到数组中缺失的所有数”。“所有数组”,看着以为是输入会有多个数组。另外,“数”和“数字”含义不一样。
这道题的难度是“简单”,但是满足进阶要求 “不使用额外空间且时间复杂度为 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
}
排序(假设具体实现是基于快速排序的)的时间复杂度是,空间复杂度是(系统栈的开销)。
找缺少的数的for循环,时间复杂度是(用了二分查找),空间复杂度。
所以,整体上,时间复杂度是,空间复杂度是。
解法二,哈希表
先将数组中的数都放入一个哈希表(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
}
数组中的数插入哈希表,时间复杂度,空间复杂度。
找缺少的数的for循环,时间复杂度,空间复杂度。
所以,整体上,时间复杂度,空间复杂度。
解法三,桶
利用桶排序的思想,将数组中的数放在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
}
数组中的数放到桶里,时间复杂度,空间复杂度。
遍历所有桶找缺少的数的for循环,时间复杂度,空间复杂度。
所以,整体上,时间复杂度,空间复杂度。
解法四,原地调整
这个解法比较难想到,只有它符合进阶解法的时间和空间复杂度要求。
先就地调整数组,再找缺少的数。具体细节看代码和注释。
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循环的时间复杂度是。第二个for循环的时间复杂度也是。
就地调整,空间复杂度。
所以,整体上,时间复杂度,空间复杂度。