note4cs/技术/Go/算法相关.md
2025-03-19 14:36:09 +08:00

373 lines
8.7 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 性能优化运算
~~~ go
2^x = 1 << x
x/2 x >> 1
~~~
## 加油站
在一条环路上有 `n` 个加油站,其中第 `i` 个加油站有汽油 `gas[i]` 升。
你有一辆油箱容量无限的的汽车,从第 `i` 个加油站开往第 `i+1` 个加油站需要消耗汽油 `cost[i]` 升。你从其中的一个加油站出发,开始时油箱为空。
给定两个整数数组 `gas` 和 `cost` ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 `-1` 。如果存在解,则 **保证** 它是 **唯一** 的。
~~~ go
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
}
~~~
## 逆波兰表达式
~~~ go
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算法
~~~ go
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
哈希表
~~~ go
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
快慢指针
~~~ go
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)
~~~ go
// 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` ,检查是否可以通过由它的一个子串重复多次构成。
~~~ go
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
}
~~~
## 快速排序
```go
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已有序)。
***
## 反转链表
```go
/**
* 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
}
```
***
## 二分查找
```go
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
}
```
## 字符串相加
```go
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
拒绝采样
```go
func rand10() int {
for {
row := rand7()
col := rand7()
idx := (row-1)*7 + col
if idx <= 40 {
return 1 + (idx-1)%10
}
}
}
```
## 最大子序和
```go
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
}
```
## 阶乘后的零
~~~ go
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。