16 KiB
16 KiB
性能优化运算
2^x = 1 << x
x/2 x >> 1
递增的三元子序列
给你一个整数数组 nums
,判断这个数组中是否存在长度为 3
的递增子序列。
如果存在这样的三元组下标 (i, j, k)
且满足 i < j < k
,使得 nums[i] < nums[j] < nums[k]
,返回 true
;否则,返回 false
。
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还是满足的)
字符串的最大公因子
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)
}
快乐数
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
}
判断IP地址
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 {
//首先判断字符串是不是大于7(1.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 {
//首先判断字符串是不是小于15(1.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
}
快速排序
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) 策略:
- 选基准:从数组中选择一个元素作为基准(pivot)。
- 分区:将数组分为两部分:
- 左半部分所有元素 ≤ 基准
- 右半部分所有元素 ≥ 基准
- 递归:对左右子数组重复上述步骤,直到子数组长度为 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 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
拒绝采样
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的个数即可。具体步骤如下:
- 统计5的倍数 :每个能被5整除的数至少贡献一个5。
- 统计25的倍数 :每个能被25整除的数额外贡献一个5(因为25 = 5×5)。
- 统计更高次幂 :类似地,统计125、625等更高次幂的倍数,直到商为0。