萌新也能看懂的 Golang 题解(二)

1795. 最佳路径(BFS、层序遍历)

难度:困难;主观评价:中等。

BFS(层序遍历)应用题,具体看注释。

看到有向带权图的最短路径,第一反应其实是 Dijkstra,但是由于这道题存在 Dijkstra 无法处理的情况:经过的顶点更少的路径权值比经过节点更多的路径高,所以只能 BFS。

与常见 BFS 题目最大的区别就是对于终点的处理:遍历到终点时,不能把终点标记为 visited,否则会出现同层其他顶点访问不到终点的情况。

另外我很久没做这种邻接矩阵的 BFS 了,一般做的都是那种给你个矩阵和起始位置让你判断上下左右四个点那种,遇到像这样的“典型 BFS”直接歇菜。但是做完了回头一看也不难,没有用到什么很高深的知识点或者完全没接触过的算法,肯定算不上困难,但是不会层序遍历的肯定做不出这道题,所以主观评价给个中等吧。

ps: 这道题感觉稀疏图的情况比较常见,如果用邻接表的话可能会更好一点,当然这题问题规模比较小,用邻接矩阵也不会 OOM。

ps2: 执行耗时最短的那位老哥的解法实在是太秀了,大家可以 AC 之后去看一下,反正我是学不来。。。

时间复杂度:O(n),每个节点都需要被访问至少一次(终点会被访问多次)。

空间复杂度:O(n),队列中的元素个数与问题规模成正比。

 

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 上机编程认证
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */
package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "strings"
    "math"
)

type connection struct {
    srcBoard int
    snkBoard int
    weight   int
}

type boardPair struct {
    srcBoard int
    snkBoard int
}

type bfsEntry struct {
    board  int // 单板编号
    weight int // 从起点到当前节点的总权值
}

// bfs 层序遍历,找到从 src 到 dst 的最短路,并且返回最短权值
func bfs(graph [1000][1000]int, src, dst int) int {
    visited := [1000]bool{}
    q := make([]bfsEntry, 0)
    // 起始节点入队
    q = append(q, bfsEntry{board: src, weight: 0})
    minWeight := math.MaxInt32
    for len(q) > 0 {
        // 层序遍历
        levelSize := len(q)
        for i := 0; i < levelSize; i++ {
            // 拿出队头元素,标记
            node := q[0]
            q = q[1:]
            visited[node.board] = true
            // 把该节点邻接的所有没访问过的节点 (graph[node][nextNode]) 全都加入队列
            for nextNode := 0; nextNode < 1000; nextNode++ {
                if graph[node.board][nextNode] != 0 && !visited[nextNode] {
                    q = append(q, bfsEntry{board: nextNode, weight: node.weight + graph[node.board][nextNode]})
                    if nextNode == dst && node.weight+graph[node.board][nextNode] < minWeight {
                        // 终点不需要标记为 visited,否则同层节点就访问不到了
                        minWeight = node.weight + graph[node.board][nextNode]
                    }
                }
            }
        }
        // 如果 minWeight 的值已经被修改了,说明到终点前最后一层节点也遍历完了,直接返回
        if minWeight != math.MaxInt32 {
            return minWeight
        }
    }
    return -1
}


// 待实现函数,在此函数中填入答题代码
func getBestRoute(boardPairList []boardPair, connectionsList []connection) []int {
      result := make([]int, 0, len(boardPairList))
    // 构造一个二维数组存储的有向图
    graph := [1000][1000]int{}
    for _, c := range connectionsList {
        graph[c.srcBoard][c.snkBoard] = c.weight
    }
    for _, c := range boardPairList {
        result = append(result, bfs(graph, c.srcBoard, c.snkBoard))
    }
    return result
}

func main() {
    inputReader := bufio.NewReader(os.Stdin)
    boardPairList, connectionsList, err := readInput(inputReader)
    if err != nil {
        return
    }
    result := getBestRoute(boardPairList, connectionsList)
    for _, value := range result {
        fmt.Println(value)
    }
}

