簡體   English   中英

如何添加,將n個數字乘以數組中的給定范圍,並在O(n)時間內反轉數組中的范圍?

[英]How to add, multiply n numbers to a given range in array and also reverse the range in the array in O(n) time?

假設我們有一個數組L [] = {4,1,2,6}。 我們需要做的是將一個由字符A,R,M組成的字符串S作為輸入,並應用以下算法:

for i from 1 to N do 

    if ith letter of S is 'R'
        reverse L[i...N]
    else if ith letter of S is 'A'
        add A to all numbers of L[i..N].
    else if ith letter of S is 'M'
        multiply B to all numbers of L[i..N].

    for all number in L[i..N], module them by C.
print L[i]

end

如何優化此算法以使其在O(n)時間內運行? 數組的長度以及字符串是n。 無論如何,在這個算法中的任何優化(如刪除循環以僅添加和乘法)都將受到歡迎。

這個答案很長,但是我已經用相當多的解釋以一種相當容易理解的方式寫了它,所以請跟我說一下。

假設AB都是整數,可以在O(N)時間內解決這個問題。 否則你可以在此時停止閱讀。 但我認為有必要將算法分解為幾個不同的步驟,每個步驟都是O(N) 所以它仍然是O(N)整體。

可能問題中最困難的部分是弄清楚如何在線性時間內運行此步驟:

    if ith letter of S is 'R'
        reverse L[i...N]

如果我們只是繼續盯着原始算法,我們將確信即使其他步驟可以在線性時間內實現,這個步驟也永遠不能在線性時間內完成。 但事實並非如此。 我們該怎么做呢? 我想到的方法是從雙端隊列/雙端隊列數據結構中借用一個想法。 由於我們知道數組L有多長,我們只保留3個變量, leftmostrightmostisReversed

  • leftmost將保持L數組當前最左邊未使用的索引,因此leftmost的初始化為1 ,因為我們正在使用一個索引數組,如你的問題中所述(術語'unused'將在后面解釋)。
  • rightmost將保持L數組當前最右邊未使用的索引,因此初始化為N ,即L的長度。
  • isReversed用於指示陣列是否正在反轉。 這被初始化為false

我們手頭的第一個任務是在應用所有reverse操作后計算出數組L的原始元素的最終順序。 我們甚至不需要反轉陣列一次以達到與反轉相同的效果。 這可以通過遍歷輸入串S一次,並且在所有反向操作之后確定陣列L哪個元素應該在每個位置來完成。 為簡單起見,我們創建了一個新的整數數組L'將舉行最后原創要素L將所有的反向操作之后,並試圖填補L'

假設我們在索引i ,並且S[i] == 'R' ,所以我們設置isReversed = true表示我們正在反轉子陣列[i..N] isReversed == true ,我們知道子陣列[i..N]正在被反轉,因此L'[i]處的元素應該是最右邊的未使用元素,其索引rightmost 因此我們設置L'[i] = L[rightmost] ,並且rightmost 1rightmost = rightmost - 1 )。 相反,如果isReversed == false我們不會反轉子[i..N] ,因此L'[i]處的元素應該是最左邊的未使用元素,其索引leftmost 因此,我們設置L'[i] = L[leftmost] ,和增量 leftmost1leftmost = leftmost - 1 )。 后續reverse將否定isReversed的值。

因此,當前的算法在C ++中看起來像這樣(我假設你對C ++沒問題,因為你的一個問題的標簽是C ++):

// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;

for (int i = 1; i <= N; i++) {
    if (S[i] == 'R') {
        // negate isReversed
        isReversed = !isReversed;
    }

    if (isReversed) {
        Lprime[i] = L[rightmost];
        rightmost = rightmost - 1;
    } else {
        Lprime[i] = L[leftmost];
        leftmost = leftmost + 1;
    }
}

確認這是正確的,雖然我認為是這樣。

現在我們來看看原始算法的其余部分:

    else if ith letter of S is 'A'
        add A to all numbers of L[i..N].
    else if ith letter of S is 'M'
        multiply B to all numbers of L[i..N].

    for all number in L[i..N], module them by C.

困難的部分似乎是需要在子陣列[i..N]為每個索引i執行C模數。 但基於我有限的理解,這是模運算,我們並不需要在每個i子陣列[i..N]上執行它。 但是不要相信我的話。 我對數論的理解非常簡陋。

不僅如此,還可以簡化添加和乘法的步驟。 這里的技巧是保留2個額外的變量,讓我們稱它們為multiplicativeFactoradditiveConstant multiplicativeFactor用於保存我們需要乘以L'[i]的常數。 這最初是1 additiveConstant變量,顧名思義,是用來存儲任何常量,我們需要添加到L'[i]相乘后L'[i]multiplicativeFactor完成。 additiveConstant被初始化為0

