【ソードフィンガーオファー】-ビット操作で解決する問題


ビット演算とは、数値を2進数で表した後の各ビットの0または1の演算を指します。合計5つの操作があります:AND、OR、XOR、左シフト、右シフト

1.バイナリの1の数

問題の要件:
関数を実装し、整数を入力し、1の数を数値のバイナリ表現で出力してください。たとえば、9をバイナリとして表すと1001、2ビットは1になります。したがって、9を入力すると、関数は2を出力します。

問題の分析:
方法1:
バイナリの右端のビットを毎回判断する無限ループが発生する可能性があります。n&1を使用して結果が1かどうかを確認します。1の場合は、整数の右端のビットが1で、それ以外の場合は0です。 。判断後、nを右に移動します。
しかし、このメソッドは、負の数に遭遇すると無限ループに入ります。

方法2:解の効率が高くない
。最後の方法では、数値nを毎回移動しますが、この方法では、数値1を毎回左に移動します。数値nの各バイナリビットが1かどうかを確認します。
そのような解のサイクルの数は、整数の2進数の数と同じです。

方法3:最も効果的な方法
このルールについて考えてみましょう。数値が0でない場合、バイナリ表現に少なくとも1つの1が含まれます。次に、数nが1減ったとき。2つのケースがあります
。最初のケース:nの右端は1です。1ずつ減分した後、最後のビットは0になり、他のすべてのビットは変更されません。
ここに画像の説明を挿入
2番目のタイプ:nの右端は1ではありません。図に示すように、2番目のビットは1から0に変化し、2番目のビットが1になった後の0は変化しません。
ここに画像の説明を挿入
要約すると、整数から1を減算し、元の整数とのAND演算を実行すると、整数の右端の1が0に変更されます。次に、整数の2進数に1があるのと同じ数の整数であれば、そのような演算は何回でも実行できます。

int NumberofN(int n)
{
	int count = 0;
	while (n)
	{
		n = n & (n - 1);
		count++;
	}
	return count;
}

第二に、配列内の数値の出現回数

トピックの要件:
配列に1回だけ現れる2つの数値。2つの数値を除いて、整数配列は2回出現します。一度しか現れないこれら二つの数字を見つけるプログラムを書いてください。時間の複雑さはO(n)、空間の複雑さはO(1)です。

問題分析:
最初にこの質問をしてください。私たちは無知に感じるかもしれません。しかし、このように考えましょう。問題を少し簡単にしましょう。配列では、1つの数値のみが1回だけ表示され、残りの数値は2回表示されます。
このようにして、ビット演算のXORプロパティを使用して問題を解決できます。**いずれかの数値XOR自体は0に等しいため、**配列内の数値は1つずつXORされるか、末尾の後に残っている数値が単一の数値です。次のように:
ここに画像の説明を挿入

したがって、次に、配列を1つだけの数値で2つの配列に分割する方法を考える必要があります。
配列のすべての数値を最初から最後までXORします。最終結果は、1回だけ現れる2つの数値のXOR結果です。配列{2,4,3,6,3,2,5,5}など。4 ^ 6 = 0010
ここに画像の説明を挿入
上記の計算によれば、結果の数値のバイナリ表現では、少なくとも1ビットが1であることがわかります。したがって、最後から2番目の桁が1かどうかに基づいて、配列を2つのサブ配列に分割します。最初の配列は{2,3,6,3,2}です。2番目の配列は{4,5,5}です。次に、2つの配列を個別にXORして、最初のワード配列に1回だけ現れる数が6であり、2番目のサブ配列に1回だけ現れる数が4であることを見つけます。

上記の分析で。次のステップは、コードを実装することです。
まず、整数numのバイナリ表現の右端のビットを見つけます。

unsigned int FindFirstBitIs(int num)
{
	int indexBit = 0;
	while( ((num & 1) == 0) && (indexBit < 8 * sizeof(int)))
	{
		num = num >> 1;
		++indexBit;
	}
	return indexBit;
}

numのバイナリ表現の右からのindexBitビットが1かどうかを判断しましょう

bool IsBit1(int num, unsigned int indexBit)
{
	num = num >> indexBit;
	return (num & 1);
}

最終的なコードが実装されます
。データは入力配列で、num1とnum2は除算後の2つの配列です。

