【后端部分】青训营题目的思维拓展:解题思路分享

2,418 阅读10分钟

后端笔试题目-编程题解

前言

近日无事,写题有感,遂作此文
本文所涉及的的题目来自于青训营后端笔试练习(编程)题,题目的题解基于我的个人实践,很多东西基于我个人的理解,所以有望大家多多指教。
为了照顾更多读者,我使用了PythonGolang两种语言分别编写了题目的题解。如果你发现了本文的错误之处,欢迎你在评论区留言,我会及时的进行修改。如果你有其他的想法,也欢迎在评论区留言,我会在看到评论的第一时间回复。
Tips为了更好地整合文章,我已经将本文章收录至专栏“青训营题解”,专栏内有其他方向的内容,感兴趣的小伙伴可以关注一下专栏。

题目:36进制加法

实现一个 36 进制的加法 0-9 a-z。
示例
输入:["abbbb","1"],输出:"abbbc"

题解

写题思路

本题的难点在于如何实现进制转换,实现进制转换的方法有很多,比如:逐位取数、取对数、位权法、查表法等,我使用的是位权法。
位权法的核心思想是将待转换的数的每一位与对应的位权相乘,再将所有的积相加得到转换结果。
以我的Python解法为例,我在代码的第二行和第三行,使用列表推导式遍历 n1n2 的每个字符,并使用列表 cov_table 中的索引查找对应的数字。每个字符对应的数字乘以 36 的幂次方再累加到结果中,即将待转换的数的每一位与对应的位权相乘,再将所有的积相加得到转换结果。

位权法的数学原理:x=i=0n1aibix=\sum_{i=0}^{n-1}a_ib^i

位权法可以用来实现任意进制之间的转换,只需要修改进制数 bb 即可。 其中,xx 是待转换的数,aia_i 是该数的第 ii 位的值,bb 是转换的进制数,nn 是该数的位数。

例如,将数字 ABC(十六进制)转换为十进制的过程可以用如下公式表示: x=A×162+B×161+C×160x=A \times 16^2 + B \times 16^1 + C \times 16^0
其中,AABBCC 分别表示数字 ABC 的第三位、第二位、第一位的值。

代码实现

python解法

def add_36(n1, n2):
    cov_table = [str(i) for i in range(10)] + [chr(i) for i in range(ord('a'),ord('z')+1)]
    num1 = sum(cov_table.index(c) * 36 **i for i, c in enumerate(n1[::-1]))
    num2 = sum(cov_table.index(c) * 36 **i for i, c in enumerate(n2[::-1]))
    num = num1 + num2
    result = ''
    while num > 0:
        result = chr(num % 36 + ord('a') - 10) + result
        num //= 36
    return result

golang解法

package main

import (
	"fmt"
	"strconv"
)

func add36(num1, num2 string) string {
	// 创建转换表
	convertTable := "0123456789abcdefghijklmnopqrstuvwxyz"

	// 将字符串转换为数字
	num1Int, _ := strconv.ParseInt(num1, 36, 64)
	num2Int, _ := strconv.ParseInt(num2, 36, 64)

	// 将两个数字相加
	resultInt := num1Int + num2Int

	// 将结果转换为字符串
	resultStr := strconv.FormatInt(resultInt, 36)

	return resultStr
}

结果展示

image.png

题目:电影院座位推荐

抖音电影票业务支持电影院选座,需要在用户买票时自动推荐座位,如果一个用户买了多张票,则需要推荐相邻(上下相邻、左右相邻都可)的座位。现在使用一个二维数组来表示电影院的座位,数组中 0 表示未被选座,1 表示已被选座或者为障碍物,请实现一个方法求出给定影院中最大可推荐的相邻座位个数。

示例 输入:
[1,0,0,1,0,0,0]
[1,0,0,0,0,1,1]
[0,0,0,1,0,0,0]
[1,1,0,1,1,0,0]

输出:18

题解

