1.华为机试102道题解
2.华为机考题目
2023年7月30日 19:30~22:00
机考提示&注意事项(考前必看):
1、注意编译环境的变化及语言选择,选自己熟悉的语言机考。
2、机考共3道题,150分钟完成。
3、题目难度为:一星和两星;2道一星的题目,各100分;1道两星的题目,200分;150分合格通过。
4、2道一星题目可以2道题切换来看,优先做最有把握的,但一旦切换到两星题目就不能切换回来看一星的题目!
5、1星题目有把握2道满分通过也好,不建议空白试卷,说不准你刚好差几分,2星题目能帮你得到几分,就刚好能通过哈!
6、Python和iava的通过率高些,听说相对简单些,C/C++相对难些。
7、C一定要选择C语言做,切勿C语言选择
C++做题,因为C++写在C语言不一定编译能通过!同样,C++一定也要用C++语言做!还有C要用标准的VC 6.0编译环境。
8、Java要用标准的eclipse和满足测试标准JDK软件包。
9、机考链接有效期7天。
10、机考前一定要在牛客网题库全部题目(100多道题)刷完,充分准备后才机考,因为机考通过率20%,一旦不通过半年内不得重考,不能参加华为其他面试,刷题链接:
https://www.nowcoder.com/ta/huawei。
11、考前请电脑桌面清理干净,关闭无关的窗口。
12、请使用最新版chrome浏览器作答(72版本以上),考试过程中需开启摄像头、屏幕录制及手机监控,如果监控异常可能会影响您的成绩。请按指引调试好设备后再开始答题。
13、编程题支持本地IDE编码后复制粘贴至考试页面,不做跳出限制。请勿浏览除本地编译器以外的其他页面,否则会进行风险标识,影响您的成绩。(本地要用满足测试标准的IDE,保证本地编译环境与牛客网一致,考试时一定要在考试环境下进行)。
14、考试时允许使用草稿纸,请提前准备纸笔。考试过程中允许上厕所等短暂离开,但请控制离开时间。
15、考试期间如遇到断电、断网、死机等问题,可以关闭浏览器重新打开试卷链接即可继续做题。
16、遇到问题请及时与HR联系。
17、飞行模式 打开WiFi。
第一题:
题目描述
一贫如洗的樵夫阿里巴巴在去砍柴的路上,无意中发现了强盗集团的藏宝地,藏宝地有编号从0-N的箱子,每个箱子上面贴有一个数字。
阿里巴巴念出一个咒语数字,查看宝箱是否存在两个不同箱子,这两个箱子上贴的数字相同,同时这两个箱了的编号之差的绝对值小于等于咒语数字,
如果存在这样的一对宝箱,请返回最先找到的那对宝箱左边箱子的编号,如果不存在则返回-1
输入描述
第一行输入一个数字字串,数字之间使用逗号分隔,例如: 1,2,3,1
1<=字串中数字个数<=100000
-100000<=每个数字值<=100000
第二行输入咒语数字,例如: 3
1<=咒语数字<=100000
输出描述
存在这样的一对宝箱,请返回最先找到的那对宝箱左边箱子的编号,如果不存在则返回-1
1、
Go
package main
import (
"fmt"
"strings"
)
func findMatchingBox(nums []int, target int) int {
boxMap := make(map[int]int)
for i, num := range nums {
if j, ok := boxMap[num]; ok && i-j <= target {
return j
}
boxMap[num] = i
}
return -1
}
func main() {
var input string
fmt.Scan(&input)
numsStr := strings.Split(input, ",")
nums := make([]int, len(numsStr))
for i, numStr := range numsStr {
fmt.Sscanf(numStr, "%d", &nums[i])
}
var target int
fmt.Scan(&target)
result := findMatchingBox(nums, target)
fmt.Println(result)
}
2、
Go
package main
import (
"fmt"
"strings"
)
func findBoxIndex(nums []int, target int) int {
indexMap := make(map[int]int)
for i, num := range nums {
if prevIndex, ok := indexMap[num]; ok && i-prevIndex <= target {
return prevIndex
}
indexMap[num] = i
}
return -1
}
func main() {
var input string
fmt.Scanln(&input)
var target int
fmt.Scanln(&target)
numStrs := strings.Split(input, ",")
nums := make([]int, len(numStrs))
for i, numStr := range numStrs {
fmt.Sscanf(numStr, "%d", &nums[i])
}
result := findBoxIndex(nums, target)
fmt.Println(result)
}
这个程序首先读取输入的数字字串和咒语数字。然后,它将数字字串分割成一个整数切片,并使用 findBoxIndex 函数查找满足条件的宝箱编号。findBoxIndex 函数使用一个哈希映射来记录每个数字最后出现的索引。它遍历数字切片,对于每个数字,检查它是否在哈希映射中已经存在,并且与当前索引的差小于等于咒语数字。如果是,则返回之前出现的索引;否则,将当前索引添加到哈希映射中。如果遍历完整个切片后仍然没有找到满足条件的宝箱,函数返回 -1。
阿里巴巴找黄金宝箱
一贫如洗的樵夫阿里巴巴在去砍柴的路上,无意中发现了强盗集团的藏宝地,藏宝地有编号从0~N的箱子,每个箱子上面贴有一个数字,箱子中可能有一个黄金宝箱。
黄金宝箱满足排在它之前的所有箱子数字和等于排在它之后的所有箱子数字和;第一个箱子左边部分的数字和定义为0;最后一个宝箱右边部分的数字和定义为0。
请帮阿里巴巴找到黄金宝箱,输出第一个满足条件的黄金宝箱编号,如果不存在黄金宝箱,请返回-1。
1、
Go
package main
import (
"fmt"
)
func findGoldenBox(nums []int) int {
n := len(nums)
// 计算总和
totalSum := 0
for _, num := range nums {
totalSum += num
}
// 左边部分的和
leftSum := 0
// 右边部分的和
rightSum := totalSum
for i := 0; i < n; i++ {
rightSum -= nums[i]
if leftSum == rightSum {
return i
}
leftSum += nums[i]
}
return -1
}
func main() {
var n int fmt.Scan(&n)
nums := make([]int, n)
for i := 0; i < n; i++ {
fmt.Scan(&nums[i])
}
result := findGoldenBox(nums)
fmt.Println(result)
}
第二题:
输入描述:第一行为两个数字M,N,分别表示目录的个数和待查询的目录id,1<=M<=100 ,1<=N<=200。接下来M行,每行为1个目录的数据:目录id本目录中文件大小(子目录id列表),子目录列表中的子目录id 以逗号分隔。
输出描述:带查询目录及其子目录的大小之和。
1、这是一个典型的树状结构的处理问题。我们可以使用深度优先搜索(DFS)或广度优先搜索(BFS)来解决这个问题。下面是一个使用Go实现DFS的示例。
首先,我们需要一个数据结构来表示目录,包括它的id、文件大小,以及它的子目录列表。然后我们需要一个函数来计算一个目录及其所有子目录的文件大小之和。
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
"strings"
)
type Directory struct {
id int
size int
children []*Directory
}
var directoryMap = make(map[int]*Directory)
func getSize(dir *Directory) int {
totalSize := dir.size
for _, child := range dir.children {
totalSize += getSize(child)
}
return totalSize
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Scan()
line := scanner.Text()
parts := strings.Split(line, " ")
M, _ := strconv.Atoi(parts[0])
N, _ := strconv.Atoi(parts[1])
for i := 0; i < M; i++ {
scanner.Scan()
line = scanner.Text()
parts = strings.Split(line, " ")
id, _ := strconv.Atoi(parts[0])
size, _ := strconv.Atoi(parts[1])
dir := &Directory{
id: id,
size: size,
children: []*Directory{},
}
directoryMap[id] = dir
if len(parts) > 2 {
for _, childIdStr := range strings.Split(parts[2], ",") {
childId, _ := strconv.Atoi(childIdStr)
dir.children = append(dir.children, directoryMap[childId])
}
}
}
fmt.Println(getSize(directoryMap[N]))
}
这个程序首先读取目录的数量和待查询的目录id,然后读取每个目录的数据并存储在一个map中。然后,它使用递归函数getSize计算待查询目录及其所有子目录的文件大小之和,并输出结果。
注意:在本示例中,我们假设输入数据是按照从父目录到子目录的顺序给出的,这样我们可以在读取每个目录的数据时立即找到其子目录。如果输入数据不是这样排序的,那么我们需要先读取所有目录的数据,然后再构建目录树。
2、
Go
package main
import (
"fmt"
)
type Directory struct {
ID int
Size int
Subdirs []int
Children []*Directory
}
func calculateTotalSize(dir *Directory) int {
totalSize := dir.Size
for _, subdirID := range dir.Subdirs {
subdir := dir.Children[subdirID]
totalSize += calculateTotalSize(subdir)
}
return totalSize
}
func main() {
var m, n int
fmt.Scanln(&m, &n)
directories := make([]*Directory, m+1)
for i := 1; i <= m; i++ {
var id, size, numSubdirs int
fmt.Scanln(&id, &size, &numSubdirs)
subdirs := make([]int, numSubdirs)
for j := 0; j < numSubdirs; j++ {
fmt.Scan(&subdirs[j])
}
directory := &Directory{
ID: id,
Size: size,
Subdirs: subdirs,
}
directories[id] = directory
}
for i := 1; i <= m; i++ {
directory := directories[i]
directory.Children = make([]*Directory, len(directory.Subdirs))
for j, subdirID := range directory.Subdirs {
subdir := directories[subdirID]
directory.Children[j] = subdir
}
}
result := calculateTotalSize(directories[n])
fmt.Println(result)
}
首先根据输入的目录数据构建了一个目录树,然后使用递归函数 calculateTotalSize 计算给定目录及其子目录的大小之和。最后打印输出结果。
3、
Go
package main
import ( "fmt" )
type Directory struct {
ID int
Size int
Children []int
}
func main() {
var M, N int
fmt.Scan(&M, &N)
directories := make(map[int]Directory)
for i := 0; i < M; i++ {
var id, size int
var children []int
fmt.Scan(&id, &size)
var num int
fmt.Scan(&num)
for j := 0; j < num; j++ {
var childID int
fmt.Scan(&childID)
children = append(children, childID)
}
directory := Directory{
ID: id,
Size: size,
Children: children,
}
directories[id] = directory
}
result := getDirectorySize(directories, N)
fmt.Println(result)
}
func getDirectorySize(directories map[int]Directory, directoryID int) int {
directory := directories[directoryID] size := directory.Size
for _, childID := range directory.Children {
size += getDirectorySize(directories, childID)
}
return size
}
4、
Go
package main
import (
"bufio"
"fmt"
"os"
"strconv"
)
const N = 1000
func dfs(u int, g [][]int, w []int) int {
// 初始化为当前目录的大小
res := w[u]
for _, v := range g[u] {
// 获取所有子目录的大小
res += dfs(v, g, w)
}
return res
}
func isDigit(c byte) bool {
return c >= '0' && c <= '9'
}
func main() {
scanner := bufio.NewScanner(os.Stdin)
scanner.Split(bufio.ScanWords)
scanner.Scan()
m, _ := strconv.Atoi(scanner.Text())
scanner.Scan()
idx, _ := strconv.Atoi(scanner.Text())
// g[i] 表示目录 i 的所有一级子目录
g := make([][]int, N+1)
// w[i] 表示目录 i 的文件大小
w := make([]int, N+1)
for i := 0; i < m; i++ {
scanner.Scan()
x, _ := strconv.Atoi(scanner.Text())
scanner.Scan()
w[x], _ = strconv.Atoi(scanner.Text())
scanner.Scan()
s := scanner.Text()
lenS := len(s)
for j := 0; j < lenS; j++ {
// 如果不是数字,说明是分隔符
if !isDigit(s[j]) {
continue
}
t := 0
k := j
for k < lenS && isDigit(s[k]) {
t = t*10 + int(s[k]-'0')
k += 1
}
// t 是其中一个子目录,如此由 x 指向 t ,构造出一个目录树
g[x] = append(g[x], t)
j = k - 1
}
}
// 从查询的目录 idx 开始,求出这个目录和其所有子目录的大小
fmt.Println(dfs(idx, g, w))
}
第三题:
题目
给你一个字符串数组,每个字符串均由小写字母组成和一个字符规律由小写字母和.和*组成,识别字符串数组中哪些字符串可以匹配到字符规律上。匹配任意单个字符 * 匹配 0 个或多个任意字符,判断字符串是否匹配,是要头部整个字符串的而不是部分字符串
输入
第一行为空格分割的多个字符串 1 < 单个字符串长度 < 100, 1 < 字符串个数 < 100 ,第二行为字符规律 1 <= 字符规律长度 <= 50 不需要考虑异常场景
输出
匹配的字符串在阵列中的下标(从0开始)多个匹配时,下标升序,并用分隔符分割若均不匹配输出-1
1、
Go
package main
import (
"fmt"
"strings"
)
func isMatch(s, p string) bool {
m, n := len(s), len(p)
dp := make([][]bool, m+1)
for i := range dp {
dp[i] = make([]bool, n+1)
}
dp[0][0] = true
for i := 0; i <= m; i++ {
for j := 1; j <= n; j++ {
if p[j-1] == '*' {
dp[i][j] = dp[i][j-2] || (i > 0 && (s[i-1] == p[j-2] || p[j-2] == '.') && dp[i-1][j])
} else {
dp[i][j] = i > 0 && (s[i-1] == p[j-1] || p[j-1] == '.') && dp[i-1][j-1]
}
}
}
return dp[m][n]
}
func findMatchedStrings(strs []string, pattern string) string {
var res []int
for i, str := range strs {
if isMatch(str, pattern) {
res = append(res, i)
}
}
if len(res) == 0 {
return "-1"
}
return strings.Trim(strings.Replace(fmt.Sprint(res), " ", ",", -1), "[]")
}
func main() {
var strs []string
var pattern string
fmt.Scan(&strs)
fmt.Scan(&pattern)
fmt.Println(findMatchedStrings(strs, pattern))
}
该实现中,isMatch 函数用于判断一个字符串是否可以匹配到给定的字符规律上。该函数的实现参考了 LeetCode 上的第 10 题的解法。
findMatchedStrings 函数用于找到所有可以匹配到给定字符规律的字符串的下标。如果没有任何字符串可以匹配到字符规律,则返回 -1。该函数的实现中使用了 strings 和 fmt 包中的函数来格式化输出。
2、
Go
package main
import (
"fmt"
"strings"
)
func matchStrings(stringsArr []string, pattern string) string {
var result []string
for i, str := range stringsArr {
if isMatch(str, pattern) {
result = append(result, fmt.Sprintf("%d", i))
}
}
if len(result) > 0 {
return strings.Join(result, " ")
}
return "-1"
}
func isMatch(str, pattern string) bool {
if len(str) != len(pattern) {
return false
}
for i := 0; i < len(str); i++ {
if pattern[i] != '.' && pattern[i] != '*' && str[i] != pattern[i] {
return false
}
}
return true
}
func main() {
var stringsArr []string
var pattern string
fmt.Print("Enter the strings separated by space: ")
fmt.Scanf("%s", &stringsArr)
fmt.Print("Enter the pattern: ")
fmt.Scanf("%s", &pattern)
result := matchStrings(stringsArr, pattern)
fmt.Println("Output:", result)
}
这个程序首先读取输入的字符串数组和字符规律,然后调用matchStrings函数来进行匹配。在matchStrings函数中,它遍历字符串数组,并检查每个字符串是否与给定的字符规律匹配,如果匹配,则将该字符串的索引添加到结果数组中。最后,它将结果数组转换为字符串并返回。
isMatch函数用于检查单个字符串是否与给定的字符规律匹配。它首先检查字符串长度是否与字符规律长度相同,然后逐个字符进行比较。如果字符规律中的字符不是.或*且与字符串中的字符不匹配,则返回false。
3、可以使用正则表达式来实现字符串的匹配。首先将字符规律转换为正则表达式的格式,然后逐个遍历字符串数组,使用正则表达式进行匹配。
Go
package main
import (
"fmt"
"regexp"
"strings"
)
func main() {
var strs []string
var pattern string
// 读取输入
fmt.Scan(&strs)
fmt.Scan(&pattern)
// 将字符规律转换为正则表达式的格式
pattern = strings.ReplaceAll(pattern, ".", "\\.")
pattern = strings.ReplaceAll(pattern, "*", ".*")
// 构建正则表达式对象
reg := regexp.MustCompile("^" + pattern + "$")
// 遍历字符串数组,进行匹配
matches := make([]int, 0)
for i, str := range strs {
if reg.MatchString(str) {
matches = append(matches, i)
}
}
// 输出匹配的字符串下标
if len(matches) > 0 {
fmt.Println(strings.Trim(strings.Join(strings.Fields(fmt.Sprint(matches)), " "), "[]"))
} else {
fmt.Println(-1)
}
}
输入示例:
abc def ghi
a.*c
输出示例:
0
输入示例:
abc def ghi
a.*d
输出示例:
-1
3.华为技术面试题目
最长重复子串
描述
定义重复字符串是由两个相同的字符串首尾拼接而成。例如:"abcabc" 是一个长度为 6 的重复字符串,因为它由两个 "abc" 串拼接而成;"abcba" 不是重复字符串,因为它不能由两个相同的字符串拼接而成。
给定一个字符串,请返回其最长重复子串的长度。
若不存在任何重复字符子串,则返回 0。
本题中子串的定义是字符串中一段连续的区间。
数据范围:字符串长度不大于 10^3 ,保证字符串一定由小写字母构成。
示例1
输入:"ababc"
返回值:4
说明:
abab为最长的重复字符子串,长度为4
示例2
输入:"abcab"
返回值:0
说明:
该字符串没有重复字符子串
使用双指针实现
思路:
- 遍历字符串,以每个字符为起始点,找出以该字符为起始点的最长重复子串长度。
- 对于每个起始点,使用双指针的方式,一个指针从起始点开始,一个指针从起始点的下一个位置开始,依次向后比较两个指针所指的字符是否相同,直到找到不相同的字符,记录下此时的子串长度。
- 更新最长重复子串长度。
- 返回最长重复子串长度。
代码实现如下:
Go
func longestDupSubstring(s string) int {
n := len(s)
if n == 0 {
return 0
}
maxLen := 0
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
if s[i] == s[j] {
k := 1
for i+k < n && j+k < n && s[i+k] == s[j+k] {
k++
}
if k > maxLen {
maxLen = k
}
}
}
}
return maxLen
}
复杂度分析:
- 时间复杂度:O(n^3),其中 n 是字符串的长度。两层循环遍历字符串,内部还有一层循环用于查找重复子串。
- 空间复杂度:O(1)。没有使用额外的空间。
优化时间复杂度
优化时间复杂度是指通过改进算法或数据结构,使得算法的时间复杂度更低,从而提高算法的执行效率。
常见的优化时间复杂度的方法有:
- 使用更高效的算法:通过找到更优的算法来解决问题,例如使用快速排序替代冒泡排序等。
- 减少循环次数:避免不必要的循环,尽量减少循环的次数。
- 使用空间换时间:通过使用额外的数据结构来减少算法的时间复杂度,例如使用哈希表来提高查找的效率。
- 剪枝:在搜索算法中,通过剪枝策略来减少搜索空间,从而减少算法的时间复杂度。
- 动态规划:通过将问题分解成子问题,并保存子问题的解,避免重复计算,从而减少算法的时间复杂度。
- 图算法的优化:例如使用Dijkstra算法替代暴力搜索算法来解决最短路径问题。
需要注意的是,优化时间复杂度往往需要在算法设计的初期进行考虑,而不是在编码的过程中进行优化。同时,优化时间复杂度并不一定总是能够得到最优解,有时候需要权衡时间复杂度和空间复杂度之间的平衡。
使用滑动窗口和哈希表实现
要实现最长重复子串的问题,并且优化时间复杂度,可以使用滑动窗口和哈希表的方法。
具体步骤如下:
- 定义两个指针left和right,分别表示滑动窗口的左右边界。
- 使用哈希表记录每个字符出现的最新位置。
- 初始化最长重复子串的长度maxLen为0。
- 遍历字符串,当right指针到达字符串末尾时,结束循环。
- 如果当前字符在哈希表中已经存在,并且其最新位置大于等于left指针,则更新left指针为该字符最新位置的下一个位置。
- 更新maxLen为当前窗口的长度与maxLen的最大值。
- 更新当前字符的最新位置为right指针。
- 返回maxLen作为最长重复子串的长度。
以下是使用Golang实现的代码:
Go
func findLongestDuplicateSubstr(s string) int {
n := len(s)
if n <= 1 {
return 0
}
left, right := 0, 1
maxLen := 0
lastIndex := make(map[byte]int)
for right < n {
if lastIndex[s[right]] >= left {
left = lastIndex[s[right]] + 1
}
maxLen = max(maxLen, right-left+1)
lastIndex[s[right]] = right
right++
}
return maxLen
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
该算法的时间复杂度为O(n),其中n为字符串的长度。
使用动态规划实现
golang实现最长重复子串的长度,动态规划
这是一个经典的动态规划问题,用Golang实现最长重复子串的长度可以采用动态规划的思路:
- 定义一个二维数组dp,dp[i][j]表示以字符串s的第i个字符和第j个字符为结尾的最长重复子串的长度。
- 初始化dp数组的第一行和第一列都为0,因为以第一个字符为结尾的子串长度都为0。
- 遍历字符串s的每个字符,同时维护一个最大长度maxLen,用来记录当前找到的最长重复子串的长度。
- 如果s的第i个字符和第j个字符相等(i ≠ j),则dp[i][j] = dp[i-1][j-1] + 1,表示当前字符可以和前一个字符构成更长的重复子串。
- 更新maxLen为dp[i][j]和maxLen中的较大值。
- 最后返回maxLen即可。
下面是用Golang实现的代码示例:
Go
func LongestRepeatingSubstring(s string) int {
n := len(s)
dp := make([][]int, n+1)
for i := 0; i <= n; i++ {
dp[i] = make([]int, n+1)
}
maxLen := 0
for i := 1; i <= n; i++ {
for j := i+1; j <= n; j++ {
if s[i-1] == s[j-1] {
dp[i][j] = dp[i-1][j-1] + 1
maxLen = max(maxLen, dp[i][j])
}
}
}
return maxLen
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
使用时,调用LongestRepeatingSubstring
函数并传入要计算的字符串s,即可得到最长重复子串的长度。
使用KMP算法实现
golang实现最长重复子串的长度,KMP
要使用KMP算法实现最长重复子串的长度,需要先理解KMP算法的原理。
KMP算法是一种用于字符串匹配的算法,其核心思想是通过预处理模式串(即要匹配的子串),构建一个next数组,用于在匹配过程中快速跳过已经匹配过的部分。
下面是用Golang实现最长重复子串的长度的KMP算法代码示例:
Go
func LongestRepeatingSubstring(s string) int {
n := len(s)
// 构建next数组
next := make([]int, n)
for i := 1; i < n; i++ {
j := next[i-1]
for j > 0 && s[i] != s[j] {
j = next[j-1]
}
if s[i] == s[j] {
j++
}
next[i] = j
}
// 找到最长重复子串的长度
maxLen := 0
for i := 1; i < n; i++ {
maxLen = max(maxLen, next[i])
}
return maxLen
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
使用时,调用LongestRepeatingSubstring
函数并传入要计算的字符串s,即可得到最长重复子串的长度。
注意,这里的KMP算法的实现是用于计算最长重复子串的长度,而不是用于字符串匹配。所以在构建next数组时,每次比较的是s[i]和s[j],而不是s[i]和s[j+1]。