头像

SolitudeAlma

中山大学南方学院




离线:5小时前


最近来访(687)
用户头像
meteor_04
用户头像
大佬带带我
用户头像
xzqq
用户头像
羊村村花
用户头像
Acwer
用户头像
一万小时定律
用户头像
一航小号
用户头像
狼王穆图
用户头像
小郑同学
用户头像
學會ヅ微笑
用户头像
Hxxj
用户头像
村雨无铭
用户头像
明日香
用户头像
一朵奔涌的浪花
用户头像
略略略_3
用户头像
AcWing2AK
用户头像
金木樨
用户头像
Deconx
用户头像
香香小马
用户头像
张顺飞

活动打卡代码 LeetCode 1282. 用户分组

SolitudeAlma
9小时前

废物代码

func groupThePeople(groupSizes []int) [][]int {
    a := make(map[int]int)
    tmp := make([]int, len(groupSizes))
    res := make([][]int, 0)

    for i := 0; i < len(groupSizes); i ++ {
        a[groupSizes[i]] += 1
    }

    for k, _ := range a {
        for {
            if a[k] == 0 {
                break;
            }
            var item []int
            for i := 0; i < k; i ++ {
                for j := 0; j < len(groupSizes); j ++ {
                    if groupSizes[j] == k && tmp[j] != 1 {
                        item = append(item, j)
                        tmp[j] = 1
                        break
                    }
                }
            }
            res = append(res, item)
            a[k] -= k
        }
    }

    return res
}

lc官方题解

func groupThePeople(groupSizes []int) (ans [][]int) {
    groups := map[int][]int{}
    for i, size := range groupSizes {
        groups[size] = append(groups[size], i)
    }
    for size, people := range groups {
        for i := 0; i < len(people); i += size {
            ans = append(ans, people[i:i+size])
        }
    }
    return
}

作者:LeetCode-Solution
链接:https://leetcode.cn/problems/group-the-people-given-the-group-size-they-belong-to/solution/yong-hu-fen-zu-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。



题目描述

给定一个长度为 N 的数组,数组中的第 i 个数字表示一个给定股票在第 i 天的价格,再给定一个非负整数 f,表示交易股票的手续费用。

设计一个算法来计算你所能获取的最大利润。

你可以无限次地完成交易,但是你每次交易都需要支付手续费。

注意: 你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

输入格式

第一行包含两个整数 $N$ 和 $f$,分别表示数组的长度以及每笔交易的手续费用。

第二行包含$N$个不超过$10000$的正整数,表示完整的数组。

输出格式

输出一个整数,表示最大利润。

数据范围

$1 \le N \le 105$,
$1 \le f \le10000$

输入样例

6 2
1 3 2 8 4 9

输出样例

8

样例解释

在第$1$天(股票价格 $= 1$)的时候买入,在第$4$天(股票价格 $= 8$)的时候卖出,这笔交易所能获得利润$= 8-1-2 = 5$ 。随后,在第$5$天(股票价格$= 4$)的时候买入,在第$6$天 (股票价格$= 9$)的时候卖出,这笔交易所能获得利润$= 9-4-2 = 3$。共得利润$5+3 = 8$。


算法1

(贪心) $O(1)$

blablabla

时间复杂度 $O(n)$

$n$ 为数组的长度

参考文献

Go 代码

丢人的贪心

package main

import (
    "bufio"
    "os"
    "fmt"
)

var (
    a []int
    n, f int
)

func maxProfit(prices []int, fee int) int {
    res, l := 0, 0;
    for i := 0; i < len(prices) - 1; i ++ {
        if prices[i] > prices[i + 1] && prices[i] - prices[l] - fee > 0 {
            res += prices[i] - prices[l] - fee
            l = i + 1
        } else if i == len(prices) - 2 {
            if prices[i] > prices[i + 1] && prices[i] - prices[l] - fee > 0 {
                res += prices[i] - prices[l] - fee
            } else if prices[i + 1] - prices[l] - fee > 0 {
                res += prices[i + 1] - prices[l] - fee
            }
        }
    }

    return res
}


