754. 到达终点数字 : 逐步剖析如何取得最小步数

本文介绍LeetCode上编号754的题目“到达终点数字”的解决方案,包括核心思路解析、示例演示及Java、TypeScript和Python代码实现。

题目描述

这是 LeetCode 上的 754. 到达终点数字 ,难度为 中等

Tag : 「数学」

在一根无限长的数轴上,你站在 0 的位置。终点在 target 的位置。

你可以做一些数量的移动 numMoves :

  • 每次你可以选择向左或向右移动。
  • i 次移动(从  i == 1 开始,到 i == numMoves),在选择的方向上走 i 步。

给定整数 target,返回 到达目标所需的 最小 移动次数(即最小 numMoves ) 。

示例 1:

输入: target = 2

输出: 3

解释:
第一次移动,从 0 到 1 。
第二次移动,从 1 到 -1 。
第三次移动,从 -1 到 2 。
复制代码

示例 2:

输入: target = 3

输出: 2

解释:
第一次移动,从 0 到 1 。
第二次移动,从 1 到 3 。
复制代码

提示:

  • −109 <=target<=109-10^9 <= target <= 10^9−109 <=target<=109
  • target!=0target != 0target!=0

数学

提示一:数轴上的任意点都以起点(000 点)对称,只需要考虑对称点的任意一边

由于题目没有限制我们「不能到达哪些点」以及「出发的起始方向」,因此以起点为中心的左右两边对称。

即:左边所能到达任意一个点,都能通过调整所达路径的方向来将终点调整到右边。

同时由于起点是一个特殊的位置 000 点,因此相应的「正数点」和「负数点」对称,我们仅需考虑一边(例如正数域)即可。

提示二:先往靠近 target 的方向移动,到达或越过 target 的时候则停止

只考虑 target 为正的情况,我们假定起始先往靠近 target 的方向移动(即所有步数均为正值),根据是「到达」还是「越过」target 位置分情况讨论:

  • 若能直接到达 target,此时消耗的必然是最小步数,可直接返回;
  • 若越过了 target,假设此时消耗的步数为 kkk,所走的距离为 dist=k×(k+1)2>targetdist = \frac{k \times (k + 1)}{2} > targetdist=2k×(k+1)​>target,我们可以考虑是否需要增加额外步数来到达 target

提示三:越过 target 时,如何不引入额外步数

若不引入额外步数,意味着我们需要将此前某些移动的方向进行翻转,使得调整后的 dist=targetdist = targetdist=target。

我们假设需要调整的步数总和为 tot,则有 dist−2×tot=targetdist - 2 \times tot = targetdist−2×tot=target,变形可得 tot=dist−target2tot = \frac{dist - target}{2}tot=2dist−target​。

若想满足上述性质,需要确保能找到这样的 tot,即 tot 合法,

不难推导出当 disttarget 差值为「偶数」时(两者奇偶性相同),我们可以找到这样的 tot,从而实现不引入额外步数来到达 target 位置。

由于我们的 distdistdist 是由数列 [1,2,3,...,k][1,2,3,...,k][1,2,3,...,k] 累加而来,因此必然能够在该数列 [1,2,3...k][1,2,3...k][1,2,3...k] 中通过「不重复选择某些数」来凑成任意一个小于等于 distdistdist 的数。

提示四:越过 target 时,如何尽量减少引入额外步数

disttarget 差值不为「偶数」时,我们只能通过引入额外步数(继续往右走)来使得,两者差值为偶数。

可以证明,最多引入步数不超过 444 步,可使用得两者奇偶性相同,即不超过 444 步可以覆盖到「奇数」和「偶数」两种情况。

根据 kkk 与 444 的余数关系分情况讨论:

  • 余数为 000,即 k=4Xk = 4Xk=4X,由于 dist=k(k+1)2=4X(4X+1)2=2X(4X+1)dist = \frac{k(k+1)}{2} = \frac{4X(4X+1)}{2} = 2X(4X+1)dist=2k(k+1)​=24X(4X+1)​=2X(4X+1),其中一数为偶数,distdistdist 为偶数;
  • 余数为 111,即 k=4X+1k = 4X + 1k=4X+1,由于 dist=k(k+1)2=(4X+1)(4X+2)2=(4X+1)(2X+1)dist = \frac{k(k+1)}{2} = \frac{(4X+1)(4X+2)}{2} = (4X+1)(2X+1)dist=2k(k+1)​=2(4X+1)(4X+2)​=(4X+1)(2X+1),两个奇数相乘为奇数,distdistdist 为奇数;
  • 余数为 222,即 k=4X+2k = 4X + 2k=4X+2,dist=k(k+1)2=(4X+2)(4X+3)2=(2X+1)(4X+3)dist = \frac{k(k+1)}{2} = \frac{(4X+2)(4X+3)}{2} = (2X+1)(4X+3)dist=2k(k+1)​=2(4X+2)(4X+3)​=(2X+1)(4X+3),两个奇数相乘为奇数,distdistdist 为奇数;
  • 余数为 333,即 k=4X+3k = 4X + 3k=4X+3,dist=k(k+1)2=(4X+3)(4X+4)2=(4X+3)(2X+2)dist = \frac{k(k+1)}{2} = \frac{(4X+3)(4X+4)}{2} = (4X+3)(2X+2)dist=2k(k+1)​=2(4X+3)(4X+4)​=(4X+3)(2X+2),其中一数为偶数,distdistdist 为偶数。

因此在越过 target 后,最多引入不超过 444 步可使得 disttarget 奇偶性相同。

