这周的双周竞赛算法考察得也不是那么强,其中最后一道数学题没有做出来,主要原因还是第三道题脑子短路了,一直 OOM,最后只剩下 5 分钟来思考第 4 题。而且一下子的思路就错了,导致最终悲剧。

Q1. 根据数字二进制下 1 的数目排序

这道题比较简单,主要还是需要将原来数组的值转换成 1 的个数,并且要注意 1 个数相同的情况下的处理:

[[email protected]]# cat Q1.go
import "sort"

type q1 struct {
    origin int
    count1 int
}

func sortByBits(arr []int) []int {
    var (
        i      int
        length = len(arr)
        newArr = make([]q1, length)
    )

    for i = 0; i < length; i++ {
        newArr[i] = q1{
            origin: arr[i],
            count1: count1(arr[i]),
        }
    }

    sort.Slice(newArr, func(i, j int) bool {
        if newArr[i].count1 == newArr[j].count1 {
            return newArr[i].origin < newArr[j].origin
        }
        return newArr[i].count1 < newArr[j].count1
    })

    for i = 0; i < length; i++ {
        arr[i] = newArr[i].origin
    }
    return arr
}
func count1(n int) (rst int) {
    for n != 0 {
        if n&1 != 0 {
            rst++
        }
        n >>= 1
    }
    return
}

Q2. 每隔 n 个顾客打折

这道题扎一看上去很复杂,但是,经过上次的经验之后我冷静了许多,描述越复杂,题目越简单,所以,直接写:

[[email protected]]# cat Q2.go
type Cashier struct {
    count         int
    discount      int
    discountCount int
    prices        map[int]int
}

func Constructor(n int, discount int, products []int, prices []int) Cashier {
    c := Cashier{
        count:         0,
        discount:      discount,
        discountCount: n,
        prices:        map[int]int{},
    }
    for i := 0; i < len(products); i++ {
        c.prices[products[i]] = prices[i]
    }
    return c
}

func (this *Cashier) GetBill(product []int, amount []int) (rtn float64) {
    this.count++
    total := 0
    for i := 0; i < len(product); i++ {
        total += this.prices[product[i]] * amount[i]
    }
    if this.count%this.discountCount == 0 {
        rtn = float64(total) * float64(100-this.discount) / 100
    } else {
        rtn = float64(total)
    }
    return
}

Q3. 包含所有三种字符的子字符串数目

这道题目我是用 DP 解答的,DP 有两条公式,分别代表的含义为:

DP1[n]:以字母 s[n] 开头的子串
DP2[n]:不包含字母 s[n] 的子串

那么推导公式为:

DP2[n] = DP1[n-1] + DP2[n-1]

这里 DP1[n] 怎么求是我这种解法的难点,假设 s[n:] 是包含 3 个字符的字符串,那么,以 s[n] 开头的子串有最多有几种可能性就取决于 s[n:n+x] 包含3个字符时,x 的最小值,我就在求这个 x 的时候载了,最后醒悟过来,可以每次遍历的时候直接记录。

这里之所以取决于 x 是因为假设这么一个字符串:abccccccc,主要包含前 3 个字符,后面能接几个字符就有几种可能:

abc
abcc
abccc
abcccc
abccccc
abcccccc
... ...

所以代码可以这么写:

[[email protected]]# cat Q3.go
func numberOfSubstrings(s string) int {
    var (
        i        int
        length   = len(s)
        dp1      = make([]int, length)
        dp2      = make([]int, length)
        charsIdx = []int{length, length, length}
    )
    charsIdx[s[length-1]-'a'] = length - 1
    charsIdx[s[length-2]-'a'] = length - 2

    for i = length - 3; i >= 0; i-- {
        charsIdx[s[i]-'a'] = i
        dp2[i] = dp1[i+1] + dp2[i+1]
        maxIdx := maxInt3(charsIdx[0], charsIdx[1], charsIdx[2])
        dp1[i] = length - maxIdx
    }

    return dp1[0] + dp2[0]
}

func maxInt3(a, b, c int) int {
    if a < b {
        a = b
    }
    if a < c {
        return c
    }
    return a
}

Q4. 有效的快递序列数目

这是一道纯数学题,我一开始就想错了,我往火车进出站的情况去考虑了,当然,最后5分钟就翻车了。这里我看了别人的解答,很简单,就是一个排列数,公式为:

f[n] = f[n-1] * n * (2*n-1)

所以代码就很简单了:

[[email protected]]# cat Q4.go
var MaxN = 1000000007

func countOrders(n int) int {
    var (
        i  int
        dp = make([]int, n+1)
    )
    dp[1] = 1
    for i = 2; i < n+1; i++ {
        //f(n) = f(n - 1) * n * (2n - 1).
        dp[i] = dp[i-1] * i * ((i << 1) - 1)
        if dp[i] > MaxN {
            dp[i] %= MaxN
        }
    }
    return dp[n]
}

update @ 2020-02-23 10:09

有时间自己来推导一下公式,既然是排列组合的题目,那么就考虑插入法来算,假设已经知道了 (n - 1)个快递的时候有 F(n-1) 种方式,那么选取其中任何一种,用于放置第 N 个快递的取件,那么就可能有 2(n-1) + 1 个位置可以放:

图 1:取件可能放置的位置

假设取件放在 Li 位置上,那么送件就存在 (2n-i) 种可能,这里送件不能在取件之前,所以,总共存在:

图 2:一种情况可能存在取件送件的可能性

归纳一下就是:n * (2 * n - 1)

所以总共就是 F(n) = F(n-1) * n * (2 * n - 1)