note4cs/技术/Go/算法相关.md
2025-03-20 18:27:05 +08:00

14 KiB
Raw Blame History

性能优化运算

2^x = 1 << x
x/2 x >> 1

快乐数

func isHappy(n int) bool {
    slow, fast := n, step(n)
    for fast != 1 && slow != fast {
        slow = step(slow)
        fast = step(step(fast))
    }
    return fast == 1
}

func step(n int) int {
    sum := 0
    for n > 0 { //通过循环逐位计算
        sum += (n%10) * (n%10)
        n = n/10
    }
    return sum
}

查找二维数组

func searchMatrix(matrix [][]int, target int) bool {
    row := sort.Search(len(matrix), func(i int) bool { return matrix[i][0] > target }) - 1
    if row < 0 {
        return false
    }
    col := sort.SearchInts(matrix[row], target)
    return col < len(matrix[row]) && matrix[row][col] == target
}

LRU缓存

// 定义LRU缓存结构体包含当前大小、最大容量、键值对映射表以及双向链表的头尾节点
type LRUCache struct {
    size int                  // 当前缓存中元素的数量
    capacity int             // 缓存的最大容量
    cache map[int]*DLinkedNode // 键到节点的映射表,用于快速查找节点
    head, tail *DLinkedNode  // 双向链表的虚拟头节点和虚拟尾节点,便于操作边界条件
}

// 定义双向链表节点结构体,包含键、值以及前后指针
type DLinkedNode struct {
    key, value int           // 节点存储的数据键和值
    prev, next *DLinkedNode  // 指向前一个和后一个节点的指针
}

// 初始化一个双向链表节点仅设置键和值前后指针默认为nil
func initDLinkedNode(key, value int) *DLinkedNode {
    return &DLinkedNode{
        key: key,
        value: value,
    }
}

// 构造函数创建一个新的LRU缓存实例并初始化其内部数据结构
func Constructor(capacity int) LRUCache {
    l := LRUCache{
        cache: make(map[int]*DLinkedNode), // 初始化键值对映射表
        head: initDLinkedNode(0, 0),      // 创建虚拟头节点
        tail: initDLinkedNode(0, 0),      // 创建虚拟尾节点
        capacity: capacity,                // 设置缓存最大容量
    }
    l.head.next = l.tail // 将虚拟头节点指向虚拟尾节点
    l.tail.prev = l.head // 将虚拟尾节点指向虚拟头节点
    return l             // 返回构造好的LRU缓存实例
}

// 根据给定键从LRU缓存中获取对应的值如果不存在则返回-1
func (this *LRUCache) Get(key int) int {
    if _, ok := this.cache[key]; !ok { // 判断键是否存在于缓存中
        return -1                      // 如果不存在,直接返回-1
    }
    node := this.cache[key]            // 获取到对应节点
    this.moveToHead(node)              // 将该节点移动到链表头部(表示最近访问)
    return node.value                  // 返回节点的值
}

// 向LRU缓存中添加或更新键值对如果缓存已满则移除最久未使用的节点
func (this *LRUCache) Put(key int, value int)  {
    if _, ok := this.cache[key]; !ok { // 判断键是否已经存在于缓存中
        node := initDLinkedNode(key, value) // 创建新节点
        this.cache[key] = node            // 将新节点加入到映射表中
        this.addToHead(node)              // 将新节点添加到链表头部(表示最近访问)
        this.size++                       // 增加缓存大小计数
        if this.size > this.capacity {    // 如果缓存大小超过最大容量
            removed := this.removeTail()  // 移除链表尾部节点(最久未使用)
            delete(this.cache, removed.key) // 删除映射表中对应的键值对
            this.size--                   // 减少缓存大小计数
        }
    } else {                            // 如果键已经存在
        node := this.cache[key]         // 获取到对应节点
        node.value = value              // 更新节点的值
        this.moveToHead(node)           // 将该节点移动到链表头部(表示最近访问)
    }
}

// 将指定节点添加到双向链表头部
func (this *LRUCache) addToHead(node *DLinkedNode) {
    node.prev = this.head       // 设置节点的前驱为虚拟头节点
    node.next = this.head.next  // 设置节点的后继为原头部节点
    this.head.next.prev = node  // 将原头部节点的前驱指向当前节点
    this.head.next = node       // 将虚拟头节点的后继指向当前节点
}

