Huawei odマシンテストの面接の質問

1.HUAWEIマシンテスト102問の解答

2. Huawei のコンピュータベースのテスト問題

2023年7月30日 19:30~22:00

コンピューターベースのテストのヒントと注意事項 (テスト前に必ずお読みください):

1. コンパイル環境の変更と言語選択に注意し、コンピューターベースのテストで使い慣れている言語を選択してください。

2. コンピューターベースのテストには 3 つの質問があり、完了までに 150 分かかります。

3. 問題の難易度は、1 つ星と 2 つ星で、1 つ星 2 問、各 100 点、2 つ星 1 問、200 点、合格点 150 点です。

4. 2 つの星 1 つの質問は、2 つの質問を切り替えることで、最も確実なものを優先して表示できますが、一度星 2 つの質問に切り替えると、星 1 つの質問に戻ることはできません。

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. コンピュータベースのテストの前に、Ni​​uke.com テストバンクのすべての問題 (100 問以上) を完了し、十分な準備をしてからコンピュータベースのテストを受ける必要があります。は20%で、一度テストに合格しなかった場合、半年以内は再度テストを受けることはできず、Huaweiに参加することもできません。その他のインタビュー、ブラシ質問のリンク:

https://www.nowcoder.com/ta/huawei。

11. 試験前に、コンピュータのデスクトップをクリーンアップし、関係のないウィンドウを閉じてください。

12. 質問には最新バージョンの Chrome ブラウザ (バージョン 72 以降) を使用してください。テスト中はカメラ、画面録画、携帯電話のモニタリングをオンにする必要があります。モニタリングが異常な場合、スコアが低下する可能性があります。影響を受ける。質問に回答する前に、ガイドラインに従って機器を調整してください。

13. プログラミングの質問は、ローカル IDE コーディングをサポートしており、制限を飛び越えることなく、コピーしてテスト ページに貼り付けることができます。ローカルコンパイラ以外のページは閲覧しないでください。閲覧するとリスク特定が行われ、成績に影響します。(ローカルのコンパイル環境が Niuke.com と一致していることを確認するには、ローカルでテスト標準を満たす IDE を使用する必要があり、テストはテスト環境で実行する必要があります)。

14. 試験中はメモ用紙の使用が許可されています。事前に紙とペンをご用意ください。試験中のトイレなどの短時間の退出は認められますが、退出時間は管理してください。

15. 試験中に停電、ネットワーク障害、コンピュータのクラッシュなどの問題が発生した場合は、ブラウザを閉じて、試験問題へのリンクを再度開くと、問題への取り組みを続けることができます。

16. 問題が発生した場合は、時間内に人事部にご連絡ください。

17. 機内モードで WiFi をオンにします。

最初の質問:

トピックの説明

貧しい木こりのアリババは、薪割りの途中で強盗団の宝物庫を偶然見つけましたが、その宝物庫には0からNまでの番号が振られた箱があり、それぞれの箱に番号が書かれています。

アリババは呪文番号を読み取って、宝箱の中に 2 つの異なる箱があり、2 つの箱の番号が同じであり、2 つの箱の番号の差の絶対値が以下であるかどうかを確認します。呪文番号、

そのような宝箱のペアが存在する場合は、最初に見つかった宝箱のペアの左側にある箱の番号を返し、存在しない場合は -1 を返します。

説明を入力してください

最初の行にカンマで区切って数字の文字列を入力します (例: 1,2,3,1)。

1<=文字列内の数値数<=100000

-100000 <= 各数値 <= 100000

2 行目にスペル番号を入力します。例: 3

1<=呪文番号<=100000

出力の説明

このような宝箱のペアが存在します。最初に見つかった宝箱のペアの左のボックスの番号を返します。存在しない場合は、-1 を返します。

1、

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、

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を0とします。

アリババが金の宝箱を見つけるのを手伝ってください。条件を満たす最初の金の宝箱の番号を出力してください。金の宝箱がない場合は、-1 を返してください。

1、

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)

}

2 番目の質問:

入力の説明: 最初の行は 2 つの数値 M と N で、それぞれクエリ対象のディレクトリの数とディレクトリの ID を表します (1<=M<=100、1<=N<=200)。次の M 行には、各行に 1 つのディレクトリのデータが含まれます。このディレクトリ内のディレクトリ ID ファイル サイズ (サブディレクトリ ID のリスト)、およびサブディレクトリ リスト内のサブディレクトリ ID はカンマで区切られます。

