GO面试题汇总 (2023 6月5日)

172 阅读7分钟
  1. 写一个函数,接收一个字符串参数,返回字符串中第一个只出现一次的字符。例如,输入"abacddbe",返回"c"。
  2. 实现一个并发安全的计数器,要求支持以下三个方法:
    • Increment():将计数器加1。
    • Decrement():将计数器减1。
    • GetValue():获取当前计数器的值。
  3. 给定一个整数切片,编写一个函数,将其中的所有重复元素移除,并返回新的切片。要求保持原有元素的顺序不变。例如,输入[1, 2, 3, 2, 4, 1],返回[1, 2, 3, 4]。
  4. 实现一个LRU(最近最少使用)缓存机制,要求支持以下两个操作:
    • Get(key string) (value string, ok bool):根据给定的键获取对应的值,如果键不存在,则返回空字符串和false。
    • Set(key string, value string):将给定的键值对添加到缓存中,如果缓存已满,则删除最近最少使用的键值对。
  5. 编写一个函数,接收一个字符串切片,将其中的字符串按照字符的长度从小到大进行排序。如果两个字符串长度相同,则按照字典序进行排序。例如,输入["go", "python", "java", "ruby"],返回["go", "java", "ruby", "python"]。

1. 写一个函数,接收一个字符串参数,返回字符串中第一个只出现一次的字符。例如,输入"abacddbe",返回"c"。

以下是一个实现函数的示例,该函数接收一个字符串参数,并返回字符串中第一个只出现一次的字符:

func firstUniqueChar(str string) string {
	charCount := make(map[rune]int)

	// 统计字符出现的次数
	for _, ch := range str {
		charCount[ch]++
	}

	// 遍历字符串找到第一个只出现一次的字符
	for _, ch := range str {
		if charCount[ch] == 1 {
			return string(ch)
		}
	}

	return "" // 没有只出现一次的字符
}

func main() {
	str := "abacddbe"
	result := firstUniqueChar(str)
	fmt.Println(result) // 输出: "c"
}

在上述示例中,我们首先使用 charCount 哈希表统计字符串中每个字符出现的次数。然后,我们遍历字符串,找到第一个只出现一次的字符,并将其作为结果返回。

在主函数中,我们调用 firstUniqueChar 函数,并将字符串 "abacddbe" 作为参数传递。最后,输出结果为 "c"。

请注意,上述示例中的实现是基于 Unicode 字符集的。如果你的应用程序中使用的是其他字符集,可以相应地进行调整。此外,该实现假设输入字符串非空,如果输入为空字符串,可以根据需要进行额外处理。

2. 实现一个并发安全的计数器,要求支持以下三个方法:

*   `Increment()`:将计数器加1。
*   `Decrement()`:将计数器减1**。**
*   `GetValue()`:获取当前计数器的值。

下面是一个并发安全的计数器的实现,支持 Increment()Decrement()GetValue() 三个方法:

import "sync"

type Counter struct {
	value int
	mutex sync.Mutex
}

func NewCounter() *Counter {
	return &Counter{}
}

func (c *Counter) Increment() {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.value++
}

func (c *Counter) Decrement() {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	c.value--
}

func (c *Counter) GetValue() int {
	c.mutex.Lock()
	defer c.mutex.Unlock()

	return c.value
}

在上述代码中,Counter 结构体包含一个整数字段 value 表示计数器的值,以及一个互斥锁 mutex 用于保护计数器的并发访问。

NewCounter() 函数用于创建一个新的计数器实例。

Increment() 方法使用互斥锁对计数器的值加1。

Decrement() 方法使用互斥锁对计数器的值减1。

GetValue() 方法使用互斥锁获取当前计数器的值。

通过使用互斥锁,可以确保多个 goroutine 对计数器的并发访问是安全的,避免了竞态条件和数据不一致的问题。

3. 给定一个整数切片,编写一个函数,将其中的所有重复元素移除,并返回新的切片。要求保持原有元素的顺序不变。例如,输入[1, 2, 3, 2, 4, 1],返回[1, 2, 3, 4]。

下面是一个移除整数切片中重复元素的函数实现:

func RemoveDuplicates(nums []int) []int {
	seen := make(map[int]bool)
	result := []int{}

	for _, num := range nums {
		if !seen[num] {
			result = append(result, num)
			seen[num] = true
		}
	}

	return result
}

在上述代码中,我们使用一个 seen 哈希表来记录已经出现过的元素。然后,我们遍历输入的整数切片 nums,如果当前元素 numseen 中不存在,则将其添加到结果切片 result 中,并将 seen[num] 设置为 true 表示已经见过该元素。

最后,返回结果切片 result,其中包含了所有不重复的元素,并保持了原有的顺序。

