mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
812 字
2 分钟
算法题
2024-06-07

本文整理了面试中常见的算法题型,涵盖数组、链表、树、堆、动态规划等核心考点。

Tip

复杂度速查表

一、顺时针旋转矩阵#

1.1 题目描述#

给定一个 n×n 的矩阵,顺时针旋转 90 度。

1.2 解题思路#

对于旋转后的位置 (i, j),变换规律是:

  • 原位置 (n-1-j, i) → 新位置 (i, j)

也可以分两步完成:

  1. 按主对角线翻转
  2. 按水平中线翻转

1.3 代码实现#

// 方法一:原地旋转
func rotate(matrix [][]int) {
n := len(matrix)
// 按主对角线翻转
for i := 0; i < n; i++ {
for j := i + 1; j < n; j++ {
matrix[i][j], matrix[j][i] = matrix[j][i], matrix[i][j]
}
}
// 按水平中线翻转
for i := 0; i < n/2; i++ {
for j := 0; j < n; j++ {
matrix[i][j], matrix[n-1-i][j] = matrix[n-1-i][j], matrix[i][j]
}
}
}
// 方法二:公式法(需要额外空间)
func rotate2(matrix [][]int) {
n := len(matrix)
result := make([][]int, n)
for i := 0; i < n; i++ {
result[i] = make([]int, n)
}
for i := 0; i < n; i++ {
for j := 0; j < n; j++ {
result[j][n-1-i] = matrix[i][j]
}
}
copy(matrix, result)
}

1.4 复杂度分析#

  • 时间复杂度:O(n²)
  • 空间复杂度:O(1)(原地)或 O(n²)(额外空间)

二、合并区间#

2.1 题目描述#

给出一个区间的集合,合并所有重叠的区间。

2.2 解题思路#

  1. 按区间的起始位置排序
  2. 遍历区间,如果当前区间起始 ≤ 上一个区间结束,则合并
  3. 否则开始新的区间

2.3 代码实现#

type Interval struct {
Start int
End int
}
func merge(intervals []Interval) []Interval {
if len(intervals) == 0 {
return intervals
}
// 按起始位置排序
sort.Slice(intervals, func(i, j int) bool {
return intervals[i].Start < intervals[j].Start
})
result := []Interval{intervals[0]}
for i := 1; i < len(intervals); i++ {
last := &result[len(result)-1]
if intervals[i].Start <= last.End {
// 重叠,合并(取较大结束位置)
if intervals[i].End > last.End {
last.End = intervals[i].End
}
} else {
// 不重叠,直接添加
result = append(result, intervals[i])
}
}
return result
}

2.4 复杂度分析#

  • 时间复杂度:O(n log n)(排序)
  • 空间复杂度:O(1)(原地)或 O(n)(结果存储)

三、堆排序#

3.1 堆的基本概念#

堆是一棵完全二叉树,根据堆序性质分为:

  • 大根堆:父节点 ≥ 子节点
  • 小根堆:父节点 ≤ 子节点

堆的物理存储通常用数组,节点关系:

  • 父节点:(i-1)/2
  • 左子节点:2*i + 1
  • 右子节点:2*i + 2

3.2 代码实现#

func heapSort(arr []int) {
n := len(arr)
// 构建大根堆
for i := n/2 - 1; i >= 0; i-- {
heapify(arr, n, i)
}
// 逐个取出堆顶元素
for i := n - 1; i > 0; i-- {
arr[0], arr[i] = arr[i], arr[0] // 交换堆顶和末尾
heapify(arr, i, 0) // 调整堆
}
}
func heapify(arr []int, n, i int) {
largest := i
left := 2*i + 1
right := 2*i + 2
if left < n && arr[left] > arr[largest] {
largest = left
}
if right < n && arr[right] > arr[largest] {
largest = right
}
if largest != i {
arr[i], arr[largest] = arr[largest], arr[i]
heapify(arr, n, largest)
}
}

3.3 复杂度分析#

  • 时间复杂度:O(n log n)
  • 空间复杂度:O(1)

四、获取第 k 大的元素#

4.1 方法一:排序后取第 k 大#

func findKthLargest1(nums []int, k int) int {
sort.Ints(nums)
return nums[len(nums)-k]
}
  • 时间复杂度:O(n log n)

4.2 方法二:快速选择(基于快速排序)#

func findKthLargest2(nums []int, k int) int {
return quickSelect(nums, 0, len(nums)-1, len(nums)-k)
}
func quickSelect(nums []int, left, right, k int) int {
if left == right {
return nums[left]
}
pivotIndex := left + rand.Intn(right-left+1)
pivotIndex = partition(nums, left, right, pivotIndex)
if k == pivotIndex {
return nums[k]
} else if k < pivotIndex {
return quickSelect(nums, left, pivotIndex-1, k)
} else {
return quickSelect(nums, pivotIndex+1, right, k)
}
}
func partition(nums []int, left, right, pivotIndex int) int {
pivot := nums[pivotIndex]
nums[pivotIndex], nums[right] = nums[right], nums[pivotIndex]
storeIndex := left
for i := left; i < right; i++ {
if nums[i] < pivot {
nums[storeIndex], nums[i] = nums[i], nums[storeIndex]
storeIndex++
}
}
nums[right], nums[storeIndex] = nums[storeIndex], nums[right]
return storeIndex
}
  • 时间复杂度:O(n)(期望),最坏 O(n²)
  • 空间复杂度:O(1)

4.3 方法三:堆#