func readInput(inputReader *bufio.Reader) ([]boardPair, []connection, error) {
    var n, m int
    if _, err := fmt.Fscanf(inputReader, "%d %d\n", &n, &m); err != nil {
        return nil, nil, fmt.Errorf(err.Error())
    }

    connectionsList := make([]connection, 0, n)
    for i := 0; i < n; i++ {
        datas, err := readIntSlice(inputReader, " ")
        if err != nil {
            return nil, nil, fmt.Errorf(err.Error())
        }
        connectionsList = append(connectionsList, connection{datas[0], datas[1], datas[2]})
    }
    boardPairList := make([]boardPair, 0, m)
    for i := 0; i < m; i++ {
        datas, err := readIntSlice(inputReader, " ")
        if err != nil {
            return nil, nil, fmt.Errorf(err.Error())
        }
        boardPairList = append(boardPairList, boardPair{datas[0], datas[1]})
    }
    return boardPairList, connectionsList, nil
}

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")
    lineBuf = strings.TrimSpace(lineBuf)
    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
}
View Code

 

 

第二批

1807. 矩阵转置(数学)

难度:简单;主观评价:简单。

简单模拟题+数学题(判断完全平方数)。

先判断矩阵长度是否为完全平方数(开根号然后自身相乘,判断和开根号之前的数是否一致,如果不是则直接输出 ERROR),然后开始按列遍历矩阵,每次跳过一行数字,下一行对应的位置就是下一个要处理的数字。

时间复杂度:O(n^2),其中 n 为正方形矩阵的行数,矩阵中的每个元素都会被访问一次。

空间复杂度:O(1),注意返回值所用的空间不需要参与计算。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
 * Description: 正方矩阵[1172]
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
    "math"
)

func main() {
    inputReader := bufio.NewReader(os.Stdin)
    inputStr, err := readInput(inputReader)
    if err != nil {
        return
    }
    result := multiTranspose(inputStr)
    fmt.Println(result)
}

// 待实现函数,在此函数中填入答题代码
func multiTranspose(input string) string {
    // 判断矩阵是否为正方形
    n := len(input)
    ns := int(math.Sqrt(float64(n)))
    if ns*ns != n {
        return "ERROR"
    }
    ans := strings.Builder{}
    for colIdx := 0; colIdx < ns; colIdx++ {
        // 每次跳过 ns 个数字(跳过一行)
        for rowIdx := 0; rowIdx < ns; rowIdx++ {
            ans.WriteByte(input[colIdx+ns*rowIdx])
        }
    }
    return ans.String()
}

func readInput(reader *bufio.Reader) (string, error) {
    lineBuf, err := reader.ReadString('\n')
    if err != nil && err != io.EOF {
        return "", err
    }
    lineBuf = strings.TrimRight(lineBuf, "\r\n")
    lineBuf = strings.TrimRight(lineBuf, " ")
    return lineBuf, nil
}
View Code

 

1808. 整理工号(模拟)

难度:简单;主观评价:简单。

模拟题,没啥好说的,注意题目中说明的“合法工号”的判断条件。

不是合法工号的条件有:

  1. 去除所有空格后,长度大于 9 或者长度为 1。
  2. 第一个字符不是字母。
  3. 除了第一个字符之外的其他字符含有字母。

这些条件都很容易模拟,根据这些条件过滤输入数组,然后将过滤后剩下的字符串整理成题目要求的格式即可。

去重排序时需要注意 Go 语言的比较操作符在比较字符串时就是按照字典序进行比较的,所以可以直接使用比较操作符。如果遇到不是按照字典序进行排序的题目(如让你按照一定规则比较一些 IP 地址等),也可以使用 sort.Slice() 的函数参数自定义比较逻辑。

时间复杂度:O(nlogn),其中整理操作的时间复杂度为 O(m*n),n 为输入数组的长度,需要遍历输入数组一次;m 为输入数组中最长的合法工号的长度,最长为 9;O(nlogn) 为排序算法的时间复杂度;综合时间复杂度为 O(nlogn)。

空间复杂度:O(n),去重使用的哈希表占用的空间与合法工号个数成正比。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "sort"
    "strings"
    "unicode"
)

// isValidId 判断给定的 id 是否是一个合法的工号
func isValidId(id string) bool {
    trimmedId := strings.ReplaceAll(id, " ", "")
    // 首字母不是字母或者长度大于 9 或者只有一个字母,直接丢弃
    if len(trimmedId) > 9 || len(trimmedId) <= 1 || !unicode.IsLetter(rune(trimmedId[0])) {
        return false
    }
    // 其余必须全是数字
    for i := 1; i < len(trimmedId); i++ {
        if !unicode.IsDigit(rune(trimmedId[i])) {
            return false
        }
    }
    return true
}

