Leetcode每日一题-2021.04

Leetcode每日一题-2021.04

Leetcode刷题地址
此博文为个人学习笔记,记录2021年4月Leetcode每日一题刷题记录

4.1-1006.笨阶乘

题目描述

分析

本题其实是阶乘的一个变形,只是将原来的*,变成了*,/,+,-
核心在于怎么将这一个变化用代码进行表述
其实涉及到操作符,并且存在操作符优先级的问题,我们都可以考虑用栈操作
在这一题我选择用switch判定
本题注意点:

  1. 操作符有优先级,不能直接对返回值ret进行乘除加减,所以涉及到值的记录
  2. 操作符的表示

详情见代码注释
代码

// 看完代码注释之后,可以看下例:
// 如 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)空间复杂度

  1. 考虑使用双指针
  2. 我的基本思路,计算所有方块的面积non(即数组求和),计算方块+积水的面积area
  3. 最后使用返回area-non即可求得积水面积
  4. 所以本题的重点在于求解non和area
    1. non的求解就是对整个height数组求和,简单,无需过于探究
    2. 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
}

代码虽然长,实际上简化为三步骤:

  1. 变量定义
  2. 初始化
  3. 左右指针移动

时间复杂度:O(n), 空间复杂度O(1)

运行结果
在这里插入图片描述

4.3-1143.最长公共子序列

题目描述
在这里插入图片描述在这里插入图片描述
分析

考虑动态规划

  1. dp[i][j] 表示text1从0开始长度为i的子串,text2从0开始子串长度为j的公共子串长度
  2. 当 i=0, j=0时,其中一个字串长度为0,公共子串长度一定为0
  3. 当i,j >0 时,分析状态转移
    1. 如果 text1[i] = text2[j], 那么相较于dp[i][j]来说,公共字符增加一个
      所以dp[i+1][j+1] = dp[i][j] + 1
    2. 如果text1[i] != text2[j], 那么dp[i+1][j+1]可能来源于 dp[i][j+1] 或者 dp[i+1][j]
      求解最长公共子串,选择两者最大值
    3. 得到状态转移方程
      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.森林中的兔子

题目描述
在这里插入图片描述
分析

本题可以用例子来催动解题思路

  1. answer=[2,4,2,2,3,1,2,2]
    1. 由于题目要求的是最少拥有多少只,所以以最优情况去催动
    2. 先对answer排序 answer=[1,2,2,2,2,2,3,4]
    3. 循环遍历,第一个值1, 索引0 表示允许后面有1个值与他相同(因为要考虑最少值,所以认为相同的话,就说明他们互相说的包含彼此,是同一个颜色)
    4. 从0往后遍历,索引1与他不同,那么说明至少有两只是他描述的同一个颜色的,ret+=2
    5. 此时索引为1,值为2,表示后面最多允许有2个值与他相同
    6. 从1往后遍历,2, 3都为2,认为他们描述的是彼此。(注意,此时虽然后面还有2,但是由于最多允许2只,所以后面的认为是其他的颜色),ret += 3
    7. 此时索引为4,值为2,表示后面最多允许有2个值与他相同
    8. 往后遍历,索引6与他不同,ret+=3
      9.依次往后,遍历索引6,7 , ret+=4, ret += 5
    9. 最后返回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. 数组已被排序
  2. 原地修改数组

原地修改数组的条件描述为:

  1. 不改变原来的数组结构,只改变数组元素值
  2. 比如数组[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
}

提交结果
在这里插入图片描述

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值