萌新也能看懂的 Golang 题解(三)
1811. 素数行李箱密码(BFS、层序遍历)
难度:困难;主观评价:中等。
本题与 1795. 最佳路径 相似,最大的难点是生成素数数组和想到用层序遍历 BFS 解题,算法本身是非常简单的——没错,比 1795 简单很多,甚至连邻接矩阵都不需要构造。
由于每次操作之后的结果必须都是素数,所以可以认为与当前搜索到的素数字符串仅相差一位的所有素数字符串都是“邻接于当前结点的结点”,也就是层序遍历中的“下一层”。
生成 0 - 9999 的素数可以使用素数筛算法(建议背下来),并且由于所有测试用例使用的素数集合都是相同的,所以可以使用打表的方法将这些素数提前生成并保存起来。为了节约时间,这里使用一个 []int
以从大到小的顺序保存所有的素数,并使用一个 map[string]struct{}
保存这些素数的四位字符串,以便在后续 BFS 过程中可以快速找出与当前素数字符串仅相差一位的所有素数字符串。
具体看代码吧。
时间复杂度:O(n),为 BFS 算法的时间复杂度。
空间复杂度:O(n),队列的长度与问题规模(从 initState 到 dstState 的路径长度)成正比。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved. * Description: 保险箱[1183] * Note: 缺省代码仅供参考,可自行决定使用、修改或删除 * 只能import Go标准库 */ package main import ( "bufio" "fmt" "os" "strconv" ) var ( // 素数使用 map 保存,便于找到与目标仅相差一个字符的素数 primes = []int{2} primeStr = map[string]struct{}{"0002": {}} ) func init() { // 素数打表 genPrimes(9999) } // genPrimes 生成小于 n 的素数并保存在 primes 数组中 func genPrimes(n int) { // 这里 i += 2 是因为除了 2 之外的偶数都是合数 for i := 3; i <= n; i += 2 { if isPrime(i) { primes = append(primes, i) primeStr[fmt.Sprintf("%04d", i)] = struct{}{} } } } // isPrime 判断某个数是否为素数 func isPrime(n int) bool { for _, v := range primes { if n < v*v { // 只需要判断到根号 n 即可 return true } if n%v == 0 { // 能被某个素数整除,那肯定不是素数 return false } } return true } // nextStep 找出与 prime 素数字符串仅相差一个字符(即邻接于 prime)的素数字符串列表 func nextStep(prime string) []string { var ret []string // 分别尝试四位中的每一位 for i := 0; i < 4; i++ { // 每一位分别取 0 - 9 for j := 0; j <= 9; j++ { num := prime[0:i] + strconv.Itoa(j) + prime[i+1:] if _, ok := primeStr[num]; ok { ret = append(ret, num) } } } return ret } // 待实现函数,在此函数中填入答题代码 func unlock(initState string, dstState string) int { // 特殊情况:如果初始数字就是密码,则可以直接返回无需搜索 if initState == dstState { return 0 } // 无法解锁返回 -1 ans := -1 // visited 中的元素已被访问过 visited := make(map[string]struct{}) // 构造队列,并且把起点放入队列 q := []string{initState} Bfs: for len(q) > 0 { ans++ l := len(q) for i := 0; i < l; i++ { // 取队头元素,标记已访问过 node := q[0] q = q[1:] visited[node] = struct{}{} // 判断这个顶点是否为目标顶点 dstState,如果是则跳出循环 if node == dstState { break Bfs } // 与该点邻接的顶点入队 for _, p := range nextStep(node) { if _, ok := visited[p]; !ok { q = append(q, p) } } } } return ans } func main() { inputReader := bufio.NewReader(os.Stdin) initState, dstState, err := readInput(inputReader) if err != nil { return } result := unlock(initState, dstState) fmt.Println(result) } func readInput(reader *bufio.Reader) (string, string, error) { var initState, dstState string if _, err := fmt.Fscanf(reader, "%s %s\n", &initState, &dstState); err != nil { return "", "", err } return initState, dstState, nil }
第三批
1822. 电话拦截(模拟、排序)
难度:中等;主观评价:简单。
sort.Slice()
应用题,重点在于通配符的判断和如何设计数据结构保证最后能按呼叫顺序返回通话记录。
对于没有通配符的号码,可以使用 map 保存,使用时判断 map 中是否包含对应的 key;对于有通配符的号码可以去掉结尾的 * 号后使用数组保存,使用时遍历这个数组并用 strings.HasPrefix()
判断当前的电话号码是否满足某个带有通配符的匹配规则。
使用 map 保存通话记录可以很方便地编辑某个号码的通话记录信息。由于 Go 的 map 是无序的,所以在设计保存结构时,需要加上序号,并且在每次向这个 map 添加新元素(即新增通话记录)时都更新这个序号,最后使用 sort.Slice()
按照序号排序并生成所需格式即可。
时间复杂度:O(n * m),其中 m 为输入的白名单中带有通配符的电话号码的个数。
空间复杂度:O(n),record 哈希表占用的空间与输入中不同电话号码的个数成正比。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. * Description: 上机编程认证 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除 * 只能import Go标准库 */ package main import ( "bufio" "fmt" "io" "os" "sort" "strconv" "strings" ) var ( whiteList map[string]struct{} // 无通配符 whiteListWildcard []string // 有通配符 record map[string]recordEntry // key:电话号码 value:接通拒绝次数、通话记录顺序 ) // recordEntry 通话记录 type recordEntry struct { seq int accept int reject int number string } // 待实现函数,在此函数中填入答题代码 func getPhoneRecord(records []string) [][]string { // 初始化全局变量 whiteList = make(map[string]struct{}) whiteListWildcard = make([]string, 0) record = make(map[string]recordEntry) // seq + 1 表示下一条通话记录的序号 seq := 0 for _, r := range records { rs := strings.Split(r, " ") cmd, number := rs[0], rs[1] if cmd == "W" { // 添加一个号码到白名单中 addWhiteList(number) } else { // 添加一条通话记录 addRecord(number, &seq) } } return collectRecords() } // collectRecords 整理并排序 record map 中的通话记录信息,得到所需返回值 func collectRecords() [][]string { recordArr := make([]recordEntry, 0, len(record)) for k, v := range record { recordArr = append(recordArr, recordEntry{ seq: v.seq, accept: v.accept, reject: v.reject, number: k, }) } sort.Slice(recordArr, func(i, j int) bool { return recordArr[i].seq < recordArr[j].seq }) ret := make([][]string, 0, len(record)) for _, r := range recordArr { ret = append(ret, []string{r.number, strconv.Itoa(r.accept), strconv.Itoa(r.reject)}) } return ret } // inWhiteList 判断某个号码是否在白名单中 func inWhiteList(number string) bool { // 判断是否在无通配符的白名单中 if _, ok := whiteList[number]; ok { return true } // 不在无通配符的白名单中,判断是否在有通配符的白名单中 for _, wl := range whiteListWildcard { if strings.HasPrefix(number, wl) { return true } } return false } // addWhiteList 向白名单中添加一个电话号码 func addWhiteList(number string) { if whiteList == nil { whiteList = make(map[string]struct{}) } if strings.HasSuffix(number, "*") { whiteListWildcard = append(whiteListWildcard, strings.TrimSuffix(number, "*")) return } whiteList[number] = struct{}{} } // addRecord 向 record map 添加一条通话记录 func addRecord(number string, seq *int) { if record == nil { record = make(map[string]recordEntry) } // 如果有记录,这里能取到值 r := record[number] if _, ok := record[number]; !ok { // 没有记录,需要给 r 重新赋值并更新序号 *seq++ // 这里不需要设置 number,因为 number 字段只有在最后 map 转 slice 的时候会用到 r = recordEntry{ seq: *seq, accept: 0, reject: 0, } } // 按照是否在白名单中给 accept 和 reject 设置相应的值 if inWhiteList(number) { r.accept++ } else { r.reject++ } // 最后把重新赋值的 record[number] 放回 map 中 record[number] = r } func main() { reader := bufio.NewReader(os.Stdin) records, err := readInputStrArrayFromNlines(reader) if err != nil { fmt.Println(err.Error()) return } result := getPhoneRecord(records) for _, val := range result { fmt.Println(strings.Join(val, " ")) } } func readInputStrArrayFromNlines(reader *bufio.Reader) ([]string, error) { var line int if _, err := fmt.Fscanf(reader, "%d\n", &line); err != nil { return []string{}, err } result := make([]string, 0, line) for i := 0; i < line; i++ { lineBuf, err := reader.ReadString('\n') if err != nil && err != io.EOF { return nil, err } lineBuf = strings.TrimRight(lineBuf, "\r\n") lineBuf = strings.TrimSpace(lineBuf) result = append(result, lineBuf) } return result, nil }
1825. 按身高和体重排队(排序)
难度:简单;主观评价:简单。
sort.Slice()
应用题。
为了便于理解和编码,这里将每一位运动员的数据都保存在一个结构体中,然后对这个结构体数组进行排序,最后把所有运动员的编号按顺序收集到一个切片中返回。
当然这道题如果想优化到极致的话可以将空间复杂度优化到 O(1),具体方法是直接生成答案数组并参考要求对答案数组排序。
时间复杂度:O(nlogn),为排序算法的时间复杂度。
空间复杂度:O(n),主要来自 students 数组。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. * Description: 考生实现代码 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除 * 只能import Go标准库 */ package main import ( "bufio" "fmt" "io" "os" "sort" "strconv" "strings" ) type student struct { number int height int weight int } // TODO 在此补充你的代码 func sortStudents(n int, heights, weights []int) []int { students := make([]student, 0, n) for i := 0; i < n; i++ { students = append(students, student{ number: i + 1, height: heights[i], weight: weights[i], }) } sort.Slice(students, func(i, j int) bool { if students[i].height == students[j].height { // 身高相同则比较体重 if students[i].weight == students[j].weight { // 身高体重都相同则比较编号 return i < j } return students[i].weight < students[j].weight } return students[i].height < students[j].height }) ret := make([]int, 0, n) for _, s := range students { ret = append(ret, s.number) } return ret } func main() { var n int if _, err := fmt.Scanf("%d", &n); err != nil { return } inputReader := bufio.NewReader(os.Stdin) heights, err := readIntSlice(inputReader, " ") if err != nil { return } weights, err := readIntSlice(inputReader, " ") if err != nil { return } result := sortStudents(n, heights, weights) if len(result) > 0 { fmt.Print(result[0]) } for i := 1; i < len(result); i++ { fmt.Print(" ", result[i]) } } func readIntSlice(reader *bufio.Reader, sep string) ([]int, error) { lineBuf, err := reader.ReadString('\n') if err != nil && err != io.EOF { return nil, fmt.Errorf(err.Error()) } lineBuf = strings.TrimRight(lineBuf, "\r\n") line := strings.Split(lineBuf, sep) var result []int for _, v := range line { i, err := strconv.ParseInt(v, 10, 32) if err != nil { return nil, fmt.Errorf(err.Error()) } result = append(result, int(i)) } return result, nil }
1826. 整数对最小和(TopK)
难度:简单;主观评价:中等。
先吐槽一下这令人迷惑的难度评级,TopK 竟然评了个简单?且不说这题在算法难度上完爆被评为中等的 1822,LeetCode 上最简单的 TopK(LeetCode 215.Kth Largest Element in an Array)都是中等。。。这要是哪位不会 TopK 的倒霉蛋在入门级科目一抽到这种题,估计他的内心是崩溃的。
言归正传,既然是 TopK 问题且寻找的是最小的 k 个和,那么我们可以建立一个大小为 k 的大顶堆,并且遍历两个数组枚举每一对整数和。当堆没满时,可以将和直接加入堆;当堆满后,如果和小于堆顶,就弹出堆顶并且把和加入堆;如果和大于堆顶,则直接跳过,并且由于两个数组都是已经排好序的,所以此时可以直接跳过剩余的数。最后把堆中元素加起来即得到答案。
这题对于 Go 语言的主要难点是如何实现一个堆(实现 heap.Interface
),以及自行实现 peek()
方法:从堆中弹出一个元素再将它插入回去。
时间复杂度:O((n*m)logk),其中 n 为 array1 的长度,m 为 array2 的长度,logk 为将堆调整为大顶堆的时间复杂度。
空间复杂度:O(k),堆占用的空间与 k 成正比。
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/* * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved. * Description: 上机编程认证 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除 * 只能import Go标准库 */ package main import ( "bufio" "container/heap" "errors" "fmt" "io" "os" "strconv" "strings" ) // maxHeap 大顶堆 type maxHeap []int func (h *maxHeap) Len() int { return len(*h) } func (h *maxHeap) Less(i, j int) bool { return (*h)[i] > (*h)[j] } func (h *maxHeap) Swap(i, j int) { (*h)[i], (*h)[j] = (*h)[j], (*h)[i] } func (h *maxHeap) Push(x interface{}) { *h = append(*h, x.(int)) } func (h *maxHeap) Pop() interface{} { popped := (*h)[len(*h)-1] elems := *h elems = elems[:len(elems)-1] *h = elems return popped } // peek 返回堆顶元素但不弹出 func (h *maxHeap) peek() int { popped := heap.Pop(h) heap.Push(h, popped) return popped.(int) } // 待实现函数,在此函数中填入答题代码 func getMinSum(array1 []int, array2 []int, k int) int { // 创建一个大顶堆 h := &maxHeap{} heap.Init(h) for i := 0; i < len(array1); i++ { for j := 0; j < len(array2); j++ { sum := array1[i] + array2[j] // 堆没有满时,当前和可以直接加入堆,不需要其他处理 if h.Len() < k { heap.Push(h, sum) continue } // 堆满了且当前和大于堆顶时,由于数组都是从小到大排好序的,所以这里可以直接跳过后续的值 if h.Len() >= k && h.peek() < sum { break } // 否则去掉堆顶并且把当前和加入堆 heap.Pop(h) heap.Push(h, sum) } } // 遍历完成之后,求和 ret := 0 for h.Len() > 0 { ret += heap.Pop(h).(int) } return ret } func main() { reader := bufio.NewReader(os.Stdin) array1, err1 := readInputIntArray(reader) if err1 != nil { fmt.Println(err1.Error()) return } array2, err2 := readInputIntArray(reader) if err2 != nil { fmt.Println(err2.Error()) return } k, err3 := readInputInt(reader) if err3 != nil { fmt.Println(err3.Error()) return } result := getMinSum(array1, array2, k) fmt.Println(result) } func readInputIntArray(reader *bufio.Reader) ([]int, error) { var num int if _, err := fmt.Fscanf(reader, "%d\n", &num); err != nil { return []int{}, err } if num == 0 { return []int{}, nil } lineBuf, err := reader.ReadString('\n') if err != nil && err != io.EOF { return nil, err } lineBuf = strings.TrimRight(lineBuf, "\r\n") lineBuf = strings.TrimSpace(lineBuf) intNums := map2IntArray(lineBuf, " ") if len(intNums) != num { return []int{}, errors.New("int string len is error.") } return intNums, nil } func map2IntArray(str string, dem string) []int { tempArray := strings.Split(str, dem) result := make([]int, len(tempArray)) for index, value := range tempArray { value = strings.TrimSpace(value) intVal, _ := strconv.Atoi(value) result[index] = intVal } return result } func readInputInt(reader *bufio.Reader) (int, error) { var num int if _, err := fmt.Fscanf(reader, "%d\n", &num); err != nil { return 0, err } return num, nil }
本文来自博客园,作者:易先讯,转载请注明原文链接:https://www.cnblogs.com/gongxianjin/p/17178309.html