出力の説明: クエリ ディレクトリとそのサブディレクトリのサイズの合計。

1. これは典型的な木構造処理の問題です。この問題を解決するには、深さ優先検索 (DFS) または幅優先検索 (BFS) を使用できます。以下は、Go で DFS を実装する例です。

まず、ディレクトリを表すデータ構造 (ID、ファイル サイズ、サブディレクトリのリストなど) が必要です。次に、ディレクトリとそのすべてのサブディレクトリのファイル サイズの合計を計算する関数が必要になります。

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 を読み取り、次に各ディレクトリのデータを読み取り、マップに格納します。次に、再帰関数 getSize を使用して、クエリ対象のディレクトリとそのすべてのサブディレクトリのファイル サイズの合計を計算し、結果を出力します。

注: この例では、各ディレクトリのデータを読み取るときに、そのサブディレクトリをすぐに見つけることができるように、入力データが親ディレクトリからサブディレクトリの順序で与えられることを前提としています。入力データがこのようにソートされていない場合は、最初にすべてのディレクトリのデータを読み取ってから、ディレクトリ ツリーを構築する必要があります。

2、

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、

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、

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))
}

3 番目の質問:

トピック

文字列の配列が与えられると、各文字列は小文字で構成され、文字パターンは小文字と . および * で構成され、文字列配列内のどの文字列が文字パターンに一致するかを識別します。任意の 1 文字と一致* 0 個以上の任意の文字と一致し、文字列が一致するかどうかを判断します。文字列の一部ではなく、先頭の文字列全体が一致します。

入力

最初の行はスペースで区切られた複数の文字列です 1 < 単一文字列の長さ < 100、1 < 文字列の数 < 100、2 行目は文字パターン 1 <= 文字パターンの長さ <= 50 異常なシナリオを考慮する必要はありません

出力

配列内の一致した文字列の添字(0から始まる)が複数回一致した場合、添字は昇順にソートされ区切り文字で区切られ、どれも一致しない場合は-1を出力します。

1、

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 を返します。この関数の実装では、文字列および fmt パッケージの関数を使用して出力をフォーマットします。

2、

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. 正規表現を使用して文字列を照合できます。まず、文字パターンが正規表現形式に変換され、次に文字列配列が 1 つずつ走査され、正規表現が照合に使用されます。

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デフギ

交流

出力例:

0

入力例:

ABCデフギ

広告

出力例:

-1

3. ファーウェイ技術面接の質問

最長の繰り返し部分文字列

説明する

定義 繰り返し文字列は、2 つの同一の文字列を先頭と末尾で連結することによって形成されます。たとえば、「abcabc」は 2 つの「abc」文字列を連結して形成されるため、長さ 6 の繰り返し文字列です。「abcba」は、2 つの同一の文字列を連結して形成できないため、繰り返し文字列ではありません。

文字列を指定すると、その最長の繰り返し部分文字列の長さを返します。

繰り返される文字部分文字列がない場合は 0 を返します。

この質問における部分文字列の定義は、文字列内の連続した間隔です。

データ範囲: 文字列の長さは 10^3 以下であり、文字列は小文字で構成されている必要があります。

例1

「ababc」と入力します。

戻り値: 4

例証します:

abab は、長さが 4 の最長の反復文字部分文字列です。

例 2

「abcab」と入力します

戻り値: 0

例証します:

文字列には繰り返しの部分文字列がありません

ダブルポインターを使用して実装

アイデア:

  1. 各文字から始めて文字列を走査すると、その文字で始まる最も長く繰り返される部分文字列の長さが見つかります。
  2. 各開始点に対してダブル ポインタを使用し、1 つのポインタは開始点から開始し、1 つのポインタは開始点の次の位置から開始し、異なる文字が見つかるまで 2 つのポインタが指す文字が同じかどうかを比較します。 、この時点での部分文字列の長さを記録します。
  3. 最も長く繰り返される部分文字列の長さを更新します。
  4. 最も長く繰り返される部分文字列の長さを返します。

コードは次のように実装されます。

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 は文字列の長さです。ループの 2 つの層が文字列を横断し、ループの内側の層は繰り返される部分文字列を見つけるために使用されます。
  • 空間複雑度: O(1)。余分なスペースは使用されません。

時間の複雑さを最適化する