// toValid 转换为标准格式的工号
func toValid(id string) string {
    // 去掉空格
    id = strings.ReplaceAll(id, " ", "")
    idBytes := make([]byte, 1, 9)
    idBytes[0] = id[0]
    // 补 0
    for i := 0; i < 9-len(id); i++ {
        idBytes = append(idBytes, '0')
    }
    // 写入其余部分
    idBytes = append(idBytes, id[1:]...)
    return strings.ToLower(string(idBytes))
}

func orderID(ids map[string]struct{}) []string {
    res := make([]string, 0, 0)
    for id := range ids {
        res = append(res, id)
    }
    sort.Slice(res, func(i, j int) bool {
        return res[i] < res[j]
    })
    return res
}

// 待实现函数,在此函数中填入答题代码
func regularID(inputs []string) []string {
    // map 去重
    idMap := make(map[string]struct{})
    for _, id := range inputs {
        // 判断工号是否合法
        if !isValidId(id) {
            continue
        }
        // 格式化
        formatID := toValid(id)
        idMap[formatID] = struct{}{}
    }
    // 字典序排序
    ans := orderID(idMap)
    return ans
}

func main() {
    var n int
    _, err := fmt.Scanf("%d", &n)
    if err != nil {
        return
    }

    inputReader := bufio.NewReader(os.Stdin)
    var inputs []string
    for i := 0; i < n; i++ {
        input, err := inputReader.ReadString('\n')
        if err != nil && err != io.EOF {
            fmt.Printf("input is %s, and error is %s\n", input, err)
            break
        }
        input = strings.TrimRight(input, "\r\n")
        inputs = append(inputs, input)
    }

    result := regularID(inputs)
    for _, v := range result {
        fmt.Println(v)
    }
}
View Code

 

1812. 求矩阵列的最大值中的最小值(模拟)

难度:简单;主观评价:简单。

由于是找每一列的最大值,再找它们中的最小值,所以遍历矩阵的时候不能按照常规的按行遍历的方法,而是要按列遍历,每遍历一次找出一个最大值,然后找出这些最大值中的最小值。

时间复杂度:O(n*m),其中 n 和 m 为矩阵的行数和列数,矩阵中每个元素都会被遍历一次。

空间复杂度:O(1),只使用了常数个数的存储空间。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */
package main

import (
    "bufio"
    "fmt"
    "io"
    "math"
    "os"
    "strconv"
    "strings"
)

// 待实现函数,在此函数中填入答题代码
func getTheMinWithColMaxs(matrix [][]int) int {
    // 循环 每列表排序取出最大值构成数组
    matrixArr := make([]int, 0)
    // 先按列遍历
    for col := 0; col < len(matrix[0]); col++ {
        // 找每一列的最大值
        max := math.MinInt64
        // 再按行遍历
        for row := 0; row < len(matrix); row++ {
            if matrix[row][col] > max {
                max = matrix[row][col]
            }
        }
        matrixArr = append(matrixArr, max)
    }
    // 取得到数组的最小值
    ans := math.MaxInt64
    for _, matrixMin := range matrixArr {
        if matrixMin < ans {
            ans = matrixMin
        }
    }
    return ans
}

func main() {
    reader := bufio.NewReader(os.Stdin)
    ints, err := readIntSlice(reader, " ")
    if err != nil {
        return
    }
    n := ints[0]
    matrix := make([][]int, 0, n)
    for i := 0; i < n; i++ {
        row, err := readIntSlice(reader, " ")
        if err != nil {
            return
        }
        matrix = append(matrix, row)
    }
    fmt.Print(getTheMinWithColMaxs(matrix))

}
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
}
View Code

 

1813. 用给定的数字组成 IP 地址(递归回溯、全排列)

难度:简单;主观评价:中等。

一眼全排列,用递归回溯的方法进行求解。

注意答案要进行去重,例如“08”和“8”是同一个答案,可以使用 map 存储转换为 int 的字节数组进行去重。

搞不懂为什么这道题定的是简单,如果不知道递归回溯这种算法恐怕是做不出来这道题的,当然这题在递归回溯类问题中算是简单的,套一下递归回溯算法的模板就可以了,所以主观评价给个中等吧,毕竟 LeetCode 上涉及到递归回溯的题好像也没几道简单题。

时间复杂度:我实在是不会分析递归算法的时间复杂度,总之很高就对了,但不会超时。

空间复杂度:O(n),函数调用栈的深度与可用的数字个数成正比。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
 * Description: 用给定的数字组成IP地址[1206]
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */

package main

