[LeetCode] Reach a Number 達到一個數字


 

You are standing at position 0 on an infinite number line. There is a goal at position target.

On each move, you can either go left or right. During the n-th move (starting from 1), you take n steps.

Return the minimum number of steps required to reach the destination.

Example 1:

Input: target = 3
Output: 2
Explanation:
On the first move we step from 0 to 1.
On the second step we step from 1 to 3.

 

Example 2:

Input: target = 2
Output: 3
Explanation:
On the first move we step from 0 to 1.
On the second move we step  from 1 to -1.
On the third move we step from -1 to 2.

 

Note:

  • target will be a non-zero integer in the range [-10^9, 10^9].

 

這道題讓我們從起點0開始,每次可以向數軸的左右兩個方向中的任意一個走,第一步走距離1,第二步走距離2,以此類推,第n步走距離n,然后給了我們一個目標值 target,問我們最少用多少步可以到達這個值。博主分析了給的兩個例子后,開始想的是用貪婪算法來做,就是如果加上距離大於目標值的話,就減去這個距離,到是當目標值是4的話,貪婪算法會fail。一般貪婪算法不行的話,很自然的博主就會想想能否用DP來做,但這道題感覺很難很難重現為子問題,因為每一步走的步數都不一樣,這個步數又跟最小步數息息相關,所以很難寫出狀態轉移方程啊,只得放棄。后來博主嘗試用 BFS 來做,就是每次都把當前能到大的所有的點,都加上和減去當前距離,形成新的位置,加入數組中,當某個新的位置達到目標值時返回,但是這種解法會TLE,當目標值很大的話,相當的不高效。其實這道題的正確解法用到了些數學知識,還有一些小 trick,首先來說說小 trick,第一個 trick 是到達 target 和 -target 的步數相同,因為數軸是對稱的,只要將到達 target 的每步的距離都取反,就能到達 -target。下面來說第二個 trick,這個是解題的關鍵,比如說目標值是4,那么如果我們一直累加步數,直到其正好大於等於target時,有:

0 + 1 = 1

1 + 2 = 3

3 + 3 = 6

第三步加上3,得到了6,超過了目標值4,超過了的距離為2,是偶數,那么實際上我們只要將加上距離為1的時候,不加1,而是加 -1,那么此時累加和就損失了2,那么正好能到目標值4,如下:

0 - 1 = -1

-1 + 2 = 1

1 + 3 = 4

那么,我們的第二個 trick 就是,當超過目標值的差值d為偶數時,只要將第 d/2 步的距離取反,就能得到目標值,此時的步數即為到達目標值的步數。那么,如果d為奇數時,且當前為第n步,那么我們看下一步 n+1 的奇偶,如果 n+1 為奇數,那么加上 n+1 再做差,得到的差值就為偶數了,問題解決,如果 n+1 為偶數,那么還得加上 n+2 這個奇數,才能讓差值為偶數,這樣就多加了兩步。分析到這里,我們的解題思路也就明晰了吧:

我們先對 target 取絕對值,因為正負不影響最小步數。然后我們求出第n步,使得從1累加到n剛好大於等於 target,那么利用求和公式就有:

target = n * (n + 1) / 2

變成一元二次方程方程即為:

n^2 + n - 2*target = 0

用初中的一元二次方程的求和公式,就有:

n = (-1 + sqrt(1 + 8*target)) / 2

當然算出來可能不是整數,所以要取整,這里使用 ceil 來取整。如果此時 sum 和 target 正好相等,perfect!直接返回n,否則就是計算差值,如果差值時偶數,那么也直接返回n,如果是奇數,判斷此時n的奇偶,如果n是奇數,則返回 n+2,若n是偶數,返回 n+1,參見代碼如下:

 

解法一:

class Solution {
public:
    int reachNumber(int target) {
        target = abs(target);
        long n = ceil((-1.0 + sqrt(1 + 8.0 * target)) / 2);
        long sum = n * (n + 1) / 2;
        if (sum == target) return n;
        long res = sum - target;
        if ((res & 1) == 0) return n;
        return n + ((n & 1) ? 2 : 1);
    }
};

 

我們也可以不用求和公式來快速定位n,而是通過累加來做,res 為我們的當前步數,也是最終需要返回的結果,sum 是加上每步距離的累加值,如果 sum 小於 target,或者 sum 減去 target 的差值為奇數,我們進行循環,步數 res 自增1,然后 sum 加上步數 res,最后跳出循環的條件就是差值為偶數,也符合我們上的分析,參見代碼如下:

 

解法二:

class Solution {
public:
    int reachNumber(int target) {
        target = abs(target);
        int res = 0, sum = 0;
        while (sum < target || (sum - target) % 2 == 1) {
            ++res;
            sum += res;
        }
        return res;
    }
};

 

下面這種解法是解法一的精簡版,兩行搞定碉堡了!

 

解法三:

class Solution {
public:
    int reachNumber(int target) {
        int n = ceil((sqrt(1 + 8.0 * abs(target)) - 1) / 2), d = n * (n + 1) / 2 - target;
        return n + (d % 2) * (n % 2 + 1);
    }
};

 

參考資料:

https://leetcode.com/problems/reach-a-number/

https://leetcode.com/problems/reach-a-number/discuss/112969/C++-O(1)-Solution-without-loop

https://leetcode.com/problems/reach-a-number/discuss/112992/Learn-from-other-with-my-explanations

https://leetcode.com/problems/reach-a-number/discuss/112982/2-liner-a-math-problem-with-explanation

 

LeetCode All in One 題目講解匯總(持續更新中...)


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM