等式方程的可滿足性——TypeScript並查集實現

總的來說,思路還是很清晰的。

一看到這個題就想到了圖,==構成的是連通關係,只要有連通關係,就可以構造圖來解決問題。而解決圖的問題,比較常見的思路的就是BFS(或者DFS)和並查集,這個題感覺用BFS和BFS有點麻煩,那就用並查集了:

function equationsPossible(equations: string[]): boolean {
  const parent: number[] = [];
  for (let i = 0; i < 26; ++i) {
    parent.push(i);
  }

  function find(i: number) {
    while (i !== parent[i]) {
      i = parent[i];
    }
    return i;
  }

  function union(a: number, b: number) {
    parent[find(a)] = find(b);
  }

  for (const equation of equations) {
    if (equation[1] === "=") {
      let leftId = equation[0].charCodeAt(0) - 0x61,
        rightId = equation[3].charCodeAt(0) - 0x61;
      union(leftId, rightId);
    }
  }
  for (const equation of equations) {
    if (equation[1] === "!") {
      let leftId = equation[0].charCodeAt(0) - 0x61,
        rightId = equation[3].charCodeAt(0) - 0x61;
      if (find(leftId) === find(rightId)) {
        return false;
      }
    }
  }
  return true;
}

可能有幾個地方需要注意:

  1. 相對來說,因爲ts / js的語言特性,可以嵌套函數,所以在使用“全局變量”(比如這裏的parent數組)的時候會比較優雅一點。但是因爲這語言裏沒有字符類型,所以在處理這種涉及字符的問題就比較麻煩,需要去調用charCodeAt方法去獲取對應的ASCII碼值,然後再進行數值運算。

  2. 爲什麼用更安全的codePointAt方法,而是選擇了不那麼安全的charCodeAt?因爲題目裏說了是小寫英文字母,不需要考慮超過\uFFFF的字符。

  3. 爲什麼不用字符串來做並查集,而是用數字?用字符串來做就得用Map(或者對象),因爲需要維護一個字符串到字符串的映射關係。而且用Map或者對象之後還要考慮類型上可能的undefined(雖然實際上並不會發生),寫起來有點麻煩。

    當然運行起來好像是字符串快一點(因爲可以減少每次轉換成數字的運算量),而且對調用者比較友好(如果是一個封裝好的並查集)。在最後會放兩個用字符串的實現,一個是Map,一個是對象。對象的實現是最優雅的,但性能最差。

    總的來說,就我這裏的實際運行結果來看,Map > 數字 > 對象。

  4. 關於find的實現,事實上這麼寫也是可以的:

    function find(i: number) {
      while (i !== parent[i]) {
        i = parent[i];
      }
      return i;
    }
    

    但是現在的實現可以減少循環次數,一定程度上可以提高性能。關於減少循環次數可能能夠提高性能,可以參考“達夫設備(Duff’s device)”;當然這個與具體的運行環境有關。

  5. 減少==的使用可以避免可能的類型轉換,提高性能。

附錄

Map實現

function equationsPossible(equations: string[]): boolean {
  const parent = new Map<string, string>();
  for (let i = 0; i < 26; ++i) {
    const s = String.fromCharCode(0x61 + i);
    parent.set(s, s);
  }

  function find(i: string) {
    while (i !== parent.get(i)) {
      parent.set(i, parent.get(parent.get(i)!)!);
      i = parent.get(i)!;
    }
    return i;
  }

  function union(a: string, b: string) {
    parent.set(find(a), find(b));
  }

  for (const equation of equations) {
    if (equation[1] === "=") {
      union(equation[0], equation[3]);
    }
  }
  for (const equation of equations) {
    if (equation[1] === "!") {
      if (find(equation[0]) === find(equation[3])) {
        return false;
      }
    }
  }
  return true;
}

對象實現

interface Parent {
  [key: string]: string;
}

function equationsPossible(equations: string[]): boolean {
  const parent: Parent = {};
  for (let i = 0; i < 26; ++i) {
    const s = String.fromCharCode(0x61 + i);
    parent[s] = s;
  }

  function find(i: string) {
    while (i !== parent[i]) {
      parent[i] = parent[parent[i]];
      i = parent[i];
    }
    return i;
  }

  function union(a: string, b: string) {
    parent[find(a)] = find(b);
  }

  for (const equation of equations) {
    if (equation[1] === "=") {
      union(equation[0], equation[3]);
    }
  }
  for (const equation of equations) {
    if (equation[1] === "!") {
      if (find(equation[0]) === find(equation[3])) {
        return false;
      }
    }
  }
  return true;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章