はじめに
整数の配列 nums と整数 target が与えられるので、0 <= i < j <= n かつ nums[i] + nums[j] < target を満たす組の数をカウントせよ、という問題。
簡単そうですよね。簡単です。
とりあえず、いわゆる brute force で解けます。
初手
解いちゃうのどうなの?と思いますが、とりあえず解く方法が分かっていたので二重ループで解きました。時間計算量は最良ではないです。
|
|
i を固定して j をカウントアップし、一つ一つ条件に合う組をカウントしていく方法です。直感的です。
最良と思われる手
Two Pointers を使います。
そして、0 <= i < j <= n から勝手に課してしまった制約を取り払います。
それは「与えられた nums の配列は並び替えずそのまま使う」というものです。
この制約は、あくまで 2 つのインデックスの大小関係のみを定義しており、与えられた配列 nums の順番に制限を課すものではありません。
つまり、並び替えてもいいのです。ソートしましょう。ソートすれば、Two Pointers で解けます。
|
|
正直なところ、解き方は以下に詳しく、私もこれを見たので、これ以上の分かりやすい説明はできません。
が、あえて私の言葉で説明します。
とりあえず、昇順にソートします。話はそれからです。 昇順にソートすると、一番左と一番右は、もちろんそれぞれ最小値と最大値となります。 あとは、個別のケースを考えます。まず、たとえば、最小値 + 最大値 < target となった場合、どうでしょう? 最小値の方のポインタを右にずらした場合、一つ前と同じかそれより大きい合計になることが明らかです。 なので、最大値を指す右側のポインタを一つ左にずらします。すると、最大値と同じかそれより小さい値が指し示されるので、target より合計が小さくなる可能性があります。
左のポインタを left、右のポインタを right としたとき、nums[left] + nums[right] < target となった場合はどうすべきでしょう?
この場合、right をいくら左に移動していっても、必ず target より小さくなります。これは、配列が昇順にソートされているからです。
なので、right - left 分だけ条件を満たす組があることが分かります。つまり、ans += right - left となります。
これで左側と右側両方のポインタの動きが理解できたので、後はそれをコードに起こすだけです。
この方法は、最初のソートによる O(nlogn) と、要素分だけポインタを動かすので O(n) の時間計算量となりますが、O(nlogn) としてしまって良いと思います。 初手は二重ループでしたので O(n^2) でした。十分高速ですね。