時間計算量の最適化とは、アルゴリズムまたはデータ構造を改善してアルゴリズムの時間計算量を下げ、それによってアルゴリズムの実行効率を向上させることを指します。

時間計算量を最適化する一般的な方法は次のとおりです。

  1. より効率的なアルゴリズムを使用する: バブル ソートの代わりにクイック ソートを使用するなど、より優れたアルゴリズムを見つけて問題を解決します。
  2. ループの数を減らす: 不要なループを避け、ループの数を最小限に抑えます。
  3. 時間のためにスペースを使用する: ハッシュ テーブルを使用してルックアップの効率を向上させるなど、追加のデータ構造を使用してアルゴリズムの時間の複雑さを軽減します。
  4. 枝刈り: 検索アルゴリズムでは、枝刈り戦略によって検索スペースが削減され、それによってアルゴリズムの時間の複雑さが軽減されます。
  5. ダイナミック プログラミング: 問題をサブ問題に分解し、サブ問題の解を保存することで計算の繰り返しを回避し、アルゴリズムの時間の複雑さを軽減します。
  6. グラフ アルゴリズムの最適化: たとえば、最短経路問題を解決するには、総当たり検索アルゴリズムの代わりにダイクストラ アルゴリズムを使用します。

時間計算量の最適化は、多くの場合、コーディング プロセス中に最適化するのではなく、アルゴリズム設計の初期段階で考慮する必要があることに注意してください。同時に、時間計算量の最適化が常に最適な解決策につながるとは限らず、場合によっては時間計算量と空間計算量のバランスを取る必要があります。

スライディング ウィンドウとハッシュ テーブルを使用して実装

最長反復部分文字列の問題を実現し、時間計算量を最適化するために、スライディング ウィンドウとハッシュ テーブルの方法を使用できます。

具体的な手順は次のとおりです。

  1. 左右の 2 つのポインターを定義します。これらは、それぞれスライディング ウィンドウの左境界線と右境界線を表します。
  2. ハッシュ テーブルを使用して、各文字が出現する最新の位置を記録します。
  3. 最も長く繰り返される部分文字列の長さ maxLen を 0 に初期化します。
  4. 文字列をたどって、右ポインタが文字列の終端に到達すると、ループが終了します。
  5. 現在の文字がハッシュ テーブルに既に存在し、その最新の位置が左ポインタ以上である場合は、左ポインタを文字の最新の位置の次の位置に更新します。
  6. maxLen を現在のウィンドウの長さと maxLen の最大値になるように更新します。
  7. 現在の文字の最新の位置を右ポインタとして更新します。
  8. maxLen を最長の繰り返し部分文字列の長さとして返します。

Golang を使用して実装されたコードは次のとおりです。

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 を使用して最も長く繰り返される部分文字列の長さを認識すると、動的プログラミングの考え方を採用できます。

  1. 2 次元配列 dp を定義します。dp[i][j] は、文字列 s の i 番目の文字と j 番目の文字で終わる最長の繰り返し部分文字列の長さを表します。
  2. 最初の文字で終わる部分文字列の長さは 0 であるため、dp 配列の最初の行と最初の列を 0 に初期化します。
  3. 文字列 s の各文字を走査し、最大長 maxLen を維持します。これは、現在見つかっている最も長く繰り返される部分文字列の長さを記録するために使用されます。
  4. s の i 番目の文字が j 番目の文字と等しい場合 (i ≠ j)、dp[i][j] = dp[i-1][j-1] + 1 となり、現在の文字がより長い繰り返し部分文字列を形成する前の文字と比較できます。
  5. maxLen を dp[i][j] と maxLen の大きい値に更新します。
  6. 最後に、maxLen を返すだけです。

Golang で実装されたコード例は次のとおりです。

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 アルゴリズムは、文字列マッチングのためのアルゴリズムです。その中心的な考え方は、パターン文字列 (つまり、一致する部分文字列) を前処理して次の配列を構築し、これを使用して、一致プロセス中に一致した部分をすばやくスキップすることです。

以下は、Golang を使用して最長の繰り返し部分文字列の長さを認識する KMP アルゴリズムのコード例です。

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 アルゴリズムの実装は、文字列のマッチングではなく、最も長く繰り返される部分文字列の長さを計算するために使用されることに注意してください。したがって、次の配列を構築するときは、s[i] と s[j+1] ではなく、s[i] と s[j] が毎回比較されます。

おすすめ

転載: blog.csdn.net/niwoxiangyu/article/details/132240984