惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Vercel News
Vercel News
O
OpenAI News
Engineering at Meta
Engineering at Meta
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
月光博客
月光博客
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More
WordPress大学
WordPress大学
宝玉的分享
宝玉的分享
GbyAI
GbyAI
T
The Blog of Author Tim Ferriss
Google DeepMind News
Google DeepMind News
B
Blog RSS Feed
CTFtime.org: upcoming CTF events
CTFtime.org: upcoming CTF events
云风的 BLOG
云风的 BLOG
罗磊的独立博客
S
SegmentFault 最新的问题
The Register - Security
The Register - Security
Hugging Face - Blog
Hugging Face - Blog
D
DataBreaches.Net
U
Unit 42
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
B
Blog
阮一峰的网络日志
阮一峰的网络日志
P
Proofpoint News Feed
雷峰网
雷峰网
V
Visual Studio Blog
小众软件
小众软件
aimingoo的专栏
aimingoo的专栏
N
Netflix TechBlog - Medium
酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hackread – Cybersecurity News, Data Breaches, AI and More
Y
Y Combinator Blog
博客园 - 【当耐特】
G
Google Developers Blog
L
LangChain Blog
Stack Overflow Blog
Stack Overflow Blog
I
InfoQ
Martin Fowler
Martin Fowler
F
Fortinet All Blogs
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
The Cloudflare Blog
AI
AI
Google Online Security Blog
Google Online Security Blog
Hacker News - Newest:
Hacker News - Newest: "LLM"
博客园 - Franky
Blog — PlanetScale
Blog — PlanetScale
Webroot Blog
Webroot Blog
PCI Perspectives
PCI Perspectives
爱范儿
爱范儿
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org

hayato

Chromium's DOM Deep Dive: Understanding Shadow DOM and Tree Structures - hayato Introducing the Task Scheduler in Chromium's Network Stack: A 0.3% Improvement in Loading Speed - hayato 出版のお知らせ 「普通の人が資産運用で99点をとる方法とその考え方」 - hayato Resource loading with Content Addressable Bundles - hayato 普通の人が資産運用で99点をとる方法とその考え方 - hayato Imperative Shadow DOM Distribution API - hayato Google にソフトウェアエンジニアとして入社して 10 年と 10 日がたちました - hayato Web のつくりかた: ソフトウェアエンジニアリングと雰囲気で理解する Web 標準とブラウザのつくりかた - hayato ブラウザのしくみ: データ構造とアルゴリズムと雰囲気で理解する DOM と Shadow DOM - hayato Rust が最強のプログラミング言語である証明 - hayato 六本木ではたらくソフトウェアエンジニアへのよくある質問とその答え (FAQ) (2015 - 2017) - hayato What's New in Shadow DOM v1 (by examples) - hayato Simple timer application for the pomodoro technique - hayato Google の面接試験で出る (はずがない)「海賊と金貨」問題を空気を読まないでプログラムで解いてみる - hayato hayato.io (4topcoder) - hayato SRM432 - hayato SRM405 - hayato SRM386 - hayato SRM385 - hayato SRM384 - ゲーム - hayato SRM369 - hayato SRM368 - hayato SRM366 - typo 2 連発 - hayato Python - generator で素数生成 - hayato New Launguage - 新言語追加の予定? - hayato lxml で HTML スクレーピング - hayato SRM358 - Today is a Bad Day (-50). - hayato SRM349 FizzBuzz 問題が簡単すぎるそんなあなたへ。「赤いマーブル問題」。 - hayato google-code-prettify を導入してみる - hayato Subset の生成 - hayato 2006 年のまとめ - SRM332 - hayato Google Code Jam 2006 - Championship Round - hayato Blogger Beta に移行 - hayato Google Code Jam 2006 - Round1 に挑戦 - Generics と AutoBoxing - hayato Google Code Jam 2006 - Qualification Round 通過 - hayato Google Code Jam 2006 - Qualification Round に挑戦 - hayato Google Code Jam 2006 - Practice1 - hayato Google Code Jam 2006 - 開催決定 - hayato Stay tuned... Global Google Code Jam 2006 - 秋に開催? - hayato SRM308 - Hard DP - hayato SRM304 - 条件付確率 - hayato JavaWorld に寄稿しました - hayato 2006 TopCoder Open Final Round - hayato IBM Rational Software Development Forum Tokyo で講演します - hayato SRM298 - ひとふで書き - hayato SRM293 - ビンゴ - hayato 2006 TopCoder Open - Round1 撃墜 - hayato 2006 TopCoder Open - Odds 発表 - hayato 2006 TopCoder Open Qualification Round - hayato SRM291 - OutOfMemory - hayato デブサミ 2006 で講演します - hayato SRM287 - 気分は中学生?連立2元方程式 - hayato SRM281 - 2006 年はポイントゼロスタート - hayato SRM280 - Challenge 祭り - hayato SRM277 - 初賞金げっと - hayato マラソン・マッチ - hayato reStructuredText で blogger - hayato 2006 TopCoder Open - NSA がスポンサーに - hayato SRM276 - お買い物は楽しく DP で - hayato SRM273 - bit 演算は int で行われるのね - hayato SRM272 - Division1 - 4 度目の正直 - hayato JavaOne Tokyo で講演します - hayato SRM270 - Integer == Integer - hayato SRM268 - Division2 脱出 - hayato SRM266 - NASA, we have a problem... - hayato SRM265 - Division2 落ち... - hayato SRM262 - 初 Division1 は Typo に終わる - hayato SRM260 - TopCoder への挑戦のはじまり - hayato
Suffix Array - hayato
2007-03-26 · via hayato

