数组

[TOC]

数组

试题3.找出数组中重复的数字

在一个长度为n的数组里的所有数字都在0~n-1的范围内。数组中某些数字是重复的, 但不知道有几个数字重复了, 也不知道每个数字重复了,几次。请找出数组中任意一个重复的数字。例如,如果输入长度为7的数组 {2,3,1,0,2,5,3}, 那么对应的输出是重复的数字2或者3。

方法一:对数组排序,时间 O(nlogn),排序之后找出相同的数就容易了。

方法二:通过一个哈希表,可以O(1) 找到目标,时间为 O(n),但需要 O(n)的哈希表的额外空间开销。

方法三:思路从前往后把每个数放到与自己值相等的index位置上,有点原地排序的意思。

// 方法三:循环实现
func FindDuplication(in []int) int {
    n := len(in)
    // 数据判断合法性
    for i := 0; i < n; i++ {
        if in[i] < 0 || in[i] >= n {
            return -1
        }
    }

    for i := 0; i < n; i++ {
        // 如果当前 i 与 in[i] 不相等,就与in[in[i]] 交换,知道满足 i == in[i],然后往后i++
        // 当 in[i] == in[in[i]] 相等时,说明找到重复数据,结束
        for i != in[i] {
            if in[i] == in[in[i]] {
                return in[i]
            }
            in[i], in[in[i]]= in[in[i]], in[i]
        }
    }
    return -1
}


代码中尽管有一个两重循环, 但每个数字最多只要交换两次就能找到属于它自己的位置, 因此总的时间复杂度是On)。另外,所有的操作步骤都是在输入数组上进行的, 不需要额外分配内存, 因此空间复杂度为O(1)。

测试用例:

  • 包含一个或多个重复数据的输入
  • 没有重复数据的输入
  • 有超出数组大小的数据输入
func TestFindDuplication(t *testing.T) {
    tests := []struct {
        name string
        in   []int
        want int
    }{
        {"normal input", []int{2,3,1,0,2,5,3}, 2},
        {"not found", []int{2,3,1,0,4,5,6}, -1},
        {"index out of range", []int{2,13,1,0,2,5,3}, -1},
        {"index out of range", []int{-2,3,1,0,2,5,3}, -1},
    }

    for _, test := range tests {
        t.Run(test.name, func(t *testing.T) {
            got := FindDuplication(test.in)
            if got != test.want {
                t.Fatalf(" got: %d\nwant: %d\n", got, test.want)
            }
        })
    }
}

试题21:调整数组顺序使奇数位于偶数前面

题目:输入一个整数数组,实现一个函数来调整该数组中数字的顺序, 使得所有奇数位于数组的前半部分, 所有偶数位于数组的后半部分。

// 如果不限制,奇数,偶数内部顺序的相对稳定
// 使用两个index 分别从数组首尾往中间移动
func ReOrderArray1( array []int ) []int {
    i := 0
    j := len(array) - 1

    for i < j {
        // i 遇到奇数跳过
        if array[i] % 2 == 1 {
            i++
        }

        // j 遇到偶数跳过
        if array[j] % 2 == 0 {
            j--
        }

        // 如果 i 处于偶数,j处于奇数,则交换,然后往中间移动
        if array[i] % 2 == 0 && array[j] % 2 == 1 && i < j{
            array[i], array[j] = array[j], array[i]
            i++
            j--
        }
    }

    return array
}

变形:输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变

func ReOrderArray( array []int ) []int {
    tmp := make([]int, 0)

    oddNum := 0
    for i := 0; i < len(array); i++ {
        if array[i] % 2 == 1 {
            array[oddNum] = array[i]
            oddNum++
        } else {
            // 偶数
            tmp = append(tmp, array[i])
        }
    }

    for i := 0; i < len(tmp); i++ {
        array[oddNum+i] = tmp[i]
    }

    return array
}

试题29:顺时针打印矩阵(边界陷阱)

输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字, 例如,如果输入如下4 X 4矩阵:则依次打印出数字1,2,3,4,8,12,16,15,14,13,9,5,6,7,11,10.

1  2  3  4
5  6  7  8
9  10 11 12
13 14 15 16
// 测试用例:
// 输入 0*0 1*1
// 输入 1*4  4*1
// 输入 4*4
func PrintMatrix( matrix [][]int ) []int {
    h := len(matrix)
    if h  == 0 {
        return nil
    }
    w :=len(matrix[0])
    if  w == 0 {
        return nil
    }
    ret := make([]int, 0, w*h)

    iMin, jMin := 0, 0
    iMax, jMax := w - 1, h - 1
    //var i, j int
    for iMin <= iMax && jMin <= jMax {
        // 右移
        for i := iMin; i <= iMax; i++ {
            ret = append(ret, matrix[jMin][i])
        }
        jMin++
        if jMin > jMax {
            return ret
        }
        // 下移
        for j := jMin;j <= jMax; j++ {
            ret = append(ret, matrix[j][iMax])
        }
        iMax--
        // 左移
        for i:= iMax ;i >= iMin; i-- {
            ret = append(ret, matrix[jMax][i])
        }
        jMax--
        if iMin > iMax {
            return ret
        }
        // 上移
        for j := jMax;j >= jMin; j-- {
            ret = append(ret, matrix[j][iMin])
        }
        iMin++
    }
    return ret
}

