lc-go(1)
目的是通过写lc来提交对golang语法的熟悉程度
题目
344. 反转字符串
难度简单740
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s
的形式给出。
不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。
本题比较简单, 需要注意的就是GO里面没有while关键字, 通过for来循环
1 | func reverseString(s []byte) { |
376. 摆动序列
难度中等889
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 **摆动序列 。**第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
本题的关键点在于题目的要求的是求序列的长度, 因此我们不需要统计全部, 只需要两个up , down变量分别统计 最后一个元素为上升/下降的序列的长度 ,
然后通过前一个变量来判断当前的元素应该放到升/降序列中 , 最后返回两个序列的最大长度即可
注意
math.Max( )
的参数为float64
类型
- 事实上math package中的绝大多数方法的参数的类型都是
float64
详细参考 math package - math - Go Packages
1 | func wiggleMaxLength(nums []int) int { |
11. 盛最多水的容器
难度中等4205
给定一个长度为 n
的整数数组 height
。有 n
条垂线,第 i
条线的两个端点是 (i, 0)
和 (i, height[i])
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
返回容器可以储存的最大水量。
**说明:**你不能倾斜容器。
解法
双指针, 从两边缩减 , 每次移动短板的那一边, 维护res返回即可
原本打算使用math.Max() , 但是go 的math包的方法的大多数参数类型都是float , 所以只能自己写一个
1 | func maxArea(height []int)int { |
12. 整数转罗马数字
难度中等1084
罗马数字包含以下七种字符: I
, V
, X
, L
,C
,D
和 M
。
1 | 字符 数值 |
例如, 罗马数字 2 写做 II
,即为两个并列的 1。12 写做 XII
,即为 X
+ II
。 27 写做 XXVII
, 即为 XX
+ V
+ II
。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII
,而是 IV
。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX
。这个特殊的规则只适用于以下六种情况:
I
可以放在V
(5) 和X
(10) 的左边,来表示 4 和 9。X
可以放在L
(50) 和C
(100) 的左边,来表示 40 和 90。C
可以放在D
(500) 和M
(1000) 的左边,来表示 400 和 900。
给你一个整数,将其转为罗马数字。
解法
我们可以利用map来存储需要的数字, 注意题目给出的参数num的范围
提示:
1 <= num <= 3999
那么我们最多只需要到1000 的字符 M
首先把数字对应的字符对应在map中 , 然后进行贪心 , 每次都减去当前数字能减去的最大的数字 , 然后追加字符串 , 最终返回即可
对于strs 的说明:
对于
(1,10)
的数字 , 我们只需要知道I IV V
即可表示出全部的数字因为罗马数字对于
**4
以及**9
的表示方法是为10的当前位数幂次方减去指定的数组 , 比如 9 就是表示为IX
, 其中X为10 , I为1 , 表示X-I = 9
1 | func intToRoman(num int) string { |
执行用时:12 ms, 在所有 Go 提交中击败了30.36%的用户
内存消耗:3 MB, 在所有 Go 提交中击败了78.57%的用户
下面再来看另一种哈希表的写法
来自题解
1 | var valueSymbols = []struct { |
执行用时:8 ms, 在所有 Go 提交中击败了62.09%的用户
内存消耗:3 MB, 在所有 Go 提交中击败了91.35%的用户
- 如果只想取值,不需要索引,可以用"_"下划线占位索引。
通过对比发现第二次代码的执行效率更高是因为使用了append()
方法
修改了第一次的代码,
1 | func intToRoman(num int) string { |
执行用时:8 ms, 在所有 Go 提交中击败了62.09%的用户
内存消耗:2.9 MB, 在所有 Go 提交中击败了94.92%的用户
发现执行耗时是一样的 , 实际上的时间提升来自于 使用了 append() , 相较于直接使用字符串相加, 效率更高
34. 在排序数组中查找元素的第一个和最后一个位置
难度中等2235
给你一个按照非递减顺序排列的整数数组 nums
,和一个目标值 target
。请你找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target
,返回 [-1, -1]
。
你必须设计并实现时间复杂度为 O(log n)
的算法解决此问题。
代码
1 | func searchRange(nums []int, target int) []int { |
121. 买卖股票的最佳时机
难度简单2897
给定一个数组 prices
,它的第 i
个元素 prices[i]
表示一支给定股票第 i
天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0
。
1 | func maxProfit(prices []int) int { |
79. 单词搜索
难度中等1563
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
需要注意的是如果需要在一个方法内部递归调用当前的方法, 并且这个方法的定义方式是 :=
比如下面的代码
这样是无法运行的
1 | func main() { |
可以看到编译器并不能找到我们定义的方法a
这个时候就需要提前的去声明func a() , 就像下面这样
1 | func main() { |
回归原题, 本题的做法是使用递归 , 如果执行到当前的字符长度已经大于/等于word的长度 , 说明单词在board中 ,
代码如下
1 | func exist(board [][]byte, word string) bool { |
75. 颜色分类
难度中等1562
给定一个包含红色、白色和蓝色、共 n
个元素的数组 nums
,**原地**对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
必须在不使用库内置的 sort 函数的情况下解决这个问题。
示例 1:
1 | 输入:nums = [2,0,2,1,1,0] |
示例 2:
1 | 输入:nums = [2,0,1] |
提示:
n == nums.length
1 <= n <= 300
nums[i]
为0
、1
或2
通过读题 , 其实本题就是一个排序 , 特殊的一点就是需要排序的数组只有三种元素
解法
既然有三种元素, 那么我们可以维护三个指针, 把原来的数组分成三个分区
- 0分区
- 1分区
- 2分区
由于数组需要排序, 因此当我们的0分区扩大的时候 指针1 2 都需要移动 , 同理在我们的分区1扩大的时候指针2需要移动, 而分区2扩大的时候则是只需要指针2移动即可
那么我们可以得到下面的代码
需要注意的是go 里面没有这样的写法
nums[idx++]=1
, 虽然运算符中有++运算符 , 但是却不能使用这样的写法
1 | func sortColors(nums []int) { |
84. 柱状图中最大的矩形 : 从暴力写法到单调栈优化
难度困难2387
给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例 1:
1 | 输入:heights = [2,1,5,6,2,3] |
示例 2:
1 | 输入: heights = [2,4] |
提示:
1 <= heights.length <=105
0 <= heights[i] <= 104
暴力写法
我们遍历所有的矩形的高度 , 从当前的矩形向两边散开 , 然后维护一个最大值最后返回
1 | func largestRectangleArea(heights []int) int { |
时间复杂度:O(N2),外层循环 n
次,内层循环每次最多 n
次。
空间复杂度:O(1)。
单调栈优化
首先我们考虑一种特殊的情况, 就是每个柱子都是单调自增的 ,
那么此时对于每个柱子的左右边界也都是已知的 , 对于柱子 i , 他的左边界就是他自身 , 右边界就是最右边的那个最高的柱子 ( 如果右边没有柱子那么右边界也是他自身) ,
那么对应到代码里面 , 也就是 l=i, r=n-1 ; size= heigths[i] * (r-l+1)
1 | l,r:=i,n-1 |
然后柱子是几乎不可能总是单调递增的 , 下面的这种情况更加符合一般情况
对于上面的柱子, 当第五个柱子出现的时候, 此时的柱子不满足单调性 ,
那么对于第四个柱子来说 , 他的左右边界都是4 , 我们可以确定这个柱子能勾勒的最大的矩形面积是 5 ,
这个时候我们可以假装吧这个柱子 删除 ,
然后我们开始来计算第三个柱子可以勾勒的最大的矩形的面积 , 其实也就是下图中的红色区域的面积 , 计算的方式为 heights[2] * ( 3-2+1)
那么我们再假装删除 第三个柱子 , 对于第二个柱子 , 已经满足了单调性 , 因此我们继续计算即可
通过这样不断的进行遍历 , 计算, 我们维护一个最大的矩形面积最后返回即可
这里写的时候使用了两个哨兵 , 通过添加 哨兵 来减少代码量 , 取消多余的比较/判空
java代码如下
1 | class Solution { |
go代码如下
1 | func largestRectangleArea(heights []int) int { |
使用切片
1 | func largestRectangleArea(heights []int) int { |
1017. 负二进制转换
难度中等112
给你一个整数 n
,以二进制字符串的形式返回该整数的 **负二进制(base -2
)**表示。
**注意,**除非字符串就是 "0"
,否则返回的字符串中不能含有前导零。
模拟
我们判断n从低位到高位的每一位, 如果当前位为1 ,
那么我们可以这样理解 ,
对于 二进制的11 结果为 3 , 对于 -2 进制的 11 , 结果为
111
在二进制的偶数位上,0或1的贡献和负二进制是一样的,只有在奇数位且为1时是相反的,如果在这里填了1,那么后面就需要补偿这个1。
因此算法中使用了一个奇偶变换的k,在正常二进制下这一位为1时,不论如何都做一步n=n-k,在奇偶位上作用是不同的,
在奇数时k是-1,相当于给n+1补偿了这一位的负数损失,
在偶数位上k是1,给n-1相当于把此时奇数的n变为了偶数,保证后续n/2时一定能没有余数,两种能统一成一行n=n-k还是挺优雅的,核心效果还是在奇数位。
1 | func baseNeg2(n int) string { |
96. 不同的二叉搜索树
难度中等2202
给你一个整数 n
,求恰由 n
个节点组成且节点值从 1
到 n
互不相同的 二叉搜索树 有多少种?返回满足题意的二叉搜索树的种数。
示例 1:
1 | 输入:n = 3 |
示例 2:
1 | 输入:n = 1 |
提示:
1 <= n <= 19
我们考虑这样的思路 , 假设n个节点存在的BST的个数为G(n)
, 以f(i)
表示以i
为根的BST的个数 ,
那么G(n) = f(1)+ f(2)+ f(3)+ f(4)+ ........+ f(n)
即可得出 , 当i 为根节点时 , 左子树的节点个数为i-1
个 , 右子树的节点个数为n-i
个 , 那么我们可以得出
f(i) = G(i-1) * G(n-i)
代入第一个公式即可得出
G(n)= G(0)*G(n-1)+ G(1)*G(n-2)+ G(2)*G(n-3)+ .....+ G(n-1)*G(0)
那么不难看出上面的公式出现了许多的重复计算 , 因此我们可以通过一个memo[][]
数组来记录 memo[lo][hi]
的二叉树的个数 , 然后通过递归方法来计算左右子树的BST个数, 统计返回即可
递归方法 : cnt(lo, hi int) int
参数 :
- lo : 节点范围的左边界
- hi : 节点范围的右边界
返回值 : 指定范围存在的BST的个数
还需要注意的一点就是golang在定义二维数组的时候 , make([][]int,n+1)
只是定义了一个n+1 行的数组, 实际上每一行的capacity都是0 , 如下图所示
上面的代码的执行结果是 00000000000
, 因此实际上我们是需要再手动的去设置一下每个一维数组的容量
1 | memo := make([][]int, n+1) |
代码如下
注意由于在cnt 函数中递归调用了cnt函数 , 因此需要预先声明函数
var cnt func(lo, hi int) int
1 | func numTrees(n int) int { |
语法
func len(V Type) int
其作用是用于计算数组(包括数组指针)、切片(slice )、map、channel、字符串等数据类型的长度,
注意,结构休(struct)、整型布尔等不能作为参数传给len函数。
- 数组或数组指针:返回元素个数
- map和slice: 元素个数
- channel : 通道中未读的元素个数
- 字符串:字节数,并非字符串的字符数
- 当V的值为nil值,len返回0
数组及切片
我们首先从概念上来区分数组以及切片 ,
-
数组是具有相同 唯一类型 的一组已编号且长度固定
-
切片(slice)是对数组一个连续片段的引用
切片的长度可以在运行时修改,最小为 0 最大为相关数组的长度:切片是一个 长度可变的数组。
优点 因为切片是引用,所以它们不需要使用额外的内存并且比使用数组更有效率,所以在 Go 代码中 切片比数组更常用。
定义数组
var identifier [len]type
- 可以在后面跟上
{}
初始化元素
1 | func main(){ |
定义切片
var identifier []type
不需要声明长度
1 | func main(){ |
一个切片在未初始化之前默认为 nil,长度为 0。
切片的初始化格式是:var slice1 []type = arr1[start:end]
。
注意区间格式是
[start,end)
1
2 nums := [5]int{}
fmt.Println(len(nums[0:3]))上面代码的打印结果为3
通过数组赋值切片
1 | func main(){ |
那么通过这个机制 , 我们可以完成许多操作
比如 slice = slice[:len(slice)-1]
删除切片的最后一个元素
或者取得切片的左半边元素 slice = slice[0:(len(slice)+1)/2]
注意 绝对不要用指针指向 slice。切片本身已经是一个引用类型,所以它本身就是一个指针!!
方法定义
比如现在需要写一个简单的求两数之和的方法
简单的写法
1 | func sum(a int ,b int) int{ |
借助变量
1 | sum:=func(a int ,b int) int{ |
直接声明返回值
1 | func sum(a int ,b int)(res int){ |
FOR循环
Go 语言for循环遍历最全用法总结 - 知乎 (zhihu.com)
我们查看下面的代码
1 | func main() { |
上面代码的打印结果为
1 | 0 1 2 3 4 5 |
第一次循环中的 i 为索引 , 第二次循环中的 i 为nums数组中的元素
append()
- append()用来将元素添加到切片末尾并返回结果。
- 调用append函数必须用原来的切片变量接收返回值
- append追加元素,如果slice还有容量的话,就会将新的元素放在原来slice后面的剩余空间里,当底层数组装不下的时候,Go就会创建新的底层数组来保存这个切片,slice地址也随之改变。
- 分配了新的地址后,再把原来slice中的元素逐个拷贝到新的slice中,并返回。
追加元素
1 | func main(){ |
range()
range
关键字用于遍历数组、切片、字符串、map 和通道等可迭代的数据结构。
使用 range
关键字,可以迭代一个集合并返回索引和元素值。在有些情况下,只需要元素值不需要索引,可以使用 _
替换索引,表示不关心该值,比如:
1 | goCopy Codenumbers := []int{1, 2, 3, 4, 5} |
输出结果为:
1 | 1 |
如果需要索引和元素值,使用以下语法:
1 | goCopy Codefor index, value := range collection { |
其中 index
表示当前元素在集合中的索引,value
表示当前元素的值。
例如,遍历一个 map 并输出所有的 key 和 value:
1 | ages := map[string]int{ |
输出结果为:
1 | Alice is 25 years old |