今天我们来解决一个有趣的数组跳跃问题。给定一个整数数组nums,对于任意起点索引i,我们可以按照特定规则在数组中移动:
我们的目标是找出从每个位置i出发,经过任意次合法移动后能够访问到的最大值。如果没有合法移动,则结果就是nums[i]本身。
以题目给出的示例nums = [2,1,3]为例:
最终结果为[2,2,3]。
这个问题看似简单,但直接暴力求解会非常低效。我们需要找到一些关键性质来优化:
基于以上观察,我们可以采用以下预处理策略:
首先我们创建preMax数组:
go复制n := len(nums)
preMax := make([]int, n)
preMax[0] = nums[0]
for i := 1; i < n; i++ {
preMax[i] = max(preMax[i-1], nums[i])
}
这个循环从左到右计算每个位置的前缀最大值。例如对于[2,1,3],preMax将是[2,2,3]。
接下来我们从右向左处理:
go复制sufMin := math.MaxInt
for i := n - 1; i >= 0; i-- {
if preMax[i] > sufMin {
preMax[i] = preMax[i+1]
}
sufMin = min(sufMin, nums[i])
}
这里的关键逻辑是:
需要注意几个边界条件:
算法进行了两次线性遍历:
除了输入数组外,我们只需要:
go复制package main
import (
"fmt"
"math"
)
func maxValue(nums []int) []int {
n := len(nums)
if n == 0 {
return nil
}
preMax := make([]int, n)
preMax[0] = nums[0]
for i := 1; i < n; i++ {
preMax[i] = max(preMax[i-1], nums[i])
}
sufMin := math.MaxInt
for i := n - 1; i >= 0; i-- {
if i < n-1 && preMax[i] > sufMin {
preMax[i] = preMax[i+1]
}
sufMin = min(sufMin, nums[i])
}
return preMax
}
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a < b {
return a
}
return b
}
func main() {
testCases := []struct {
nums []int
want []int
}{
{[]int{2, 1, 3}, []int{2, 2, 3}},
{[]int{5, 4, 3, 2, 1}, []int{5, 4, 3, 2, 1}},
{[]int{1, 2, 3, 4, 5}, []int{5, 5, 5, 5, 5}},
{[]int{3, 1, 4, 1, 5, 9, 2, 6}, []int{9, 9, 9, 9, 9, 9, 6, 6}},
{[]int{1}, []int{1}},
}
for _, tc := range testCases {
got := maxValue(tc.nums)
fmt.Printf("Input: %v\nExpected: %v\nGot: %v\n\n", tc.nums, tc.want, got)
}
}
为了验证算法的正确性,我们需要证明以下几点:
考虑一般情况,假设在位置i:
我们可以优化空间使用,注意到preMax数组可以复用nums数组(如果不介意修改输入):
go复制func maxValue(nums []int) []int {
n := len(nums)
if n == 0 {
return nil
}
// 复用nums数组作为preMax
for i := 1; i < n; i++ {
if nums[i] < nums[i-1] {
nums[i] = nums[i-1]
}
}
sufMin := math.MaxInt
for i := n - 1; i >= 0; i-- {
if i < n-1 && nums[i] > sufMin {
nums[i] = nums[i+1]
}
if nums[i] < sufMin {
sufMin = nums[i]
}
}
return nums
}
这个问题有几个有趣的变种:
这类跳跃游戏问题在实际中有多种应用:
在实现这类算法时,容易犯以下错误:
调试时可以:
这个问题可以进一步扩展:
这些扩展问题都值得深入思考和实践。