2025-12-20:到达目标点的最小移动次数。用go语言,给出四个整数 sx、sy、tx、ty,表示无限二维格子上的起点 (sx, sy) 和终点 (tx,

35 阅读7分钟

2025-12-20:到达目标点的最小移动次数。用go语言,给出四个整数 sx、sy、tx、ty,表示无限二维格子上的起点 (sx, sy) 和终点 (tx, ty)。在任意格点 (x, y) 处,令 m 为 x 与 y 中的较大者。每一步可以选择将当前点的横坐标增加 m(到 (x+m, y)),或者将纵坐标增加 m(到 (x, y+m))。要求计算从起点精确到达终点所需的最少步数;如果无法到达,则返回 -1。

0 <= sx <= tx <= 1000000000。

0 <= sy <= ty <= 1000000000。

输入: sx = 1, sy = 2, tx = 5, ty = 4。

输出: 2。

解释:

最优路径如下:

移动 1:max(1, 2) = 2。增加 y 坐标 2,从 (1, 2) 移动到 (1, 2 + 2) = (1, 4)。

移动 2:max(1, 4) = 4。增加 x 坐标 4,从 (1, 4) 移动到 (1 + 4, 4) = (5, 4)。

因此,到达 (5, 4) 的最小移动次数是 2。

题目来自力扣3609。

算法步骤详解

该算法采用了一种逆向思维,即从目标点 (tx, ty) 开始,逐步反向操作,试图回到起点 (sx, sy)。这样做的好处是,在每一步反向操作中,我们可以明确地知道上一步是从哪个点移动过来的,从而避免了正向搜索时可能出现的多种选择。

具体过程如下:

  1. 初始化与循环条件

    • 算法从终点 (tx, ty) 开始,初始化移动次数 ans 为0。
    • 循环的继续条件是:只要当前点 (x, y) 不等于起点 (sx, sy),循环就会继续。在每次循环开始时,移动次数 ans 会加1,表示我们即将进行一步反向操作。
  2. 可行性检查

    • 在每一步反向操作前,算法会检查当前点 (x, y) 的坐标是否仍然大于或等于起点 (sx, sy) 的对应坐标(即 x >= sxy >= sy)。如果 x < sxy < sy,这意味着在反向移动的过程中,我们已经“越过”了起点,这是不可能的,因此直接返回 -1,表示无法到达。
  3. 处理特殊情况 (x == y)

    • 当当前点的横纵坐标相等时(x == y),这是一个特殊状态。根据移动规则,到达这个点的上一步,其较大值 m 必须等于当前的 x(或 y)。然而,从坐标值小于 m 的点通过增加 m 到达 (x, y) 的方式有多种可能。
    • 为了简化处理并确保路径唯一性,算法在此设定了一个策略:如果起点 (sx, sy) 的纵坐标 sy 大于0,则将当前横坐标 x 置为0;否则,将纵坐标 y 置为0。这个操作模拟了反向移动中,将坐标重置到一个基准状态,然后通过 continue 语句立即开始下一轮循环,以新的 (x, y) 值进行判断。
  4. 确保 x > y 以简化逻辑

    • 由于移动规则依赖于 max(x, y),为了简化后续的判断,算法通过一个交换操作,确保在后续处理中,当前点的横坐标 x 总是大于纵坐标 y。同时,起点坐标 (sx, sy) 也进行相应的交换,以保持逻辑一致性。
  5. 主要反向操作

    • 这是算法的核心部分,决定了如何从当前点 (x, y) 反向推算出上一个点。
    • 情况一 (x > y * 2):如果横坐标 x 远大于纵坐标 y(具体条件是大于 y 的两倍),那么可以推断,上一步很可能是通过增加横坐标 m(也就是当时的 max(x, y),在当前状态下这个最大值就是 x 本身)到达当前点的。因此,反向操作就是将当前横坐标 x 除以2(x = x / 2)。在执行此操作前,会检查 x 是否为偶数,因为只有偶数才能被2整除并得到一个整数坐标,否则返回 -1
    • 情况二 (x <= y * 2):如果 x 没有远大于 y,那么上一步操作很可能是通过增加纵坐标 m 到达当前点的,但根据规则,m 是上一步两点中的较大值。在这种情况下,更合理的反向操作是从当前点 (x, y) 的横坐标 x 中减去纵坐标 yx = x - y)。这模拟了上一步的纵坐标增加操作的反向过程。
  6. 返回结果

    • 当循环结束时,意味着当前点 (x, y) 已经与起点 (sx, sy) 重合。此时,变量 ans 记录的就是从终点反向走回起点所需的步数,这正等同于从起点走到终点的最小移动次数,将其返回即可。

复杂度分析

  • 总的时间复杂度:该算法的时间复杂度主要取决于从终点 (tx, ty) 反向移动回起点 (sx, sy) 所需的步数。在最坏情况下,例如当 ty 远大于 tx 时,反向操作可能主要执行减法(x = x - y),这类似于欧几里得算法,其时间复杂度可以近似为 O(log(min(tx, ty)))。这是一种非常高效的时间复杂度。
  • 总的额外空间复杂度:算法只使用了固定数量的整数变量(如 sx, sy, x, y, ans),没有使用任何随着输入规模增长而增长的辅助数据结构(如队列、栈或数组)。因此,其额外空间复杂度是 O(1),即常数空间复杂度。