写题思路

做过”01背包“这道题目的小伙伴在看到这道题的第一瞬间应该可以反应过来:这不就是一道动态规划题目嘛,还是很简单的。如果你有兴趣,可以尝试用动态规划的思想来尝试解决这道题,我会把我写的动规的解法放在文末”码上掘金“里供大家参考。在这里,我介绍一下本题使用DFS和BFS的解法。
BFS(广度优先搜索) 和 DFS(深度优先搜索) 都是图论中常用的搜索算法,它们可以用来遍历图中的所有节点,寻找满足某些条件的路径。
对于本题,如果使用 BFS 或 DFS 进行搜索,可以从每一个可以占用的座位出发,向相邻的座位进行拓展,直到无法拓展为止。在拓展的过程中,可以使用一个变量记录已经拓展的座位数,并更新最大值。
但是,BFS 和 DFS 的时间复杂度较高,在处理大规模数据时可能会超时。

使用 DFS 解决本题的思路如下:

  1. 定义四个方向数组,表示向左、向右、向上、向下移动一格。
  2. 定义一个二维数组,表示当前位置是否已经被访问过。
  3. 定义一个递归函数,用来从当前位置开始 DFS。
  4. 在递归函数中,判断当前位置是否已经被访问过或者是障碍物。如果是,则不进行 DFS,直接返回 0。否则,将当前位置标记为已访问,并初始化可推荐座位个数为 1。
  5. 遍历当前位置的每个相邻位置,如果相邻位置未被访问过且不是障碍物,则继续 DFS。
  6. 最后,返回可推荐座位个数。

使用 BFS 解决本题的思路如下:

  1. 定义四个方向数组,表示向左、向右、向上、向下移动一格。
  2. 定义一个二维数组,表示当前位置是否已经被访问过。
  3. 初始化一个队列,将所有可以占用的座位加入队列。
  4. 初始化最大可推荐座位个数为 0。
  5. 反复执行以下操作,直到队列为空:
    • 取出队列的头元素。
    • 将当前位置标记为已访问。
    • 遍历当前位置的每个相邻位置,如果相邻位置未被访问过且不是障碍物,则将相邻位置加入队列。
    • 更新最大可推荐座位个数。
  6. 返回最大可推荐座位个数。

代码实现

python解法

import random
from collections import deque

# DFS解法
def maxRecommend_dfs(cinema):
    # 四个方向数组,表示向左、向右、向上、向下移动一格
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]

    # 二维数组,表示当前位置是否已经被访问过
    visited = [[0] * len(cinema[0]) for _ in range(len(cinema))]

    def dfs(x, y):
        # 如果当前位置已经被访问过或者是障碍物,则不进行 DFS
        if visited[x][y] or cinema[x][y] == 1:
            return 0

        # 标记当前位置已经被访问过
        visited[x][y] = 1

        # 初始化可推荐座位个数
        count = 1

        # 遍历当前位置的每个相邻位置
        for i in range(4):
            nx, ny = x + dx[i], y + dy[i]
            # 如果相邻位置未被访问过且不是障碍物,则继续 DFS
            if 0 <= nx < len(cinema) and 0 <= ny < len(cinema[0]) and not visited[nx][ny] and cinema[nx][ny] == 0:
                count += dfs(nx, ny)

        return count

    # 初始化最大可推荐座位个数
    result = 0
    # 遍历每个未被访问过的座位
    for i in range(len(cinema)):
        for j in range(len(cinema[0])):
            if not visited[i][j] and cinema[i][j] == 0:
                # 从当前位置开始 DFS,并更新最大可推荐座位个数
                result = max(result, dfs(i, j))
    return result
