Java&C++题解与拓展——leetcode754.到达终点数字【数学推导】

185 阅读2分钟
每日一题做题记录,参考官方和三叶的题解

题目要求

image.png

image.png

思路:数学推导

  • 因为区间的对称,所以负数和正数的处理是完全相同的,所以就全当正数算;
  • 目的是找最少的移动次数nmnm,那么可以粗暴地直接一直向着正方向走到最靠近targettarget的位置,此时有两种情况:
    • 刚好到达,那么很快乐直接得到了答案;
    • 超了,又出现了两种solution:
      • 只需要把其中一步改成反方向,就可以刚好到达;
      • 需要再增加几步,然后改一步为反方向,才可以到达;
      • 只需翻转一步,翻多了说明走多了,不是最优解。
  • 那么,翻转一步就可以刚好到达的情况满足什么条件呢?
    • 持续正向移动nmnm次走过的距离为dis=nm×(nm+1)2dis=\frac{nm\times(nm+1)}{2}
    • disdis超过了两步时,翻转第一步即可;超过四步时,翻转第二步即可……发现超过偶数步,翻转其砍半的那一步即可,所以刚好到达的情况满足(distarget)%2=0(dis-target)\%2=0
  • 由于刚好到达满足distarget=0dis-target=0也能够整除22,所以题目目标可以改为找到最小的满足(nm×(nm+1)2target)%2=0(\frac{nm\times(nm+1)}{2}-target)\%2=0nmnm
  • 所以程序编写思路就是从最靠近目标的那一步nmbegin=2×targetnm_{begin}=\lfloor\sqrt{2\times target}\rfloor开始判断:
    • 能否直接到?
    • 能否翻转一步到?
    • 能否加一步翻转一步到?
    • 能否加两步翻转一步到?
    • ……
  • 下一步考虑时间复杂度,加几步影响整体时间复杂度,所以看看最多需要加几步;
    • 加步数目的是保证disdistargettarget的差值为偶数,也就是保证disdistargettarget的奇偶一致,也就是通过nmnm影响disdis的奇偶;
    • 证明发现加两步以内可以保证disdis的奇偶被改变【具体是怎么正向推的不知道,但可以来一段不严谨的反向证明】;
    • 因为假设别的都不能判断奇偶,所以假设:
      • nm=4xnm=4x,此时dis=4x×(4x+1)2=2x×(4x+1)dis=\frac{4x\times(4x+1)}{2}=2x\times(4x+1),由于2x2x为偶数,所以disdis为偶数;
      • nm=4x+1nm=4x+1,此时dis=(4x+1)×(4x+2)2=(4x+1)×(2x+1)dis=\frac{(4x+1)\times(4x+2)}{2}=(4x+1)\times(2x+1),由于二者均为奇数,所以disdis为奇数;
      • nm=4x+2nm=4x+2,此时dis=(4x+2)×(4x+3)2=(2x+1)×(4x+3)dis=\frac{(4x+2)\times(4x+3)}{2}=(2x+1)\times(4x+3),由于二者均为奇数,所以disdis为奇数;
      • nm=4x+3nm=4x+3,此时dis=(4x+3)×(4x+4)2=(4x+3)×(2x+2)dis=\frac{(4x+3)\times(4x+4)}{2}=(4x+3)\times(2x+2),由于2x+22x+2为偶数,所以disdis为偶数;
      • 发现在两步范围内就必然可以翻转disdis的奇偶性。

Java

class Solution {
    public int reachNumber(int target) {
        if (target < 0)
            target = -target;
        int numMoves = (int) Math.sqrt(2 * target), dis = numMoves * (numMoves + 1) / 2;
        while (dis < target || (dis - target) % 2 == 1) {
            numMoves++;
            dis = numMoves * (numMoves + 1) / 2;
        }
        return numMoves;
    }
}
  • 时间复杂度:O(1)O(1)while最多两轮
  • 空间复杂度:O(1)O(1)

C++

class Solution {
public:
    int reachNumber(int target) {
        if (target < 0)
            target = -target;
        int numMoves = (int) sqrt(2 * target), dis = numMoves * (numMoves + 1) / 2;
        while (dis < target || (dis - target) % 2 == 1) {
            numMoves++;
            dis = numMoves * (numMoves + 1) / 2;
        }
        return numMoves;
    }
};
  • 时间复杂度:O(1)O(1)while最多两轮
  • 空间复杂度:O(1)O(1)

Rust

impl Solution {
    pub fn reach_number(target: i32) -> i32 {
        let mut tar = target.abs();        
        let mut numMoves = ((2 * tar) as f64).sqrt() as i32;
        let mut dis = numMoves * (numMoves + 1) / 2;
        while (dis < tar || (dis - tar) % 2 == 1) {
            numMoves += 1;
            dis = numMoves * (numMoves + 1) / 2;
        }
        numMoves
    }
}
  • 时间复杂度:O(1)O(1)while最多两轮
  • 空间复杂度:O(1)O(1)

总结

  • 开心找规律;
  • 想到了和这个差不多的解法,不过是从头开始找,时间复杂度会高一些到O(log(nm))O(\log(nm))

欢迎指正与讨论!