【ソードフィンガーオファー】-二分検索関連のサンプルまとめ

まず、回転配列の最小値

トピックの要件:

配列の最初のいくつかの要素を配列の最後に移動します。これを配列の回転と呼びます。
減少しないソートされた配列の回転を入力し、回転された配列の最小要素を出力します。
たとえば、配列{3,4,5,1,2}は{1,2,3,4,5}のローテーションであり、配列の最小値は1です。
注:指定されたすべての要素は0より大きいため、配列サイズが0の場合は0を返します。

トピック分析:

まず、我々は、アレイの回転の法則ことをクリアすることができ
、回転後の配列は、実際には2つのサブアレイに分割することができるソートされ、及びサブアレイ素子の前方の要素がより大きい又は後ろサブアレイに等しい
。このように、我々は従うことができ質問に対する答えを探るときがきました。それでも、特定の例を使用して、シンプルでわかりやすい方法で分析します。

ケース1:
配列{3、4、5、1、2}が列として使用されます。まず、配列の最初の要素と最後の要素を指す2つのポインターを定義します。網掛け部分は2番目のサブ配列です。
ここに画像の説明を挿入
この時点で中央の要素のインデックスを計算します=(0 + 4)/ 2 = 2。添え字が2の配列の要素値は5です。5>はp1が指す数値3なので、中央の値は最初のサブ配列にあります。p1を中央の数値にポイントします。
ここに画像の説明を挿入
このとき、p1とp2の中間にある数字1は、p2が指す数字よりも小さくなります。中央の数字は2番目のサブ配列にあります。p2を中央の数値にポイントします。
ここに画像の説明を挿入
このとき、p1とp2は隣接する2つの数値を
指し、次に p2は配列内の最小の数値を指します。
なぜp2が最小数になるのですか?
2つのポインターが1 だけ離れている場合、それはp1が最初に増加する配列の最後の要素をすでに指し、p2が2番目に増加する配列の最初の要素を指していることを示します。したがって、p2が指すのは配列の最小要素です。

ケース2:
配列{0,1,1,1,1}のように、 p1、p2と中央のポインターが指す数が同じである状況が発生した場合。{1,0,1,1,1}および{1,1,1,0,1}は、
ここに画像の説明を挿入
上の図に示すようにインクリメンタル配列の回転です。この場合、中央の要素がどのインクリメンタル配列に属しているかは明確ではありません、そのため、シーケンシャル検索の方法をとる必要があります。

ケース3:
ソートされた配列の最初の0要素が最後、つまり配列自体に移動された場合、これはまだ配列の回転です。
この場合、p1が指す要素を返すだけです。これが、indexMidがp1に割り当てられる理由です。

コードの実装

int MinInOrder(int* numbers, int p1, int p2)
{
    int result = numbers[p1];
    for (int i = p1 + 1; i <= p2; i++)
    {
        if (result > numbers[i])
            result = numbers[i];
    }
    return result;
}
int Min(int* numbers, int length)
{
	if (numbers == nullptr || length <= 0)
		throw new::std::exception("Invalid parameters");

	int p1 = 0;
	int p2 = length - 1;
	int indexmid = p1;
	while (numbers[p1] >= numbers[p2])
	{
		//情况一
		if (p2 - p1 == 1)
		{
			indexmid = p2;
			break;
		}
		indexmid = (p1 + p2) / 2;

		//情况二
		if (numbers[p1] == numbers[p2] == numbers[indexmid])
		{
			return MinInOrder(numbers, p1, p2);
		}
		//缩小范围
		if (numbers[indexmid] >= numbers[p1])
			p1 = indexmid;
		else if(numbers[indexmid] <= numbers[p2])
			p2 = indexmid;
	}
	return numbers[indexmid];
}

次に、ソートされた配列で数値を見つけます

トピック1:並べ替えられた配列に数値が現れる回数

ソートされた配列に数値が現れる回数を数えます。たとえば、ソートされた配列{1,2,3,3,3,3,4,5}と数値3を入力します。これは、この配列に3が4回出現するため、4を出力するためです。

分析:
配列は順序付けられているため、当然のことながら二分探索を使用することを考えています。2進検索を使用して3を見つけ、3の左側と右側を順次スキャンして、最初の3と最後の3をそれぞれ見つけることが推奨されます。

配列の最初の3を見つける:
数値3は上記のバイナリ検索に基づいて見つかりましたが、この3が配列の最初の3であるかどうかは明確ではありません。したがって、3の前の数値を判断する必要があります。
ここに画像の説明を挿入
図のXの場合、Xが3でない場合、配列の最初の3は今見つかったものです3。
図のX が3の場合、最初の3 3配列の前半にいる必要がありますが、次のラウンドでは配列の前半を検索する必要があります。
コードは次のように実装されます。

