第六届字节跳动青训营第一课 | 豆包MarsCode AI 刷题

103 阅读5分钟

计算从位置 x 到 y 的最少步数

问题描述

小F正在进行一个 AB 实验,需要从整数位置 x 移动到整数位置 y。每一步可以将当前位置增加或减少,且每步的增加或减少的值必须是连续的整数(即每步的移动范围是上一步的 -1+0 或 +1)。首末两步的步长必须是 1。求从 x 到 y 的最少步数。

输入描述

输入包含两个整数 x 和 y,表示起始位置和目标位置。

输出描述

输出从 x 到 y 所需的最小步数。


测试样例

样例1:

输入:x_position = 12, y_position = 6
输出:4

样例2:

输入:x_position = 34, y_position = 45
输出:6

样例3:

输入:x_position = 50, y_position = 30
输出:8

样例4:

输入:x_position = 0, y_position = 0
输出:0

问题分析: 这个问题的难点在于如何高效地计算出最少步数,同时满足步长的限制条件。由于步长必须是连续的整数,我们不能简单地使用绝对值差来计算步数,因为这样会忽略步长的限制。

解题思路:

  1. 假定 x < y:由于步数与起始和结束位置的顺序无关,我们可以假定 x < y,简化问题。
  2. 首末步长为 1:根据题意,首末两步的步长必须是 1。
  3. 寻找最大步长 k:假设我们走的步数中最大的数字为 k,整个序列必然存在如下数字:1 2 3 ... k k-1 k-2 ... 1。可以计算得这里的贡献为(1+2+..+k)+(1+2+...+k-1)。
  4. 等差数列求和:可以使用等差数列求和公式 f(x)=x2(1+x)f(x) = \frac{x}{2} (1 + x) 在O(1)的时间效率下计算出这部分的贡献,得到在给定步长下能够达到的最大位置,然后考虑剩下的数字。
  5. 处理剩余步数:比如上面的贡献是v,那么我们还剩下remain=y-x-v,如果remain<=k,显然我们一步把remain走完。如果remain>k,那么我们优先走k,直到走到remain<k。。

假设当前k=4,走的步数为:1,2,3,4,3,2,1,走完之后剩余remain=9,我们尽可能走大步,先走2个4,再走1个1,变成1 2 3 4 4 4 3 2 1 1 如果走完第一轮remain=2,因为remain<=k,我们可以直接走一个2步。变成 1 2 3 4 3 2 2 1

样例3:x_position = 50, y_position = 30

为方便理解,下面我们以样例3为例过一遍流程

步骤 1:确保 xPosition 小于等于 yPosition

由于 xPosition = 50 大于 yPosition = 30,我们需要交换它们:

int temp = xPosition;
xPosition = yPosition;
yPosition = temp;

交换后,xPosition = 30,yPosition = 50。

步骤 2:计算 yPosition 和 xPosition 之间的差值

差值为 yPosition - xPosition = 50 - 30 = 20。此时每次的步长为1

步骤 3:尝试不同的步长

我们需要找到一个最大的步长 k,使得序列的贡献尽可能大,同时不超过 20。

  1. 步长 i = 2

    • 计算贡献:f(2)+f(1)=4
    • 剩余需要移动的位置:remain = 20 - 4 = 16
    • 由于 4 < 16,计算此时步数 2 * i + remain / i - (remain % i == 0 ? 1 : 0) = 11 < ans,更新ans = 11。
  2. 步长 i = 3

    • 计算贡献:f(3)+f(2)=9
    • 剩余需要移动的位置:remain = 20 - 9 = 11
    • 由于 9 < 11,计算此时步数 2 * i + remain / i - (remain % i == 0 ? 1 : 0) = 9 < ans,更新ans = 9。
  3. 步长 i = 4

    • 计算贡献:f(4)+f(3)=16
    • 剩余需要移动的位置:remain = 20 - 16 = 4
    • 由于 16 >= 4,计算此时步数 2 * i + remain / i - (remain % i == 0 ? 1 : 0) = 8 < ans,更新ans = 8。
  4. 步长 i = 5

    • 计算贡献:f(5)+f(4)=25
    • 由于 25 > 20,这个步长过大,停止循环。

步骤 4:返回计算出的最小步数

在尝试的步长中,最小的步数是 8(步长为 4 时计算得出)。

因此,从位置 30 到 50 的最少步数是 8 步。

更新ans代码

ans = Math.min(ans, 2 * i + remain / i - (remain % i == 0 ? 1 : 0));

remain / i 计算贡献后还需要走步长为 i 的步数,2 * i - 1 是计算最大步长为 i 时的贡献后走的步数,这个 1 减法还是不减 ,还要看 remain % i ,如果 remain % i == 0 则在走完remain / i步后就已结束,加上2*i-1就是总步数,如最大步长为2时步长序列为:1 2 (2 2 2 2 2 2 2 2 ) 1 ,括号里的为计算完贡献后加上的步数,即remain / i;如果 remain % i ==0,则走完 remain / i步后仍未结束,还需要再走一步步长为 remain % i,如最大步长为3时,步长序列为:1 2 3 (3 3 3 2) 2 1。

代码详解:

public class Main {
    // 等差数列求和公式,计算前 x 个正整数的和
    public static int f(int x) {
        return (1 + x) * x / 2;
    }

    // 主函数,计算从 xPosition 到 yPosition 的最少步数
    public static int solution(int xPosition, int yPosition) {
        // 确保 xPosition 小于等于 yPosition
        if (xPosition > yPosition) {
            int temp = xPosition;
            xPosition = yPosition;
            yPosition = temp;
        }
        // 初始化最少步数为 yPosition 和 xPosition 之间的差值
        int ans = yPosition - xPosition;
        // 尝试不同的步长
        for (int i = 2; i < yPosition - xPosition; i++) {
            // 计算在步长 i 下能够达到的最大位置
            int v = f(i) + f(i - 1); // 1~i + (i-1)~1
            // 如果这个位置超过了目标位置,停止循环
            if (v > yPosition - xPosition) break;
            // 计算剩余需要移动的位置
            int remain = yPosition - xPosition - v;
            // 更新最少步数
            ans = Math.min(ans, 2 * i + remain / i - (remain % i == 0 ? 1 : 0));
        }
        return ans;
    }

    public static void main(String[] args) {
        // 测试用例
        System.out.println(solution(12, 6) == 4);
        System.out.println(solution(34, 45) == 6);
        System.out.println(solution(50, 30) == 8);
    }
}

实测通过 image.png