Swift教室 Swift と競技プログラミングの教室

47. Split BST

読了目安 約2分

Recursion / LeetCode 776 (Medium) — BST を値 V 以下と V 超の 2 つの BST に分割する。Premium 限定。

この章の目次

問題

LeetCode 776. Split BST(Medium / カテゴリ: Recursion)

LeetCode Premium 限定です。問題の要点は次の要約の通りです。

二分探索木(BST)は、どの頂点でも「左部分木の値 < 頂点の値 < 右部分木の値」が成り立つ木です。 その根 root と整数 target が与えられます。

この木を 2 つの BST に分割します。 1 つ目には target 以下の値だけを、2 つ目には target より大きい値だけを入れます。 target が木の中に存在するとは限りません。

元の木で親子だった頂点は、分割後も可能な限り親子関係を保ちます。 答えは「target 以下の木の根」と「target 超の木の根」の 2 要素の配列で返します。 どちらかの木が空(nil)になることもあります。

無料で読める類題: LeetCode 450. Delete Node in a BST。BST の部分木をつなぎ替える操作の練習になります。

考え方

ヒント 1

root.val <= target のとき、根と左部分木は丸ごと「以下」側に入ります。 切り分けが必要なのは右部分木だけです。

ヒント 2

右部分木を再帰で分割すると「以下の木」と「超の木」が返ります。 「以下の木」を root.right につなぎ直せば、答えは [root, 超の木] です。 root.val > target の場合は左右対称に処理します。

Swift 実装のポイント

  • 戻り値の型は [TreeNode?] です。nil を要素に持てる配列で「空の木」を表します。
  • TreeNode は class(参照型)なので、root.right = parts[0] のようなつなぎ替えがそのまま木を書き換えます。
  • 再帰は境界をまたぐ側の部分木だけを掘り進みます。触る頂点は根から下への 1 本の経路上だけです。
  • テストコードは値の振り分けだけでなく、元のノードが再利用されていることと、分割に不要な親子関係が保たれていることも検証します。
模範解答
Swift
class Solution {
    func splitBST(_ root: TreeNode?, _ target: Int) -> [TreeNode?] {
        guard let root = root else { return [nil, nil] }
        if root.val <= target {
            let parts = splitBST(root.right, target)
            root.right = parts[0]
            return [root, parts[1]]
        } else {
            let parts = splitBST(root.left, target)
            root.left = parts[1]
            return [parts[0], root]
        }
    }
}

計算量: O(h)。h は木の高さ。