TopCoder SRM187、 DNAMultiMatcher は、

String が 3 つ(それぞれの長さは最大 2500)与えられたとき、 3 つ全てに含まれる最 長の Substring の長さを求めなさい。

という問題です。これに対して、

  • 長さを Binary-Search で絞りながら、
  • String が含まれるかどうかの判定を Java 標準の java.lang.String#contains を使用

という方針で解く場合は、こうなります。

public class DNAMultiMatcher {

  public int longestMatch(String[] sequence1, String[] sequence2, String[] sequence3) {
      String s1 = join(sequence1);
      String s2 = join(sequence2);
      String s3 = join(sequence3);

      int low = 0;
      int high = s1.length();

      while (low < high) {
          int mid = low + (high-low+1)/2;
          boolean found = false;
          for (int i = 0; i + mid <= s1.length(); i++) {
              String p = s1.substring(i, i+mid);
              if (s2.contains(p) && s3.contains(p)) {
                  found = true;
                  break;
              }
          }
          if (found) {
              low = mid;
          } else {
              high = mid-1;
          }
      }
      return low;
  }

  String join(String[] sa) {
      StringBuilder sb = new StringBuilder();
      for (String s : sa) sb.append(s);
      return sb.toString();
  }
}

この場合は、ワーストケースで軽く 2 分以上かかってしまい、タイムアウトです。 String A の長さを n、String B の長さを m とした場合、Java の A.contains(B)は、普通に O(m*n)っぽいですね。

A が B を含んでいるか?を高速に判定するときに使用できるのが、 Suffix Array です。

ナイーブに実装してみると、

class SuffixArray {
    int len;
    String[] suffixes;

    SuffixArray(String s) {
        len = s.length();
        suffixes = new String[len];
        for (int i = 0; i < len; i++) {
            suffixes[i] = s.substring(i);
        }
        Arrays.sort(suffixes);
    }

    int find(String p) {
        if (len == 0) {
            return p.length() == 0 ? 0 : -1;
        }
        int low = 0;
        int high = suffixes.length-1;
        while (low < high) {
            int middle = low + (high-low)/2;
            if (suffixes[middle].compareTo(p) >= 0) {
                high = middle;
            } else {
                low = middle+1;
            }
        }
        if (suffixes[high].startsWith(p)) {
            return len - suffixes[high].length();
        }
        return -1;
    }
}

Java の java.lang.String#substring メソッドは、もとの String と内部の配列 char[]を共有した String を返すので、この実装でも無駄にメモリを消費しないはずです。

オーダーは、

  • A に対する Suffix Array の構築: O(n^2 log(n))
  • A が B を含んでいるかの判定: O(m log(n))

になるでしょうか? 改良の余地はまだまだ残っていますが、今回の目的にはこれで十分です。 (Suffix Array を O(n)で作成するアルゴリズムもあるようです。。。)

先ほどの例を、Suffix Array を使用して書き直すとこうなります。

public class DNAMultiMatcher {

  public int longestMatch(String[] sequence1, String[] sequence2, String[] sequence3) {
      String s1 = join(sequence1);
      String s2 = join(sequence2);
      String s3 = join(sequence3);

      SuffixArray sa2 = new SuffixArray(s2);
      SuffixArray sa3 = new SuffixArray(s3);

      int low = 0;
      int high = s1.length();

      while (low < high) {
          int mid = low + (high-low+1)/2;
          boolean found = false;
          for (int i = 0; i + mid <= s1.length(); i++) {
              String p = s1.substring(i, i+mid);
              if (sa2.find(p) >= 0 && sa3.find(p) >= 0) {
                  found = true;
                  break;
              }
          }
          if (found) {
              low = mid;
          } else {
              high = mid-1;
          }
      }
      return low;
  }

これで、ワーストケースでも 1 秒以内に処理できるようになり、システムテストに通ります。 Suffix Array の応用例はほかにもいろいろあり、全文検索などでもよく使われるようです。