[英]Efficient algorithm to find the n-th digit in the string 112123123412345
什么是在以下字符串中查找第 n 個位置的數字的有效算法
112123123412345123456 ... 123456789101112 ...
將整個字符串存儲在內存中對於非常大的 n 是不可行的,所以我正在尋找一種算法,可以找到上述字符串中的第 n 個數字,如果 n 非常大(即只生成前 n 個數字的替代方法)字符串)。
這里有幾個層次:數字是數字 x 的一部分,數字 x 是序列 1,2,3...x...y 的一部分,該序列是導致像 y 這樣有 z 個數字的數字。 我們將一一處理這些級別。
有 1 個數字的 9 個數字:
first: 1 (sequence length: 1 * 1)
last: 9 (sequence length: 9 * 1)
average sequence length: (1 + 9) / 2 = 5
1-digit block length: 9 * 5 = 45
有 2 個數字的 90 個數字:
first: 10 (sequence length: 9 * 1 + 1 * 2)
last: 99 (sequence length: 9 * 1 + 90 * 2)
average sequence length: 9 + (2 + 180) / 2 = 100
2-digit block length: 90 * 100 = 9000
有 900 個 3 位數字:
first: 100 (sequence length: 9 * 1 + 90 * 2 + 1 * 3)
last: 999 (sequence length: 9 * 1 + 90 * 2 + 900 * 3)
average sequence length: 9 + 180 + (3 + 2,700) / 2 = 1,540.5
3-digit block length: 900 * 1,540.5 = 1,386,450
如果您繼續計算這些值,您將找到您要查找的數字位於哪個塊(最多多少個數字的序列)中,並且您將知道該塊的起點和終點。
假設你想要百萬分之一的數字。 您會發現它位於 3 位塊中,並且該塊位於總序列中:
start of 3-digit block: 45 + 9,000 + = 9,045
start of 4-digit block: 45 + 9,000 + 1,386,450 = 1,395,495
所以在這個塊中,我們正在尋找數字:
1,000,000 - 9,045 = 990,955
現在您可以使用例如二進制搜索來查找第 990,955 位數字所在的序列; 您從 3 位數字塊中的 3 位數字開始:
first: 100 (sequence length: 9 + 180 + 1 * 3)
number: 550 (sequence length: 9 + 180 + 550 * 3)
average sequence length: 9 + 180 + (3 + 1650) / 2 = 1,015.5
total sequence length: 550 * 1,015.5 = 558,525
哪個太小了; 所以我們嘗試 550 * 3/4 = 825,看看它是太小還是太大,然后以越來越小的步長上升或下降,直到我們知道第 990,995 位數字在哪個序列中。
假設它在數字 n 的序列中; 然后我們計算直到 n-1 的所有 3 位序列的總長度,這將為我們提供我們在數字 n 的序列中尋找的數字的位置。 然后我們可以用數字 9*1, 90*2, 900*3 ... 來找出數字在哪個數字,然后數字是什么。
好吧,您有一系列序列,每個序列增加一個數字。
如果你有“x”個,那么到那個點的序列占據x * (x + 1) / 2
字符位置。 或者,另一種說法是“x”序列從x * (x - 1) / 2
(假設從零開始索引)。 這些被稱為三角數。
因此,您需要做的就是找到累積量最接近給定“n”的“x”值。 以下是三種方式:
一旦您知道值所在的序列,確定該值只是一個算術問題。
我們希望能夠搜索三種類型的結構,(1)連接d
位數字的序列,例如單個數字:
123456...
或 3 位數字:
100101102103
(2) 節中的行,其中每個節都建立在前一節的基礎上添加一個前綴。 例如,第 1 節:
1
12
123
...
或第 3 節:
1234...10111213...100
1234...10111213...100102
1234...10111213...100102103
<----- prefix ----->
和 (3) 完整的部分,盡管后者我們可以列舉出來,因為它們呈指數增長並有助於構建我們的部分前綴。 對於(1),如果我們知道位數,我們可以使用簡單的除法; 對於(2),我們可以進行二分查找。
這里的 Python 代碼也回答了大問題:
def getGreatest(n, d, prefix):
rows = 9 * 10**(d - 1)
triangle = rows * (d + rows * d) // 2
l = 0
r = triangle
while l < r:
mid = l + ((r - l) >> 1)
triangle = mid * prefix + mid * (d + mid * d) // 2
prevTriangle = (mid-1) * prefix + (mid-1) * (d + (mid-1) * d) // 2
nextTriangle = (mid+1) * prefix + (mid+1) * (d + (mid+1) * d) // 2
if triangle >= n:
if prevTriangle < n:
return prevTriangle
else:
r = mid - 1
else:
if nextTriangle >= n:
return triangle
else:
l = mid
return l * prefix + l * (d + l * d) // 2
def solve(n):
debug = 1
d = 0
p = 0.1
prefixes = [0]
sections = [0]
while sections[d] < n:
d += 1
p *= 10
rows = int(9 * p)
triangle = rows * (d + rows * d) // 2
section = rows * prefixes[d-1] + triangle
sections.append(sections[d-1] + section)
prefixes.append(prefixes[d-1] + rows * d)
section = sections[d - 1]
if debug:
print("section: %s" % section)
n = n - section
rows = getGreatest(n, d, prefixes[d - 1])
if debug:
print("rows: %s" % rows)
n = n - rows
d = 1
while prefixes[d] < n:
d += 1;
if prefixes[d] == n:
return 9;
prefix = prefixes[d - 1]
if debug:
print("prefix: %s" % prefix)
n -= prefix
if debug:
print((n, d, prefixes, sections))
countDDigitNums = n // d
remainder = n % d
prev = 10**(d - 1) - 1
num = prev + countDDigitNums
if debug:
print("num: %s" % num)
if remainder:
return int(str(num + 1)[remainder - 1])
else:
s = str(num);
return int(s[len(s) - 1])
ns = [
1, # 1
2, # 1
3, # 2
100, # 1
2100, # 2
31000, # 2
999999999999999999, # 4
1000000000000000000, # 1
999999999999999993, # 7
]
for n in ns:
print(n)
print(solve(n))
print('')
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.