【LeetCode】时间复杂度和空间复杂度

目录

1、背景

分析算法的时间复杂度和空间复杂度是衡量算法性能的关键,它们分别用于评估算法的运行时间和占用内存,本文只讲相关概念和示例。

2、时间复杂度

时间复杂度表示的是算法随着输入规模的增长,执行所需的时间增长的速率,常见时间复杂度如下:

时间复杂度 含义
O(1) 常数时间复杂度。无论输入规模如何,执行时间始终相同
O(log n) 对数时间复杂度。每次操作都能将问题规模缩小一半,如二分法查找
O(n) 线性时间复杂度。随着输入规模的增加,执行时间成正比增加,如遍历数组
O(n log n) 线性对数时间复杂度。常见于分治算法,如归并排序、快速排序
O(n^2) 二次时间复杂度。通常出现嵌套循环中,如冒泡排序、插入排序
O(2^n) 指数时间复杂度。随着输入规模增加,执行时间呈指数级增长,通常出现在递归算法中
O(n!) 阶乘时间复杂度。通常出现在排列组合相关问题中

3、时间复杂度示例

【1】O(1)

go 复制代码
package main

import "fmt"

func constantTime(arr []int) int {
    return arr[0] // 仅访问一次数组元素,不依赖于数组大小
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    fmt.Println(constantTime(arr)) // 输出: 1
}

无论数组大小如何,执行时间是固定的。

【2】O(log n)

go 复制代码
package main

import "fmt"

// 二分查找
func binarySearch(arr []int, target int) int {
    left, right := 0, len(arr)-1
    for left <= right {
        mid := left + (right-left)/2
        if arr[mid] == target {
            return mid
        } else if arr[mid] < target {
            left = mid + 1
        } else {
            right = mid - 1
        }
    }
    return -1
}

func main() {
    arr := []int{1, 3, 5, 7, 9, 11, 13}
    fmt.Println(binarySearch(arr, 5)) // 输出: 2
}

每次将搜索区间缩小一半。

【3】O(n)

go 复制代码
package main

import "fmt"

// 遍历数组
func linearTime(arr []int) int {
    sum := 0
    for _, v := range arr {
        sum += v // 遍历每个元素
    }
    return sum
}

func main() {
    arr := []int{1, 2, 3, 4, 5}
    fmt.Println(linearTime(arr)) // 输出: 15
}

遍历整个数组,执行次数与数组大小成正比。

【4】O(n log n)

go 复制代码
package main

import "fmt"

// 归并排序
func mergeSort(arr []int) []int {
    if len(arr) <= 1 {
        return arr
    }

    mid := len(arr) / 2
    left := mergeSort(arr[:mid])
    right := mergeSort(arr[mid:])

    return merge(left, right)
}

func merge(left, right []int) []int {
    result := []int{}
    i, j := 0, 0
    for i < len(left) && j < len(right) {
        if left[i] < right[j] {
            result = append(result, left[i])
            i++
        } else {
            result = append(result, right[j])
            j++
        }
    }
    result = append(result, left[i:]...)
    result = append(result, right[j:]...)
    return result
}

func main() {
    arr := []int{5, 4, 3, 2, 1}
    fmt.Println(mergeSort(arr)) // 输出: [1 2 3 4 5]
}

归并排序的时间复杂度是O(n log n),因为它不断将数组分为两半,递归的深度是log n,而合并过程是O(n)。

【5】O(n^2)

go 复制代码
package main

import "fmt"

// 冒泡排序
func bubbleSort(arr []int) {
    for i := 0; i < len(arr); i++ {
        for j := 0; j < len(arr)-1-i; j++ {
            if arr[j] > arr[j+1] {
                arr[j], arr[j+1] = arr[j+1], arr[j] // 交换元素
            }
        }
    }
}

func main() {
    arr := []int{5, 4, 3, 2, 1}
    bubbleSort(arr)
    fmt.Println(arr) // 输出: [1 2 3 4 5]
}

