目錄

今天 (2/2) 的 Daily 是難度為 medium 的1291. Sequential Digits

題目分析

題目

An integer has sequential digits if and only if each digit in the number is one more than the previous digit.

我們定義「順子數」為:每一位上的數字都比前一位上的數字大1的整數。

Return a sorted list of all the integers in the range [low, high] inclusive that have sequential digits.

請你回傳由 [low, high] 範圍內所有順子數組成的有序清單(從小到大排序)。

Example 1:

Input: low = 100, high = 300
Output: [123,234]

Example 2:

Input: low = 1000, high = 13000
Output: [1234,2345,3456,4567,5678,6789,12345]

Constraints:

10 <= low <= high <= 10^9

分析

題目所要求的「順子數」(An integer has sequential digits)指:像123 4567 56789這種,相鄰位數均為遞增1的數列所構成的數。

根據測資的範圍(10<=),個位數不需考慮。

(也就是不限長度的順子啦)

所求為返回一個陣列,其中要包含所有介於[ low, high ]閉區間之內,且屬於「順子數」的整數,超過兩個數時,要以遞增排序。


解題思路

方案一、暴力解(Brute force)

其實在一開始舉例的時候就很容易發覺,題目所要求的順子數並沒有很多,甚至是可以直接快速人工列舉完畢的。

兩位數的「順子數」共有8個:

12 23 34 45 56 67 78 89

三位數的「順子數」共有7個:

123 234 345 456 567 678 789

依此類推,八位數與九位數的「順子數」分別有2和1個:

12345678 23456789

123456789

也就是,在題目測資所求範圍之內(10 <= low <= high <= 10^9),最多只會有8+7+6+…+2+1 = 36個「順子數」。

如果是在 Contest ,與其花費時間去考慮迴圈的邊界條件等等,直接窮舉所有9位數以內的順子數再用上下邊界去篩選,應該是不錯的選擇。

方案一實作 by Python3:

class Solution:
    def sequentialDigits(self, low: int, high: int) -> List[int]:
        nums = [
            12,23,34,45,56,67,78,89,
            123,234,345,456,567,678,789,
            1234,2345,3456,4567,5678,6789,
            12345,23456,34567,45678,56789,
            123456,234567,345678,456789,
            1234567,2345678,3456789,
            12345678,23456789,
            123456789
        ]
        return filter(lambda x: x>=low and x<=high, nums)

恩對樸實無華(X)

用filter或列表生成式把上下界篩出來就完事了。

方案二、迴圈法

如果是面試的話,或許暴力解無法彰顯我們的智慧(?),所以即使沒有必要,仍可以用迴圈來自動生成所有順子數。

迴圈的做法一樣有很多種,想炫技(?)的話可以拿牛刀殺雞,看是要遞迴、DP或者backtract都可以。不過這邊展示一下簡單的方法:

因為所有的「順子數」都包含在'123456789’ 這個最長的順子數之間,所以可以用「字串取子字串」的方式,去取得所有長度為2~9的順子數。

123456789
ij        12  從i=1開始
i j       123 讓j從2~9
...
i       j 123456879
---------
 ij       23  然後i=2
 ...          讓j從3~9
 i      j 23456789
---------
...       依此類推
---------
       ij 直到ij = 89

不難發現,具體的範圍是:

i 從 1 ~ 8

j 從 i+1 ~ 9

如此一般,做兩層的迴圈,就能自動化的產生在前述解法一中人工產生的所有「順子數」

需要留意的是,這樣產生的順序並沒有照數字大小排列。因此,在最後要重新排序。

對複雜度敏感的人可能會想到,我呼叫排序算法,時間複雜度會從原本的O(n)變成O(log n)耶。

不過這裡因為n=36,基本上可以不管他。

實際上,透過改變迴圈的寫法,也能讓產生出來的順序就恰為二位數>三位數>…>九位數。不過這並非本題的考點,就暫且省略吧。

方案二實作 by Python3:

class Solution:
    def sequentialDigits(self, low: int, high: int) -> List[int]:
        s = '123456789'
        res = []
        for i in range(8):
            for j in range(i+2,10):
                if int(s[i:j]) in range(low, high+1):
                    res.append(int(s[i:j]))
        res.sort()
        return res

Python3 一行解

如果是希望答案的行數越少越好的python廚(X),一定喜歡在easy題搞這種一行解:

return sorted(int('123456789'[i:j]) for i in range(8) for j in range(i+2,10) if int('123456789'[i:j])in range(low, high+1))

複雜度分析

假設n為測資範圍內順子數的總數:

方案一:時間、空間複雜度均為O(n)

  • 將所有順子數列出,故空間複雜度O(n)

  • 遍歷所有順子數判斷是否位於low~high區間,故時間複雜度為O(n)

方案二:時間複雜度為O(log n),空間複雜度為O(n)

  • 遍歷'123456789’的子字串的過程,恰好遍歷所有的順子數各一次,故時間複雜度為O(n)

    隨後的排序時間複雜度為O(log n)

    O(log n) + O(n) = O(log n)

  • 需要空間存放所有產生出來的順子數,故空間複雜度為O(n)

實際上本題因為n非常小(36),方案二不見得會比較慢。

不過也因為n很小,標準解的T接近常數時間,下列這種解法,應該就會導致TLE

從low遍歷到high,依序判斷這個數是否為「順子數」

因為測資範圍是10 ~ 10^9,遠大於前述解法的36筆數據。

可知本題的考點在於,並非「判斷是否為順子數」,而是「如何產生所有順子數」。


結語

這題是難度詐欺的 medium,會卡題應該是像最後一段所述,搞錯了題目的基本思路。

感謝你的閱讀,如果有不同的想法或心得,歡迎留言一同討論!