// 从双向链表中移除指定节点
func (this *LRUCache) removeNode(node *DLinkedNode) {
    node.prev.next = node.next // 将前驱节点的后继指向后继节点
    node.next.prev = node.prev // 将后继节点的前驱指向前驱节点
}

// 将指定节点移动到双向链表头部
func (this *LRUCache) moveToHead(node *DLinkedNode) {
    this.removeNode(node) // 首先移除该节点
    this.addToHead(node)  // 然后将其添加到链表头部
}

// 移除双向链表尾部节点并返回该节点
func (this *LRUCache) removeTail() *DLinkedNode {
    node := this.tail.prev // 获取尾部节点(不包括虚拟尾节点)
    this.removeNode(node)  // 移除该节点
    return node            // 返回被移除的节点
}

加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

func canCompleteCircuit(gas []int, cost []int) int {
    n := len(gas)
    totalGas, currentGas,start := 0, 0, 0

    for i := 0; i < n; i++ {
        totalGas += gas[i] - cost[i]
        currentGas += gas[i] - cost[i]
        // 如果当前油量小于0说明从start到i之间不能作为一个有效的起始点
        if currentGas < 0 {
            start = i + 1
            currentGas = 0
        }
    }
    if totalGas >= 0 {
        return start
    }
    return -1
}

逆波兰表达式

func evalRPN(tokens []string) int {
	stk := []int{}
	for _, token := range tokens {
		if token == "+" {
			stk = append(stk[:len(stk)-2], stk[len(stk)-2]+stk[len(stk)-1])
		} else if token == "-" {
			stk = append(stk[:len(stk)-2], stk[len(stk)-2]-stk[len(stk)-1])
		} else if token == "*" {
			stk = append(stk[:len(stk)-2], stk[len(stk)-2]*stk[len(stk)-1])
		} else if token == "/" {
			stk = append(stk[:len(stk)-2], stk[len(stk)-2]/stk[len(stk)-1])
		} else {
			i, err := strconv.Atoi(token)
			if err != nil {
				return 0
			}
			stk = append(stk, i)
		}
	}
	return stk[0]
}

二叉树的层序遍历

DFS算法

package main

import . "nc_tools" // 引入工具包,假设其中定义了树节点结构 `TreeNode`

// BFS迭代法通法
func levelOrder(root *TreeNode) [][]int {
    // 如果根节点为空,直接返回空的二维数组
    if root == nil {
        return [][]int{}
    }

    queue := []*TreeNode{} // 定义一个队列,用于存储当前层的节点
    queue = append(queue, root) // 将根节点加入队列
    levels := [][]int{} // 定义一个二维数组,用于存储每一层的节点值

    // 当队列不为空时,继续遍历
    for len(queue) > 0 {
        n := len(queue) // 获取当前层的节点数量
        level := []int{} // 定义一个一维数组,用于存储当前层的节点值

        // 遍历当前层的所有节点
        for i := 0; i < n; i++ {
            root = queue[0]           // 取出队列的第一个节点
            queue = queue[1:]         // 将该节点从队列中移除

            level = append(level, root.Val) // 将当前节点的值加入当前层的结果中

            // 如果左子节点存在,将其加入队列
            if root.Left != nil {
                queue = append(queue, root.Left)
            }

            // 如果右子节点存在,将其加入队列
            if root.Right != nil {
                queue = append(queue, root.Right)
            }
        }

        // 将当前层的结果加入最终结果中
        levels = append(levels, level)
    }

    // 返回所有层的结果
    return levels
}

存在重复元素II

哈希表

func containsNearbyDuplicate(nums []int, k int) bool {
    m := make(map[int]int)
    for i, value := range nums{
        if _, ok := m[value]; ok {
            if (i - m[value]) <= k {
                return true
            } else {
                m[value] = i
            }
        } else {
            m[value] = i
        }
    }
    return false
}

移动0

快慢指针

func moveZeroes(nums []int) {
    left, right := 0, 0
    for right < len(nums) {
        if nums[right] != 0 {
            nums[left], nums[right] = nums[right], nums[left]
            left++
        }
        right++
    }
}

Pow(x, n)

// myPow 计算x的n次幂通过快速幂算法实现时间复杂度O(log n)
// 当n为负数时根据数学性质x^(-n) = 1/(x^n)转换为正指数计算 
func myPow(x float64, n int) float64 {
    if n >= 0 {
        return quick(x, n)
    }
    return 1.0 / quick(x, -n) // 负指数转为倒数计算 
}