# BFS解法
def maxRecommend_bfs(cinema):
    # 四个方向数组,表示向左、向右、向上、向下移动一格
    dx = [-1, 1, 0, 0]
    dy = [0, 0, -1, 1]

    # 二维数组,表示当前位置是否已经被访问过
    visited = [[0] * len(cinema[0]) for _ in range(len(cinema))]

    # 定义一个队列,用来存储 BFS 搜索时遍历到的位置
    queue = deque()

    # 初始化最大可推荐座位个数
    result = 0
    # 遍历每个未被访问过的座位
    for i in range(len(cinema)):
        for j in range(len(cinema[0])):
            if not visited[i][j] and cinema[i][j] == 0:
                # 将当前位置加入队列
                queue.append((i, j))
                # 标记当前位置已经被访问过
                visited[i][j] = 1

                # 初始化当前推荐座位个数
                count = 1
                # 循环遍历队列中的每个位置
                while queue:
                    # 从队列中取出一个位置
                    x, y = queue.popleft()
                    # 遍历当前位置的每个相邻位置
                    for i in range(4):
                        nx, ny = x + dx[i], y + dy[i]
                        # 如果相邻位置未被访问过且不是障碍物,则加入队列
                        if 0 <= nx < len(cinema) and 0 <= ny < len(cinema[0]) and not visited[nx][ny] and cinema[nx][ny] == 0:
                            queue.append((nx, ny))
                            #标记相邻位置已经被访问过
                            visited[nx][ny] = 1
                            # 更新当前推荐座位个数
                            count += 1
                            # 更新最大可推荐座位个数
                            result = max(result, count)
    return result
# 数据生成器
def generate_random_seats(rows, cols):
    seats = [[random.randint(0, 1) for _ in range(cols)] for _ in range(rows)]
    return seats

golang解法

// DFS 实现
func maxSeatsDFS(seats [][]int) int {
	// 初始化答案
	maxSeats := 0

	// 创建 visited 数组记录每个位置是否访问过
	visited := make([][]bool, len(seats))
	for i := range visited {
		visited[i] = make([]bool, len(seats[0]))
	}

	// DFS 搜索函数
	var search func(seats [][]int, visited [][]bool, i, j int) int
	search = func(seats [][]int, visited [][]bool, i, j int) int {
		// 如果越界或者当前位置为障碍物或者已经访问过,返回 0
		if i < 0 || i >= len(seats) || j < 0 || j >= len(seats[0]) || seats[i][j] == 1 || visited[i][j] {
			return 0
		}
		// 标记当前位置为已访问
		visited[i][j] = true
		// 递归搜索上下左右四个方向
		return 1 + search(seats, visited, i-1, j) + search(seats, visited, i+1, j) + search(seats, visited, i, j-1) + search(seats, visited, i, j+1)
	}

	// 遍历所有位置,计算最大的相邻座位个数
	for i := 0; i < len(seats); i++ {
		for j := 0; j < len(seats[0]); j++ {
			if seats[i][j] == 0 {
				maxSeats = max(maxSeats, search(seats, visited, i, j))
			}
		}
	}
	return maxSeats
}

// BFS实现
func maxSeatsBFS(cinema [][]int) int {
	// 四个方向数组,表示向左、向右、向上、向下移动一格
	dx := []int{-1, 1, 0, 0}
	dy := []int{0, 0, -1, 1}

	// 二维数组,表示当前位置是否已经被访问过
	visited := make([][]int, len(cinema))
	for i := range visited {
		visited[i] = make([]int, len(cinema[0]))
	}

	// 定义一个队列,用来存储 BFS 搜索时遍历到的位置
	queue := []int{}

	// 初始化最大可推荐座位个数
	result := 0
	// 遍历每个未被访问过的座位
	for i := 0; i < len(cinema); i++ {
		for j := 0; j < len(cinema[0]); j++ {
			if visited[i][j] == 0 && cinema[i][j] == 0 {
				// 将当前位置加入队列
				queue = append(queue, i*len(cinema[0])+j)
				// 标记当前位置已经被访问过
				visited[i][j] = 1

				// 初始化当前推荐座位个数
				count := 1
				// 循环遍历队列中的每个位置
				for len(queue) > 0 {
					// 从队列中取出一个位置
					x, y := queue[0]/len(cinema[0]), queue[0]%len(cinema[0])
					queue = queue[1:]
					// 遍历当前位置的每个相邻位置
					for i := 0; i < 4; i++ {
						nx, ny := x+dx[i], y+dy[i]
						// 如果相邻位置未被访问过且不是障碍物,则加入队列
						if nx >= 0 && nx < len(cinema) && ny >= 0 && ny < len(cinema[0]) && visited[nx][ny] == 0 && cinema[nx][ny] == 0 {
							queue = append(queue, nx*len(cinema[0])+ny)
							//标记相邻位置已经被访问过
							visited[nx][ny] = 1
							// 更新当前推荐座位个数
							count++
							// 更新最大可推荐座位个数
							result = max(result, count)
						}
					}
				}
			}
		}
	}
	return result
}