func main() {

    reader := bufio.NewReader(os.Stdin)

    fmt.Fscan(reader, &n, &f)

    a = make([]int, n)

    for i := 0; i < n; i ++ {
        fmt.Fscan(reader, &a[i])
    }

    res := maxProfit(a, f)

    fmt.Printf("%d\n", res)
}

正片

package main

import (
    "bufio"
    "os"
    "fmt"
)

var (
    a []int
    n, f int
)

func maxProfit(prices []int, fee int) int {
    n := len(prices)
    buy := prices[0] + fee;
    profit := 0;
    for i := 1; i < n; i++ {
        if prices[i]+fee < buy {
            buy = prices[i]+fee
        } else if prices[i] > buy {
            profit += prices[i]-buy
            buy = prices[i]
        }
    }
    return profit
}


func main() {

    reader := bufio.NewReader(os.Stdin)

    fmt.Fscan(reader, &n, &f)

    a = make([]int, n)

    for i := 0; i < n; i ++ {
        fmt.Fscan(reader, &a[i])
    }

    res := maxProfit(a, f)

    fmt.Printf("%d\n", res)
}

算法2

(dp) $O(n)$或$(1)

blablabla

时间复杂度 $O(n)$

$n$ 为数组的长度

Go 代码

package main

import (
    "bufio"
    "os"
    "fmt"
)

var (
    a []int
    n, f int
)

func maxProfit(prices []int, fee int) int {
    n := len(prices)
    dp := make([][2]int, n)
    dp[0][1] = -prices[0]
    for i := 1; i < n; i++ {
        dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] + prices[i] - fee)
        dp[i][1] = max(dp[i - 1][1], dp[i - 1][0] - prices[i])
    }
    return dp[n - 1][0]
}

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

func main() {

    reader := bufio.NewReader(os.Stdin)

    fmt.Fscan(reader, &n, &f)

    a = make([]int, n)

    for i := 0; i < n; i ++ {
        fmt.Fscan(reader, &a[i])
    }

    res := maxProfit(a, f)

    fmt.Printf("%d\n", res)
}


分享 面试经历

优倍快

项目

jwt:如何确定jwt就是你颁发的;用了什么算法;流程是怎样的;怎么判断是否过期;如何让jwt失效
游戏引擎:用了requestAnimationFrame,一秒六十帧,不断地让canvas绘画用户和用户发射的火球。
项目的结构: 因为没想过这个问题,然后开始吞吞吐吐。

mysql

索引(具体问题忘了) 好像很多公司都问,但是感觉有点难看懂,就只看了事务和锁

其他

讲讲缓存(我问他是redis吗,面试官说什么缓存都可以,不知道该从何说起,然后就说不了解)

JSON Web Token 入门教程

小公司

go

gmp了解吗
map怎么去key
go有链表吗 标准库实现了并发安全的双链表 container/list

msql

也问过索引
慢查询了解吗

redis

redis基本数据结构

计网

http和https的区别 SSL/TLS协议了解吗
tcp粘包拆包了解吗,udp有粘包问题吗




原文链接

数据类型篇

Redis常见数据类型和应用场景

我们都知道 Redis 提供了丰富的数据类型,常见的有五种:String(字符串),Hash(哈希),List(列表),Set(集合)、Zset(有序集合)。

随着 Redis 版本的更新,后面又支持了四种数据类型: BitMap(2.2 版新增)、HyperLogLog(2.8 版新增)、GEO(3.2 版新增)、Stream(5.0 版新增)。

String

介绍

String是最基本的key-value结构,key是唯一标识,value是具体的值,value不仅可以是字符串,也可以是数字(整数或者浮点数),value最多可以容纳的数据长度是512M

内部实现

String类型的底层数据结构实现主要是int和SDS(简单动态字符串)。