提示五:如何不通过「遍历」或「二分」的方式找到一个合适的 k 值,再通过不超过 444 步的调整找到答案

我们期望找到一个合适的 k 值,使得 dist=k×(k+1)2<targetdist = \frac{k \times (k + 1)}{2} < targetdist=2k×(k+1)​<target,随后通过增加 k 值来找到答案。

利用求和公式 dist=k×(k+1)2dist = \frac{k \times (k + 1)}{2}dist=2k×(k+1)​,我们可以设定 k=⌊2×target)⌋k = \left \lfloor \sqrt{2 \times target}) \right \rfloork=⌊2×target​)⌋ 为起始值,随后逐步增大 k 值,直到满足「disttarget 奇偶性相同」。

Java 代码:

class Solution {
    public int reachNumber(int target) {
        if (target < 0) target = -target;
        int k = (int) Math.sqrt(2 * target), dist = k * (k + 1) / 2;
        while (dist < target || (dist - target) % 2 == 1) {
            k++;
            dist = k * (k + 1) / 2;
        }
        return k;
    }
}
复制代码

TypeScript 代码:

function reachNumber(target: number): number {
    if (target < 0) target = -target
    let k = Math.floor(Math.sqrt(2 * target)), dist = k * (k + 1) / 2
    while (dist < target || (dist - target) % 2 == 1) {
        k++
        dist = k * (k + 1) / 2
    }
    return k
}
复制代码

Python 代码:

class Solution:
    def reachNumber(self, target: int) -> int:
        if target < 0:
            target = -target
        k = int(math.sqrt(2 * target))
        dist = k * (k + 1) / 2
        while dist < target or (dist - target) % 2 == 1:
            k += 1
            dist = k * (k + 1) / 2
        return k
复制代码
  • 时间复杂度:O(1)O(1)O(1)
  • 空间复杂度:O(1)O(1)O(1)

最后

这是我们「刷穿 LeetCode」系列文章的第 No.754 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

PF 可视化结果沿着真实轨迹没到一半就没了,还有最后的精轨迹与真实轨迹也不符,最后一个直接和瞄anchor重合,请仔细分析代码是为什么,还有这个是运行时终端的数据,给你看看是否对分析有用=== 开始地磁匹配优化 === === 开始地磁匹配优化 === 粗匹配完成 (0.03s) ADE优化完成 (1.28s) 时间步 0: 执行重采样 (N_eff=250.9/800) 时间步 3: 执行重采样 (N_eff=199.6/800) 时间步 5: 执行重采样 (N_eff=277.5/800) 时间步 9: 执行重采样 (N_eff=268.7/800) 时间步 20: 执行重采样 (N_eff=241.0/800) 时间步 22: 执行重采样 (N_eff=166.5/800) 时间步 24: 执行重采样 (N_eff=187.5/800) 时间步 25: 执行重采样 (N_eff=196.5/800) 时间步 28: 执行重采样 (N_eff=175.7/800) 时间步 29: 执行重采样 (N_eff=262.9/800) 时间步 30: 执行重采样 (N_eff=291.2/800) 时间步 31: 执行重采样 (N_eff=216.7/800) 时间步 32: 执行重采样 (N_eff=29.9/800) 时间步 33: 执行重采样 (N_eff=145.6/800) 时间步 34: 执行重采样 (N_eff=67.2/800) 时间步 35: 执行重采样 (N_eff=250.1/800) 时间步 37: 执行重采样 (N_eff=261.6/800) 时间步 39: 执行重采样 (N_eff=315.8/800) 时间步 42: 执行重采样 (N_eff=319.7/800) 时间步 45: 执行重采样 (N_eff=254.8/800) 时间步 47: 执行重采样 (N_eff=242.7/800) 时间步 48: 执行重采样 (N_eff=309.8/800) 时间步 50: 执行重采样 (N_eff=144.4/800) 时间步 51: 执行重采样 (N_eff=156.0/800) 时间步 52: 执行重采样 (N_eff=95.6/800) E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 53 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 54 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 55 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 56 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 57 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 58 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") E:\网页下载\MagneticMatcher.py:334: UserWarning: 时间步 59 所有粒子失效,使用上一时刻最优粒子 warnings.warn(f"时间步 {t} 所有粒子失效,使用上一时刻最优粒子") 粒子滤波完成 (1.96s), 平均有效样本数: 395.9/800 d:\A\Lib\site-packages\sklearn\cluster\_kmeans.py:1429: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=4. warnings.warn( d:\A\Lib\site-packages\sklearn\base.py:1473: ConvergenceWarning: Number of distinct clusters (2) found smaller than n_clusters (8). Possibly due to duplicate points in X. return fit_method(estimator, *args, **kwargs) d:\A\Lib\site-packages\sklearn\cluster\_kmeans.py:1429: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=4. warnings.warn( d:\A\Lib\site-packages\sklearn\base.py:1473: ConvergenceWarning: Number of distinct clusters (2) found smaller than n_clusters (8). Possibly due to duplicate points in X. return fit_method(estimator, *args, **kwargs) d:\A\Lib\site-packages\sklearn\cluster\_kmeans.py:1429: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=4. warnings.warn( d:\A\Lib\site-packages\sklearn\cluster\_kmeans.py:1429: UserWarning: KMeans is known to have a memory leak on Windows with MKL, when there are less chunks than available threads. You can avoid it by setting the environment variable OMP_NUM_THREADS=4. warnings.warn( 禁忌搜索完成 (0.35s) 总耗时: 3.62s
07-24
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值