コード カプリス アルゴリズム トレーニング キャンプ 6 日目 | ハッシュ テーブルの理論的基礎、242. 効果的なアルファベット、349. 2 つの配列の交差、202. ハッピー ナンバー、1. 2 つの数値の合計

今日学んだ記事とビデオへのリンク

ハッシュテーブルの理論的根拠 記事リンク:リンク
242 記事リンク:リンク
242 動画解説リンク:リンク
349 記事リンク:リンク
349 動画解説リンク:リンク
202 記事リンク:リンク
202 動画解説なし
記事リンク1:リンク
1 動画解説リンク:リンク

ハッシュテーブルの理論的基礎

ハッシュテーブルの内部実装原理

公式説明: ハッシュ テーブルは、キー コードの値に従って直接アクセスされるデータ構造です。(直感的な認識力を持たない人が多いと推測されます)

私たちがよく知っている配列からハッシュ テーブルについて理解しましょう。実際、配列は ハッシュ テーブル です

ハッシュ テーブルのキー コードは配列のインデックスの添字であり、次の図に示すように、その添字を介して配列の要素に直接アクセスします。 ハッシュ テーブルの用途: 一般に、ハッシュ テーブルが存在するかどうかを迅速に判断するために使用され
ここに画像の説明を挿入
ます。要素がセット内に現れます

例: 名前がこの学校にあるかどうかを確認したい場合。

列挙すると時間計算量は O(n) ですが、ハッシュ テーブルを使用する場合の時間計算量は O(1) だけです。

操作方法: この学校の生徒の名前をハッシュ テーブルに入力するだけで、クエリ時にインデックスを通じてその生徒が学校に在籍しているかどうかを直接知ることができます。

では、生徒の名前はハッシュ テーブルとどのように関係するのでしょうか?

ここでハッシュ関数が使用され、学生名をハッシュ テーブルにマッピングします。

ハッシュ関数

ハッシュ関数は、生徒の名前をハッシュ テーブルのインデックスに直接マッピングし、インデックスの添え字をクエリすることで、その生徒が学校に在籍しているかどうかをすぐに知ることができます。

ハッシュ関数は、 hashCodeを通じて名前を値に変換します(特定のエンコーディングを使用して、他のデータ形式を異なる値に変換します)。
ここに画像の説明を挿入
しかし、hashCodeで取得した値がハッシュテーブルのサイズより大きい(tableSizeより大きい)場合はどうすればよいでしょうか?

このとき、マッピングされたインデックス値がすべてハッシュ テーブルに含まれることを確認するために、値に対して再度モジュロ演算を実行して、ハッシュ テーブルにマッピングできることを確認します。

しかし、学生の数がハッシュ テーブルのサイズよりも大きい場合はどうなるでしょうか? 現時点では、複数のクラスメート名が同時に同じインデックス添字位置にマッピングされることは避けられません。このケースを通じて、ハッシュの衝突が誘発されます。

ハッシュ衝突

複数の要素がインデックス添え字の位置にマップされると、ハッシュ衝突が発生します。
ここに画像の説明を挿入
一般に、ジッパー法と線形検出法の 2 つの解決策があります。

ジッパー方式

上の図に示すように、Xiao Li と Xiao Wang はインデックス 1 で競合しており、競合する要素はリンク リストに格納されています。インデックスから Xiao Li と Xiao Wang を見つけることができます。
ここに画像の説明を挿入
(データのサイズはdataSize、ハッシュテーブルのサイズはtableSize)

この方法では、配列の空の値によって大量のメモリが浪費されたり、リンク リストが長すぎるために検索時間が増加したりしないように、適切なリンク リスト サイズを選択する必要があります。

リニアプロービング

この方法を使用する場合は、tableSize が dataSize よりも大きいことを確認する必要があります。衝突はハッシュ テーブル内のスロットによって解決されます。

3 つの一般的なハッシュ構造

  • 配列
  • セット(コレクション)
  • 地図(マッピング)

C++ では、set と map はそれぞれ、次の 3 つのデータ構造を提供します。基礎となる実装とその利点と欠点を次の表に示します。
ここに画像の説明を挿入
ここに画像の説明を挿入
使用オプション:

  • ハッシュ問題を解決するには、クエリと削除の効率が最適であるunowned_setが推奨されます
  • コレクションを注文する必要がある場合は、set を使用します
  • 順序付けされたデータと繰り返しデータの両方が必要な場合は、マルチセットを使用します