SDS和我们认识的C字符串不太一样,之所以没有使用C语言的字符串表示,因为SDS相比于C的原生字符串:
1. SDS不仅可以保存文本数据,还可以保存二进制数据。 因为SDS使用len属性的值而不是空字符来判断字符串是否结束,并且SDS的所有API都会以处理二进制的方式来处理SDS存放在buf[]数组里的数据。所以SDS不光能存放文本数据,还能保存图片、音频、视频、压缩文件这样的二进制数据。
2. SDS 获取字符串长度的时间复杂度是 O(1)。因为 C 语言的字符串并不记录自身长度,所以获取长度的复杂度为 O(n);而 SDS 结构里用len属性记录了字符串长度,所以复杂度为O(1)
3. Redis的SDS API是安全的,拼接字符串不会造成缓冲区溢出。因为SDS在拼接字符串之前会检查SDS空间是否满足要求,如果空间不够会自动扩容,所以不会导致缓冲区溢出的问题。

字符串对象的内部编码(encoding)有 3 种 :int、raw和 embstr。

常用指令

普通字符串的基本操作:

# 设置 key-value 类型的值
> SET name lin
OK
# 根据 key 获得对应的 value
> GET name
"lin"
# 判断某个 key 是否存在
> EXISTS name
(integer) 1
# 返回 key 所储存的字符串值的长度
> STRLEN name
(integer) 3
# 删除某个 key 对应的值
> DEL name
(integer) 1

批量设置:

# 批量设置 key-value 类型的值
> MSET key1 value1 key2 value2 
OK
# 批量获取多个 key 对应的 value
> MGET key1 key2 
1) "value1"
2) "value2"

计数器(字符串的内容为整数的时候可以使用):

# 设置 key-value 类型的值
> SET number 0
OK
# 将 key 中储存的数字值增一
> INCR number
(integer) 1
# 将key中存储的数字值加 10
> INCRBY number 10
(integer) 11
# 将 key 中储存的数字值减一
> DECR number
(integer) 10
# 将key中存储的数字值键 10
> DECRBY number 10
(integer) 0

过期(默认为永不过期):

# 设置 key 在 60 秒后过期(该方法是针对已经存在的key设置过期时间)
> EXPIRE name  60 
(integer) 1
# 查看数据还有多久过期
> TTL name 
(integer) 51

#设置 key-value 类型的值,并设置该key的过期时间为 60 秒
> SET key  value EX 60
OK
> SETEX key  60 value
OK

不存在就插入:

# 不存在就插入(not exists)
>SETNX key value
(integer) 1
应用场景

– tips: key中L:的作用类似把key分类,在可视化软件上可以看到它是像文件夹那样展开的

  1. 缓存对象
    使用 String 来缓存对象有两种方式:
    • 直接缓存整个对象的 JSON,命令例子:SET user:1 '{"name":"xiaolin", "age":18}'
    • 采用将 key 进行分离为 user:ID:属性,采用 MSET 存储,用 MGET 获取各属性值,命令例子:MSET user:1:name xiaolin user:1:age 18 user:2:name xiaomei user:2:age 20
      把常用信息,字符串,图片或者视频等信息放到redis中,redis作为缓存层,mysql做持久化层,降低mysql的读写压力。
  2. 计数
    因为 Redis 处理命令是单线程,所以执行命令的过程是原子的。因此 String 数据类型适合计数场景,比如计算访问次数、点赞、转发、库存数量等等。
    比如计算文章的阅读量:
    # 初始化文章的阅读量 > SET aritcle:readcount:1001 0 OK #阅读量+1 > INCR aritcle:readcount:1001 (integer) 1 #阅读量+1 > INCR aritcle:readcount:1001 (integer) 2 #阅读量+1 > INCR aritcle:readcount:1001 (integer) 3 # 获取对应文章的阅读量 > GET aritcle:readcount:1001 "3"
  3. 分布式锁
    SET 命令有个 NX 参数可以实现「key不存在才插入」,可以用它来实现分布式锁:
    • 如果 key 不存在,则显示插入成功,可以用来表示加锁成功;
    • 如果 key 存在,则会显示插入失败,可以用来表示加锁失败。

一般而言,还会对分布式锁加上过期时间,分布式锁的命令如下:

SET lock_key unique_value NX PX 10000
  • lock_key 就是 key 键;
  • unique_value 是客户端生成的唯一的标识;
  • NX 代表只在 lock_key 不存在时,才对 lock_key 进行设置操作;
  • PX 10000 表示设置 lock_key 的过期时间为 10s,这是为了避免客户端发生异常而无法释放锁。

