ARTS_3
概述#
ARTS 是耗子叔发起的编程挑战:
每周完成一个ARTS: 每周至少做一个 leetcode 的算法题、阅读并点评至少一篇英文技术文章、学习至少一个技术技巧、分享一篇有观点和思考的技术文章。(也就是 Algorithm、Review、Tip、Share 简称ARTS)
Algorithm#
https://leetcode.cn/problems/n-queens-ii/ N皇后Ⅱ(把上次的改改就过去了,不讲)
https://leetcode.cn/problems/maximal-rectangle/ 最大矩形
这道题面试也比较常出,只要搞懂他的前置(柱形图最大矩形)其实就没什么难做的了。
我们可以非常容易想到的做法,对于每一行都跑柱形图最大矩形就行。那我们也很可以轻易的得到的做法,就是把柱形图最大矩形的做法拿过来套就可以。
所以在这里具体讲一下柱形图最大矩形的思路:
最主要是运用了单调栈的思想,因为暴力方法会重复遍历之前的位置,用单调栈处理的话我们只需要关心之后的增量,而之前的位置就只需要在弹栈的时候处理就行了。
就拿下图举例:
你程序运行到下标为2的地方时,需要遍历0, 1两个下标
你程序运行到下标为3的地方时,需要遍历0, 1, 2两个下标
然后其实0, 1两个下标是我们重复遍历过了,所以我们希望能有种优化方式让我们,减少这一次的遍历。
观察发现最大矩形面积取决于最小的矩形的高度 x 连续大于等于它的宽度,然后我们发现一个很重要的性质,不断把当前高度最高的柱形抽出,它最大能扩展的宽度一定会在比它小矩形之前。
于是我们就可以维护一个单调递增的栈,在每次弹栈的时候就很容易得出他能扩展的宽度。
当这个栈维护完之后,我们再一个个弹栈。此时栈内元素已经是当前最小的元素了,比他大的都已经被处理好(弹栈了),所以在他右边一定都是比他大的元素(如果有比它小的,他肯定已经再维护的时候被弹栈了),可以一直拓展,所以最右能拓展到的地方是n,最左能拓展到的地方是前一个元素的位置。
算出宽度 x 当前高度,一直这么算下去即可得到答案。
又臭又长的go代码:
func GetMaxMatrix(height []int) (ret int) {
n := len(height)
stack := make([]int, 0)
for i := 0; i < n; i++ {
for {
if len(stack) != 0 && height[stack[len(stack)-1]] > height[i] {
pre_i := stack[len(stack)-1]
stack = stack[:len(stack)-1]
w := i
if len(stack) != 0 {
w = i - stack[len(stack)-1] - 1
}
temp := w * height[pre_i]
if temp > ret {
ret = temp
}
} else {
break
}
}
stack = append(stack, i)
}
for i := len(stack); i != 0; i = len(stack) {
pre_i := stack[len(stack)-1]
stack = stack[:len(stack)-1]
w := n
if len(stack) != 0 {
w = n - stack[len(stack)-1] - 1
}
temp := w * height[pre_i]
if temp > ret {
ret = temp
}
}
return
}
func maximalRectangle(matrix [][]byte) int {
if len(matrix) == 0 {
return 0
}
height := make([]int, len(matrix[0]))
var ans int
for i := range matrix {
for j := range matrix[i] {
if matrix[i][j] == '1' {
height[j]++
} else {
height[j] = 0
}
}
res := GetMaxMatrix(height)
if ans < res {
ans = res
}
}
return ans
}
Tips#
Goland一键运行多个main#
在压测框架中,每次运行client与server都需要点两次按钮
这样次数多了十分不便。
发现goland的Run Configuration里有个叫compound的类型,
点开来发现可以加入多个Go Build Configuration
于是我们就可以先创建Go Build Configuration
并将args设置好(这里run kind选择package或者file都可以)
Run kind的选择
file选项:运行的仅仅就是这个file以及这个file引用的其他package,如果用了同一package下的其他文件的对象,那么会报错。file的main
file选项:运行的这个file所在的package。
之后添加进Compound就行了。
常见问题:#
1. Compound里的运行顺序是按照上面显示的顺序吗?#
似乎当通过
运行时是按顺序来的,而通过
的
来运行时,顺序是不固定的。
我猜测虽然通过for go来运行,但是停止的机制可能是跟goland内部实现有关,所以停止+重启的顺序不相同。
简单写了一个server跟六个client测试了一下
server:
clients:
compound
结果如下:
2. Compound与go:generate 是相同的吗?#
也就是说他是通过go.exe的generate命令去调用go run运行main package,相当于是go run,也就是新开了一个进程去跑,所以是相同的。
更多generate的学习:http://c.biancheng.net/view/4442.html
Review#
https://betterprogramming.pub/how-to-speed-up-your-struct-in-golang-76b846209587
这篇文章主要讲的是struct
的内存优化,emm我寻思,这种struct
的内存对齐不是常识?
但凡学过cs的人都应该会知道吧...
结果这篇文章还被很多人点赞了,emm也不是说这样的文章不好,但是其实他都没讲什么东西。就讲了一个内存优化。但是其实优化这一步,是在最后功能实现完善之后才考虑的。而且定长的struct出现的机会比较少。
感觉这一步优化也是有点点鸡肋,也有可能是因为我的水平不够,没法体会文章的深奥。
这篇文章讲的是golang中的六种error处理,个人感觉非常实用,浅浅Review一下下
Align to the left#
简而言之就是将err
通过=
或者 :=
的方式来接收,并返回
但是这样的话会遇到 nested if checks
,也就是嵌套的if
// Handling Happy case first - leading to nested if checks...
func example() error {
err := somethingThatReturnsError()
if err == nil {
//Happy processing
err = somethingElseThatReturnsError()
if err == nil {
//More Happy processing
} else {
return err
}
} else {
return err
}
}
所以更推荐遇到error就直接return
func ABetterExample() error {
err := somethingThatReturnsError()
if err != nil {
return err
}
// Happy processing
err = somethingElseThatReturnsError()
if err != nil {
return err
}
// More Happy processing
}
这一点在工程上也是很推荐的,遇到需要的或者error直接就返回,而不是写嵌套的check语句
Retry recoverable Errors#
这里的话就是提到,需要重试可以重试的error,比如I/O那类。
作者主要提到一个包,叫做backoff
这个包有一些比较好用的函数,可以帮助你进行不同类型的retry,比如指数退避进行重试。
Wrapping Errors#
这里的也是提到一个外部包"github.com/pkg/errors"
,完全兼容golang自带的error,但是会有些很酷的特性,比如errors.Wrap()函数,它可以打印error的调用堆栈信息,可以方便你定位error出现的位置。
func testingError2() error {
return errors.New("New Error")
}
func testingError(accountNumber string) error {
err := testingError2()
if err != nil {
return errors.Wrap(err, "Error occurred while processing Card Number "+accountNumber)
}
return nil
}
func main() {
err := testingError("Acct1")
fmt.Printf("%+v", errors.Unwrap(err))
}
稍微把玩了十几分钟,感觉确实很强大,这样我就不需要去特地打印调用栈了。感觉有机会用到我们的工程上啊。这玩意儿不必golang自带的errors强一百倍?
Logging Strategies#
这个主要讲的是如何将error优雅地log出来,官方自带的log就跟记事本一样,除了一些基本功能之外就没什么了。
文章主要讲了三种外部包:
- Glog: https://github.com/golang/glog
- Logrus: https://github.com/sirupsen/logrus
- Zap: https://github.com/uber-go/zap
具体有兴趣可以自己学一下这些log的使用。
Error Checks#
这个就是不要把error忽略掉,比如:
func testingError(accoutNumber string) error {
var err error
_ = errors.New("errors.New with _")
errors.New("errors.New not capturing return")
return err
}
这样的话第三行和第四行的error其实都没有处理,但是当代码很多的时候这个地方就会忽略掉。于是就需要Error检查工具:
https://github.com/kisielk/errcheck
之后你可以通过这样的命令行:
errcheck -blank ./...
获得这样的输出:
temp.go:16:2: _ = errors.New("Error capturing return using _")
temp.go:18:12: errors.New("Error not capturing return")
Multiple Errors#
如果你的func 会出现很多error,你又不想要他一出现error就退出,而是把这些error收集起来,再一起返回,那么这个时候就可以使用https://github.com/hashicorp/go-multierror
像下面这样使用即可
func step1() error {
return errors.New("Step1")
}
func step2() error {
return errors.New("Step2")
}
func main() {
var result error
if err := step1(); err != nil {
result = multierror.Append(result, err)
} if err := step2(); err != nil {
result = multierror.Append(result, err)
}
fmt.Println(multierror.Flatten(result))
}
如果多个goroutine的话可以使用这个:https://pkg.go.dev/golang.org/x/sync/errgroup
可以去看官方文档学习
看了这篇文章之后,发现golang的三方库真的好强大啊,怎么好用的东西那么多,感觉还是有必要去好好了解一下,少造点轮子。多学习怎么造轮子。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人
2019-10-26 计蒜客练习题:水果店(嵌套map)
2019-10-26 计蒜客练习题:蒜头君面试(map + max_element)