前置知识:静态空间方式实现前缀树、异或运算、递归
题目1.接头密匙
测试链接
答案
package main
import (
"strconv"
"strings"
)
const (
MAXN = 2000001
ROAD_COUNT = 12 // 0-9 # -
)
var (
tree = [MAXN][ROAD_COUNT]int{}
pass = [MAXN]int{}
end = [MAXN]int{}
cnt = 1
)
/**
* @param b int整型二维数组
* @param a int整型二维数组
* @return int整型一维数组
*/
func countConsistentKeys(b [][]int, a [][]int) []int {
ans := make([]int, len(b))
for _, arr := range a {
Insert(calcStr(arr))
}
for i, arr := range b {
ans[i] = PrefixNumber(calcStr(arr))
}
Clear()
return ans
}
func calcStr(arr []int) string {
var builder strings.Builder
n := len(arr)
for i := 1; i < n; i++ {
builder.WriteString(strconv.Itoa(arr[i] - arr[i-1]))
builder.WriteByte('#')
}
return builder.String()
}
func Insert(word string) {
n := len(word)
treeIdx := 0
pass[treeIdx]++
for i := 0; i < n; i++ {
idx := getIdx(word[i])
if tree[treeIdx][idx] == 0 {
tree[treeIdx][idx] = cnt
cnt++
}
treeIdx = tree[treeIdx][idx]
pass[treeIdx]++
}
end[treeIdx]++
}
func Search(word string) int {
n := len(word)
treeIdx := 0
for i := 0; i < n; i++ {
treeIdx = tree[treeIdx][getIdx(word[i])]
if treeIdx == 0 {
return 0
}
}
return end[treeIdx]
}
func PrefixNumber(word string) int {
n := len(word)
treeIdx := 0
for i := 0; i < n; i++ {
treeIdx = tree[treeIdx][getIdx(word[i])]
if treeIdx == 0 {
return 0
}
}
return pass[treeIdx]
}
func Delete(word string) {
if Search(word) == 0 {
return
}
n := len(word)
treeIdx := 0
pass[treeIdx]--
for i := 0; i < n; i++ {
idx := getIdx(word[i])
pass[tree[treeIdx][idx]]--
if pass[tree[treeIdx][idx]] == 0 {
tree[treeIdx][idx] = 0
return
}
treeIdx = tree[treeIdx][idx]
}
end[treeIdx]--
}
func Clear() {
for i := 0; i < cnt; i++ {
for j := 0; j < ROAD_COUNT; j++ {
tree[i][j] = 0
}
pass[i] = 0
end[i] = 0
}
cnt = 1
}
func getIdx(char byte) int {
if char == '-' {
return 10
}
if char == '#' {
return 11
}
return int(char - '0')
}
复杂度
- 时间复杂度:
- 空间复杂度:,这是树上的节点数量
其中,10
是指 int32
差值的十进制字符长度,这只是大体估计,没有算上 -
和 #
题目 2.数组中两个数的最大异或值
题目描述
给你一个整数数组 nums
,返回 nums[i] XOR nums[j]
的最大运算结果,其中 0 ≤ i ≤ j < n
测试链接
思路
以数字的二进制字符串构建前缀树
答案
const (
INT_BIT_COUNT = int(unsafe.Sizeof(int(0)) * 8) // int 占的位数
MAXN = 3000001
ROAD_COUNT = 2 // 0 1
)
var (
tree = [MAXN][ROAD_COUNT]int{}
pass = [MAXN]int{}
end = [MAXN]int{}
cnt = 1
high int // 最高哪个位有 1
)
func findMaximumXOR(nums []int) int {
ans := 0 // 自己可以异或自己,所以至少可以是 0
calcHigh(nums)
for _, num := range nums {
Insert(getBinaryStr(num))
}
for _, num := range nums {
prefix := make([]byte, high)
curAns := 0
for i := high - 1; i >= 0; i-- {
// 想要的
prefix[high-1-i] = '0'
if num>>i&1 == 0 {
prefix[high-1-i] = '1'
}
if PrefixNumber(string(prefix[:high-i])) > 0 {
curAns |= 1 << i
} else {
// 没有:只能接受拥有的
if prefix[high-1-i] == '0' {
prefix[high-1-i] = '1'
} else {
prefix[high-1-i] = '0'
}
}
}
if curAns > ans {
ans = curAns
}
}
Clear()
return ans
}
// 计算最高哪个位有 1
func calcHigh(nums []int) {
maxx := 0
for _, num := range nums {
if num > maxx {
maxx = num
}
}
high = INT_BIT_COUNT - bits.LeadingZeros(uint(maxx))
}
func getBinaryStr(num int) string {
ans := make([]byte, high)
for i := high - 1; i >= 0; i-- {
ans[high-1-i] = byte(num>>i&1) + '0'
}
return string(ans)
}
func Insert(word string) {
n := len(word)
treeIdx := 0
pass[treeIdx]++
for i := 0; i < n; i++ {
idx := getIdx(word[i])
if tree[treeIdx][idx] == 0 {
tree[treeIdx][idx] = cnt
cnt++
}
treeIdx = tree[treeIdx][idx]
pass[treeIdx]++
}
end[treeIdx]++
}
func Search(word string) int {
n := len(word)
treeIdx := 0
for i := 0; i < n; i++ {
treeIdx = tree[treeIdx][getIdx(word[i])]
if treeIdx == 0 {
return 0
}
}
return end[treeIdx]
}
func PrefixNumber(word string) int {
n := len(word)
treeIdx := 0
for i := 0; i < n; i++ {
treeIdx = tree[treeIdx][getIdx(word[i])]
if treeIdx == 0 {
return 0
}
}
return pass[treeIdx]
}
func Delete(word string) {
if Search(word) == 0 {
return
}
n := len(word)
treeIdx := 0
pass[treeIdx]--
for i := 0; i < n; i++ {
idx := getIdx(word[i])
pass[tree[treeIdx][idx]]--
if pass[tree[treeIdx][idx]] == 0 {
tree[treeIdx][idx] = 0
return
}
treeIdx = tree[treeIdx][idx]
}
end[treeIdx]--
}
func Clear() {
for i := 0; i < cnt; i++ {
for j := 0; j < ROAD_COUNT; j++ {
tree[i][j] = 0
}
pass[i] = 0
end[i] = 0
}
cnt = 1
}
func getIdx(char byte) int {
return int(char - '0')
}
优化:
const (
INT_BIT_COUNT = int(unsafe.Sizeof(int(0)) * 8) // int 占的位数
MAXN = 3000001
ROAD_COUNT = 2 // 0 1
)
var (
tree = [MAXN][ROAD_COUNT]int{}
cnt = 1
high int // 最高哪个位有 1
)
func findMaximumXOR(nums []int) int {
ans := 0 // 自己可以异或自己,所以至少可以是 0
maxx := 0
for _, num := range nums {
if num > maxx {
maxx = num
}
}
high = INT_BIT_COUNT - bits.LeadingZeros(uint(maxx))
for _, num := range nums {
Insert(num)
}
for _, num := range nums {
ans = max(ans, maxXor(num))
}
Clear()
return ans
}
func maxXor(num int) int {
// 最终异或的结果(尽量大)
ans := 0
treeIdx := 0
status := 0 // num 某一位的状态
want := 0 // num 某一位希望遇到的状态
for i := high - 1; i >= 0; i-- {
status = (num >> i) & 1
want = status ^ 1
if tree[treeIdx][want] == 0 {
// 不能达成
want ^= 1
}
// want 变成真的往下走的路
ans |= (status ^ want) << i
treeIdx = tree[treeIdx][want]
}
return ans
}
func Insert(num int) {
treeIdx := 0
for i := high - 1; i >= 0; i-- {
idx := (num >> i) & 1
if tree[treeIdx][idx] == 0 {
tree[treeIdx][idx] = cnt
cnt++
}
treeIdx = tree[treeIdx][idx]
}
}
func Clear() {
for i := 0; i < cnt; i++ {
for j := 0; j < ROAD_COUNT; j++ {
tree[i][j] = 0
}
}
cnt = 1
}
哈希表的方法
思路和前缀树差不多
const (
INT_BIT_COUNT = int(unsafe.Sizeof(int(0)) * 8) // int 占的位数
)
var (
emptyStruct = struct{}{}
)
func findMaximumXOR(nums []int) int {
ans := 0 // 自己可以异或自己,所以至少可以是 0
set := map[int]struct{}{}
maxx := 0
for _, num := range nums {
if num > maxx {
maxx = num
}
}
// 最高哪个位有 1
high := INT_BIT_COUNT - bits.LeadingZeros(uint(maxx))
for i := high - 1; i >= 0; i-- {
// ans: 31...i+1 位都已经达成目标
// 第 i 位能不能达成?
better := ans | (1 << i) // 希望第 i 位能取 1
clear(set)
for _, num := range nums {
// num: 保留 31...i 位的信息,低位刷成 0
num = (num >> i) << i
set[num] = emptyStruct
// 如果 better^num 存在,说明 set 之前加过这样的数,设这个数为 x
// better^num == x -> x^num == better
// 即:better 能实现
if _, has := set[better^num]; has {
ans = better // 希望成真
break
}
// else { ans 维持原状,即第 i 位还是 0 }
}
}
return ans
}
复杂度
前缀树做法和哈希表做法复杂度都是一样的,只是哈希表做法常数更优
时间复杂度:,空间复杂度:, 是数值范围, 的原因是遍历数值的二进制位
题目3.在二维字符数组中搜索可能的单词
题目描述
给定一个 m x n
二维字符网格 board
和一个单词(字符串)列表 words
, 返回所有二维网格上的单词
单词必须按照字母顺序,通过 相邻的单元格 内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母在一个单词中不允许被重复使用。
提示:
m == board.length
n == board[i].length
1 <= m, n <= 12
board[i][j]
是一个小写英文字母1 <= words.length <= 3 * 10^4
1 <= words[i].length <= 10
words[i]
由小写英文字母组成words
中的所有字符串互不相同
测试链接
答案
const (
MAXN = 10001
)
var (
tree = [MAXN][26]int{}
pass = [MAXN]int{}
// 前缀树的定制:不记录结尾的个数了,因为输入没有重复,个数始终为 1
// 而是记录整个字符串是什么,方便直接收集
end = [MAXN]string{}
cnt = 1
ans []string
theBoard [][]byte
n int
m int
)
func findWords(board [][]byte, words []string) []string {
ans = []string{}
n = len(board)
if n == 0 {
return ans
}
m = len(board[0])
theBoard = board
for _, word := range words {
Insert(word)
}
for i := 0; i < n; i++ {
for j := 0; j < m; j++ {
dfs(i, j, 0)
}
}
Clear()
return ans
}
// 来到了二维网格的 [i][j] 位置
// treeIdx: 当前来到的前缀树节点
// 返回值: 收集到了几个字符串
func dfs(i, j, treeIdx int) int {
// 越界或之前走过
if i < 0 || i == n || j < 0 || j == m || theBoard[i][j] == 0 {
return 0
}
char := theBoard[i][j]
treeIdx = tree[treeIdx][char-'a']
if treeIdx == 0 {
return 0
}
if pass[treeIdx] == 0 { // 剪枝
return 0
}
// 从 [i][j] 出发,收集了几个字符串
count := 0
if end[treeIdx] != "" {
count++
ans = append(ans, end[treeIdx])
end[treeIdx] = ""
}
// 标记 [i][j] 位置,防止下次再来到
theBoard[i][j] = 0
count += dfs(i-1, j, treeIdx)
count += dfs(i+1, j, treeIdx)
count += dfs(i, j-1, treeIdx)
count += dfs(i, j+1, treeIdx)
theBoard[i][j] = char
pass[treeIdx] -= count // 为了剪枝
return count
}
func Insert(word string) {
n := len(word)
treeIdx := 0
pass[treeIdx]++
for i := 0; i < n; i++ {
idx := word[i] - 'a'
if tree[treeIdx][idx] == 0 {
tree[treeIdx][idx] = cnt
cnt++
}
treeIdx = tree[treeIdx][idx]
pass[treeIdx]++
}
end[treeIdx] = word
}
func Clear() {
for i := 0; i < cnt; i++ {
for j := 0; j < 26; j++ {
tree[i][j] = 0
}
pass[i] = 0
end[i] = ""
}
cnt = 1
}
复杂度
时间复杂度:
其中, 是二维网格大小, 是每个字符串最大长度是 10,每个字符都要向上下左右四个方向探索
不管用不用前缀树都是这个复杂度,只不过前缀树可以大量剪枝,优化常数时间
空间复杂度:,前缀树的大小