要約:要素が set に含まれるかどうかを迅速に判断する必要がある場合は、ハッシュを考慮する必要があります。(時間のためにスペースを犠牲にする)

242. 効果的なアナグラム

最初にタイトルを見てください

タイトルの説明:
2 つの文字列 s と t が与えられた場合、t が s のアナグラムであるかどうかを判断する関数を作成します。

アナグラム: 同じ数の同じ文字が同じ順序ではありません。


単語内の文字が別の単語に出現しているかどうかを判断するには、まずハッシュ テーブルを使用することが考えられますs 内の文字の出現数を記録する配列を定義します。

また、文字数は合計 26 で、ASCII 文字も a から z まで連続しているため、サイズ 26 の配列レコードを定義し、0 に初期化します。

文字列 s をトラバースし、s[i] - 'a' が配置されている要素に対して +1 演算を実行しますこのようにして、 s 内の文字の出現数をカウントできます。

次に、文字列 t をトラバースし、t に表示される文字マッピング ハッシュ テーブルのインデックスの値に対して -1 演算を実行します。

最後に、レコード配列の要素が 0 ではないこと、つまり、両者の対応する文字数に差があることを確認し、 false を返します。

レコード配列のすべての要素が 0 の場合、文字列 s と t がアナグラムであることを意味し、 true を返します。

コードカプリスを読んだ感想

考え方は私と同じです

導入中に遭遇した困難

困難はありませんでした

コード

class Solution {
    
    
public:
    bool isAnagram(string s, string t) {
    
    
        int record[26]={
    
    0};
        for(int i = 0;i < s.size();i++){
    
    
            record[s[i] - 'a']++;
        }
        for(int i = 0;i < t.size();i++){
    
    
            record[t[i] - 'a']--;
        }
        for(int i = 0;i < 26;i++){
    
    
            if(record[i] != 0){
    
    
                return false;
            }
        }
        return true;
    }
};

349. 2 つの配列の交差

最初にタイトルを見てください

タイトル説明:

2 つの配列 nums1 と nums2 を指定すると、それらの共通部分を返します。出力内の各要素は一意で出力結果の順序は無視できます

次のようなアイデアがあります。

注:出力結果の各要素は一意である必要があります。つまり、出力結果は重複排除されており、出力結果の順序は無視できます。

タイトルを変更して値のサイズを制限すると、配列をハッシュ テーブルとして使用できます。

コードカプリスを読んだ感想

動画ではsetとarrayの2つの方法が出てきますが、私は配列を使ったことがあるので主にsetの使い方を理解しています。

セットの適用シナリオ:ハッシュ値が比較的小さく分散しておりスパンが大きい場合、この時点で配列を使用すると、スペースの無駄が発生しますこの場合、セットを使用する必要があります。

上記のことから、C++ ではset に利用可能な3 つのデータ構造が提供されていることがわかります。

  • std::set
  • std::マルチセット
  • std::unordered_set

std::set および std::multiset の基礎となる実装は赤黒ツリーであり、std::unowned_set の基礎となる実装はハッシュ テーブルです。

タイトルではデータを並べ替える必要がなく、データの繰り返しも許可されないため、現時点では読み書きにunowned_setを使用するのが最も効率的です。

std::unowned_set の基礎となる実装はハッシュ テーブルです。unordered_set を使用すると、読み取りと書き込みの効率が最も高くなります。データを並べ替える必要がなく、データの繰り返しも許可されないため、unowned_set が選択されます。

この考え方を次の図に示します。
ここに画像の説明を挿入

導入中に遭遇した困難

適切なunordered_setテンプレートはまだあまり馴染みがないため、学習する必要があります。

コード

class Solution {
    
    
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
    
    
        unordered_set<int> result_set;
        unordered_set<int> nums_set(nums1.begin(),nums1.end());
        for(int num : nums2){
    
    
            if(nums_set.find(num) != nums_set.end()){
    
    
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(),result_set.end());
    }
};

質問202. ハッピーナンバー

最初にタイトルを見てください

タイトル説明:

数値 n が幸せな数値であるかどうかを判断するアルゴリズムを作成します。

「ハッピーナンバー」とは、次のように定義されます。正の整数の場合、毎回その数値を各位置の数値の二乗和に置き換え、数値が 1 になるまでこのプロセスを繰り返すか、無限ループになる可能性がありますが、 1に変わることはありません。それが1になれば、この数字は幸せな数字です。

n が適切な数値の場合は True を返し、そうでない場合は False を返します。

次のようなアイデアがあります。