func findKthLargest3(nums []int, k int) int {
// 小根堆,堆顶是当前第 k 大的元素
h := &IntHeap{}
heap.Init(h)
for _, num := range nums {
heap.Push(h, num)
if h.Len() > k {
heap.Pop(h)
}
}
return (*h)[0]
}
type IntHeap []int
func (h IntHeap) Len() int { return len(h) }
func (h IntHeap) Less(i, j int) bool { return h[i] < h[j] } // 小根堆
func (h IntHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
func (h *IntHeap) Push(x interface{}) { *h = append(*h, x.(int)) }
func (h *IntHeap) Pop() interface{} {
old := *h
n := len(old)
x := old[n-1]
*h = old[:n-1]
return x
}
  • 时间复杂度:O(n log k)
  • 空间复杂度:O(k)

五、更多经典算法题#

5.1 两数之和#

// Hash 表解法
func twoSum(nums []int, target int) []int {
m := make(map[int]int)
for i, num := range nums {
if j, ok := m[target-num]; ok {
return []int{j, i}
}
m[num] = i
}
return nil
}

5.2 合并两个有序链表#

func mergeTwoLists(l1, l2 *ListNode) *ListNode {
dummy := &ListNode{}
cur := dummy
for l1 != nil && l2 != nil {
if l1.Val < l2.Val {
cur.Next = l1
l1 = l1.Next
} else {
cur.Next = l2
l2 = l2.Next
}
cur = cur.Next
}
if l1 != nil {
cur.Next = l1
} else {
cur.Next = l2
}
return dummy.Next
}

5.3 验证二叉搜索树#

func isValidBST(root *TreeNode) bool {
return helper(root, math.MinInt64, math.MaxInt64)
}
func helper(node *TreeNode, min, max int) bool {
if node == nil {
return true
}
if node.Val <= min || node.Val >= max {
return false
}
return helper(node.Left, min, node.Val) && helper(node.Right, node.Val, max)
}

六、二叉树层序遍历#

6.1 题目描述#

给定二叉树,返回其节点值的层序遍历结果(从上到下,从左到右)。

6.2 代码实现#

func levelOrder(root *TreeNode) [][]int {
if root == nil {
return nil
}
result := [][]int{}
queue := []*TreeNode{root}
for len(queue) > 0 {
levelSize := len(queue)
level := []int{}
for i := 0; i < levelSize; i++ {
node := queue[0]
queue = queue[1:]
level = append(level, node.Val)
if node.Left != nil {
queue = append(queue, node.Left)
}
if node.Right != nil {
queue = append(queue, node.Right)
}
}
result = append(result, level)
}
return result
}
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

七、LRU 缓存#

7.1 题目描述#

实现一个 LRU(最近最少使用)缓存,支持 get 和 put 操作,时间复杂度 O(1)。

7.2 解题思路#

使用哈希表 + 双向链表的组合:

  • 哈希表:O(1) 查找
  • 双向链表:O(1) 插入/删除

7.3 代码实现#

type LRUCache struct {
capacity int
cache map[int]*DListNode
head *DListNode
tail *DListNode
}
type DListNode struct {
key, value int
prev, next *DListNode
}
func Constructor(capacity int) LRUCache {
head, tail := &DListNode{}, &DListNode{}
head.next = tail
tail.prev = head
return LRUCache{
capacity: capacity,
cache: make(map[int]*DListNode),
head: head,
tail: tail,
}
}
func (l *LRUCache) Get(key int) int {
if node, ok := l.cache[key]; ok {
l.moveToFront(node)
return node.value
}
return -1
}
func (l *LRUCache) Put(key, value int) {
if node, ok := l.cache[key]; ok {
node.value = value
l.moveToFront(node)
return
}
if len(l.cache) >= l.capacity {
// 删除最少使用的节点(链表尾部)
delete(l.cache, l.tail.prev.key)
l.removeNode(l.tail.prev)
}
node := &DListNode{key: key, value: value}
l.cache[key] = node
l.addToFront(node)
}
func (l *LRUCache) moveToFront(node *DListNode) {
l.removeNode(node)
l.addToFront(node)
}
func (l *LRUCache) removeNode(node *DListNode) {
node.prev.next = node.next
node.next.prev = node.prev
}
func (l *LRUCache) addToFront(node *DListNode) {
node.next = l.head.next
node.prev = l.head
l.head.next.prev = node
l.head.next = node
}

八、动态规划#

8.1 最长递增子序列#

func lengthOfLIS(nums []int) int {
if len(nums) == 0 {
return 0
}
// dp[i] 表示以 nums[i] 结尾的最长递增子序列长度
dp := make([]int, len(nums))
for i := range dp {
dp[i] = 1
}
maxLen := 1
for i := 1; i < len(nums); i++ {
for j := 0; j < i; j++ {
if nums[i] > nums[j] {
dp[i] = max(dp[i], dp[j]+1)
}
}
maxLen = max(maxLen, dp[i])
}
return maxLen
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
  • 时间复杂度:O(n²)
  • 空间复杂度:O(n)

8.2 零钱兑换#

func coinChange(coins []int, amount int) int {
// dp[i] 表示凑成金额 i 需要的最少硬币数
dp := make([]int, amount+1)
for i := 1; i <= amount; i++ {
dp[i] = amount + 1 // 初始化为不可能的值
}
dp[0] = 0
for i := 1; i <= amount; i++ {
for _, coin := range coins {
if coin <= i {
dp[i] = min(dp[i], dp[i-coin]+1)
}
}
}
if dp[amount] > amount {
return -1
}
return dp[amount]
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
  • 时间复杂度:O(amount × n)
  • 空间复杂度:O(amount)

九、参考#


参考#

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

算法题
https://blog.souloss.com/posts/interview/algorithms/
作者
Souloss
发布于
2024-06-07
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时