int GetFirstK(int* data, int length, int k, int start, int end)
{
    if (start > end)
        return -1;
    int mid = (start + end) / 2;
    int middleData = data[mid];

    if (middleData == k)
    {
        if ((mid > 0 && data[mid - 1] != k) || mid == 0)
            return mid;
        else
            end = mid - 1;
    }
    else if (middleData > k)
    {
        end = mid - 1;
    }
    else
    start = mid + 1;
    return GetFirstK(data, length, k, start, end);
}

Not found 3は-1を返し、配列の最初の3はその添え字を返します

配列
の最後の3つを見つける最後の3つを見つける方法は上記と同様です。再帰に基づいて記述されたコードは次のとおりです。

int GetLastK(int* data, int length, int k, int start, int end)
{
    if (start > end)
        return -1;
    int mid = (start + end) / 2;
    int middleData = data[mid];
    if (middleData == k)
    {
        if ((mid < length-1 && data[mid + 1] != k) || mid == length-1)
            return mid;
        else
            start = mid + 1;
    }
    else if (middleData < k)
    {
        start = mid + 1;
    }
    else
      end = mid - 1;
    return GetLastK(data, length, k, start, end);
}

配列の最初の3と最後の3の添え字を見つけたら、配列内の出現回数を計算できます。コードは次のとおりです。

int GetNumberOfK(int* data, int length, int k)
{
    int count = 0;
    if (data != nullptr && length > 0)
    {
        int first = GetFirstK(data, length, k, 0, length - 1);
        int last = GetLastK(data, length, k, 0, length - 1);

        if (first > -1 && last > -1)
            count = last - first + 1;
    }
    return count;
}

トピック2:0〜n-1の数字がない

長さn-1の増加する配列内のすべての数値は一意であり、各数値は0〜n-1の範囲内にあり、範囲内のn個の数値のうち、配列内にない数値は1つだけです、この番号を調べてください

問題分析:
方法1:
最初に0からn-1までのすべての数値の合計をs1として計算し、次に配列内のすべての数値の合計をs2として計算できます。s1-s2の差は0〜n-1です。不足している番号。
しかし、質問で与えられた配列を増やすという規則は使用されておらず、時間の複雑さがO(n)であるため、この方法は適切ではありません。

方法2:これらの数値0〜n-1は配列で並べ替えられているため
、バイナリ検索のアイデアを使用して解決
できます。したがって、これらの数値の始まりは、配列のインデックスと同じです。次に、mの後の数と添え字は対応する関係ではない
ここに画像の説明を挿入
ため、この問題は、添え字と等しくないソート済み配列の最初の要素見つけることに変換されます。

1.中間要素と下付き文字の値が等しい
場合、次のラウンドで正しい部分を検索するだけでよい2.中間要素と下付き文字の値が等しくない場合

  1. そして、彼の前の要素が彼の添え字と等しくない場合、要素は欠落している要素です
  2. そして、彼の前の要素は彼の添え字と同じではありません。つまり、次の検索ラウンドで右側を検索するだけで済みます。

要約すると、コードの実装は次のとおりです。

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

    int left = 0;
    int right = length - 1;
    
    while (left <= right)
    {
        int mid = (left + length) / 2;
        if (numbers[mid] != mid)
        {
            if (numbers[mid - 1] == mid - 1 || mid == 0)
                return mid;
            else
                right = mid - 1;
        }
        else
            left = mid + 1;
    }
    //缺失的是最后一个数字
    if (left == length)
        return length;

    return -1;
}

トピック3:配列内の値と添え字が等しい要素

単調増加する配列の各要素が整数であり、一意であるとします。添え字と等しい値を持つ配列内の要素を検索する関数をプログラムしてください。たとえば、配列{-3、-1、1、3、5}では、数値3はその添え字と同じです。

問題の分析:
i番目の数値の値が添え字iよりも大きいと仮定すると、配列の増加の法則を十分に活用し、右側の数値は対応する添え字よりも大きくなります。次の検索ラウンドでは、彼女の左側の数字のみを検索する必要があります
。i番目の数字の値が下付き文字iより小さいとすると、彼の左側の数字は、対応するエディターよりも小さくなります。次の検索では、彼女の右側にある数字の中を検索するだけです。
上記のステートメントに基づいて、コードは次のように実装されます。

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

    int left = 0;
    int right = length - 1;

    while (left <= right)
    {
        int mid = (right - left ) / 2 + left;
        if (numbers[mid] = mid)
            return mid;
        if (numbers[mid] < mid)
            left = mid + 1;
        else
            right = mid + 1;
    }
    return -1;
}
公開された98元の記事 ウォンの賞賛9 ビュー3654

おすすめ

転載: blog.csdn.net/qq_43412060/article/details/105338437