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)。这样做的好处是,在每一步反向操作中,我们可以明确地知道上一步是从哪个点移动过来的,从而避免了正向搜索时可能出现的多种选择。
具体过程如下:
-
初始化与循环条件
- 算法从终点
(tx, ty)开始,初始化移动次数ans为0。 - 循环的继续条件是:只要当前点
(x, y)不等于起点(sx, sy),循环就会继续。在每次循环开始时,移动次数ans会加1,表示我们即将进行一步反向操作。
- 算法从终点
-
可行性检查
- 在每一步反向操作前,算法会检查当前点
(x, y)的坐标是否仍然大于或等于起点(sx, sy)的对应坐标(即x >= sx且y >= sy)。如果x < sx或y < sy,这意味着在反向移动的过程中,我们已经“越过”了起点,这是不可能的,因此直接返回-1,表示无法到达。
- 在每一步反向操作前,算法会检查当前点
-
处理特殊情况 (x == y)
- 当当前点的横纵坐标相等时(
x == y),这是一个特殊状态。根据移动规则,到达这个点的上一步,其较大值m必须等于当前的x(或y)。然而,从坐标值小于m的点通过增加m到达(x, y)的方式有多种可能。 - 为了简化处理并确保路径唯一性,算法在此设定了一个策略:如果起点
(sx, sy)的纵坐标sy大于0,则将当前横坐标x置为0;否则,将纵坐标y置为0。这个操作模拟了反向移动中,将坐标重置到一个基准状态,然后通过continue语句立即开始下一轮循环,以新的(x, y)值进行判断。
- 当当前点的横纵坐标相等时(
-
确保 x > y 以简化逻辑
- 由于移动规则依赖于
max(x, y),为了简化后续的判断,算法通过一个交换操作,确保在后续处理中,当前点的横坐标x总是大于纵坐标y。同时,起点坐标(sx, sy)也进行相应的交换,以保持逻辑一致性。
- 由于移动规则依赖于
-
主要反向操作
- 这是算法的核心部分,决定了如何从当前点
(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中减去纵坐标y(x = x - y)。这模拟了上一步的纵坐标增加操作的反向过程。
- 这是算法的核心部分,决定了如何从当前点
-
返回结果
- 当循环结束时,意味着当前点
(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;
}