Skip to content

Conversation

@syoshida20
Copy link
Owner

@syoshida20 syoshida20 commented May 6, 2025

問題URL

https://leetcode.com/problems/first-unique-character-in-a-string/

問題文

Given a string s, find the first non-repeating character in it and return its index. If it does not exist, return -1.

Example 1:

  • Input: s = "leetcode"
  • Output: 0
  • Explanation:
    The character 'l' at index 0 is the first character that does not occur at any other index.

Example 2:

  • Input: s = "loveleetcode"
  • Output: 2

Example 3:

  • Input: s = "aabb"
  • Output: -1

Constraints:

  • 1 <= s.length <= 105
  • s consists of only lowercase English letters.

@syoshida20 syoshida20 marked this pull request as ready for review May 21, 2025 21:57

* Pythonのcollections.Counterの内部実装
* https://github.com/python/cpython/blob/a66bae8bb52721ea597ade6222f83876f9e939ba/Lib/collections/__init__.py#L551
* Pure Pythonで書かれている。

Choose a reason for hiding this comment

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

_count_elements についてはCの実装があれば使われるようですね。

}
}
return -1
};

Choose a reason for hiding this comment

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

読みやすかったです。

Copy link

Choose a reason for hiding this comment

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

読みやすいです。個人的にはcountは切り出さずにsetのカッコの中にそのまま入れてしまってもいい気がします。

https://github.com/python/cpython/blob/a66bae8bb52721ea597ade6222f83876f9e939ba/Lib/collections/__init__.py#L89

* C++のmapは、keyでソートされている。
https://cplusplus.com/reference/map/map/
Copy link

Choose a reason for hiding this comment

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

std::mapは平衡二分木で,pythonのdictやJSのMapに対応するハッシュテーブルベースのデータ構造としてはstd::unordered_mapがあります.

* Ryotaro25
* PR: https://github.com/Ryotaro25/leetcode_first60/pull/16/
* 387.FirstUniqueCharacterinaString/step4.cpp の参照を実装している箇所が読めない。
--> ポインターや参照の箇所が理解できていない。次の問題までに勉強する。
Copy link

Choose a reason for hiding this comment

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

これ的外れだったら放念してください.
ポインターはデータの置いてあるアドレスを格納した変数です.
C言語で,ローカルスコープの変数を呼び出した関数内で変更しようとすると,デフォルトの挙動では変数がコピーされて渡されるので,もとの変数は書きかわりません.
なので呼び出した先で呼び出し元の変数を変更するには,元の変数のアドレス (=住所) を渡して,これの中身を見る (デリファレンス,*をつけます) ことが必要になります.
以下の例で言うと,fを呼び出した時は呼び出し元の引数は変化せず,gを呼び出した際には変化します.

#include <stdio.h>

void f(int a) {
  a = 10;
}

void g(int *a) {
  *a = 10;
}

int main() {
  int a = 0;
  f(a); // 変化しない
  printf("%d\n", a); // 出力: 0
  g(&a); // 変化する,`&`はアドレスを取得する演算子
  printf("%d\n", a); // 出力: 10
}

そこで,Cではしばしば,ポインタの書き換えを呼び出した関数内で行う,ということになると,ポインタのポインタのポインタの...というような面倒なことになることがあります.

一方,C++ではこの部分のインターフェースを参照という形で直感的で簡潔にしてくれていて,明示的にポインタを用いないでも,内部にポインタを用いて呼び出した関数内での変更を反映できるようになっています.

上の例で行けば,

#include <stdio.h>
// 引数が,`&`付きで,参照になっている
void f(int& a) {
  a = 10; // デリファレンスはしてないが,呼び出し元が書き換わる
}

int main() {
  int a = 0;
  f(a); // 今度はポインタを取らないが
  printf("%d\n", a); // これは10になっている
}

この方は,この参照の機能を自分で,Cライクな様式である,ポインタを用いて再実装してみた,ということをされているのだと思います.

* kazukiii
* PR: https://github.com/kazukiii/leetcode/pull/16/files
* nodchipさんのコメント
* 以下を理解できていないので、勉強する。
Copy link

Choose a reason for hiding this comment

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

このよしあしの差はかすかです。ただ、背景の仕組みは分かっていて欲しいです。
レジスターの大きさと値の大きさのどちらが大きいかの問題で、機械語にしたときに値が8バイト以下だと扱いが変わるので参照にしないほうが速そうだという話です。

Copy link
Owner Author

@syoshida20 syoshida20 May 26, 2025

Choose a reason for hiding this comment

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

本を読み、nodchipさんとodaさんの指摘が理解できました。

プロセッサの記憶階層は、以下のようになっている。

  • CPUの汎用レジスタは、 64ビットアーキテクチャだと8バイトを格納できる。 容量は64バイト-256バイトで、1サイクルでアクセスできる。
  • L1キャッシュは、容量が8KB-256KBで、数サイクルでアクセスできる。。
  • L2キャッシュは、1時キャッシュの10倍程度の容量を持ち、10-20サイクルでアクセスできる。
  • メモリは、容量は数GBで、数百サイクルでアクセスできる。

値が8バイト以下に収まるデータを考えた際に、

  • 参照渡しにした場合

    • レジスタには、ポインターが格納される。
    • 実際の値にアクセスするために、初回はメモリアクセス、2回目以降はL1キャッシュ、L2キャッシュへのアクセスが必要となる。
    • だから、定数倍遅くなる。
  • 値渡しにした場合

    • レジスタには、 値が直接格納される。
    • 直接レジスタから値を読み取るため1サイクルで済む。

実際にはコンパイラーが最適化してくれる可能性もあります。
このよしあしの差はかすかです。

コンパイラの中で、以下のような最適化が行われる。 だから、参照渡しでも値渡しと同等の性能が得られることがある。

  • 参照の間接参照を除去
  • 値を直接レジスタで処理

(プロセッサを支える技術の3章の「キャッシュの仕組み」から「プロセッサの記憶階層」 を参照しました。)

Copy link

Choose a reason for hiding this comment

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

値渡しにしても、なんらかの意味でメモリーから取ってくるはずなので、数百クロックもの差はないでしょう。
ただ、アドレス参照が一回増えるかもしれないという感覚があるからで、それが最適化で消えるかもしれないのも確かです。

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます!
コンパイル結果を見て、実際にどうなっているか比較するというのをやってみようと思います。

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants