Skip to content

Conversation

@kazukiii
Copy link
Owner

問題へのリンク
https://leetcode.com/problems/first-unique-character-in-a-string/description/

次に解く問題
560. Subarray Sum Equals K

README.mdへ頭の中の言語化と記録をしています。

- ソートして見ていく
- ソートすることにより同じ文字は隣合う
- 元のインデックスを保持しておく必要あり
- time: O(n lon n), space: O(n log n) -> time, spaceともにソートの分
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ソートするときの空間計算量は O(n) ではないですか? in-place なら O(1) になると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。空間計算量O(log n)の書き間違いでした。(C++のstd:sort()の標準の実装であるIntrosortはquick sortベースのため、最悪の場合はスタックサイズO(log n)使うという認識です)

ですが、この問題の場合元のインデックスを保持する関係でO(n)になりますね。。

class Solution {
public:
    int firstUniqChar(string s) {
        vector<Character> s_with_index;
        for (int i = 0; i < s.size(); i++) {
            s_with_index.push_back({s[i], i});
        }
        sort(s_with_index.begin(), s_with_index.end(), [](const Character& a, const Character& b) {
            return a.character < b.character;
        });

        int count = 1;
        int answer = numeric_limits<int>::max();
        for (int i = 0; i < s_with_index.size(); i++) {
            if (i < s_with_index.size() - 1 && s_with_index[i].character == s_with_index[i + 1].character) {
                count++;
                continue;
            }
            if (count == 1) {
                answer = min(answer, s_with_index[i].index);
            }
            count = 1;
        }

        return answer != numeric_limits<int>::max() ? answer : -1;
    }

private:
    struct Character {
        char character;
        int index;
    };
};

@@ -0,0 +1,28 @@
class Solution {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step2 のように、 index を保持しないほうがシンプルに感じます。

char_to_count[s[i]].index = i;
}

int answer = INT_MAX;
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

step2 のように、見つかったら即 return した方がシンプルに感じます。

また、 INT_MAX より numeric_limits::max() を使用したほうが、 C++ 感があると思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

std::numeric_limits 知りませんでした。これから使っていこうと思います。
https://en.cppreference.com/w/cpp/types/numeric_limits

}

int answer = INT_MAX;
for (const auto& [character, count]: char_to_count) {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

x64 アーキテクチャーを仮定するのであれば、値が 64 ビット (8 バイト) 以内に収まる場合は、参照にしないほうが良いと思います。理由は、値が 64 ビット (8 バイト) 以内に収まる場合、汎用レジスター 1 本に格納できるためです。参照にした場合、変数にアクセスする際に毎回ポインター経由でアクセスされるようになるため、定数倍遅くなります。
ただ、実際にはコンパイラーが最適化してくれる可能性もあります。生成されるアセンブラーを確認したほうが良いかもしれません。

Effective C++ 第3版
20項 値渡しよりconst参照渡しを使おう
https://www.maruzen-publishing.co.jp/item/b294734.html

あたりに書いてあったと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます、このようなコメント嬉しいです。CPUアーキテクチャについては詳しくないので、時間を取って勉強しようと思います。
現状、汎用レジスターのサイズ = 値渡しか参照渡しかの判断基準 と暗記しておきます。(現代のコンピュータでは一般に64bit)

- time: O(n lon n), space: O(n log n) -> time, spaceともにソートの分
- 度数分布を取得してループ
- これも元のインデックスを保持する必要ありそう
- もしかしたらC++のunordered_mapは挿入順を保持する?
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あー、unordered_map がどんなものか確認しておいてください。

map は「挿入ではなくて key の」順序が保たれます。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます。HashTableの標準的な実装方法について調べました。

以下、理解した内容です。
HashTableは、標準的にはbucketの配列として実装される。keyのハッシュ値に基づいて各バケットに分配。
なので、iteratorを使って挿入した順に取り出せるという保証は全くない。
ハッシュ衝突の解決には、一般にchain法とopen address法の2種類ある。C++ではchain法で実装されることが多い。(cpythonはopen address法で実装されているようです)
chain法で衝突を解決する場合、bucketの実装にはlinked listを使用する。

一方、mapはbalanced BST(標準的にはred-black tree)を使って実装されており、各ノードにkey, valueを持っており、keyの大小関係によって並び替えられる。
https://en.cppreference.com/w/cpp/container/map

for (int i = 0; i < s.size(); i++) {
if (char_to_count[s[i]] == 1) return i;
}
return -1;
Copy link

@Yoshiki-Iwasa Yoshiki-Iwasa Jun 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

-1に名前をつけてあげるのも可読性をあげる選択肢かなと思います

const int NOT_FOUND = -1

みたいな
マジックナンバーは極力回避する方がいいのかなと

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントありがとうございます。マジックナンバーは極力避けた方が良いですね。

Comment on lines +4 to +5
map<int, char> first_index_to_char;
unordered_map<char, int> char_to_first_index;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ここの変数名、ちょっと違和感があります。
まず、変数名は"それが何であるか"を示すものという感覚があります。

"どのような構造か"は型情報から得られるので変数名に露出する必要は無いと思います

first_index_to_charchar_to_first_indexそれぞれなんのための変数かコードを下まで読まないとわからないので認知不可が高く感じました。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ちょっと完全に意図が汲み取れていないのですが、mapの構造に関連するような x_to_y のような変数名は認知不可が高いということでしょうか?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

恐らく変数名に型情報(今回の場合はchar)を入れる必要はない(又はもっと優先度の高い情報がある)という話ではないでしょうか。
多分charは型情報というよりは対象の文字という意味で使われているとは思うのですが。

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

あ、charの方ですね。おっしゃる通り対象の文字という意味で使っていました。
とはいえ、型名と被ると混乱しますね。気をつけます。

char_to_first_index[s[i]] = i;
}

return !first_index_to_char.empty() ? first_index_to_char.begin()->first : -1;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
return !first_index_to_char.empty() ? first_index_to_char.begin()->first : -1;
return first_index_to_char.empty() ? -1: first_index_to_char.begin()->first;

のほうがシンプルかなと思いました

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants