note4cs/技术/Go/算法相关.md
2025-03-31 12:58:00 +08:00

620 lines
16 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
~~~
## 递增的三元子序列
给你一个整数数组 `nums` ,判断这个数组中是否存在长度为 `3` 的递增子序列。
如果存在这样的三元组下标 `(i, j, k)` 且满足 `i < j < k` ,使得 `nums[i] < nums[j] < nums[k]` ,返回 `true` ;否则,返回 `false` 。
~~~ go
func increasingTriplet(nums []int) bool {
n := len(nums)
if n < 3 {
return false
}
first, sencond := nums[0], math.MaxInt32
for i := 1;i < n;i++ {
num := nums[i]
if num > sencond {
return true
} else if num > first {
sencond = num
} else {
first = num
}
}
return false
}
~~~
赋初始值的时候已经满足second > first了现在找第三个数third
(1) 如果third比second大那就是找到了直接返回true
(2) 如果third比second小但是比first大那就把second指向third然后继续遍历找third
(3) 如果third比first还小那就把first指向third然后继续遍历找third这样的话first会跑到second的后边但是不要紧因为在second的前边老first还是满足的
## 字符串的最大公因子
~~~ go
func gcdOfStrings(str1 string, str2 string) string {
if str1+str2 != str2+str1 {
return ""
}
return str1[:gcd(len(str1), len(str2))]
}
// 欧几里得法求最大公因数
func gcd(a, b int) int {
if b == 0 {
return a
}
return gcd(b, a%b)
}
~~~
## 快乐数
~~~ go
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
}
~~~
## 查找二维数组
~~~ go
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缓存
~~~ go
// 定义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` 。如果存在解,则 **保证** 它是 **唯一** 的。
~~~ 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
}
~~~
## 判断IP地址
~~~ go
import "strings"
/**
* 验证IP地址
* @param IP string字符串 一个IP地址字符串
* @return string字符串
*/
func solve( IP string ) string {
if isIpv4(IP) {
return "IPv4"
}
if isIpv6(IP) {
return "IPv6"
}
return "Neither"
}
func isIpv4(ip string) bool {
//首先判断字符串是不是大于71.1.1.1首尾不能是 .
if len(ip) < 7 || ip[0] == '.' || ip[len(ip) - 1] == '.' {
return false
}
//分割之后长度不能少于4
strSlice := strings.Split(ip,".")
if len(strSlice) != 4 {
return false
}
for _, str := range strSlice {
k := len(str)
//开头不能是0
// str = []byte(str)
if k == 0 || str[0] == '0' && k > 1 || k > 3 {
return false
}
num := 0
for i:=0;i<len(str);i++{
//必须是数字
c := str[i]
if c < '0' || c > '9' {
return false
}
num = num * 10 + int((c-'0'))
//不能超过255
if num > 255 {
return false
}
}
}
return true
}
func isIpv6(ip string) bool {
//首先判断字符串是不是小于151.1.1.1),首尾不能是 .
if len(ip) < 7 || ip[0] == '.' || ip[len(ip) - 1] == '.' {
return false
}
strSlice := strings.Split(ip,":")
if len(strSlice) != 8 {
return false
}
for _, str := range strSlice {
k := len(str)
//开头不能是0
// str = []byte(str)
if k == 0 || k > 4 {
return false
}
for i:=0;i<len(str);i++{
c := string(str[i])
//必须是数字
if !((c <= "f" && c >= "a") || (c <= "F" && c >= "A") || (c >= "0" && c <= "9")) {
return false
}
}
}
return true
}
~~~
## 快速排序
```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 solve(s string, t string) (ans string) {
buf := make([]byte, 0, max(len(s), len(t))+1)
add := 0
for i, j := len(s)-1, len(t)-1; i >= 0 || j >= 0; {
if i >= 0 {
add += int(s[i] - '0')
i--
}
if j >= 0 {
add += int(t[j] - '0')
j--
}
buf = append(buf, byte(add%10)+'0')
add /= 10
}
if add > 0 {
buf = append(buf, byte(add)+'0')
}
for _, v := range buf {
ans = string(v) + ans
}
return ans
}
```
## 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。