你可以调用 RemoveDuplicates 函数,并传入一个整数切片作为参数进行测试。例如:

func main() {
	nums := []int{1, 2, 3, 2, 4, 1}
	result := RemoveDuplicates(nums)
	fmt.Println(result) // 输出: [1 2 3 4]
}

上述示例会打印出 [1 2 3 4],表示重复元素已被移除,只保留了不重复的元素。

4. 实现一个LRU(最近最少使用)缓存机制,要求支持以下两个操作:

*   `Get(key string) (value string, ok bool)`:根据给定的键获取对应的值,如果键不存在,则返回空字符串和false。
*   `Set(key string, value string)`:将给定的键值对添加到缓存中,如果缓存已满,则删除最近最少使用的键值对。

下面是一个基于哈希表和双向链表的 LRU 缓存实现:

import (
	"container/list"
	"fmt"
)

type LRUCache struct {
	capacity int
	cache    map[string]*list.Element
	lruList  *list.List
}

type CacheItem struct {
	key   string
	value string
}

func NewLRUCache(capacity int) *LRUCache {
	return &LRUCache{
		capacity: capacity,
		cache:    make(map[string]*list.Element),
		lruList:  list.New(),
	}
}

func (lru *LRUCache) Get(key string) (string, bool) {
	if ele, ok := lru.cache[key]; ok {
		lru.lruList.MoveToFront(ele)
		return ele.Value.(*CacheItem).value, true
	}
	return "", false
}

func (lru *LRUCache) Set(key string, value string) {
	if ele, ok := lru.cache[key]; ok {
		ele.Value.(*CacheItem).value = value
		lru.lruList.MoveToFront(ele)
	} else {
		if len(lru.cache) >= lru.capacity {
			// 淘汰最近最少使用的缓存项
			back := lru.lruList.Back()
			delete(lru.cache, back.Value.(*CacheItem).key)
			lru.lruList.Remove(back)
		}
		newItem := &CacheItem{key, value}
		newEle := lru.lruList.PushFront(newItem)
		lru.cache[key] = newEle
	}
}

func main() {
	cache := NewLRUCache(2)

	cache.Set("key1", "value1")
	cache.Set("key2", "value2")
	fmt.Println(cache.Get("key1")) // 输出: value1

	cache.Set("key3", "value3")
	fmt.Println(cache.Get("key2")) // 输出: ""

	cache.Set("key4", "value4")
	fmt.Println(cache.Get("key1")) // 输出: ""

	fmt.Println(cache.Get("key3")) // 输出: value3
	fmt.Println(cache.Get("key4")) // 输出: value4
}

在上述代码中,LRUCache 结构体包含一个容量字段 capacity,一个哈希表 cache 用于快速查找缓存项,以及一个双向链表 lruList 用于存储缓存项的访问顺序。

Get 方法根据给定的键获取对应的值,并将访问过的缓存项移动到链表头部表示最近使用。

Set 方法插入或更新缓存项,并在缓存已满时淘汰最近最少使用的缓存项。

main 函数中,我们创建了一个容量为 2 的 LRU 缓存,并进行了一系列操作和输出。你可以根据需要修改示例代码,观察 LRU 缓存的行为。

注意:上述示例是一种简单的实现方式,适用于单线程环境。在并发环境下,需要增加适当的同步机制来保证线程安全。

5. 编写一个函数,接收一个字符串切片,将其中的字符串按照字符的长度从小到大进行排序。

如果两个字符串长度相同,则按照字典序进行排序。例如,输入["go", "python", "java", "ruby"],返回["go", "java", "ruby", "python"]。

以下是一个按照字符串长度从小到大排序的函数实现:

import (
	"sort"
)

func SortStringsByLength(strs []string) []string {
	sort.Slice(strs, func(i, j int) bool {
		if len(strs[i]) != len(strs[j]) {
			return len(strs[i]) < len(strs[j])
		}
		return strs[i] < strs[j]
	})

	return strs
}

在上述代码中,我们使用 sort.Slice 函数进行排序,传入一个比较函数作为参数。比较函数根据字符串的长度和字典序进行排序。

如果两个字符串的长度不同,则按照长度从小到大进行排序。

如果两个字符串的长度相同,则按照字典序进行排序。

最后,返回排序后的字符串切片 strs

你可以调用 SortStringsByLength 函数,并传入一个字符串切片作为参数进行测试。例如:

func main() {
	strs := []string{"go", "python", "java", "ruby"}
	result := SortStringsByLength(strs)
	fmt.Println(result) // 输出: [go java ruby python]
}

上述示例会打印出 ["go", "java", "ruby", "python"],表示字符串已按照长度从小到大进行排序,如果长度相同,则按照字典序排序。

总结

GO面试题汇总(2023 6月5日).png