C++
 Computer >> コンピューター >  >> プログラミング >> C++

C++で配列内の重複要素を検出する方法:O(n)時間・O(1)の追加メモリを実現する符号マーキング技法

はじめに

0からn-1までの範囲の数値が格納された配列を考えます。このとき、同じ数値が複数回出現することがあります。追加のメモリ領域を使わずに、これらの重複している数値をすべて見つけるのが本記事の目的です。

例えば、n = 7 の場合で配列が [5, 2, 3, 5, 1, 6, 2, 3, 4, 5] のとき、答えは 5, 2, 3 となります。

アルゴリズムの考え方:符号マーキング技法

この問題を解く鍵となるのは「符号マーキング(sign marking)」というテクニックです。配列の各値が必ず 0 ~ n-1 の範囲に収まっているため、その値自体を配列のインデックスとして利用できます。具体的には、以下の手順に従います。

  • 配列内の各要素 e について、次の処理を行います。
    • e の絶対値をインデックスとして、A[|e|] の符号を確認します。
    • 符号が正であれば、その要素を負の値に書き換えます(「この値は既に登場した」という目印になります)。
    • すでに負になっている場合は、その値は重複していることを意味します。

この方法なら、ハッシュセットなどの追加データ構造を一切使わずに、元の配列自身を目印として活用できるため、時間計算量 O(n)、空間計算量 O(1) を実現できます。

C++での実装例

#include<iostream>
#include<cmath>
using namespace std;

void findDuplicates(int arr[], int size) {
    for (int i = 0; i < size; i++) {
        if (arr[abs(arr[i])] >= 0)
            arr[abs(arr[i])] *= -1;
        else
            cout << abs(arr[i]) << " ";
    }
}

int main() {
    int arr[] = {5, 2, 3, 5, 1, 6, 2, 3, 4, 1};
    int n = sizeof(arr)/sizeof(arr[0]);
    findDuplicates(arr, n);
}

実行結果

5 2 3 1

コードの解説

findDuplicates 関数では、配列を先頭から順に走査します。各要素について abs(arr[i]) で絶対値を取り、それをインデックスとして該当位置の値が正かどうかを判定します。

  • 正の場合:まだ一度も訪れていない値なので、その位置の値を負に反転させて「訪問済み」の印を付けます。
  • 負の場合:既に同じ値が出現しているため、abs(arr[i]) を出力して重複として報告します。

上記のサンプルコードでは、入力配列 {5, 2, 3, 5, 1, 6, 2, 3, 4, 1} に対して、重複値である 5, 2, 3, 1 が出力されます。

注意点

この手法は「配列の値が 0 以上 n-1 以下の整数である」という前提条件が必要です。また、元の配列の符号情報が失われるため、処理後に元の配列を復元したい場合は、再度絶対値を取って戻す必要があります。負の値や範囲外の値が含まれる配列には適用できない点にも留意してください。

  1. C++で二分木の重複する部分木を検出する方法

    問題の概要二分木が与えられたとき、その中に存在する重複する部分木(duplicate subtrees)をすべて見つける問題を考えてみましょう。ここでいう「重複」とは、構造とノードの値が完全に一致する部分木が2つ以上存在することを意味します。各種類の重複部分木について、代表としてどれか1つの根ノードを返せばよいことになっています。たとえば、次のような二分木があるとします。この木に含まれる重複する部分木は、以下の2つです。値 4 を持つ単一ノードの部分木(2か所に出現)根が 2 で、子に 4 を持つ部分木(2か所に出現)解法のアプローチ:部分木のシリアライズこの問題を効率的に解く鍵となるのは、部

  2. C++でソートされていない配列からフロア値とシーリング値を検索する方法

    フロア値とシーリング値とはこの記事では、ソートされていない配列からフロア値(floor)とシーリング値(ceiling)を求める方法を、C++のコード例とともに解説します。フロア値とは「x以下の要素のうち最大の値」、シーリング値とは「xより大きい要素のうち最小の値」を指します。たとえば、配列 A = [5, 6, 8, 9, 6, 5, 5, 6]、x = 7 の場合を考えてみましょう。7以下の最大の要素は 6、7より大きい最小の要素は 8 であるため、フロア値は 6、シーリング値は 8 となります。アルゴリズムの考え方この問題は、線形探索(リニアサーチ)のアプローチで効率よく解けます。配列を