void FindNumsAppearOnce(int data[], int length, int* num1, int* num2)
{
	if (data == nullptr || length < 2)
		return;

	int resultExclusiveOR = 0;//数组中所有数字异或过后的结果
	for (int i = 0; i < length; i++)
	{
		resultExclusiveOR ^= data[i];
	}

	unsigned int indexof1 = FindFirstBitIs(resultExclusiveOR);//找到异或后二进制结果中1的位置

	*num1 = *num2 = 0;
	for (int j = 0; j < length; j++)
	{
		if(IsBit1(data[j],indexof1)//数组中该位置为1的数在num1数组中进行异或,最后异或完剩下的值即为单出来的值
			*num1 ^= data[j];
		else
			*num2 ^= data[j];
	}
}

3.一度だけ現れる配列内の唯一の数

主なアイデア:
配列に1回だけ現れる数値に加えて、他の数値は3回現れる。一度だけ現れる数字を見つけてください。問題
分析:
この質問を詳しく見ると、前の質問と多くの類似点があることがわかりましたが、少し異なります。配列内の他の数値が2回だけ出現する場合、XORは単一の数値を取得できます。しかし、今では他の数値が3回出現し、3つの同一の数値をXORした結果はまだその数値であるため、上記のアイデアは実現できません。
次に、ビット演算について考えてみましょう。
配列が{5,5,5,2}であるとします。数値が3回出現する場合、2進数表現の各ビットも3回出現することはわかっています。数字の3つの出現すべてのバイナリ表現の各数字を個別に合計すると、各数字の合計は次のように3で除算できます。
ここに画像の説明を挿入
このとき、すべての数字のバイナリ表現の各数字を合計します。何を見つける
ここに画像の説明を挿入
3によってビットと割り切れるならばそうでない場合は1、番号が0である1のバイナリ表現に対応し、タイトル2として、のみ表示されますので、ことを、私たちが見つけ
上記付き法律、私たちはコードを書くことができます。バイナリ表現の各ビットの合計を格納するには、長さ32の補助配列が必要です。

int FindNumberAppearingOnce(int numbers[], int length)
{
	if (numbers == nullptr || length <= 0)
		throw new std::exception("Invalid input.");

	int bitSum[32] = { 0 };
	//外层循环主要用于遍历数组中的每一个数字
	for (int i = 0; i < length; i++)
	{
		//通过移动bitMask和数字中的每一位进行与操作,判断该位是否为1
		int bitMask = 1;

		//内层循环主要用于遍历该数字中的每一位
		for (int j = 32; j >= 0; j--)
		{
			int bit = numbers[i] & bitMask;
			if (bit & 1 != 0)
				bitSum[j] += 1;
		}
		bitMask << 1;
	}

	//依次输出只出现一次数字的每一位
	int result = 0;
	for (int i = 0; i < 32; i++)
	{
		result = result << 1;//移动每一位
		result += bitSum[i] % 3;//每一位和被3整除的结果即为只出现一次数字的该位
	}
	return result;
}

第4に、加算、減算、乗算、および除算して加算しない

タイトルの要件:
2つの整数の合計を求める関数を記述します。関数本体で「+」、「-」、「*」、「/」の4つの演算子を使用してはいけません。

問題分析:
この問題を分析する前に、問題を単純化し、従来のソリューションとして想像してみましょう。たとえば、10進数の加算を行うと、5 + 17 = 22の結果が得られます。
最初のステップでは、キャリーではなくメンバーのみを追加し、結果は12です。
2番目のステップは、5 + 7のうち10のキャリーでキャリーを実行することです。
3番目のステップは、上記の2つの結果を加算することです12 + 10 = 22

次に、同じ方法を使用しますが、この方法を2進加算に適用し、代わりにビット演算を使用します。たとえば、5のバイナリは101で、7のバイナリは10001です。
最初のステップは、キャリーに関係なく各ビットを追加することです。二項演算では、0 + 0 = 0、1 + 1 = 0、0 + 1 = 1、1 + 0 = 1であるためです。興味深いことに、そのような加算の結果はXORの結果であることがわかりました。
ここに画像の説明を挿入
ステップ2:キャリーを検討します。0+ 0,0 + 1,1 + 0の場合、キャリーは生成されず、1 + 1のみがキャリーを生成します。アナロジービット操作、すぐに思いついた操作。キャリーのプロセスは、これらの2つの2進数に対してAND演算を実行し、AND演算後の結果を最後に1ビット左にシフトするようなものです。
ここに画像の説明を挿入
ステップ3:加算のプロセスは、キャリーが生成されなくなるまで前の2つのステップを繰り返します。
ここに画像の説明を挿入
コードは次のように実装されます。 :

int Add(int num1, int num2)
{
	int sum, carry;
	do
	{
		sum = num1 ^ num2;
		carry = (num1 & num2) << 1;
		num1 = sum;
		num2 = carry;
	} while (num2 != 0);

	return num1;
}
公開された98元の記事 ウォンの賞賛9 ビュー3651

おすすめ

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