Go完整代码如下:

package main

import (
	"fmt"
)

func minMoves(sx, sy, x, y int) (ans int) {
	for ; x != sx || y != sy; ans++ {
		if x < sx || y < sy {
			return -1
		}
		if x == y {
			if sy > 0 {
				x = 0
			} else {
				y = 0
			}
			continue
		}
		// 保证 x > y
		if x < y {
			x, y = y, x
			sx, sy = sy, sx
		}
		if x > y*2 {
			if x%2 > 0 {
				return -1
			}
			x /= 2
		} else {
			x -= y
		}
	}
	return
}

func main() {
	sx := 1
	sy := 2
	tx := 5
	ty := 4
	result := minMoves(sx, sy, tx, ty)
	fmt.Println(result)
}

在这里插入图片描述

Python完整代码如下:

# -*-coding:utf-8-*-

def min_moves(sx: int, sy: int, x: int, y: int) -> int:
    """
    计算从起点(sx, sy)到目标点(x, y)的最小移动步数
    
    移动规则(从目标反向回溯到起点):
    1. 如果x == y,重置其中一个坐标为0
    2. 否则,如果x > 2*y,将x减半(x必须为偶数)
    3. 否则,x减去y
    4. 如果x < sx或y < sy,说明无法到达
    
    参数:
        sx, sy: 起点坐标
        x, y: 目标点坐标
        
    返回:
        最小移动步数,如果无法到达则返回-1
    """
    ans = 0
    while x != sx or y != sy:
        # 如果已经小于起点坐标,说明无法到达
        if x < sx or y < sy:
            return -1
            
        # 规则1: 当x和y相等时,重置其中一个坐标
        if x == y:
            if sy > 0:
                x = 0
            else:
                y = 0
            ans += 1
            continue
            
        # 保证x > y以简化处理
        if x < y:
            x, y = y, x
            sx, sy = sy, sx
            
        # 规则2: 当x远大于y时,尝试减半操作
        if x > 2 * y:
            # 如果x是奇数且不能直接减半,则无法到达
            if x % 2 > 0:
                return -1
            x //= 2
        else:
            # 规则3: 否则减去y
            x -= y
            
        ans += 1
        
    return ans


def main():
    # 测试用例
    sx, sy = 1, 2
    tx, ty = 5, 4
    
    result = min_moves(sx, sy, tx, ty)
    print(f"从({sx}, {sy})到({tx}, {ty})的最小步数: {result}")
    
    # 更多测试用例
    test_cases = [
        (1, 2, 5, 4),  # 原测试用例
        (1, 1, 3, 5),  # 其他测试
        (0, 0, 8, 4),
        (2, 3, 2, 3),  # 起点和终点相同
        (1, 1, 1, 3),  # 可能无法到达的情况
    ]
    
    print("\n更多测试结果:")
    for sx, sy, tx, ty in test_cases:
        result = min_moves(sx, sy, tx, ty)
        print(f"从({sx}, {sy})到({tx}, {ty}): {result}")


if __name__ == "__main__":
    main()

在这里插入图片描述

C++完整代码如下:

#include <iostream>
using namespace std;

/**
 * 计算从起点(sx, sy)到目标点(tx, ty)的最小移动步数
 *
 * 移动规则(从目标点反向回溯到起点):
 * 1. 如果x == y,重置其中一个坐标为0
 * 2. 否则,如果x > 2*y,将x减半(x必须为偶数)
 * 3. 否则,x减去y
 * 4. 如果x < sx或y < sy,说明无法到达
 *
 * @param sx 起点x坐标
 * @param sy 起点y坐标
 * @param tx 目标点x坐标
 * @param ty 目标点y坐标
 * @return 最小移动步数,如果无法到达则返回-1
 */
int minMoves(int sx, int sy, int tx, int ty) {
    int ans = 0;
    int x = tx, y = ty;

    // 反向模拟,从目标点向起点回溯
    while (x != sx || y != sy) {
        // 如果当前坐标已经小于起点,无法到达
        if (x < sx || y < sy) {
            return -1;
        }

        // 规则1: 当x和y相等时
        if (x == y) {
            if (sy > 0) {
                x = 0;
            } else {
                y = 0;
            }
            ans++;
            continue;
        }

        // 保证x > y以简化后续判断
        if (x < y) {
            swap(x, y);
            swap(sx, sy);
        }

        // 规则2: 当x远大于y时尝试减半
        if (x > 2 * y) {
            // x必须是偶数才能减半
            if (x % 2 > 0) {
                return -1;
            }
            x /= 2;
        }
        // 规则3: 否则减去y
        else {
            x -= y;
        }

        ans++;
    }

    return ans;
}

int main() {
    int sx = 1, sy = 2;
    int tx = 5, ty = 4;
    int result = minMoves(sx, sy, tx, ty);
    cout << result << endl;
    return 0;
}

在这里插入图片描述