当青训营遇上码上掘金

125 阅读3分钟

当青训营遇上码上掘金

主题 3:寻友之旅

题目

小青要找小码去玩,他们的家在一条直线上,当前小青在地点 N ,小码在地点 K (0≤N , K≤100 000),并且小码在自己家原地不动等待小青。小青有两种交通方式可选:步行和公交。
步行:小青可以在一分钟内从任意节点 X 移动到节点 X-1 或 X+1
公交:小青可以在一分钟内从任意节点 X 移动到节点 2×X (公交不可以向后走)

请帮助小青通知小码,小青最快到达时间是多久?
输入: 两个整数 N 和 K
输出: 小青到小码家所需的最短时间(以分钟为单位)

思路

最短时间即最少的移动次数,数据范围在 0 到 1e5 之间,可以使用广度优先搜索 BFS 算法,结合 visited 数组存储某个位置是否已经被访问来减少重复计算次数。

BFS 队列使用自行封装的 Queue 结构体,最开始放入 N。之后不断从队列中取出元素,记为 X。若 X==K,则程序结束;否则就将可能到达的 X-1、X+1、2*X 三个位置加入队列,迭代上面的过程直到 X==K。

visited 数组大小应大于 2*K,数组元素初始化都为 false(N 为 true),每访问一个位置将对应的 visited 数组的值设为 true。

代码

源码:寻友之旅 - 码上掘金 (juejin.cn)

func minSteps(N, K int) int {
	if N == K {
		return 0
	}
	queue := &Queue{arr: []int{}}
	addNext := func(next int) {
		queue.Push(next)
		visited[next] = true
	}
	addNext(N)
	step := 1
	for !queue.IsEmpty() {
		length := queue.Length()
		for i := 0; i < length; i++ {
			now := queue.Pop()
			if now == K {
				return step
			}

			if now < K && !visited[now*2] {
				addNext(now * 2)
			}
			if now < K && !visited[now+1] {
				addNext(now + 1)
			}
			if now > 0 && !visited[now-1] {
				addNext(now - 1)
			}
		}
		step++
	}
	return -1
}

主题 4:攒青豆

题目

现有 n 个宽度为 1 的柱子,给出 n 个非负整数依次表示柱子的高度,排列后如下图所示,此时均匀从上空向下撒青豆,计算按此排列的柱子能接住多少青豆。(不考虑边角堆积)

攒青豆.png

以下为上图例子的解析:

输入:height = [5,0,2,1,4,0,1,0,3]  
输出:17  
解析:上面是由数组 [5,0,2,1,4,0,1,0,3] 表示的柱子高度,在这种情况下,可以接 17 个单位的青豆。

思路

经典题目“接雨水”,这里采用动态规划解法。

对于下标 iii,下雨后水能到达的最大高度等于下标 iii 两边的最大高度的最小值,下标 iii 处能接的雨水量等于下标 iii 处的水能到达的最大高度减去 height[i]

朴素的做法是对于数组 height 中的每个元素,分别向左和向右扫描并记录左边和右边的最大高度,然后计算每个下标位置能接的雨水量。假设数组 height 的长度为 n,该做法需要对每个下标位置使用 O(n) 的时间向两边扫描并得到最大高度,因此总时间复杂度是 O(n^2)

上述做法的时间复杂度较高是因为需要对每个下标位置都向两边扫描。如果已经知道每个位置两边的最大高度,则可以在 O(n) 的时间内得到能接的雨水总量。使用动态规划的方法,可以在 O(n) 的时间内预处理得到每个位置两边的最大高度。

创建两个长度为 n 的数组 leftMax 和 rightMax。对于 0≤ i <n, leftMax[i] 表示下标 iii 及其左边的位置中,height 的最大高度,rightMax[i] 表示下标 iii 及其右边的位置中,height 的最大高度。

显然,leftMax[0]=height[0],rightMax[n−1]=height[n−1],两个数组的其余元素的计算如下:

  • 当 1 ≤ i ≤ n−1 时,leftMax[i]=max⁡(leftMax[i−1], height[i]);
  • 当 0 ≤ i ≤ n−2 时,rightMax[i]=max⁡(rightMax[i+1], height[i])。

因此可以正向遍历数组 height 得到数组 leftMax 的每个元素值,反向遍历数组 height 得到数组 rightMax 的每个元素值。

在得到数组 leftMax 和 rightMax 的每个元素值之后,对于 0 ≤ i < n,下标 iii 处能接的雨水量等于 min⁡(leftMax[i], rightMax[i])−height[i]。遍历每个下标位置即可得到能接的雨水总量。

代码

源码:攒青豆 - 码上掘金 (juejin.cn)

func trap(height []int) int {
    n := len(height)
    if n == 0 {
        return 0
    }

    leftMax := make([]int, n)
    rightMax := make([]int, n)

    leftMax[0] = height[0]
    for i := 1; i < n; i++ {
        leftMax[i] = max(leftMax[i-1], height[i])
    }

    rightMax[n-1] = height[n-1]
    for i := n-2; i >= 0; i-- {
        rightMax[i] = max(rightMax[i+1], height[i])
    }

    ans := 0
    for i := 0; i < n; i++ {
        ans += min(leftMax[i], rightMax[i]) - height[i]
    }
    return ans
}