import (
    "bufio"
    "fmt"
    "io"
    "math"
    "os"
    "strconv"
    "strings"
)

// isValidNumber 判断某一个字节数组是否为合法的 0-255 数字
func isValidNumber(num []byte) bool {
    if len(num) > 0 && len(num) <= 3 {
        number, err := strconv.Atoi(string(num))
        if err != nil {
            return false
        }
        if number >= 0 && number <= 255 {
            return true
        }
    }
    return false
}

func backTrace(choices []byte, numChars []byte, ans map[int]struct{}) {
    // 满足条件退出
    if len(choices) >= 4 {
        return
    }
    if isValidNumber(choices) {
        number, err := strconv.Atoi(string(choices))
        if err != nil {
            return
        }
        ans[number] = struct{}{}
    }
    // 循环条件
    for i := 0; i < len(numChars); i++ {
        // 做选择
        choices = append(choices, numChars[i])
        backTrace(choices, numChars, ans)
        // 撤销选择
        choices = choices[:len(choices)-1]
    }
}

// 待实现函数,在此函数中填入答题代码
func getIPCount(numChars string) int64 {
    // 求出给出符合条件的组合数(递归回溯公式) 去重
    ans := make(map[int]struct{})
    backTrace([]byte{}, []byte(numChars), ans)
    // 返回组合数的4次方
    return int64(math.Pow(float64(len(ans)), float64(4)))
}

func main() {
    inputReader := bufio.NewReader(os.Stdin)
    numChars, err := readInput(inputReader)
    if err != nil {
        fmt.Println(err.Error())
        return
    }
    result := getIPCount(numChars)
    fmt.Println(result)
}

func readInput(reader *bufio.Reader) (string, error) {
    lineBuf, err := reader.ReadString('\n')
    if err != nil && err != io.EOF {
        return "", fmt.Errorf(err.Error())
    }
    lineBuf = strings.TrimRight(lineBuf, "\r\n")

    return lineBuf, nil
}
View Code

 

1809. 最佳升级时间窗(滑窗)

难度:中等;主观评价:简单。

一眼滑窗。

题目的难点是滑窗最长可以为周期的长度,且可以跨过周期,也就是说需要开双倍数组,或者用类似循环队列的方法解决(右指针移动时,对数组长度取模,这种方法可以将空间复杂度优化到 O(1))。

如果像我一样开双倍数组,返回时需要右指针下标对数组长度取模(样例 2)。

此外,滑窗被动收缩(左指针被动右移)的条件除了滑窗内部的数值之和大于容忍值之外,还有滑窗长度大于一个周期(需要收缩到一个周期,样例 3)。

times 变量用来记录滑窗内的数值之和,为了防止溢出需要用 uint64 类型。

以上难度都很容易想到,所以主观评价给个简单吧。

时间复杂度:O(n),需要遍历双倍数组一次。

空间复杂度:O(n),本解法中开了双倍数组模拟跨过周期的情况,需要使用与数组长度成正比的空间。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */
package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "strings"
)

// 待实现函数,在此函数中填入答题代码
func getBestTimeWindow(usersPerHour []int, threshold int) []int {
    period := append(usersPerHour, usersPerHour...)
    ans := []int{-1, -1}
    times, max := uint64(0), 0
    // 右指针主动右移
    for left, right := 0, 0; left < len(usersPerHour); right++ {
        times += uint64(period[right])
        // 当滑窗长度大于一个周期长度(样例 3)或者滑窗内的时间大于容忍值时,左指针被动右移
        for right-left+1 > len(usersPerHour) || times > uint64(threshold) {
            times -= uint64(period[left])
            left++
            if left >= len(usersPerHour) {
                break
            }
        }
        if right-left+1 > max {
            max = right - left + 1
            ans[0], ans[1] = left, right%len(usersPerHour)
        }
    }
    return ans
}

func main() {
    reader := bufio.NewReader(os.Stdin)
    ints, err := readIntSlice(reader, " ")
    if err != nil {
        return
    }
    threshold := ints[0]
    usersPerHour, err := readIntSlice(reader, " ")
    if err != nil {
        return
    }
    result := getBestTimeWindow(usersPerHour, threshold)
    fmt.Print(result[0], result[1])
}

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
}
View Code

 

1810. 日志敏感信息屏蔽(哈希表、模拟)

难度:中等;主观评价:中等。