// quick 递归实现快速幂算法,采用分治策略降低计算次数
// 核心思想每次将指数折半底数平方利用公式x^n = x^(n/2) * x^(n/2)n为偶数时
func quick(x float64, n int) float64 {
    if n == 0 {
        return 1 // 递归终止条件任何数的0次方均为1 
    }
    y := quick(x, n/2) // 将问题规模减半,分治策略核心步骤 
    if n%2 == 0 {
        return y * y // 偶数次幂:直接返回子问题平方 
    }
    return y * y * x // 奇数次幂额外乘以一次底数x 
}

重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

func repeatedSubstringPattern(s string) bool {
    n := len(s)
    for i := 1; i*2 <= n; i++ {
        if n%i == 0 {
            match := true
            for j := i; j < n; j++ {
                if s[j] != s[j-i] {
                    match = false
                    break
                }
            }
            if match {
                return true
            }
        }
    }
    return false
}

快速排序

func QuickSort(arr []int) {
    if len(arr) <= 1 {
        return
    }

    // 选择最后一个元素作为基准
    pivot := arr[len(arr)-1]
    i := 0 // 标记小于基准的边界

    // 分区过程
    for j := 0; j < len(arr)-1; j++ {
        if arr[j] <= pivot {
            arr[i], arr[j] = arr[j], arr[i]
            i++
        }
    }

    // 将基准放到正确的位置
    arr[i], arr[len(arr)-1] = arr[len(arr)-1], arr[i]

    // 递归排序左右子数组
    QuickSort(arr[:i])
    QuickSort(arr[i+1:])
}

快速排序采用 分治法Divide and Conquer 策略:

  1. 选基准从数组中选择一个元素作为基准pivot
  2. 分区:将数组分为两部分:
    • 左半部分所有元素 ≤ 基准
    • 右半部分所有元素 ≥ 基准
  3. 递归:对左右子数组重复上述步骤,直到子数组长度为 1 或 0已有序

反转链表


/**
 * Definition for singly-linked list.
 * type ListNode struct {
 *     Val int
 *     Next *ListNode
 * }
 */

func reverseList(head *ListNode) *ListNode {
    var prev *ListNode
    curr := head
    for curr != nil {
        next := curr.Next
        curr.Next = prev
        prev = curr
        curr = next
    }
    return prev
}

二分查找

func search(nums []int, target int) int {
left, right := 0, len(nums)-1
    for left <= right {
        mid := left + (right-left)/2
        if nums[mid] == target {
            return mid
        } else if nums[mid] > target{
            right = mid - 1
        } else {
            left = mid + 1
        }
    }
    return -1
}

字符串相加

func max(a, b int) int {
	if a > b {
		return a
	}
	return b
}

func addStrings(num1 string, num2 string) string {
	buf := make([]byte, max(len(num1), len(num2)) + 1)
	length := len(buf) - 1
	add := 0
	for i, j := len(num1)-1, len(num2)-1; j >= 0 || i >= 0; {
		num := add
		if i >= 0 {
			num += int(num1[i] - '0')
			i--
		}
		if j >= 0 {
			num += int(num2[j] - '0')
            j--
		}
        if num >= 10 {
            num %= 10
            add = 1
        } else {
            add = 0
        }
        buf[length] = byte(num+'0')
        length--
	}
    if add != 0 {
        buf[length] = '1'
    }
    if buf[0]==0{
        buf=buf[1:]
    }
    return string(buf)
}

Rand7 --> Rand10

拒绝采样

func rand10() int {
	for {
		row := rand7()
		col := rand7()
		idx := (row-1)*7 + col
		if idx <= 40 {
			return 1 + (idx-1)%10
		}
	}
}

最大子序和

func maxSubArray(nums []int) int {
    max := nums[0]
    for i := 1; i < len(nums); i++ {
        if nums[i] + nums[i-1] > nums[i] {
            nums[i] += nums[i-1]
        }
        if nums[i] > max {
            max = nums[i]
        }
    }
    return max
}

阶乘后的零

func trailingZeroes(n int) (ans int) {
     for n > 0 {
        n /= 5
        ans += n
    }
    return 
}

阶乘末尾的零由因子2和5的乘积即10产生。由于2的个数远多于5的个数因此只需统计因子5的个数即可。具体步骤如下

  1. 统计5的倍数 每个能被5整除的数至少贡献一个5。
  2. 统计25的倍数 每个能被25整除的数额外贡献一个5因为25 = 5×5
  3. 统计更高次幂 类似地统计125、625等更高次幂的倍数直到商为0。