キー: ** ループが無限である場合、合計は合計プロセス中に繰り返し表示されます**

そこで、合計が繰り返されるかどうかをハッシュ法で判定し、繰り返される場合は false を返し、そうでない場合は 1 になるまで合計を求めます。

unowned_set を使用すると、合計が繰り返されるかどうかを判断できます

コードカプリスを読んだ感想

同じ考え

導入中に遭遇した困難

値の各桁に対する単数演算にはあまり詳しくありません

コード

class Solution {
    
    
public:
    // 取数值各个位上的单数之和
    int getSum(int n) {
    
    
        int sum = 0;
        while (n) {
    
    
            sum += (n % 10) * (n % 10);
            n /= 10;
        }
        return sum;
    }
    bool isHappy(int n) {
    
    
        unordered_set<int> set;
        while(1) {
    
    
            int sum = getSum(n);
            if (sum == 1) {
    
    
                return true;
            }
            // 如果这个sum曾经出现过,说明已经陷入了无限循环了,立刻return false
            if (set.find(sum) != set.end()) {
    
    
                return false;
            } else {
    
    
                set.insert(sum);
            }
            n = sum;
        }
    }
};

1. 2 つの数値の合計

最初にタイトルを見てください

タイトル説明:

整数配列 nums とターゲット値 target が与えられた場合、配列内で合計がターゲット値となる 2 つの整数を見つけて、それらの配列の添字を返してください。

この問題を解決するのは難しいです、そして私は地図についてあまり知りません。

コードカプリスを読んだ感想

この質問には、走査した要素を保存するコレクションが必要で、配列を走査するときにこのコレクションに、要素が走査されたかどうか、つまり要素がこのコレクションに表示されているかどうかを尋ねます。

この質問では、要素が走査されたかどうかを知る必要があるだけでなく、この要素に対応する添え字も知る必要があります。保存するキー値構造、要素を保存するキー、および保存する値を使用する必要があります。添字なので、map を使用するのが適切です。

なぜ以前に使用したセットを使用するのですか?

  • 配列のサイズには制限があり、要素が少なくハッシュ値が大きすぎる場合、メモリ空間が無駄に消費されてしまいます。
  • set はコレクションであり、その中の要素は key のみにすることができます。2 つの数値の和の問題では、y が存在するかどうかを判断するだけでなく、y の添え字の位置も記録する必要があります。 x と y の値を返す必要があります。したがって、セットも使用できません。

マップはキー値のストレージ構造であり、キーを使用して値を保存したり、値を使用して値が配置されている添え字を保存したりできます。

C++ の 3 種類のマップのうち、std::unowned_map を選択します。この質問ではキーの順序は必要なく、 std::unowned_map を選択する方が効率的であるためです

マップを使用する場合は、次の 2 つの点に注意してください。

  • 地図は何に使われますか
  • マップ内のキーと値は何を表しますか?

最初の点に関して、map の目的は、訪問した要素を保存することです。配列をトラバースするときに、現在の要素と一致する要素を見つけることができるように、以前に走査した要素と対応する添え字を記録する必要があるためです。要素 (つまり、ターゲットに等しいものを追加)

2 点目に関しては、この質問では、要素を指定し、その要素が出現したかどうかを判断し、出現した場合はその要素の添え字を返す必要があります。

要素が出現するかどうかを判断するには、この要素がキーとして使用されるため、配列内の要素がキーとして使用され、キーが値に対応し、値が添え字を格納するために使用されます。

したがって、マップ内の記憶構造は {key: データ要素、value: 配列要素に対応する添字} となります。

配列を走査するときは、マップをクエリして、現在走査されている要素に一致する値があるかどうかを確認するだけで済みます。存在する場合、それが見つかった一致ペアです。そうでない場合は、現在走査されている要素をマップに入力します。マップストアは私たちが訪れた要素です。

実装プロセスは次のとおりです。
ここに画像の説明を挿入

導入中に遭遇した困難

値の各桁に対する単数演算にはあまり詳しくありません

コード

class Solution {
    
    
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    
    
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
    
    
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
    
    
                return {
    
    iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {
    
    };
    }
};

今日は収穫

1. ハッシュテーブルの基本理論を理解する

2. セットとマップの適用シナリオを知る

3. テンプレートライブラリの使い方がまだあまり上手ではないので、今後強化する必要がある

今日の勉強時間は3時間です

この記事の写真はすべて Carl のコード カプリスからのものです。本当に感謝しています。

おすすめ

転載: blog.csdn.net/m0_46555669/article/details/127060997