这题说有多难吧,也没多难,毫无算法成分就是一简单的模拟题,但是规则 3 的边界条件判断非常恶心!!!如果像考试时那样看不到不通过的测试用例的话,我绝对会在这道题上翻车的,所以主观评价给了个中等。

个人认为这种死抠细节的题比单纯的算法题难多了,最恐怖的是这种细节题不像纯算法题那样容易练习,LeetCode 上也很少有这类题目,只能说多在 OJ 上找找吧。。。

具体看注释,我翻车的地方都在注释里用“易错点”标注出来了。

细节题真的是一生之敌。

时间复杂度:O(n),需要遍历 logs 数组一次。

空间复杂度:O(n),我的解法需要将 logs 数组中的元素按照分隔符切分并保存到辅助数组中,使用的辅助数组占用的空间与 logs 数组长度成正比,可以使用直接修改 logs 数组的方法优化到 O(1)。

 

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2020. All rights reserved.
 * Description: 考生实现代码
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */
package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strings"
    "unicode"
)

// 待实现函数,在此函数中填入答题代码
func logAnonymize(keys []string, logs []string) []string {
    // 关键字整理成 map,方便判断
    keywords := make(map[string]struct{})
    for _, k := range keys {
        keywords[strings.ToLower(k)] = struct{}{}
    }
    // log 按照 : 切分成二维数组
    logKvs := make([][]string, len(logs))
    for i, log := range logs {
        logKvs[i] = strings.Split(log, ":")
    }
    for i, log := range logKvs {
        // pwd 或 password 统一替换成 6 个星号
        if log[0] == "pwd" || log[0] == "password" {
            log[1] = "******"
        } else if _, ok := keywords[strings.ToLower(log[0])]; ok && strings.ToLower(log[0][len(log[0])-2:]) == "ip" {
            // key 除了最后两个字符在 keywords 里,并且最后两个字符为 ip,按规则 2 屏蔽
            log[1] = anonymizeIp(log[1])
        } else if _, ok := keywords[strings.ToLower(log[0])]; ok {
            // key 在 keywords 里,按规则 3 屏蔽
            log[1] = anonymizeNumbers(log[1])
        }
        logs[i] = strings.Join(log, ":")
    }
    return logs
}

// anonymizeIp 屏蔽 IP(规则 2)
func anonymizeIp(ip string) string {
    ipNums := strings.Split(ip, ".")
    return fmt.Sprintf("%s.***.***.%s", ipNums[0], ipNums[3])
}

// anonymizeNumbers 屏蔽数字(规则 3)
func anonymizeNumbers(log string) string {
    // 由于 unicode.IsDigit 会把 -x 认为是一个负数,导致出错,所以要先把减号替换成日志中不会出现的逗号,返回时再替换回去
    log = strings.ReplaceAll(log, "-", ",")
    // 从右侧开始找第一个长度大于等于 4 的连续数字子串
    n := len(log)
    l, r := n-1, n-1
    for l >= 0 {
        if !unicode.IsDigit(rune(log[l])) {
            r = l
        } else {
            // 找到了大于 4 的子串,对这段子串执行屏蔽
            if r-l+1 >= 4 {
                for l >= 0 && unicode.IsDigit(rune(log[l])) {
                    l--
                }
                return doAnonymize(log, l, r)
            }
        }
        l--
    }
    // 否则直接返回原来的字符串
    return strings.ReplaceAll(log, ",", "-")
}

// doAnonymize 执行屏蔽数字操作
func doAnonymize(log string, l, r int) string {
    // 易错点 1:由于 anonymizeNumbers 函数中计算 l 时多左移了一位,所以要把它加回来
    l += 1
    // 易错点 2:由于计算子串长度使用的是 r - l,所以如果满足题意的数字是在子串结尾,就会少判断一位,这时必须把右边界 + 1
    if r == len(log)-1 {
        r = len(log)
    }
    subStringLen := r - l
    // 易错点 3:由于是从倒数第 L/4 + 1 个数字开始判断的,而第 L/4 + 1 个数字也在范围内,所以要替换成 L/4 否则会少一位
    anonymizeStart := l + subStringLen - (subStringLen/4 + subStringLen/2)
    ans := make([]byte, 0, len(log))
    ans = append(ans, log[:anonymizeStart]...)
    for i := 0; i < subStringLen/2; i++ {
        ans = append(ans, '*')
    }
    ans = append(ans, log[len(ans):]...)
    return strings.ReplaceAll(string(ans), ",", "-")
}