这道题完全没有涉及复杂的数据结构或者高级的算法, 看起来是一个很简单的问题。

但实际上解决这个问题时会在代码中包含多个循环, 并且需要判断多个边界条件。如果在把问题考虑得很清楚之前就开始写代码, 则不可避免地会越写越混乱。因此解决这个问题的关键在于先形成清晰的思路, 并把复杂的问题分解成若干个简单的问题。

看似简单,但边界条件较多,一次写对其实并不容易,所以测试用例,很重要。

试题39:数组中出现次数超过一半的数字

题目:数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。例如输入一个长度为9的数组{1,2,3,2,2,2,5,4,2}。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。如果不存在则输出0。

解法一:利用类似快排的思想,随便找一个数,比它小的往左移,比它大的往有移动。一轮循环下来,如果有个数超过一半,那么数组的中位数一定数这个数。然后再循环一次,去人是否过半了。

解法二:如果数组中有一个数字出现的次数超过数组长度的一半, 则它出现的次数比其他所有数字出现次数的和还要多。因此,我们可以在遍历数组的时候保存两个值:一个元素值 a,一个对应的次数 num。如果下一个数与当前保存的a相同,则num++,如果不同则num–,当次数 num 等于0时,当前保存的数 a 要被替换掉,并次数 num重置为1。如果有个数次数过半,最后剩下的一定是它。这里并不能确认就是它,所以要在循环一个次,进行确认。

解法三:用一个哈希表,统计次数,一次循环。就可以。

// 解法二 
// 测试用例
// 数组 []int{2,2,2,3,3,3}
// 数组 []int{1,2,3,2,2,2,5,4,2}
// 数组 []int{1}
// 数组 []int{1,2,3}
func MoreThanHalfNum_Solution( numbers []int ) int {
    if len(numbers) == 0 {
        return 0
    }

    if len(numbers) == 1 {
        return numbers[0]
    }

    cur := numbers[0]
    num := 1
    for i := 1; i < len(numbers); i++ {
        if cur == numbers[i] {
            num++
            continue
        }
        num--
        if num == 0 {
            cur = numbers[i]
            num = 1
        }
    }

    // 再次确认次数是否过半
    num = 0
    for i := 0; i < len(numbers); i++ {
        if cur == numbers[i] {
            num++
        }
    }

    if  2*num > len(numbers) {
        return cur
    }
    return 0
}

感觉三种方法都不够好。

试题53:数字在升序数组中出现次数

统计一个数字在升序数组中出现的次数。如输入 [1,2,3,3,3,3,4,5],3 返回 4.

二分法

// 测试用例
// 空数组
// 所有数大小一样
// 乱序
// 恰好在开头,结尾
func GetNumberOfK( data []int ,  k int ) int {
    if len(data) == 0 {
        return 0
    }

    count := 0
    idx1 := 0
    idx2 := len(data) - 1
    for idx1 <= idx2 {
        mid := idx1 + (idx2 - idx1) >> 1
        switch {
        case data[mid] > k:
            idx2 = mid - 1
        case data[mid] < k:
            idx1 = mid + 1
        default:
            for i := mid; i <= idx2 ;i++ {
                if data[i] != k {
                    break
                }
                count++
            }
            for i := mid - 1; i >= idx1; i-- {
                if data[i] != k {
                    break
                }
                count++
            }
            return count
        }
    }
    return 0
}

试题57:和为S的两个数

输入一个递增排序的数组和一个数字S,在数组中查找两个数,使得他们的和正好是S,如果有多对数字的和等于S,输出两个数的乘积最小的。

对应每个测试案例,输出两个数,小的先输出。输入 [1,2,4,7,11,15],15 返回值 [4,11]

// 测试用例
// 空数组
// [1,2,4,7,11,14,16],15
// [1,2,4,7,11,15], 26
// [1,2,4,7,11,15], 3
// [1,2,4,7,11,15], 30
// [7,8] 15
// [8,8] 15
// 乱序
func FindNumbersWithSum( array []int ,  sum int ) []int {
    if len(array) < 2 {
        return nil
    }
    maxIdx := len(array) - 1
    if sum > array[maxIdx] + array[maxIdx-1] {
        return nil
    }
    if sum < array[0] + array[1] {
        return nil
    }
    ret := make([]int, 0, 2)
    i := 0
    j := maxIdx
    for i < j {
        if array[i] + array[j] > sum {
            j--
        }
        if array[i] + array[j] == sum {
            ret = append(ret, array[i], array[j])
            return ret
        }
        if array[i] + array[j] < sum {
            i++
        }
    }
    return ret
}

reference

https://github.com/zhedahht/CodingInterviewChinese2

更新时间: