目次
1、一度だけ現れる数字
1、ビット演算
時間計算量と空間計算量の制約を考慮しない場合、この問題には多くの解決策があり、考えられる解決策は次のとおりです。
数値を保存するにはセットを使用します。配列内の各数値を調べます。数値がセットに存在しない場合は、その数値をセットに追加します。数値が既にセットに存在する場合は、その数値をセットから削除します。最後に残った数値が表示される数値になります。一度だけ。
ハッシュ テーブルを使用して、各数値とその数値が出現する回数を保存します。配列を走査することで、各数値の出現回数を取得してハッシュ テーブルを更新し、最後にハッシュ テーブルを走査して 1 回だけ出現する数値を取得します。
セットを使用して配列内に出現するすべての数値を格納し、配列内の要素の合計を計算します。セットでは重複する要素がないことが保証されているため、セット内のすべての要素の合計が 2 回計算されます。これは、各要素が 2 回出現する場合の要素の合計です。配列内の 1 つの要素のみが 1 回出現し、残りの要素は 2 回出現するため、配列内の要素の合計がセット内の要素の合計の 2 倍から減算され、残りの数値がのみ出現する数値になります。配列に一度。
上記の 3 つの解決策はすべて、O(n) の追加スペース使用量を必要とします (n は配列の長さ)。
線形時間計算量と一定空間計算量を実現するにはどうすればよいでしょうか?
答えは、ビット単位の演算を使用することです。この問題には、XOR 演算 ⊕ を使用できます。XOR演算には以下の3つの性質があります。
任意の数値と 0 の間で XOR 演算を実行すると、結果は元の数値のままになります (a⊕0=a)。
任意の数値はそれ自体と XOR 演算され、結果は 0、つまり a⊕a=0 になります。
XOR 演算は交換法則と結合法則、つまり a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b を満たします。
class Solution {
public:
int singleNumber(vector<int>& nums) {
int ret = 0;
for (auto e: nums) ret ^= e;
return ret;
}
};
複雑さの分析
-
時間計算量: O(n)、nn は配列の長さです。配列を 1 回繰り返すだけで済みます。
-
空間複雑度: O(1)。
次に、ほとんどの要素
説明
最も単純な総当りの方法は、配列内の各要素を列挙し、配列を走査して出現回数をカウントすることです。この方法の時間計算量は O(n^2) で、制限時間を超えてしまうため、時間計算量が O(n^2) 未満の優れた実践方法を見つける必要があります。
1. ハッシュテーブル
一連の考え
最も多く出現する要素が n/2 回より大きいことがわかっているため、ハッシュ テーブルを使用して各要素の出現数をすばやくカウントできます。
アルゴリズム
ハッシュ マップ (HashMap) を使用して、各要素と出現数を保存します。ハッシュ マップ内の各キーと値のペアについて、キーは要素を表し、値はその要素の出現数を表します。
ループを使用して配列 nums を走査し、配列内の各要素をハッシュ マップに追加します。この後、ハッシュ マップ内のすべてのキーと値のペアを反復処理し、最大値を持つキーを返します。最大値を維持するために配列 nums を走査するときに戦う方法を使用することもできます。これにより、ハッシュ マップの最終的な走査が節約されます。
class Solution {
public:
int majorityElement(vector<int>& nums) {
unordered_map<int, int> counts;
int majority = 0, cnt = 0;
for (int num: nums) {
++counts[num];
if (counts[num] > cnt) {
majority = num;
cnt = counts[num];
}
}
return majority;
}
};
複雑さの分析
時間計算量: O(n)、n は配列 nums の長さです。配列 nums を 1 回反復処理し、nums の各要素をハッシュ テーブルに挿入するのに一定の時間がかかります。走査中に最大値が維持されない場合は、走査後にハッシュ テーブルを走査する必要があります。これは、ハッシュ テーブルが占有するスペースが O(n) であるため (以下のスペースの複雑さの分析を参照)、走査時間は長くなりません。 O(n)を超えます。したがって、合計の時間計算量は O(n) になります。
空間の複雑さ: O(n)。
2. 並べ替え
一連の考え
配列 nums 内のすべての要素が単調増加または単調減少の順序でソートされている場合、添え字 n/2 (添え字は 0 から始まります) を持つ要素が多数である必要があります。
アルゴリズム
このアルゴリズムでは、最初に nums 配列を並べ替えてから、上記の添字に対応する要素を返します。以下の図は、この戦略が機能する理由を説明しています。下図において、1 番目の例は n が奇数の場合、2 番目の例は n が偶数の場合です。
class Solution { public: int majorityElement(vector<int>& nums) { sort(nums.begin(), nums.end()); return nums[nums.size() / 2]; } };
複雑さの分析
時間計算量: O(nlogn)。配列のソートの時間計算量は O(nlogn) です。
空間複雑度: O(logn)。言語に付属のソート アルゴリズムを使用する場合は、O(logn) スタック スペースを使用する必要があります。独自のヒープソートを作成する場合、使用する必要があるのは O(1) 個の追加スペースだけです。
3. ランダム化
一連の考え
n/2 を超える配列添字がモードによって占有されているため、添字に対応する要素をランダムに選択し、モードが見つかる可能性が高いことを確認します。
アルゴリズム
与えられた添え字に対応する数字が多数派である可能性が高いため、添え字をランダムに選択し、それが多数派であるかどうかを確認し、多数派である場合は戻り、そうでない場合はランダムに選択し続けます。
class Solution {
public:
int majorityElement(vector<int>& nums) {
while (true) {
int candidate = nums[rand() % nums.size()];
int count = 0;
for (int num : nums)
if (num == candidate)
++count;
if (count > nums.size() / 2)
return candidate;
}
return -1;
}
};
解決策を見てください。
3、3 つの数字の合計
解決策を見てください。