冒泡排序中有两个嵌套的循环,执行次数为n^2。

【6】O(2^n)

sql 复制代码
package main

import "fmt"

// 斐波那契递归
func fibonacci(n int) int {
    if n <= 1 {
        return n
    }
    return fibonacci(n-1) + fibonacci(n-2)
}

func main() {
    fmt.Println(fibonacci(10)) // 输出: 55
}

递归调用中每个 fibonacci 调用会产生两个新的递归调用,因此递归树的大小是指数级的。

【7】O(n!)

go 复制代码
package main

import "fmt"

// 全排列
func permute(nums []int) [][]int {
    var result [][]int
    var backtrack func(nums []int, path []int)

    backtrack = func(nums []int, path []int) {
        if len(nums) == 0 {
            result = append(result, append([]int{}, path...))
            return
        }
        for i := 0; i < len(nums); i++ {
            backtrack(append(append([]int{}, nums[:i]...), nums[i+1:]...), append(path, nums[i]))
        }
    }

    backtrack(nums, []int{})
    return result
}

func main() {
    arr := []int{1, 2, 3}
    fmt.Println(permute(arr)) // 输出: [[1 2 3] [1 3 2] [2 1 3] [2 3 1] [3 1 2] [3 2 1]]
}

全排列的个数是 n!,因为每次选择一个元素,并继续递归生成排列。

4、空间复杂度

空间复杂度表示的是算法执行时需要占用的内存空间的增长速率,常见的空间复杂度有:

空间复杂度 含义
O(1) 常数空间复杂度。算法所需内存空间不会随着输入规模的增加而增加
O(n) 线性空间复杂度。算法的内存需求与输入规模成正比
O(n^2) 二次空间复杂度。通常出现在需要存储二维数据结构的情况下

5、空间复杂度示例

【1】O(1)

go 复制代码
package main

import "fmt"

// 交换两个数
func swap(a, b int) (int, int) {
    return b, a
}

func main() {
    a, b := 5, 10
    a, b = swap(a, b)
    fmt.Println(a, b) // 输出: 10 5
}

只用了常量空间存储两个整数。

【2】O(n)

go 复制代码
package main

import "fmt"

// 创建一个数组
func createArray(n int) []int {
    arr := make([]int, n)
    return arr
}

func main() {
    arr := createArray(5)
    fmt.Println(arr) // 输出: [0 0 0 0 0]
}

创建了一个大小为 n 的数组。

【3】O(n^2)

go 复制代码
package main

import "fmt"

// 创建二维数组
func create2DArray(n int) [][]int {
    arr := make([][]int, n)
    for i := range arr {
        arr[i] = make([]int, n)
    }
    return arr
}

func main() {
    arr := create2DArray(3)
    fmt.Println(arr) // 输出: [[0 0 0] [0 0 0] [0 0 0]]
}

创建了一个 n x n 的二维数组。

相关推荐
CodeJourney.16 分钟前
DeepSeek关联PPT使用教程
数据库·人工智能·算法
xiaolin03331 小时前
146. LRU 缓存
算法·哈希·lru·双向链表
m0nesy_86801 小时前
1314--力扣情人节特别篇
java·算法·leetcode
Liu_Meihao1 小时前
【LeetCode】1. 两数之和
算法·leetcode
MiyamiKK571 小时前
leetcode_二叉树 108. 将有序数组转换为二叉搜索树
算法·leetcode·职场和发展
Wang's Blog2 小时前
数据结构与算法之数组: LeetCode 541. 反转字符串 II (Ts版)
算法·leetcode
开开又心心的学嵌入式2 小时前
GO语言基础知识
开发语言·golang
杰哥的技术杂货铺2 小时前
Golang常见面试题
开发语言·golang·go面试题
莫忘初心丶2 小时前
Golang Gin框架获取JSON输入
golang·json·gin
嵌入式学习菌2 小时前
常见的排序算法:插入排序、选择排序、冒泡排序、快速排序
java·算法·排序算法