浅いものから深いものまでの配列のアルゴリズムの質問 (対: アルゴリズムとしての chatGPT)

バックグラウンド

ことわざにあるように、古いものを見直して新しいものを学びましょう。chatGPTの効果はすごい!それは単に粉砕の効果です。しかし、まだ希望はあります。まずそれを掴み、それからイノベーションを起こしましょう。まず理解してから、それを超えてください。

ps: 前回に向けてアルゴリズムの質問アイデアをブラッシュアップします。さて、chatGPT3.5ベースのビッグモデルの魅力を感じてください。

配列とポインタの違い

C/C++ では、配列とポインターは、関連していると同時に別個の 2 つの概念です。配列を宣言するとき、その配列名は配列の最初の要素を指すポインターでもあります。ポインターを介して配列にアクセスできますが、C/C++ は配列のサイズを記録しないため、ポインターを使用して配列内の要素にアクセスする場合、プログラマは配列の境界を超えていないことを確認する必要があります。

配列とポインタの違い。以下のコードを実行すると、どのような出力が得られますか?

int GetSize(int data[])
{
    return sizeof(data);
}

int _tmain()
{
    int data1[]={1,2,3,4,5};
    int size1=sizeof(data1);

    int *data2=data1;
    int size2=sizeof(data2);

    int size3=GetSize(data1);

    printf("%d, %d, %d\n", size1, size2, size3);

}

答えは「20、4、4」を出力します。

data1 は配列であり、sizeof(data1) は配列のサイズを見つけることです。この配列には 5 つの数値が含まれており、各整数は 4 バイトを占めるため、20 バイトを占めます。

data2 はポインターとして宣言されていますが、配列 data1 の最初の番号を指していますが、本質的にはポインターです。32 ビット システムでは、どのポインターの sizeof の結果も 4 になります。

C/C++ では、配列が関数のパラメーターとして渡されると、配列は自動的に同じ型のポインターに縮退します。したがって、関数 GetSize のパラメータ データは配列として宣言されていますが、ポインタに縮退してしまい、size3 の結果は 4 のままになります。

配列内の繰り返しの数値

方法 1 : 入力配列をソートし、ソートされた配列から重複する数値を見つけます。長さ n の配列をソートする時間計算量は O(nlogn) です。

方法 2 : ハッシュ テーブルを構築し、配列を順番にスキャンします。数値をスキャンするたびに、O(1) 時間を使用して、ハッシュ テーブルにその数値が既に含まれているかどうかを判断できます。時間計算量は O(n) ですが、ハッシュ テーブルを格納する空間計算量は O(n) です。

方法 3 : 配列番号は 0~n-1 で、繰り返し配列がない場合、番号 i は添え字 i が付いた位置になります。数字が繰り返しある場合は、ある i の位置に複数の数字があり、答えは見つかっています。配列を走査して、各数値に独自の場所を持たせます。

例として配列 {2,3,1,0,2,5,3} を取り上げます。

{2,3,1,0,2,5,3}

{1,3,2, 0,2,5,3} 配列の0番目の位置は2、配列の2番目の位置は1、2つの位置の値が入れ替わります

{3,1,2,0,2,5,3} 位置0と1の値が交換されます

{0,1,2,3,2,5,3} 位置 0 と 3 の値が交換されます

位置 0、1、2、および 3 はすべてそれぞれの位置にあります。位置 4 に移動した後の値は 2 で、添字が 2 の場所にはすでに 2 があるため、繰り返しがあり、答えが得られます。が見つかると、プログラムは終了します。

以下は方法 3 の実装コードです。二重ループがありますが、各数値を最大 2 回交換してその位置を見つけることができるため、合計の時間計算量は O(n) です。さらに、すべての演算は追加のメモリ割り当てなしで入力配列に対して直接実行されるため、空間複雑度は O(1) です。

bool duplicate(int numbers[], int length, int* duplication)
{
    if(numbers ==nullptr || length<=0)
    {
        return false;
    }
    for(int i=0; i<length; ++i)
    {
        if(numbers[i]<0 || numbers[i]>length-1)
        return false; 
    }

    for(int i=0; i<length; ++i)
    {
        while(numbers[i]!=i)
        {
            if(numbers[i]==numbers[numbers[i]])
            {
                *duplication =numbers[i];
                return true;
            }
            int temp=numbers[i];
            numbers[i]=numbers[temp];
            numbers[temp]=temp;

        }
    }
    return false;
}

配列を変更せずに重複する数値を検索する

アイデア 1:

長さ n+1 の補助配列を作成し、元の配列の各数値を 1 つずつ補助配列にコピーします。元の配列でスケーリングされる数値が m の場合、それを補助配列の添字 m にコピーします。これにより、数値が重複していることを簡単に見つけることができますが、このスキームでは O(n) 個の補助スペースが必要です。