而解锁的过程就是将lock_key键删除,但不能乱删,要保证执行操作的客户端就是加锁的客户端。所以,解锁的时候,我们要先判断锁的unique_value是否为加锁客户端,是的话,才将 lock_key 键删除。
可以看到,解锁是有两个操作,这时就需要Lua脚本来保证解锁的原子性,因为Redis在执行Lua脚本时,可以以原子性的方式执行,保证了锁释放操作的原子性。

// 释放锁时,先比较 unique_value 是否相等,避免锁的误释放
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

这样一来,就通过使用 SET 命令和 Lua 脚本在 Redis 单节点上完成了分布式锁的加锁和解锁
4.共享 Session 信息
通常我们在开发后台管理系统时,会使用Session来保存用户的会话(登录)状态,这些Session信息会被保存在服务器端,但这只适用于单系统应用,如果是分布式系统此模式将不再适用。
例如用户一的 Session 信息被存储在服务器一,但第二次访问时用户一被分配到服务器二,这个时候服务器并没有用户一的 Session 信息,就会出现需要重复登录的问题,问题在于分布式系统每次会把请求随机分配到不同的服务器。
分布式系统单独存储 Session 流程图:
分布式系统单独存储 Session流程图
因此,我们需要借助 Redis 对这些Session信息进行统一的存储和管理,这样无论请求发送到那台服务器,服务器都会去同一个Redis获取相关的Session信息,这样就解决了分布式系统下Session存储的问题。
分布式系统使用同一个 Redis 存储 Session 流程图:
分布式系统使用同一个 Redis存储Session流程图




文章来源

事务篇

事务的隔离级别是怎么实现的

现在SolitudeAlma和我的卡里各有100万,我决定转100万给他,那么最后我的卡里肯定是0元,而SolitudeAlma的卡里是200万元。

转账这一动作在数据库里会涉及到一系列的操作,假设我向你卡里转账的过程是有以下几个步骤组成的:
1. 从数据库读取我的余额
2. 将我的余额减去转账的金额
3. 将修改后的余额更新到数据库里
4. 从数据库读取你的余额
5. 将你的余额加上转账的金额
6. 将修改后的余额更新到数据库里

可以看到其中涉及了两次的数据库操作

假设在执行第三个步骤之后,服务器突然掉点了或者是程序突然出错,就会发生一件蛋疼的事,我的账户扣了100万,但是钱没有到你的账户上,也就是说这100万不见了

要解决这个问题,就要保证转账业务里的所有数据库的操作是不可分割的,要么全部执行,要么全部失败,不允许出现中间状态的数据

数据库中的[事务(transction)]就能达到这样的效果

在转账操作开始之前开启事务,等所有数据库操作执行完之后,才提交事务。对已提交的事务来说,该事务对数据库所做的修改将永久生效,如果中途发生中断或者错误,那么该事务期间对数据库的修改将会被回滚到没执行该事务之前的状态


事务有哪些特性

事务是由MySQL的引擎来实现的,常见的InnoDB引擎是支持事务的

但并不是所有的引擎都支持事务,比如MySQL原生的引擎MyISAM引擎就不支持事务,也正是这样,InnoDB是使用比较多的一个引擎

