略譯

原題目連結

給你 nn 大方陣、mm 小方陣,方陣中全是大寫字母,
求小方陣順時針轉 0 度、90 度、180 度、270 度後,
在大方陣中出現幾次

題解

暴力

Hash

一個能夠加速字串比對的方式是:將字串按某個規則轉成整數
這個動作我們稱作 hash,而相同的東西經過 hash,一定會得到相同的值
比如說,全大寫字母最常用的:轉 26 進位制

於是線性時間複雜度的兩個字串比對,就會變成常數時間複雜度的兩個整數比較是否相等

假設字串 ABBABBAC 中出現幾次的 ABB
已知 ABB 轉 26 進位後的值是 0x676 + 1x26 + 1 = 27

  • ABBABBAC: 27 == 27
  • ABBABBAC: 702 != 27
  • ABBABBAC: 677 != 27
  • ABBABBAC: 27 == 27
  • ABBABBAC: 702 != 27
  • ABBABBAC: 678 != 27

出現兩次。

二次確認

轉 26 進位,很容易數字就變很大,甚至 int 很快就不夠存。
通常我們直接讓它 overflow,反正 overflow 之後還是會相同。

只是由於 int 能存的值,最多也就 2^32 種;
可能會有不同字串在 overflow 之後,值剛好相同。

但我們可以在 hash 值確定相同後,再真的拿字串去比對以確保它們相同,
仍然能夠省下相當大量的運算。

Rolling Hash

每次重新求一段字串的 hash 相對沒有效率。
但所要求的字串,其實存在相當多的重疊:

  • ABBABBAC
  • ABBABBAC
  • ABBABBAC

於是我們照一般 10 進位制的方法來做:

把不要了的最高位 A 的值刪去 (減去 A x 26^2)
餘下 BB 平移一位數 (乘以 26)
把下一個 A 加進個位數 (加上 A)

於是每個字母只被加進 hash、移出 hash 各一次
得到一個線性複雜度的方法

雖然 n 比較大時,要刪去的最高位可能是 Z x 26^15 之類的數字,
不過沒關係刪就對了。overflow / underflow 有點像是對 int 範圍取 mod 的概念,
進位後溢出去的位數被捨棄掉
所以其實加上很大的數,再減回來,還是會維持原本的值。

來自 m 的多重確認

事實上這題不需要二次確認,也可以 AC。

在 m 十分小,小到不足以 overflow 的時候,這方法得出的 hash 值是精確的,
只要沒有 overflow 就沒有不同字串 hash 值互撞的問題。

然而在 m 足夠大的時候,又因為要同時符合 m 個 hash 值相同,才被認定是出現一次,
即使其中數個和不同字串撞值了,只要其它列的 hash 值不相同就沒事。

因此出錯的機率十分低。但仍可能出錯。
以防萬一,二次確認還是比較保險的。

實作提示

  • 順時針旋轉 90 度有些複雜,實際把幾個 [i][j] 轉完跑去哪給寫下來
    能幫助思考 mapping。
    • 180 度就轉兩次,或從轉 90 度的再轉 90 度就好;270 度同理