// 定义 max 函数
func max(x, y int) int {
	if x > y {
		return x
	}
	return y
}

结果展示

image.png

题目:有效 IP 地址

有效 IP 地址正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 '.' 分隔。

例如:"0.1.2.201" 和 "192.168.1.1" 是有效 IP 地址,但是 "0.011.255.245"、"192.168.1.312" 和 "192.168@1.1" 是无效 IP 地址。

给定一个字符串 s,非数字的字符可替换为任意不包含在本字符串的数字,同样的字符只能替换为同样的数字,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你不能重新排序或删除 s 中的任何数字,你可以按任何顺序返回答案。

示例 1
输入:20212118136
输出:
20.212.118.136
202.12.118.136
202.121.18.136
202.121.181.36

示例 2
输入:11a2b22a037
输出:114.252.240.37

题解

解题思路

这道题路主要考验逻辑性,相信很多读者也发现了,leetcode上面有题和这道题相似,但和leetcode上那道题不同的是,本题加了一个条件,即:”非数字的字符可替换为任意不包含在本字符串的数字,同样的字符只能替换为同样的数字“,这就需要我们处理好替换字母的值不能和字符串本身的数字重复。
我的思路是创建一个列表来记录重复的数字

解题思路如下:

  • 定义一个递归函数,用于在字符串 s 中插入 '.',并生成可能的有效 IP 地址。
  • 在递归函数中,首先判断当前递归深度是否达到了 4,如果达到了 4,则判断当前字符串是否是有效 IP 地址,如果是,则将其加入答案中。
  • 如果当前递归深度未达到 4,则从当前位置开始,往后枚举可能的 IP 地址数字,并调用递归函数继续插入 '.'。
  • 在枚举过程中,需要注意以下几点:
    • 如果当前字符是数字,则直接添加到字符串中。
    • 如果当前字符是字母,则枚举所有可能的数字,并将当前字符替换
  • 最后,在主函数中,调用递归函数并返回答案即可。

代码实现

Python解法

import itertools as it

def create_mapping(s, numbers, mappings):
    # 创建字典,用于记录每个非数字字符对应的数字
    # 创建列表,用于记录被删除的数字,防止重复删除
    no = []
    nodigit = []
    for c in s:
        if c in no or c in nodigit:
            continue
        elif c.isdigit():
            no.append(c)
            numbers.remove(c)
        else:
            nodigit.append(c)

    for e in it.permutations(numbers, len(nodigit)):
        mapping = {}
        x = 0
        for c in nodigit:
            mapping[c] = e[x]
            x += 1
        mappings.append(mapping)
    return mappings

def is_valid_part(s, mapping):
    if not s:
        return False
    # if len(s) > 1 and s[0] == '0':
    #     return False
    for c in s:
        if c in mapping:
            s = s.replace(c, mapping[c])
    if len(s) > 1 and s[0] == '0':
        return False
    if not s.isdigit():
        return False
    if int(s) > 255:
        return False
    return True