要实现事务必须遵守4个特性,分别如下:
1. 原子性(Atomicity): 一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节,而且事务在执行过程中发生错误,会被回滚到事务开始的状态。就像网购一样,付款成功,商品到手(忽略退款等因素),付款失败,商品还在商家手中,消费者的钱也没花出去
2. 一致性(Constency):是指事务操作前后,数据满足完整性约束,数据库保持一致性状态。比如,用户 A 和用户 B 在银行分别有800元和600元,总共1400元,用户A给用户B转账200元,分为两个步骤,从A的账户扣除200元和对B的账户增加 200元。一致性就是要求上述步骤操作后,最后的结果是用户A还有600元,用户B有800元,总共1400元,而不会出现用户A扣除了200元,但用户B未增加的情况(该情况,用户A和B均为600元,总共1200元)。
3. 隔离性(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致,因为多个事务同时使用相同的数据时,不会相互干扰,每个事务都有一个完整的数据空间,对其他并发事务是隔离的。也就是说,消费者购买商品这个事务,是不影响其他消费者购买的。
4. 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。

InnoDB引擎通过什么技术来保证事务的四个特性呢?

  • 持久性是通过redo log(重做日志)来保证的
  • 原子性是通过undo log(回滚日志)来保证的
  • 隔离性是通过MVCC(多版本并发控制)或锁机制来保证的
  • 一致性是通过持久性+原子性+隔离性来保证的

并行事务会引发什么问题?

MySQL服务端是允许多个客户端连接的,这意味着MySQL会出现同时处理多个事务的情况。

那么在同时处理多个事务的时候,就可能出现脏读(dirty read)、不可重复读(non-repeatable read)、幻读(phantom read)的问题。

脏读

如果一个事务「读到」了另一个「未提交事务修改过的数据」,就意味着发生了「脏读」现象。

假设有A和B这两个事务同时在处理,事务A先开始从数据库中读取SolitudeAlma的余额数据,然后再执行更新操作,如果此时事务A还没有提交事务,而此时正好事务B也从数据库中读取SolitudeAlma的余额数据,那么事务B读取到的余额数据是刚才事务A更新后的数据,即使没有提交事务。

脏读

因为事务A是还没提交事务的,也就是它随时可能发生回滚操作,如果在上面这种情况事务A发生了回滚,那么事务B刚才得到的数据就是过期的数据,这种现象就被称为脏读。

不可重复读

在一个事务内多次读取同一个数据,如果出现前后两次读到的数据不一样的情况,就意味着发生了「不可重复读」现象。

假设有A和B这两个事务同时在处理,事务A先开始从数据库中读取小林的余额数据,然后继续执行代码逻辑处理,在这过程中如果事务B更新了这条数据,并提交了事务,那么当事务A再次读取该数据时,就会发现前后两次读到的数据是不一致的,这种现象就被称为不可重复读。

不可重复读

幻读

在一个事务内多次查询某个符合查询条件的「记录数量」,如果出现前后两次查询到的记录数量不一样的情况,就意味着发生了「幻读」现象。

假设有A和B这两个事务同时在处理,事务A先开始从数据库查询账户余额大于100万的记录,发现共有5条,然后事务B也按相同的搜索条件也是查询出了5条记录。

幻读

接下来,事务A插入了一条余额超过100万的账号,并提交了事务,此时数据库超过100万余额的账号个数就变为6。
然后事务B再次查询账户余额大于100万的记录,此时查询到的记录数量有6条,发现和前一次读到的记录数量不一样了,就感觉发生了幻觉一样,这种现象就被称为幻读。


事务的隔离级别有哪些?

前面提到,当多个事务并发执行时可能会遇到「脏读、不可重复读、幻读」的现象,这些现象会对事务的一致性产生不同程序的影响。

  • 脏读:读到其他事务未提交的数据;
  • 不可重复读:前后读取的数据不一致;
  • 幻读:前后读取的记录数量不一致。

这三个现象的严重性排序如下:

脏读 $ \gt $ 不可重复读 $ \gt $ 幻读

SQL标准提出了四种隔离级别来规避这些现象,隔离级别越高,性能效率就越低,这四个隔离级别如下:

  • $\color{#0000FF}{读未提交}$$\color{#FF00FF}{(read uncommitted)}$,指一个事务还没提交时,它做的变更就能被其他事务看到
  • $\color{#0000FF}{读提交}$$\color{#FF00FF}{(read committed)}$,指一个事务提交之后,它做的变更才能被其他事务看到
  • $\color{#0000FF}{可重复读}$$\color{#FF00FF}{(repeatable read)}$,指一个事务执行过程中看到的数据,一直跟这个事务启动时看到的数据是一致的,InnoDB 引擎的默认隔离级别
  • $\color{#0000FF}{串行化}$$\color{#FF00FF}{(serializable )}$,会对记录加上读写锁,在多个事务对这条记录进行读写操作时,如果发生了读写冲突的时候,后访问的事务必须等前一个事务执行完成,才能继续执行

按隔离水平高低排序如下:

串行化 $\gt$ 可重复读 $\gt$ 读提交 $\gt$ 读未提交

针对不同的隔离级别,并发事务时可能发生的现象也会不同。

隔离级别对应发生的现象

  • 在「读未提交」隔离级别下,可能发生脏读、不可重复读和幻读现象
  • 在「读提交」隔离级别下,可能发生不可重复读和幻读现象,但是不可能发生脏读现象
  • 在「可重复读」隔离级别下,可能发生幻读现象,但是不可能脏读和不可重复读现象
  • 在「串行化」隔离级别下,脏读、不可重复读和幻读现象都不可能会发生

$\color{#0000FF}{要解决脏读现象,就要升级到「读提交」以上的隔离级别;要解决不可重复读现象,就要升级到「可重复读」的隔离级别}$。

不过,要解决幻读现象不建议将隔离级别升级到「串行化」,因为这样会导致数据库在并发事务时性能很差(加锁)。

$\color{#0000FF}{InnoDB 引擎的默认隔离级别虽然是「可重复读」,但是它通过next-key lock 锁(行锁和间隙锁的组合)来锁住记录之间的“间隙”和记录本身,防止其他事务在这个记录之间插入新的记录,这样就避免了幻读现象。}$

接下来,举个具体的例子来说明这四种隔离级别,有一张账户余额表,里面有一条记录:

然后有两个并发的事务,事务A只负责查询余额,事务B则会将我的余额改成200万,下面是按照时间顺序执行两个事务的行为:

启动事务A 启动事务B
查询得到余额100万
查询得到余额100万
将余额100万改成200万
查询得到余额V1
提交事务B
查询得到余额V2
提交事务A
查询得到余额V3

在不同隔离级别下,事务A执行过程中查询到的余额可能会不同:

  • 在「读未提交」隔离级别下,事务 B 修改余额后,虽然没有提交事务,但是此时的余额已经可以被事务A看见了,于是事务A中余额V1查询的值是200万,余额 V2、V3 自然也是200万了
  • 在「读提交」隔离级别下,事务B修改余额后,因为没有提交事务,所以事务A中余额V1的值还是100万,等事务B提交完后,最新的余额数据才能被事务A看见,因此余额V2、V3 都是200万
  • 在「可重复读」隔离级别下,事务A只能看见启动事务时的数据,所以余额V1、余额V2的值都是100万,当事务A提交事务后,就能看见最新的余额数据了,所以余额V3的值是200万
  • 在「串行化」隔离级别下,事务B在执行将余额100万修改为200万时,由于此前事务A执行了读操作,这样就发生了读写冲突,于是就会被锁住,直到事务A提交后,事务B才可以继续执行,所以从A的角度看,余额V1、V2的值是100万,余额V3的值是200万。

这四种隔离级别具体是如何实现的呢?

  • 对于「读未提交」隔离级别的事务来说,因为可以读到未提交事务修改的数据,所以直接读取最新的数据就好了;
  • 对于「串行化」隔离级别的事务来说,通过加读写锁的方式来避免并行访问;
  • 对于「读提交」和「可重复读」隔离级别的事务来说,它们是通过Read View 来实现的,它们的区别在于创建 Read View 的时机不同,大家可以把 ReadView理解成一个数据快照,就像相机拍照那样,定格某一时刻的风景。「读提交」隔离级别是在「每个语句执行前」都会重新生成一个 Read View,而「可重复读」隔离级别是「启动事务时」生成一个 ReadView,然后整个事务期间都在用这个ReadView

注意,执行「开始事务」命令,并不意味着启动了事务。在 MySQL 有两种开启事务的命令,分别是:

  • 第一种:begin/start transaction 命令;
  • 第二种:start transaction with consistent snapshot 命令;

这两种开启事务的命令,事务的启动时机是不同的:

  • 执行了 begin/start transaction 命令后,并不代表事务启动了。只有在执行这个命令后,执行了增删查改操作的 SQL 语句,才是事务真正启动的时机;
  • 执行了 start transaction with consistent snapshot 命令,就会马上启动事务。



tips: 标题忽略,点原题链接即可

题目描述

交替打印字母和数字(channel练习)


Go 代码

/**
 * @Author: SolitudeAlma
 * @Date: 2022 2022/8/1 11:06
 */

package main

import (
    "fmt"
    "sync"
)

func main() {
    letter, number := make(chan bool), make(chan bool) // 无缓冲chan
    wait := sync.WaitGroup{}                           // 等待组

    go func() {
        i := 1
        for { // 死循环避免select卡住 select会随机执行case语句,当无缓冲chan拿不到数据会阻塞 可能会造成死锁问题
            select {
            case <-number: // 从chan取值
                fmt.Print(i)
                i++
                letter <- true // 往chan放值
            default:

            }
        }
    }()
    wait.Add(1) // 用来记录并发任务数 这里只需一个即可 参数不能为负数,即总数也不能为负数
    go func(wait *sync.WaitGroup) {
        i := 'A'
        for {
            select {
            case <-letter:
                if i >= 'Z' {
                    fmt.Print(string(i))
                    wait.Done() // wait.Add()数减一
                    close(letter) // 关闭通道 但依然能从chan接收值,但不能发送值
                    return
                }
                fmt.Print(string(i))
                i++
                number <- true
            default:

            }
        }
    }(&wait) // goroutine使用WaitGroup需要传指针
    number <- true // 通知number routine 第一个数开始进入chan 避免死锁(无缓冲chan的通病)
    wait.Wait()    // 等待 知道wait.Done()将wait.Add()的数减到0 避免main routine先执行完 两个goroutine还没执行就结束了
    close(number) // 关闭通道
}



活动打卡代码 AcWing 743. 数组中的行

SolitudeAlma
1个月前
package main

import (
    "bufio"
    "os"
    "fmt"
)

var (
    a [12][12]float64
    l int
    res float64
    s string
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    fmt.Fscan(reader, &l, &s)

    for i := 0; i < 12; i ++ {
        for j := 0; j < 12; j ++ {
            fmt.Fscan(reader, &a[i][j])
        }
    }


    for i := 0; i < 12; i ++ {
        res += a[l][i]
    }

    if s == "S" {
        fmt.Printf("%.1f\n", res)
    } else {
        fmt.Printf("%.1f\n", res / 12)
    }
}


活动打卡代码 AcWing 737. 数组替换

SolitudeAlma
1个月前
package main

import (
    "bufio"
    "fmt"
    "os"
)

var (
    a []int
)

func main() {

    reader := bufio.NewReader(os.Stdin)
    a = make([]int, 10)
    for i := 0; i < 10; i ++ {
        fmt.Fscan(reader, &a[i])
    }

    for i := range a {
        if a[i] <= 0 {
            a[i] = 1
        }
    }

    for i := range a {
        fmt.Printf("X[%d] = %d\n", i, a[i])
    }
}


活动打卡代码 AcWing 727. 菱形

SolitudeAlma
1个月前
package main

import (
    "bufio"
    "os"
    "fmt"
)

var (
    n int
)

func main() {

    reader := bufio.NewReader(os.Stdin)
    fmt.Fscan(reader, &n)

    var s string
    cnt := 1
    for i := n / 2; i > 0; i -- {

        for j := 0; j < i; j ++ {
            s += " "
        }

        for k := 0; k < cnt; k ++ {
            s += "*"
        }

        cnt += 2
        fmt.Println(s)
        s = ""
    }

    for i := 0; i < cnt; i ++ {
        s += "*"
    }
    fmt.Println(s)
    s = ""
    cnt -= 2
    for i := 1; i <= n / 2; i ++ {

        for j := 0; j < i; j ++ {
            s += " "
        }

        for k := 0; k < cnt; k ++ {
            s += "*"
        }

        cnt -= 2
        fmt.Println(s)
        s = ""
    }
}


活动打卡代码 AcWing 724. 约数

SolitudeAlma
1个月前
package main

import (
    "bufio"
    "fmt"
    "os"
)

var (
    n int
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    fmt.Fscan(reader, &n)

    for i := 1; i <= n; i ++ {
        if n % i == 0 {
            fmt.Printf("%d\n", i)
        }
    } 
}