為了更具體地看待這個,讓我們設置A = 3B = 5 假設S是字符串"AMMAAM" 這意味着以下( 注意:我們現在忽略模數C ):

  • 在索引1 ,設置L'[1] = L'[1] + 3;
  • 在索引2 ,設置L'[2] = (L'[2] + 3) * 5;
  • 在索引3 ,設置L'[3] = ((L'[3] + 3) * 5) * 5;
  • 在索引4 ,設置L'[4] = (((L'[4] + 3) * 5) * 5) + 3;
  • 在索引5 ,設置L'[5] = ((((L'[5] + 3) * 5) * 5) + 3) + 3
  • 在索引6 ,設置L'[6] = (((((L'[6] + 3) * 5) * 5) + 3) + 3) * 5

觀察到先前字符'A''M' “繼承”(或級聯)到L'的未來元素中。 讓我們稍微改變一下這些操作:

  • L'[1] = L'[1] + 3
  • L'[2] = 5 * L'[2] + (3 * 5)
  • L'[3] = 5 * 5 * L'[3] + (3 * 5 * 5)
  • L'[4] = 5 * 5 * L'[4] + (3 * 5 * 5 + 3)
  • L'[5] = 5 * 5 * L'[5] + (3 * 5 * 5 + 3 + 3)
  • L'[6] = 5 * 5 * 5 * L'[6] + (3 * 5 * 5 + 3 + 3) * 5

我們開始看到一些模式。

  • L'[i]的乘法因子總是B的冪。 添加A對此乘法因子無任何影響。 乘法因子存儲在我們上面描述的multiplicativeConstant變量中
  • 每次我們需要將L'[i]乘以一個額外的B ,需要將所有常數(由加A引起)乘以B以獲得最終常數以加到L'[i] 這是上述additiveConstant變量的目的。
  • L'[i]乘法應該在將additiveConstant加到L'[i]

因此,每個L'[i]的最終值可以表示為multiplicativeConstant * L'[i] + additiveConstant; ,算法的第二個主要部分如下所示:

int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
    if (S[i] == 'A') {
        additiveConstant += A;
    } else if (S[i] == 'M') {
        multiplicativeConstant *= B;
        // need to multiply all the constants by B as well
        additiveConstant *= B;
    }
    Lprime[i] = multiplicativeConstant * Lprime[i] + additiveConstant;
}

有一點需要注意,我沒有談過。 multiplicativeConstantadditiveConstant 整數溢出 ,以及中間計算。 如果L是一個int數組,我們很幸運,因為我們可以使用long long來避免溢出。 否則,我們必須小心中間計算不會溢出。

那么modulo C操作呢? 實際上,他們將L'[i]中的每個值保持在[0..C-1]范圍內。 基於我對數論的有限理解,我們可以像這樣執行模運算來達到同樣的效果:

int multiplicativeConstant = 1;
int additiveConstant = 0;
for (int i = 1; i <= N; i++) {
    if (S[i] == 'A') {
        additiveConstant = (additiveConstant + (A % C)) % C;
    } else if (S[i] == 'M') {
        multiplicativeConstant = (multiplicativeConstant * (B % C)) % C;
        // need to multiply all the constants by B as well
        additiveConstant = (additiveConstant * (B % C)) % C;
    }
    Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}

這解決了multiplicativeConstantadditiveConstant變量的溢出問題(但不會阻止中間計算和其他變量的溢出),並完成我們的算法。 我相信這是正確的,但請親自驗證。 我無法解釋模塊化算術的東西,因為我只知道如何使用它,所以你必須自己查找。 另外, A % CB % C部分可以完成一次,結果存儲在變量中。

最后,把所有東西放在一起

// set up new array L'
int Lprime[N+1];
int leftmost = 1;
int rightmost = N;
bool isReversed = false;

for (int i = 1; i <= N; i++) {
    if (S[i] == 'R') {
        // negate isReversed
        isReversed = !isReversed;
    }

    if (isReversed) {
        Lprime[i] = L[rightmost];
        rightmost = rightmost - 1;
    } else {
        Lprime[i] = L[leftmost];
        leftmost = leftmost - 1;
    }
}

int multiplicativeConstant = 1;
int additiveConstant = 0;
// factor out A % C and B % C
int aModC = A % C;
int bModC = B % C;
for (int i = 1; i <= N; i++) {
    if (S[i] == 'A') {
        additiveConstant = (additiveConstant + aModC) % C;
    } else if (S[i] == 'M') {
        multiplicativeConstant = (multiplicativeConstant * bModC) % C;
        // need to multiply all the constants by B as well
        additiveConstant = (additiveConstant * bModC) % C;
    }
    Lprime[i] = ((multiplicativeConstant * (Lprime[i] % C)) % C + additiveConstant) % C;
}
// print Lprime

這總體上在O(N)時間內運行。

再一次,如果你擔心整數溢出,假設L是一個int數組,你可以使用long long來計算所涉及的所有變量,你應該沒問題。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM