前言
该系列文章用于我对一周中leetcode每日一题or其他不会的题的复盘总结。
一方面用于自己加深印象,另一方面也希望能对读者的算法能力有所帮助, 同时也希望能帮助同样坚持刷题的同学加深印象~
该复盘对我来说比较容易的题我会复盘的比较粗糙,反之较为细致
解答语言:Golang
周一:307. 区域和检索 - 数组可修改(middle)
题目描述:
给你一个数组 nums
,请你完成两类查询。
- 其中一类查询要求 更新 数组
nums
下标对应的值 - 另一类查询要求返回数组
nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
用整数数组nums
初始化对象void update(int index, int val)
将nums[index]
的值 更新 为val
int sumRange(int left, int right)
返回数组nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 (即,nums[left] + nums[left + 1], ..., nums[right]
)
示例 1:
输入:
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
输出:
[null, 9, null, 8]
解释:
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9
numArray.update(1, 2); // nums = [1,2,5]
numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8
复盘:
此题我最开始写的版本是update为O(1),range为O(n)
func (this *NumArray) Update(index int, val int) {
this.lis[index]=val
}
func (this *NumArray) SumRange(left int, right int) int {
ans:=0
for i:=left;i<=right;i++{
ans+=this.lis[i]
}
return ans
}
但是很遗憾超时了。
后续利用前缀和将update改为O(n),range改为O(1)
func (this *NumArray) Update(index int, val int) {
diff:=val-this.nums[index]
this.nums[index]=val
for i:=index+1;i<len(this.lis);i++{
this.lis[i]+=diff
}
}
func (this *NumArray) SumRange(left int, right int) int {
return this.lis[right+1]-this.lis[left]
}
过了,虽然是暴力解法。。。。
正确解决看官解是通过树状数组解答的,没学过哎。。
周二:1334. 阈值距离内邻居最少的城市(middle)
题目描述:
有 n
个城市,按从 0
到 n-1
编号。给你一个边数组 edges
,其中 edges[i] = [fromi, toi, weighti]
代表 fromi
和 toi
两个城市之间的双向加权边,距离阈值是一个整数 distanceThreshold
。
返回能通过某些路径到达其他城市数目最少、且路径距离 最大 为 distanceThreshold
的城市。如果有多个这样的城市,则返回编号最大的城市。
注意,连接城市 i 和 j 的路径的距离等于沿该路径的所有边的权重之和。
示例 1:
输入: n = 4, edges = [[0,1,3],[1,2,1],[1,3,4],[2,3,1]], distanceThreshold = 4
输出: 3
解释: 城市分布图如上。
每个城市阈值距离 distanceThreshold = 4 内的邻居城市分别是:
城市 0 -> [城市 1, 城市 2]
城市 1 -> [城市 0, 城市 2, 城市 3]
城市 2 -> [城市 0, 城市 1, 城市 3]
城市 3 -> [城市 1, 城市 2]
城市 0 和 3 在阈值距离 4 以内都有 2 个邻居城市,但是我们必须返回城市 3,因为它的编号最大。
复盘:
此题显然可以通过迪杰斯特拉算法算出每个节点距离其他所有节点最近的距离,然后算出距离小于distanceThreshold城市个数,维护最小的节点得出答案
代码:
func findTheCity(n int, edges [][]int, distanceThreshold int) int {
g := make([][]int, n) // 邻接表
dist := make([]int, n) // 起点到每个点的距离
vis := make([]bool, n) // 起点是否经过点的标记
const inf int = 1e7
for i := range g {
g[i] = make([]int, n)
for j := range g[i] {
g[i][j] = inf
}
}
for _, e := range edges { //构建邻接表
f, t, w := e[0], e[1], e[2]
g[f][t], g[t][f] = w, w
}
dijkstra := func(u int) (cnt int) { // 迪杰斯特拉算法,遍历n轮,每次选出距离u最近的节点,标记为已访问,
// 再根据此节点去更新别的节点距离
for i := range vis {
vis[i] = false
dist[i] = g[u][i]
}
dist[u] = 0
for i := 0; i < n; i++ {
k := -1
for j := 0; j < n; j++ {
if !vis[j] && (k == -1 || dist[j] < dist[k]) { // 找到距离u最近节点且未visited
k = j
}
}
vis[k] = true // 每轮循环找到距离u最近且未visted的节点再通过该节点去更新其他节点距离
for j := 0; j < n; j++ {
dist[j] = min(dist[j], dist[k]+g[k][j])
}
}
for _, d := range dist {
if d <= distanceThreshold {
cnt++
}
}
return
}
ans, cnt := n, inf
for i := n - 1; i >= 0; i-- {
if t := dijkstra(i); t < cnt {
cnt = t
ans = i
}
}
return ans
}
周三:2656. K 个元素的最大和(easy)
题目描述:
给你一个下标从 0 开始的整数数组 nums
和一个整数 k
。你需要执行以下操作 恰好 k
次,最大化你的得分:
- 从
nums
中选择一个元素m
。 - 将选中的元素
m
从数组中删除。 - 将新元素
m + 1
添加到数组中。 - 你的得分增加
m
。
请你返回执行以上操作恰好 k
次后的最大得分。
示例 1:
输入: nums = [1,2,3,4,5], k = 3
输出: 18
解释: 我们需要从 nums 中恰好选择 3 个元素并最大化得分。
第一次选择 5 。和为 5 ,nums = [1,2,3,4,6] 。
第二次选择 6 。和为 6 ,nums = [1,2,3,4,7] 。
第三次选择 7 。和为 5 + 6 + 7 = 18 ,nums = [1,2,3,4,8] 。
所以我们返回 18 。
18 是可以得到的最大答案。
复盘:
简单模拟题
代码:
func maximizeSum(nums []int, k int) int {
max:=0
for _,v:=range nums{
if v>max{
max=v
}
}
max_:=max+k-1
if k&1==0{
return (max+max_)>>1*(k)+(k>>1)
}else{
return (max+max_)>>1*k
}
}
周四:2760. 最长奇偶子数组(easy)
题目描述:
给你一个下标从 0 开始的整数数组 nums
和一个整数 threshold
。
请你从 nums
的子数组中找出以下标 l
开头、下标 r
结尾 (0 <= l <= r < nums.length)
且满足以下条件的 最长子数组 :
nums[l] % 2 == 0
- 对于范围
[l, r - 1]
内的所有下标i
,nums[i] % 2 != nums[i + 1] % 2
- 对于范围
[l, r]
内的所有下标i
,nums[i] <= threshold
以整数形式返回满足题目要求的最长子数组的长度。
注意:子数组 是数组中的一个连续非空元素序列。
示例 1:
输入: nums = [3,2,5,4], threshold = 5
输出: 3
解释: 在这个示例中,我们选择从 l = 1 开始、到 r = 3 结束的子数组 => [2,5,4] ,满足上述条件。
因此,答案就是这个子数组的长度 3 。可以证明 3 是满足题目要求的最大长度。
复盘:
依旧是简单模拟题
代码:
func longestAlternatingSubarray(nums []int, threshold int) int {
ans:=0
tmpAns:=0
i:=0
for i<len(nums){
if nums[i]&1==0&&nums[i]<=threshold{
tmpAns=1
i+=1
for i<len(nums)&&nums[i]<=threshold&&nums[i]&1!=nums[i-1]&1{
i+=1
tmpAns+=1
}
ans=max(ans,tmpAns)
}
if i<len(nums)&&(nums[i]&1==1||nums[i]>threshold){
i+=1
}
}
return ans
}
周五:2736. 最大和查询(hard)
题目描述:
给你两个长度为 n
、下标从 0 开始的整数数组 nums1
和 nums2
,另给你一个下标从 1 开始的二维数组 queries
,其中 queries[i] = [xi, yi]
。
对于第 i
个查询,在所有满足 nums1[j] >= xi
且 nums2[j] >= yi
的下标 j
(0 <= j < n)
中,找出 nums1[j] + nums2[j]
的 最大值 ,如果不存在满足条件的 j
则返回 -1 。
返回数组 **answer
, 其中 **answer[i]
**是第 i
个查询的答案。
示例 1:
输入: nums1 = [4,3,1,2], nums2 = [2,4,9,5], queries = [[4,1],[1,3],[2,5]]
输出: [6,10,7]
解释:
对于第 1 个查询:xi = 4 且 yi = 1 ,可以选择下标 j = 0 ,此时 nums1[j] >= 4 且 nums2[j] >= 1 。nums1[j] + nums2[j] 等于 6 ,可以证明 6 是可以获得的最大值。
对于第 2 个查询:xi = 1 且 yi = 3 ,可以选择下标 j = 2 ,此时 nums1[j] >= 1 且 nums2[j] >= 3 。nums1[j] + nums2[j] 等于 10 ,可以证明 10 是可以获得的最大值。
对于第 3 个查询:xi = 2 且 yi = 5 ,可以选择下标 j = 3 ,此时 nums1[j] >= 2 且 nums2[j] >= 5 。nums1[j] + nums2[j] 等于 7 ,可以证明 7 是可以获得的最大值。
因此,我们返回 [6,10,7] 。
复盘:
这个题想到了先将nums1与nums2合并,并通过nums1从大到小排序,因为显然nums1先满足的话,那nums2最大的哪一个就是最后答案,基于这个理念,我写出了两个超时版本。。。。
version1:
func maximumSumQueries(nums1 []int, nums2 []int, queries [][]int) []int {
// 预处理
//合并nums1与nums2,然后排序
nums:=make([][]int,len(nums1))
for i:=range nums1{
nums[i]=[]int{nums1[i],nums2[i]}
}
sort.Slice(nums,func(i,j int)bool{
return nums[i][0]+nums[i][1]>=nums[j][0]+nums[j][1]
})
ans:=make([]int,len(queries))
for i:=range ans{
ans[i]=-1
}
for i,query:=range queries{
x:=query[0]
y:=query[1]
for _,num:=range nums{
if num[0]>=x&&num[1]>=y{
ans[i]=num[0]+num[1]
break
}
}
}
return ans
}
version2:
go
func maximumSumQueries(nums1 []int, nums2 []int, queries [][]int) []int {
// 预处理
//合并nums1与nums2,然后排序
nums:=make([][]int,len(nums1))
for i:=range nums1{
nums[i]=[]int{nums1[i],nums2[i]}
}
sort.Slice(nums,func(i,j int)bool{
if nums[i][0]!=nums[j][0]{
return nums[i][0]>nums[j][0]
}else{
return nums[i][1]>nums[j][1]
}
})
ans:=make([]int,len(queries))
for i:=range ans{
ans[i]=-1
}
for i,query:=range queries{
x:=query[0]
y:=query[1]
Ans:=-1
for _,num:=range nums{
if num[0]>=x&&num[1]>=y{
Ans=max(Ans,num[0]+num[1])
}
if num[0]<x{
break
}
}
ans[i]=Ans
}
return ans
}
其实因为我看到只有11个用例没过,或许剪枝一下也许能过,比如我按照nums1排序,nums1相同再根据nuns2排序,那显然遇到满足nums1的第一个nums2就是答案,那么就可以跳过其他的nums1,但是依然没过。。。。
看了答案才知道人与人的差距这么大,这个题让我想几个小时我可能也想不出居然可以通过单调栈去维护答案,
这里直接引用灵神的便准解析:
代码:
func maximumSumQueries(nums1, nums2 []int, queries [][]int) []int {
type pair struct{ x, y int }
a := make([]pair, len(nums1))
for i, x := range nums1 {
a[i] = pair{x, nums2[i]}
}
slices.SortFunc(a, func(a, b pair) int { return cmp.Compare(b.x, a.x) })
qid := make([]int, len(queries))
for i := range qid {
qid[i] = i
}
slices.SortFunc(qid, func(i, j int) int { return cmp.Compare(queries[j][0], queries[i][0]) })
ans := make([]int, len(queries))
type data struct{ y, s int }
st := []data{}
j := 0
for _, i := range qid {
x, y := queries[i][0], queries[i][1]
for ; j < len(a) && a[j].x >= x; j++ { // 下面只需关心 a[j].y
for len(st) > 0 && st[len(st)-1].s <= a[j].x+a[j].y { // a[j].y >= st[len(st)-1].y,因为a[j].x只可能更小
st = st[:len(st)-1]
}
if len(st) == 0 || st[len(st)-1].y < a[j].y {
st = append(st, data{a[j].y, a[j].x + a[j].y})
}
}
p := sort.Search(len(st), func(i int) bool { return st[i].y >= y })
if p < len(st) {
ans[i] = st[p].s
} else {
ans[i] = -1
}
}
return ans
}
周六:2342. 数位和相等数对的最大和(middle)
题目描述:
给你一个下标从 0 开始的数组 nums
,数组中的元素都是 正 整数。请你选出两个下标 i
和 j
(i != j
),且 nums[i]
的数位和 与 nums[j]
的数位和相等。
请你找出所有满足条件的下标 i
和 j
,找出并返回 **nums[i] + nums[j]
**可以得到的 最大值 。
示例 1:
输入: nums = [18,43,36,13,7]
输出: 54
解释: 满足条件的数对 (i, j) 为:
- (0, 2) ,两个数字的数位和都是 9 ,相加得到 18 + 36 = 54 。
- (1, 4) ,两个数字的数位和都是 7 ,相加得到 43 + 7 = 50 。
所以可以获得的最大和是 54 。
复盘:
这个题比较容易,把两个数字位数和作为lis的索引,值为数字的值的最大值,然后维护最大答案即可
代码:
func maximumSum(nums []int) int {
d := [100]int{}
ans := -1
for _, v := range nums {
x := 0
for y := v; y > 0; y /= 10 {
x += y % 10
}
if d[x] > 0 {
ans = max(ans, d[x]+v)
}
d[x] = max(d[x], v)
}
return ans
}
周日:689. 三个无重叠子数组的最大和(hard)
题目描述:
给你一个整数数组 nums
和一个整数 k
,找出三个长度为 k
、互不重叠、且全部数字和(3 * k
项)最大的子数组,并返回这三个子数组。
以下标的数组形式返回结果,数组中的每一项分别指示每个子数组的起始位置(下标从 0 开始)。如果有多个结果,返回字典序最小的一个。
示例 1:
输入: nums = [1,2,1,2,6,7,5,1], k = 2
输出: [0,3,5]
解释: 子数组 [1, 2], [2, 6], [7, 5] 对应的起始下标为 [0, 3, 5]。
也可以取 [2, 1], 但是结果 [1, 3, 5] 在字典序上更大。
复盘:
感冒了🤧不太想做,🧠转不过来了。。看了解析还是比较清晰。维护maxSum1最大值,,再通过maxSum1去维护maxSum1+maxSum2最大值、再通过maxSum1+maxSum2维护maxSum1+maxSum2+maxSum3的最大值。通过滑动窗口更新三个状态最大值,窗口更新时维护答案即可
代码:
func maxSumOfThreeSubarrays(nums []int, k int) (ans []int) {
var sum1, maxSum1, maxSum1Idx int
var sum2, maxSum12, maxSum12Idx1, maxSum12Idx2 int
var sum3, maxTotal int
for i := k * 2; i < len(nums); i++ {
sum1 += nums[i-k*2]
sum2 += nums[i-k]
sum3 += nums[i]
if i >= k*3-1 {
if sum1 > maxSum1 {
maxSum1 = sum1
maxSum1Idx = i - k*3 + 1
}
if maxSum1+sum2 > maxSum12 {
maxSum12 = maxSum1 + sum2
maxSum12Idx1, maxSum12Idx2 = maxSum1Idx, i-k*2+1
}
if maxSum12+sum3 > maxTotal {
maxTotal = maxSum12 + sum3
ans = []int{maxSum12Idx1, maxSum12Idx2, i - k + 1}
}
sum1 -= nums[i-k*3+1]
sum2 -= nums[i-k*2+1]
sum3 -= nums[i-k+1]
}
}
return
}