package main
import (
"fmt"
)
// 巧妙解法:位图 + 位运算优化
// 时间复杂度:O(n + k),空间复杂度:O(k/32)
// 其中k=500为数据范围,n为输入数量
func main() {
var n int
fmt.Scanln(&n)
// 使用位图存储数字存在状态
// 每个uint32可以存储32个数字的状态,500个数字需要16个uint32
const maxNum = 500
bitmap := make([]uint32, (maxNum+31)/32) // 向上取整
// 读取数字并在位图中标记
for i := 0; i < n; i++ {
var num int
fmt.Scanln(&num)
setBit(bitmap, num)
}
// 遍历位图输出已存在的数字
outputSortedUniqueNumbers(bitmap, maxNum)
}
// 设置位图中对应位置为1(内联优化)
func setBit(bitmap []uint32, num int) {
wordIndex := num >> 5 // 等价于 num / 32,位运算更快
bitOffset := num & 31 // 等价于 num % 32,位运算更快
bitmap[wordIndex] |= 1 << bitOffset
}
// 检查位图中指定位置是否为1(内联优化)
func getBit(bitmap []uint32, num int) bool {
wordIndex := num >> 5
bitOffset := num & 31
return (bitmap[wordIndex] & (1 << bitOffset)) != 0
}
// 输出排序后的唯一数字(利用位图天然有序的特性)
func outputSortedUniqueNumbers(bitmap []uint32, maxNum int) {
for num := 1; num <= maxNum; num++ {
if getBit(bitmap, num) {
fmt.Println(num)
}
}
}
// 更巧妙的进阶版本:使用位运算技巧批量处理
func advancedBitmapSolution() {
var n int
fmt.Scanln(&n)
// 使用计数排序的思想,但用位图优化空间
const maxNum = 500
bitmap := make([]uint64, (maxNum+63)/64) // 使用uint64提高效率
// 批量读取和处理
nums := make([]int, n)
for i := 0; i < n; i++ {
fmt.Scanln(&nums[i])
setBit64(bitmap, nums[i])
}
// 使用位运算技巧快速查找和输出
for i := 0; i < len(bitmap); i++ {
word := bitmap[i]
baseNum := i * 64
// 使用位运算快速找到所有设置的位
for word != 0 {
// 找到最低位的1的位置
trailingZeros := countTrailingZeros(word)
num := baseNum + trailingZeros + 1
if num <= maxNum {
fmt.Println(num)
}
// 清除最低位的1
word &= word - 1
}
}
}
// 设置64位位图中对应位置
func setBit64(bitmap []uint64, num int) {
wordIndex := num >> 6 // num / 64
bitOffset := num & 63 // num % 64
bitmap[wordIndex] |= 1 << bitOffset
}
// 计算尾随零的个数(使用内置函数或位运算技巧)
func countTrailingZeros(x uint64) int {
if x == 0 {
return 64
}
count := 0
for (x & 1) == 0 {
count++
x >>= 1
}
return count
}
// 面试官最爱的一行流解法(展示函数式编程思维)
func oneLineSolution() {
// 虽然在Go中不太适用,但可以展示思维
// 在其他语言中可以写成:
// Arrays.stream(nums).distinct().sorted().forEach(System.out::println)
}
// 内存极限优化版本:流式处理
func memoryOptimizedSolution() {
var n int
fmt.Scanln(&n)
// 只使用500位的位图,约63字节
var bitmap [16]uint32 // 16 * 32 = 512 bits,覆盖1-500
// 流式处理,边读边标记
for i := 0; i < n; i++ {
var num int
fmt.Scanln(&num)
if num >= 1 && num <= 500 {
bitmap[num>>5] |= 1 << (num & 31)
}
}
// 输出结果
for num := 1; num <= 500; num++ {
if (bitmap[num>>5] & (1 << (num & 31))) != 0 {
fmt.Println(num)
}
}
}
解题思路
算法分析
这道题的核心是去重排序。由于数据范围限制在1-500,我们可以设计多种高效算法:
- 朴素解法:排序后去重,时间复杂度O(n log n)
- 位图算法:利用数据范围小的特点,时间复杂度O(n + k)
- 布尔数组:类似位图但更直观,空间换时间
- 计数排序:统计每个数字出现次数,天然去重
多种解法对比
graph TD
A[输入数据] --> B{选择算法}
B -->|数据量大| C[排序+去重 O(n log n)]
B -->|数据范围小| D[位图算法 O(n + k)]
B -->|空间充足| E[布尔数组 O(n + k)]
B -->|需要统计| F[计数排序 O(n + k)]
C --> G[sort.Ints + 去重遍历]
D --> H[位运算操作]
E --> I[布尔数组标记]
F --> J[计数数组统计]
G --> K[输出结果]
H --> K
I --> K
J --> K
位图算法详解
flowchart TD
A[初始化位图bitmap] --> B[读取数字]
B --> C[设置对应位为1]
C --> D{还有数字?}
D -->|是| B
D -->|否| E[遍历位图]
E --> F{当前位是否为1?}
F -->|是| G[输出对应数字]
F -->|否| H[跳过]
G --> I{还有位?}
H --> I
I -->|是| E
I -->|否| J[结束]
计数排序优化
graph LR
A[数字1] --> B[count[1]++]
C[数字2] --> D[count[2]++]
E[数字2] --> D
F[遍历count数组] --> G[输出非零位置]
时间复杂度对比
排序+去重 | O(n log n) | O(1) | 通用解法 |
位图算法 | O(n + k) | O(k/8) | 数据范围小 |
布尔数组 | O(n + k) | O(k) | 空间充足 |
计数排序 | O(n + k) | O(k) | 需要统计 |
其中k为数据范围,本题k=500
位运算技巧
graph TD
A[数字x] --> B[字节位置: x/32]
A --> C[位偏移: x%32]
B --> D[bitmap[x/32]]
C --> E[1 << (x%32)]
D --> F[位或运算设置位]
E --> F
F --> G[bitmap[x/32] |= (1 << (x%32))]
空间优化技巧
- 位图压缩:用uint32数组,每个元素存储32个数字的存在状态
- 稀疏数组:如果数据稀疏,可以用map存储
- 流式处理:边读边处理,不存储所有数据
实际应用场景
- 大数据去重:处理海量重复数据
- 布隆过滤器:快速判断元素是否存在
- 数据库索引:位图索引在数据库中的应用
- 缓存优化:内存中快速查找和去重
这个问题虽然简单,但可以展示多种算法思维和优化技巧,特别是利用数据范围特点进行空间换时间的优化思想。

京公网安备 11010502036488号