func main() {
    readerNum := bufio.NewReader(os.Stdin)
    _, err := readerNum.ReadString('\n')
    if err != nil && err != io.EOF {
        return
    }

    //var n int
    //if _, err := fmt.Scanf("%d", &n); err != nil {
    //    return
    //}

    reader := bufio.NewReader(os.Stdin)
    keys, err := readLineToStrSlice(reader, " ")
    if err != nil {
        return
    }

    logs, err := readLineToStrSlice(reader, ",")
    if err != nil {
        return
    }
    ans := logAnonymize(keys, logs)
    result := strings.Join(ans, ",")
    fmt.Println(result)
}

func readLineToStrSlice(reader *bufio.Reader, sep string) ([]string, error) {
    lineBuf, err := reader.ReadString('\n')
    if err != nil && err != io.EOF {
        return nil, fmt.Errorf(err.Error())
    }

    lineBuf = strings.TrimRight(lineBuf, "\r\n")
    return strings.Split(lineBuf, sep), nil
}
View Code

 

 

 

1814. 代码缩进(脑筋急转弯、差分数组)

难度:中等;主观评价:中等。

乍一看很像 LeetCode 453 (Minimum Moves to Equal Array Elements),区别在于 LeetCode 453 一次只能操作一个数,这道题可以一次操作连续的多个数(即选择连续的多行代码同时操作),那么解法就不像 LeetCode 453 一样是求每个数与最小值的差,而是求所有连续上升区间中的最大值与最小值的差:因为在缩进的过程中,连续上升区间是“主动”形成的,且连续上升区间内的若干行代码可以被同时缩进,而下降区间是“被动”形成的,我们不选择缩进某行代码就会形成一个下降区间,所以在计算时我们只需要考虑连续上升区间,不需要考虑下降区间。

如果直接用这个思路,识别连续上升区间、识别并保存区间内的最小值等操作都会带来很大的麻烦,且这样做的时间复杂度为 O(n^2) 会因为问题规模太大而超时。我们可以使用差分数组来解决这个问题:设差分数组为 diff,那么某个连续上升区间内的最小值 step[i] 与最大值 steps[j] 对应的 diff[i] + diff[i+1] + … + diff[j] 即为该连续上升区间内的最大值与最小值之差。由于只需要计算上升区间,并且 diff 数组只需要使用一次,所以我们可以省略掉 diff 数组,直接把累加值保存到答案中,将空间复杂度优化到 O(1)。

如果想不到用差分数组,这道题是绝对拿不到满分的,所以与其说是算法题,我认为这道题与 LeetCode 453 一样更像是脑筋急转弯——毕竟差分数组并不是一种很复杂的算法。

时间复杂度:O(n),需要遍历 step 数组一次。

空间复杂度:O(1),只使用了常数个数的存储空间。

/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2019-2020. All rights reserved.
 * Description: 简易文本编辑器[1246]
 * Note: 缺省代码仅供参考,可自行决定使用、修改或删除
 * 只能import Go标准库
 */

package main

import (
    "bufio"
    "fmt"
    "io"
    "os"
    "strconv"
    "strings"
)

// 待实现函数,在此函数中填入答题代码
func getMinStep(steps []int) int {
    ans := steps[0]
    for i := 1; i < len(steps); i++ {
        if steps[i] > steps[i-1] {
            ans += steps[i] - steps[i-1]
        }
    }
    return ans
}

func main() {
    inputReader := bufio.NewReader(os.Stdin)
    input, err := readInput(inputReader)
    if err != nil {
        return
    }

    result := getMinStep(input)
    fmt.Println(result)
}

func readInput(reader *bufio.Reader) ([]int, error) {
    lineBuf, _ := reader.ReadString('\n')
    lineBuf = strings.TrimRight(lineBuf, "\r\n")
    num, _ := strconv.ParseInt(lineBuf, 10, 32)

    lineBuf, err := reader.ReadString('\n')
    if err != nil && err != io.EOF {
        return []int{}, fmt.Errorf(err.Error())
    }
    lineBuf = strings.TrimRight(lineBuf, "\r\n")
    lineBuf = strings.TrimSpace(lineBuf)
    stepStr := strings.Split(lineBuf, " ")
    steps := make([]int, num)
    for index, item := range stepStr {
        temp, _ := strconv.ParseInt(item, 10, 32)
        steps[index] = int(temp)
    }

    return steps, nil
}
View Code

 

 

posted @ 2023-03-04 14:53  易先讯  阅读(51)  评论(0编辑  收藏  举报