アイデア 2:

1~nの数字を真ん中のmから2つに分け、前半を1~m、後半をm+1~nとします。1 から m までの数値の数が m を超える場合は、区間のこの半分に繰り返しの数値が含まれている必要があり、そうでない場合は、区間の残りの半分の m+1~n に繰り返しの数値が含まれている必要があります。繰り返しの数字が見つかるまで、繰り返しの数字の間隔を 2 つに分割し続けます。このプロセスは、区間内の数値をカウントする追加のステップがあることを除いて、二分探索アルゴリズムに非常に似ています。

検索プロセスを分析するための例として、長さ 8 の配列 {2,3,5,4,3,2,6,7} を取り上げます。質問の意味によれば、長さは 8 つの配列で、すべての数値は 1 ~ 7 の範囲内にあります。中央の数字の 4 は、1 ~ 7 の範囲を両端に分割し、一方のセクションは 1 ~ 4、もう一方のセクションは 5 ~ 7 です。次に、区間番号 1 ~ 4 の出現回数を数えます。合計 5 回出現するため、1 ~ 4 には繰り返しの番号があるはずです。

次に、1~4の範囲を2つに分け、1つのセクションは1,2の2つの数字、もう1つのセクションは3,4の2つの数字です。1 または 2 という数字が 2 回表示されます。3 または 4 が 3 回出現したことをもう一度数えます。これは、2 つの数字 3 と 4 が繰り返される必要があることを意味します。次に、これら 2 つの数字の出現回数を数えたところ、数字 3 が 2 回出現したことがわかりました。繰り返し番号。

上記のアイデアは次のコードで実装されます。

int getDuplication(const int* numbers, int length)
{
    if(numbers == nullptr || length <= 0)
        return -1;

    int start = 1;
    int end = length - 1;
    while(end >= start)
    {
        int middle = ((end - start) >> 1) + start;
        int count = countRange(numbers, length, start, middle);
        if(end == start)
        {
            if(count > 1)
                return start;
            else
                break;
        }

        if(count > (middle - start + 1))
            end = middle;
        else
            start = middle + 1;
    }
    return -1;
}

int countRange(const int* numbers, int length, int start, int end)
{
    if(numbers == nullptr)
        return 0;

    int count = 0;
    for(int i = 0; i < length; i++)
        if(numbers[i] >= start && numbers[i] <= end)
            ++count;
    return count;
}

アイデア 2 は二分探索のアイデアに基づいており、入力長が n の配列の場合、関数 countRange は O(logn) 回呼び出され、毎回 O(n) 時間が必要となるため、合計の時間計算量はが O(nlogn) である場合、空間計算量は O(1) です。アイデア 1 の O(n) 補助空間のアルゴリズムと比較すると、それは時間を空間と交換することに属します。

このアルゴリズムでは、重複する番号をすべて見つけることが保証されていないことに注意してください。

2D配列でのルックアップ

二次元配列では、各行は左から右に昇順にソートされ、各列は上から下に昇順にソートされます。このような二次元配列と整数を入力して関数を完成させ、判定してください配列に整数が含まれているかどうか

たとえば、次の 2 次元配列で 7 を検索すると true が返され、5 を検索するとその数値は配列に含まれていないため false が返されます。

[[1 2 8 9]

[2 4 9 12]

[4 7 10 13]

[6 8 11 15]]

アイデア:

まず、特定の四角形の検索プロセスを使用して法則を見つけ、次のことを確認します。まず、配列の右上隅にある数字を選択し、その数字が探したい数字である場合は検索を終了します。が検索する数値より大きい場合は、その数値が存在する列の数値を削除します。数値が検索する数値より小さい場合は、その数値が削除された行が削除されます。つまり、検索対象の番号が配列の右上隅にない場合は、その都度配列の検索範囲から1行または1列を削除し、その都度検索範囲を絞り込むことができます。検索する番号が見つかるか、検索範囲が null になるまでステップを実行します。

bool Find(int* matrix, int rows, int columns, int number)
{
    bool found = false;

    if(matrix != nullptr && rows > 0 && columns > 0)
    {
        int row = 0;
        int column = columns - 1;
        while(row < rows && column >=0)
        {
            if(matrix[row * columns + column] == number)
            {
                found = true;
                break;
            }
            else if(matrix[row * columns + column] > number)
                -- column;
            else
                ++ row;
        }
    }

    return found;
}

イースターエッグ: chatGPT アルゴリズム

この質問をchatGPTに投げると、その理解力は本当に強すぎます。その答えは次のとおりです。

おすすめ

転載: blog.csdn.net/u010420283/article/details/129374715