文章目录
Leetcode每日一题-2021.04
Leetcode刷题地址
此博文为个人学习笔记,记录2021年4月Leetcode每日一题刷题记录
4.1-1006.笨阶乘
题目描述
分析
本题其实是阶乘的一个变形,只是将原来的*,变成了*,/,+,-
核心在于怎么将这一个变化用代码进行表述
其实涉及到操作符,并且存在操作符优先级的问题,我们都可以考虑用栈操作
在这一题我选择用switch判定
本题注意点:
- 操作符有优先级,不能直接对返回值ret进行乘除加减,所以涉及到值的记录
- 操作符的表示
详情见代码注释
代码
// 看完代码注释之后,可以看下例:
// 如 N = 10 10*9/8+7 - 6*5/4+3 - 2*1
// 第一个next = 10*9/8+7
// 第二个next = -6*5/4+3
// 第三个next = -2*1
func clumsy(N int) int {
// ret 用于接收返回值,初始为0
ret := 0
// next用于下一个乘除加的记录,记录第一个值为N
next := N
// opt记录操作数,每4个为1循环,通过取模操作控制
opt := -1
for i := N - 1; i > 0; i-- {
opt++
switch opt % 4 {
// 第一个操作符进行乘
case 0:
next *= i
// 第二个操作符进行除
case 1:
next /= i
// 第三个操作符进行加
// 注意到下一个操作符-的时候,由于优先级问题,所以必须记录新的值了
// 将当前记录的next加入ret
case 2:
next += i
ret += next
// 重新刷新记录值,直接记录第一个next为负值
// 可以巧妙的解决减问题
case 3:
next = -i
}
}
// 我们在循环里面,只有当正好是+的时候,才将记录值next刷新到ret
// 所以到这一步的时候,判断最后一次next有没有加到ret,如果没有的话,手动加一次
if opt%4 != 2 {
ret += next
}
return ret
}
运行结果
4.2-面试题17.21.直方图的水量
题目描述
分析
本题在提示里面要求用O(N)时间复杂度,O(N)空间复杂度
- 考虑使用双指针
- 我的基本思路,计算所有方块的面积non(即数组求和),计算方块+积水的面积area
- 最后使用返回area-non即可求得积水面积
- 所以本题的重点在于求解non和area
- non的求解就是对整个height数组求和,简单,无需过于探究
- area的求解,使用双指针,具体可以参照代码注释
代码
func trap(height []int) int {
n := len(height)
// 思路,左右指针 l,r
l := 0
r := n - 1
// 左右指针不是同时移动的,设置左右指针是否移动的判断标志
lfag := false
rflag := false
// min 记录当前左边和右边最小值
var min int
// lvalue, rvalue 记录当前左边和右边的值
var lvalue, rvalue int
// non 记录块数量, area 记录块+水的数量
// 最终返回area-non
non := 0
area := 0
// 初始化,先找到左边和右边的非0值
// 找左边
for l < n {
if height[l] != 0 {
break
}
l++
}
// 如果l==n,说明全是0,返回
if l == n {
return 0
}
// 找右边
for r > l {
if height[r] != 0 {
break
}
r--
}
// 如果r==l, 说明两边只有一个块,两边盛不了水咯
if r == l {
return 0
}
// 初始化 min, lvalue, rvalue
lvalue = height[l]
rvalue = height[r]
// 如果左边值小,则min等于左边,左边向右移动,等于也让左边移动
if lvalue <= rvalue {
min = lvalue
lfag = true
// 如果右边值小,则min等于右边,右边向左移动
} else {
min = rvalue
rflag = true
}
for l < r {
// 如果是左边移动
if lfag {
non += height[l]
if height[l] > min {
lvalue = height[l]
area += lvalue
// 重新评估最小值
if lvalue <= rvalue {
// 如果左边还是小于右边,min赋值,继续移动
min = lvalue
l++
} else {
min = rvalue
// 左边停止移动
lfag = false
// 右边开始移动
rflag = true
r--
}
} else {
// 不大于min 则不评估,继续移动
area += min
l++
}
}
// 如果是右边移动,逻辑同上
if rflag {
non += height[r]
if height[r] > min {
rvalue = height[r]
area += rvalue
// 重新评估最小值
if rvalue < lvalue {
// 如果右边还是小于左边,min赋值,继续移动
min = rvalue
r--
} else {
min = lvalue
// 左边开始移动
lfag = true
// 右边停止移动
rflag = false
l++
}
} else {
area += min
r--
}
}
}
return area - non
}
代码虽然长,实际上简化为三步骤:
- 变量定义
- 初始化
- 左右指针移动
时间复杂度:O(n), 空间复杂度O(1)
运行结果
4.3-1143.最长公共子序列
题目描述
分析
考虑动态规划
- dp[i][j] 表示text1从0开始长度为i的子串,text2从0开始子串长度为j的公共子串长度
- 当 i=0, j=0时,其中一个字串长度为0,公共子串长度一定为0
- 当i,j >0 时,分析状态转移
- 如果 text1[i] = text2[j], 那么相较于dp[i][j]来说,公共字符增加一个
所以dp[i+1][j+1] = dp[i][j] + 1- 如果text1[i] != text2[j], 那么dp[i+1][j+1]可能来源于 dp[i][j+1] 或者 dp[i+1][j]
求解最长公共子串,选择两者最大值- 得到状态转移方程
text1[i]=text2[j]时, dp[i+1][j+1] = dp[i][j] + 1
text1[i]!=text2[j]时, dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
代码
func longestCommonSubsequence(text1 string, text2 string) int {
// 初始化
len1 := len(text1)
len2 := len(text2)
dp := make([][]int, len1+1)
for i := range dp {
dp[i] = make([]int, len2+1)
}
// 动态规划
// 注意这里的i,j 是text1和text2的索引
for i := 0; i < len1; i++ {
for j := 0; j < len2; j++ {
if text1[i] == text2[j] {
// text索引为i,j的时候,对应的从0开始的字串长度为i+1,j+1,
// 注意i,j区别
dp[i+1][j+1] = dp[i][j] + 1
} else {
dp[i+1][j+1] = max(dp[i][j+1], dp[i+1][j])
}
}
}
return dp[len1][len2]
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
运行结果
4.4-781.森林中的兔子
题目描述
分析
本题可以用例子来催动解题思路
- answer=[2,4,2,2,3,1,2,2]
- 由于题目要求的是最少拥有多少只,所以以最优情况去催动
- 先对answer排序 answer=[1,2,2,2,2,2,3,4]
- 循环遍历,第一个值1, 索引0 表示允许后面有1个值与他相同(因为要考虑最少值,所以认为相同的话,就说明他们互相说的包含彼此,是同一个颜色)
- 从0往后遍历,索引1与他不同,那么说明至少有两只是他描述的同一个颜色的,ret+=2
- 此时索引为1,值为2,表示后面最多允许有2个值与他相同
- 从1往后遍历,2, 3都为2,认为他们描述的是彼此。(注意,此时虽然后面还有2,但是由于最多允许2只,所以后面的认为是其他的颜色),ret += 3
- 此时索引为4,值为2,表示后面最多允许有2个值与他相同
- 往后遍历,索引6与他不同,ret+=3
9.依次往后,遍历索引6,7 , ret+=4, ret += 5- 最后返回ret,应该有 2+3+3+4+5 = 17只
测试结果
代码
func numRabbits(answers []int) int {
sort.Ints(answers)
ret := 0
cur := 0
count := 0
for i := 0; i < len(answers); {
// 当前的兔子说有cur只和他同颜色
cur = answers[i]
// 最多允许有count只兔子与他同颜色
count = cur
i++
// 如果后面的兔子和i是同颜色的,因为考虑最小值,认为他们的颜色相同的
for i < len(answers) && answers[i] == cur && count > 0 {
i++
// 可以允许的后面相同的兔子数-1
count--
}
// 跳出循环, 满足条件的最多兔子数是cur+1
ret += cur + 1
}
return ret
}
提交结果
其实这种方法还可以进行优化,比如当前的cur是4,表示有4只兔子和它颜色相同,那么就表示允许接下来有4个颜色与他相同,那么只需要比较第4个和他是否相同即可。 如果不同,那么找到第一个与他不同的值作为下一个值;如果相同,那么中间的都不用进行判断了。
读者可以自己进行优化。
4.5-88.合并两个有序数组
题目描述
分析
此题是简单题,属于插入排序+双指针的简单应用
直接上代码
代码
func merge(nums1 []int, m int, nums2 []int, n int) {
sorted := make([]int, 0, m+n)
p1, p2 := 0, 0
for {
if p1 == m {
sorted = append(sorted, nums2[p2:]...)
break
}
if p2 == n {
sorted = append(sorted, nums1[p1:]...)
break
}
if nums1[p1] < nums2[p2] {
sorted = append(sorted, nums1[p1])
p1++
} else {
sorted = append(sorted, nums2[p2])
p2++
}
}
copy(nums1, sorted)
}
提交结果
此题借助了额外的数组空间解题。如果不借助额外空间的话,我们也可以这样来操作:
代码
func merge(nums1 []int, m int, nums2 []int, n int) {
// 不使用新空间,现将nums1的元素值放到nums1后面,这样保证他的值不会被无效覆盖
for i := m - 1; i >= 0; i-- {
nums1[i+n] = nums1[i]
}
// 执行插入,nums1元素值从n开始,nums2从0开始
i, j:= n, 0
// 用来放值
index := 0
for {
// 说明nums2可能有剩余
if i == m+n {
// 将nums2后面的元素追加到nums1后面
for index < i {
nums1[index] = nums2[j]
j++
index++
}
break
}
// 说明nums1可能有剩余
if j == n {
// 由于一开始已经将nums1移到后面,不用重复添加
break
}
if nums1[i] < nums2[j] {
nums1[index] = nums1[i]
i++
} else {
nums1[index] = nums2[j]
j++
}
index++
}
}
提交结果
4.6-80.删除有序数组中的重复项Ⅱ
题目描述
分析
本题有两个条件:
- 数组已被排序
- 原地修改数组
原地修改数组的条件描述为:
- 不改变原来的数组结构,只改变数组元素值
- 比如数组[1,1,1,2,2,3]—>[1,1,2,2,3,3]因为不用改变后面的值,所以实际上nums被修改为1,1,2,2,3,3]
条件是最多连续两个值相同,所以只需要在增加的时候与当前放入值的倒数第二个值做比较即可。
代码
func removeDuplicates(nums []int) int {
// index用于元素新元素的放置
index := 0
for _, val := range nums {
// 当前值与当前值的当前加入的倒数第二个值相比较,如果不同,那么追加
if index < 2 || val != nums[index-2] {
nums[index] = val
index++
}
}
return index
}
提交结果