[LeetCode] 660. Remove 9 移除9


 

Start from integer 1, remove any integer that contains 9 such as 9, 19, 29...

So now, you will have a new integer sequence: 1, 2, 3, 4, 5, 6, 7, 8, 10, 11, ...

Given a positive integer n, you need to return the n-th integer after removing. Note that 1 will be the first integer.

Example 1:

Input: 9
Output: 10

Hint: n will not exceed 9 x 10^8.

 

這道題讓我們移除所有包含數字9的數字,然后得到一個新的數列,給了一個數字n,求在這個新的數組中第n個數字。多寫些數字來看看:

0,1,2,3,4,5,6,7,8 (移除了9)

10,11,12,13,14,15,16,17,18 (移除了19)

.....

80,81,82,83,84,85,86,87,88 (移除了89)

(移除了 90 - 99 )

100,101,102,103,104,105,106,107,108 (移除了109)

可以發現,8的下一位就是10了,18的下一位是20,88的下一位是100,實際上這就是九進制的數字的規律,那么這道題就變成了將十進制數n轉為九進制數,這個就沒啥難度了,就每次對9取余,然后乘以 base,n每次自除以9,base 每次擴大10倍,參見代碼如下:

 

解法一:

class Solution {
public:
   int newInteger(int n) {
        long res = 0, base = 1;
        while (n > 0) {
            res += n % 9 * base;
            n /= 9;
            base *= 10;
        }
        return res;
   }
};

 

我們也可以寫的更簡潔一些,不用 base 變量,將結果 res 先當作字符串來處理,最后再轉回整型數,參見代碼如下:

 

解法二:

class Solution {
public:
   int newInteger(int n) {
        string res = "";
        while (n > 0) {
            res = to_string(n % 9) + res;
            n /= 9;
        }
        return stoi(res);
   }
};

 

將十進制數轉為九進制只能算 Easy 的題目,既然這道題標記了 Hard,我們就不應該只滿足於此。因為數字9是個特例,可以用上面的巧妙的解法,但如果要移除1到8中間的任意一個呢?上面的方法就不好使了,還是要來看看通用的解法。又來讀 fun4LeetCode 大神的 paper 了,這次大神收着寫的,不算太長,還是可以好好讀一讀的。這里不管是移出那個數字,新數組中的第n個數字的值m,都是要大於n本身的,將多出的數的個數用 f(1, m) 表示,則有:

 

m - f(1, m) = n

 

要求m的話,就要先求出 f(1, m) 的值,然后加上n的值,就能得到m了。這道題無法直接求出m的值,而是采用一種迭代逼近的方法來算m。最開始的時候,讓m為n,先求 f(1, n) 的值,比如說結果為k,然后再算 f(1, n + k) 的值,用得到的結果 k' 來更新k,再帶入算 f(1, n + k),直到 k == f(1, n + k) 為止,那么此時的 n + k 就是要求的m。

下面來看如何計算 f(1, m),當然不可能遍歷所有的數字,一位一位來查看有沒有要移除的數字了,太不高效了。再來看看開頭列舉的前 99 個數字中移除9后剩下的數字,統計一下,總共去掉了 19 個包含9的數字。那么想一下,如果前 99 個數字中要移除所有包含2的數字,會去掉多少個?其實還是 19 個,可以發現,前 99 個數字,不論去掉哪個數字,都會去掉 19 個數字。這是一個很重要的發現,再來看看這19個數是怎么分布的,首先每 10 個數都一定會包含一個要移除的數,比如要移除的是9,每 10 個數都會有一個9出現,而在 90 幾那一行,10 個數都會包含9,所以都要移除,那么可以總結出規律,非移除數開頭的其他9行,各移除1個,移除數開頭的 10 個都要移除,所以就有 10+9=19 個。好,那么這是前 99 個數的情況,那么前 999 個數又是什么情況呢?其實很類似,非移除數開頭的9行各有 19 個,移除數開頭的有 10x19 個,所以整個就是 19x19 個,所以 19 這個基數很重要。

好,下面來看看各位上的數字a跟要移除數d之間的關系。有三種關系,分別是小於,等於,大於:

1)當 a < d 時,比如說要移除的數字是6,那么a就是1到5中的數,我們知道,每 10 個數中只含有一個6,所以就要移除a個6就行了,如果a在百位上,就是是 a * 19 個,然后再加上下一位上移除的值,用等式來寫就是:

 

T(1, m) = a_i * (10^i - 9^i) + T(1, m % 10^i)

 

2)當 a = d 時,那么a此時為6,如果a是十位上的數,那么前面 [1, 59] 中的5個6要先移除掉,然后此時下一位有多少個數移除多個數,還要加上1。比如m如果是 63,那么 60, 61, 62, 63 這四個數要移除,怎么算的,通過 m%10 + 1 來計算,所以整個用等式來寫就是:

 

T(1, m) = a_i * (10^i - 9^i) + m % 10^i + 1

 

3)當 a > d 時,比如此時a為8,要移除的數字還是6,那么 [60, 69] 這 10 個數都要移除,那么實際上還要再移除7個6,分別是 [1,9], [10,19], [21,29], [31,39], [41,49], [51,59], [71,79] 這7個區間中的6,那么是怎么算的,通過 a - 1 來算,實際上是情況1的值再加上 10^i 個數,用等式來寫就是:

 

T(1, m) = (a_i - 1) * (10^i - 9^i) + 10^i + T(1, m % 10^i) = a_i * (10^i - 9^i) + 9^i + T(1, m % 10^i)

 

參見代碼如下:

 

解法三:

class Solution {
public:
    int newInteger(int n) {
        long d = 9, pre = 0, cur = 0;
        while (true) {
            pre = cur;
            cur = helper(n + cur, d);
            if (cur == pre) break;
        }
        return n + cur;
    }
    long helper(long m, long d) {
        long res = 0, p = 1, q = 1;
        for (long i = m; i >= 10; i /= 10) {
            p *= 10;
            q *= 9;
        }
        for (long i = m; i >= d; i %= p, p /= 10, q /= 9) {
            long a = i / p;
            res += a * (p - q);
            if (a == d) {
                res += i % p + 1; break;
            } else if (a > d) {
                res += q;
            }
        }
        return res;
    }
};

 

討論:對於移除任意數字的一般情況,熱心網友 majestyhao 給了一種更加簡便的方法,這種解法是基於解法一而來的,是說不論移除任何數字,都先當作是移除9,統統都先轉為九進制數,然后再對每一位上的數字做特殊處理,假如其大於等於要移除的數字,將就對應位上的數字加上個1,這里並不用擔心進位的問題,因為九進制的數字不會出現9,現在是將原來的九進制數當作十進制數來做加法。博主試了一些 test cases,貌似沒有什么問題,若各位看官大神們有不同意見的歡迎留言,代碼參見評論區十一樓。 

 

Github 同步地址:

https://github.com/grandyang/leetcode/issues/660

 

參考資料:

https://leetcode.com/problems/remove-9/

https://leetcode.com/problems/remove-9/discuss/106561/One-Line-Java-Solution

https://leetcode.com/problems/remove-9/discuss/106573/Alternative-solution-applicable-to-the-general-case

https://leetcode.com/problems/remove-9/discuss/106578/Share-my-O(logn)-C%2B%2B-solution-with-thinking-process-and-explanation

 

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


免責聲明!

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



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