当青训营遇上码上掘金
这道题应该是一道比较典型的动态规划问题,不过要处理得到最终计算的边界需要动一些脑筋。
首先简要地复述一下题目的要求,就是一维空间上有两个位置N和K,他们的取值都在中,在位置N的人想通过一系列操作从位置N移动到位置K,他可以通过以下几种操作进行移动
- 从当前位置向前移动一个位置,即从X到X+1
- 从当前位置向后移动一个位置,即从X到X-1
- 从当前位置向后倍数移动位置,即从X到2*X(不能向后走)
这三种操作都是消耗一份单位时间,问从N开始出发最短经过几个单位时间可以到达K位置。
首先,我们需要分析题目中给出的已知信息。由于给出的信息较少,有些信息需要自己推断出来。第一点就是,如果给出的N和K的位置一致即那么不需要操作就可以返回。那么如果N在K的右侧呢?即的时候呢,由于只给出了一种向后移动的方式如果就只能一步步向后移动即,接下来就是最后一种情况也是需要考虑最多的可能,N在K的左侧即,这种情况下三种操作都可以进行,想要得到最优的操作次数就需要进行计算才能得到了。
有了上面分析出来的信息,可以比较直观的写出一个递归函数的雏形,但是这样明显是不正确的逻辑,因为这个递归函数没有终止条件,除了之外我们还需要一个在执行操作时的终止条件。根据已知条件和上面得出的信息可知当移动到负数区段时,只剩下一种可行操作那就是由于N的取值范围并不包含负数所以走到负数位置一定是绕路行为可以认为。有了这些递推公式和终止条件就可以通过递归函数和记忆返回值得方式得到结果。
当时这样的正向推导得到的递归函数不好转化成迭代方式,并且数据量大时很容易爆栈。因此,我选择的是从终点K的位置开始向前逆推操作,最后选择逆推到N位置的最小操作数就是需要的答案。记忆的值仍是从位置X到位置K所需要的最小操作数,确定需要记忆的范围,这里也需要一些推理,可以看出从位置N到K的最大操作数就是一步一步的前进到K,那么最大操作数就可以在开始时确定,那么如果操作时通过乘2操作到了K的右侧那么距离一定不能超过否则一定是绕路的选择,因此记忆的空间就可以确定为。确定了记忆的空间之后就可以通过之前得到的条件为记忆中的元素赋上初始值。然后再从右向左倒退设一个位置的上一个操作位置可能是哪里,并尝试更新记忆值为更小的操作数值。
// 使用从终点向前推的逻辑
for i := len(memo) - 1; i >= 0; i-- {
// 如果这个中间位置可以从前面的位置乘2到达
if i % 2 == 0 && memo[i / 2] > memo[i] + 1 {
memo[i / 2] = memo[i] + 1
}
// 这个中间位置可以从前面的位置加1到达
if i > 0 && memo[i - 1] > memo[i] + 1 {
memo[i - 1] = memo[i] + 1
}
}
具体的实现代码就放在码上掘金上。