def dfs(s, path, res, mapping):
    if not s and len(path) == 4:
        res.append('.'.join(path))
        return
    if len(path) == 4:
        return
    for i in range(1, 4):
        if i > len(s):
            break
        part = s[:i]
        if is_valid_part(part, mapping):
            dfs(s[i:], path + [part], res, mapping)

def restoreIpAddresses(s):
    ans = []
    # 创建字典,用于记录每个非数字字符对应的数字
    numbers = [str(i) for i in range(10)]
    mappings = []
    mappings = create_mapping(s, numbers, mappings)
    for mapping in mappings:
        res = []
        dfs(s, [], res, mapping)

        translator = str.maketrans(mapping)
        for i in range(len(res)):
            ans.append(res[i].translate(translator))
    return ans

golang题解

func createMapping(s string, numbers []string, mapping map[byte]string) map[byte]string {
	// 创建字典,用于记录每个非数字字符对应的数字
        // 创建列表,用于记录被删除的数字,防止重复删除
	no := []string{}
        x := 0
	for i := 0; i < len(s); i++ {
		if stringInSlice(string(s[i]), no) {
			continue
		} else if unicode.IsDigit(rune(s[i])) {
			no = append(no, string(s[i]))
			numbers = remove(numbers, string(s[i]))
		} else {
			mapping[s[i]] = numbers[x]
                        x += 1
		}
	}
	return mapping
}

func stringInSlice(a string, list []string) bool {
	for _, b := range list {
		if b == a {
			return true
		}
	}
	return false
}

func remove(slice []string, element string) []string {
	for i, v := range slice {
		if v == element {
			return append(slice[:i], slice[i+1:]...)
		}
	}
	return slice
}

func isValidPart(s string, mapping map[byte]string) bool {
	if s == "" {
		return false
	}
	if len(s) > 1 && s[0] == '0' {
		return false
	}
	for i := 0; i < len(s); i++ {
		if val, ok := mapping[s[i]]; ok {
			s = strings.Replace(s, string(s[i]), val, 1)
		}
	}
	if !isNumber(s) {
		return false
	}
	if strToInt(s) > 255 {
		return false
	}
	return true
}

func isNumber(s string) bool {
	_, err := strconv.ParseInt(s, 10, 64)
	return err == nil
}

func strToInt(s string) int {
	num, _ := strconv.ParseInt(s, 10, 64)
	return int(num)
}

func dfs(s string, path []string, res *[]string, mapping map[byte]string) {
	if s == "" && len(path) == 4 {
		*res = append(*res, strings.Join(path, "."))
		return
	}
	if len(path) == 4 {
		return
	}
	for i := 1; i < 4; i++ {
		if i > len(s) {
			break
		}
		part := s[:i]
		if isValidPart(part, mapping) {
			dfs(s[i:], append(path, part), res, mapping)
		}
	}
}

func restoreIpAddresses(s string) []string {
	ans := []string{}
	// 创建字典,用于记录每个非数字字符对应的数字
	numbers := []string{"0", "1", "2", "3", "4", "5", "6", "7", "8", "9"}
	mapping := map[byte]string{}
	// 使用字典将输入字符串中的字母替换成没有出现在输入字符串中的单个数字
	mapping = createMapping(s, numbers, mapping)
	res := []string{}
	dfs(s, []string{}, &res, mapping)
	// translator := str.maketrans(mapping)
	// new_res = res[0].translate(translator)
	for i := 0; i < len(res); i++ {
		ip := ""
		for j := 0; j < len(res[i]); j++ {
			if val, ok := mapping[res[i][j]]; ok {
				ip += val
			} else {
				ip += string(res[i][j])
			}
		}
		ans = append(ans, ip)
	}
	return ans
}

结果展示

image.png

最后

如果你觉得本文对你有帮助,欢迎你留下一个大大的赞,你的支持是我更新最大的动力,下次更新会更快! (请点击Script查看代码)