Yuki Watanabe's Blog

Yuki Watanabe's Blog

2019年に経理からエンジニアに転身した人のブログ

何度も挫折した自分が、LeetCode150問でデータ構造とアルゴリズムをやっと理解できた話

データ構造とアルゴリズムを再学習する中で、LeetCodeを150問ほど解きました。その結果、これまで苦手だったグラフアルゴリズムも含め、自分で使いこなすレベルまで引き上げることができました。

本記事では、その過程で得た学びや、なぜこれまで挫折していたのかを振り返ります。

動機

これまで私は、データ構造とアルゴリズムに関する書籍を何冊か手に取り、並行してAtCoderの問題にも取り組んできました。しかし、どの書籍も終盤に差しかかるグラフの章にたどり着く頃にはモチベーションが落ちてしまい、内容の難しさも相まって、十分に理解しないまま学習を終えてしまうことが多くありました。

それでも数年に一度は学習を繰り返していたこともあり、その積み重ねが現職の入社試験におけるコーディングテストの突破につながりました。現在は転職の予定はありませんが、今後コーディングテストを受ける機会があっても安定して突破できるレベルまで、実力を上げておきたいと考えています。

また、コーディングテスト対策に限らず、コンピューターサイエンスの基礎としてデータ構造とアルゴリズムを体系的に理解することの重要性も強く感じていました。特に、これまで理解が曖昧だったグラフアルゴリズムについては、腰を据えてしっかり理解したいという思いがありました。

やったこと

今回の学習では、Web教材であるCodingInterviewCatを利用して理論を学びつつ、LeetCodeで問題演習を行い、合計で約150問の問題を解きました。150問という数は、LeetCodeユーザーの中では上位10%程度に位置する回答数であり、頻出分野を一通りカバーするための最低限のラインだと感じています。LeetCodeの問題は主にEasy〜Mediumを中心に解き、頻出パターンを網羅することを意識しました。毎日3時間程度の学習を50日続けたため、およそ150時間程度を要しました。

Web教材だけでは理解が難しい部分については、後述する書籍を補助的に参照しました(書籍に掲載されている演習問題は解いていません)。 また、LeetCodeの問題演習ではChatGPTを使い倒しました。具体的には以下のような場面です。

  • あと一歩で解けそうだが詰まっているときにヒントを得る
  • 解法の方針が正しいか確認する
  • 実装が汚くなってしまった時に、より綺麗な書き方を知る
  • 模範解答の意図が理解できないときの補助

これらを組み合わせることで、グラフを含めた主要なデータ構造・アルゴリズムを、自分でも使えるレベルまで理解することができました。

参考: Coding InterviewCat | InterviewCat - テック企業面接対策プラットフォーム

学び

LeetCodeの問題を解き続ける中で、まず大きく感じたのは「使えるデータ構造の引き出しが増えたこと」です。これまでは配列やハッシュテーブル程度しか扱えませんでしたが、スタック、キュー、ヒープ、連結リスト、木構造、Union-Findなどについて、それぞれの操作(挿入・削除・探索)にかかる計算量を理解し、状況に応じて適切に選択できるようになりました。

データ構造の理解が進んだことで、それらを前提とするアルゴリズムの理解も大きく深まりました。例えば、スタックや再帰を用いたDFS、キューを用いたBFSなどを、「なぜそのデータ構造を使うのか」という観点でも理解できるようになりました。

特に苦手意識を持っていた最短経路アルゴリズム(ダイクストラ法、ベルマン・フォード法、ワーシャル・フロイド法)についても、スムーズに理解できるようになり、自信がつきました。グラフの構造によっては、ダイクストラ法にヒープを組み合わせることで計算量を大幅に削減できるといった点も、ヒープを理解したからこそ腹落ちさせられたと思います。

また、LeetCodeはデータ構造とアルゴリズムにフォーカスした問題が多く、学んだ知識をすぐにアウトプットできる設計になっているため、「できることが着実に増えている」という実感を毎日得ることができたのも大きなポイントでした。

なぜ今までうまくいかなかったのか

今回手応えを感じたアルゴリズムとデータ構造に関する学習について、なぜ今までうまくいかなかったのかを分析してみました。

演習問題を復習していなかった

これまでの学習では、演習問題を一度解いて終わりにしてしまい、十分な復習を行えていませんでした。その結果、理解が曖昧なまま次の新しい内容に進んでしまうことが多くありました。

データ構造とアルゴリズムは、基礎的なトピックであれば単独でも理解できる場合がありますが、応用的な内容になると、他のデータ構造やアルゴリズムの理解を前提としていることが少なくありません。そのため、前提となる知識が不十分なまま新しい内容に取り組むと、途中で詰まってしまう場面が多いことに気づきました。例えば、幅優先探索(BFS)はキューが、深さ優先探索(DFS)はスタックや再帰の知識が前提となっていますよね。

そこで、演習問題の初回の理解度をスプレッドシートにまとめ、それに応じて復習を行う仕組みを取り入れました。具体的には、演習問題を以下の3つに分類しました。

  • ⭕:初見で解けた
  • 🔺:ヒントを見て解けた
  • ❌:ヒントを見ても解けなかった

平日は新規の問題に取り組み、休日は復習に充てるサイクルとしました。特に、❌が2回連続した問題(初回も復習時も解けなかった問題)については、なぜ解けなかったのかをChatGPTに壁打ちしながら整理し、自分の理解や発想のどこが不十分だったのかを深掘りするようにしました。

このように復習のプロセスを明確にしたことで、理解の抜け漏れを防ぎ、知識を定着させることができるようになりました。

AtCoder中心の学習

(※AtCoderは非常に優れたサイトであり、このサイトを通じてデータ構造とアルゴリズムの知識や経験を深めた方も多いと思います。あくまで、当時の自分には合っていなかった点について記載しています。)

これまで私は、書籍でインプットを行い、AtCoderで演習するという形で学習を進めていました。しかし、自分にはいくつか合わない点があり、効率よく学習を進めることができませんでした。

まず、AtCoderは問題文のストーリーを理解するのに時間がかかり、データ構造やアルゴリズムそのものに集中しづらいと感じていました。また、レートを上げることを目標とする場合、コンテストへの参加が前提となりますが、開催時間が夜であることが多く、朝型の自分には継続が難しいという課題もありました。(現在では、定期的なコンテスト参加が不要で一発認定が行われるPASTを併用することで、この問題はある程度解消できるかもしれません。)さらに、AtCoderはトピックごとに体系的に問題を解く構造にはなっていないため、基礎力が十分でない段階では学習効率が悪いと感じました。加えて、入出力処理を自分で実装する必要がある点も負担となり、アルゴリズムそのものに集中しづらい要因でした。

UIの観点でもいくつか課題を感じていました。AtCoderでは提出のたびにページ遷移が発生するため、試行錯誤のコストが高くなりがちです。また、Runtime Errorが発生した場合でも、どこでエラーが起きているかを把握できないという問題がありました。一方でLeetCodeは、同一画面上でサンプルケースの実行(Run)と全てのテストケースの実行(Submit)を行い成否を確認でき、エラー内容も表示されるため、デバッグがしやすい設計になっています。

加えて、方針自体は正しいにもかかわらず、境界値の扱いなど細かな部分でAC(正答)に時間がかかることも多くありました。これはLeetCodeでも同様に起こり得る問題ですが、LeetCodeではChatGPTを活用することで即座にフィードバックを得られ、改善につなげることができました。

その他の要因

書籍中心の学習では、紙面の制約から探索過程の説明が簡略化されていることが多く、「なぜその順序で探索されるのか」といったイメージが掴みにくいことも課題でした。 また、インプットに偏り、十分な演習を行っていなかったことも理解が定着しなかった原因だと感じています。

データ構造とアルゴリズムの学習するうえでの個人的結論

これらのの経験から、データ構造とアルゴリズムの学習において重要だと感じたポイントは以下の通りです。

  • 図や動画が充実したWeb教材で理論を学ぶ
  • LeetCodeで演習を積むことで理解を定着させる
  • 書籍は補助的に活用し、理解を補強する

この組み合わせが、最も効率よく学習を進める方法だと考えています。今後学び直しをするうえでも、このステップで行おうと思います。

教材の感想

今回の学習で利用した教材について、それぞれの特徴と感想をまとめます。

[Web] CodingInterviewCat

interviewcat.dev

CodingInterviewCatは「コーディング面接対策」を謳っていますが、実際にはデータ構造とアルゴリズムを体系的に学びたい人にも非常に適した教材だと感じました。自分自身、面接対策だけを目的としていたわけではありませんが、この教材がなければ今回の学習は途中で挫折していたと思います。29,800円という価格ですが、この教材のお陰で100時間以上学習を行えたため、十分払う価値があったと感じています。

理論の解説と例題がセットになっており、学んだ内容をすぐに演習できる構成になっています。このスタイルは高校数学の参考書である青チャートを元に考案されているようで、実際に非常に効果的でした。難易度が★〜★★★★★で示されている点も、問題の取捨選択に役立ちました。自分は高校時代に青チャートを何十回も解き直し数学が得意になった経験があるため、この青チャートを元に考案されたというコンセプトにかなり期待感がありましたが、実際にやってみてこの理論+演習のセットこそがこの分野の学習に最も効果的だと感じました。

特に良かった点として、BFSやDFSの導入が非常に丁寧であることが挙げられます。多くの書籍ではグリッドグラフを前提に説明されることが多いですが、本教材ではまず木構造での理解を深めてからグラフへ進む構成になっており、ステップバイステップで理解することができました。また、「スタックによるDFSは理解が難しくなりがち」といったように、初学者目線での割り切った説明も多く、つまずきにくい設計になっている点も印象的でした。

一方で、Bit Manipulationや貪欲法など一部の分野のコンテンツは未整備であるため、この点は今後の拡充に期待したいところです。

似たコンセプトの教材としてNeetCodeもありますが、英語であるため、日本語で学びたい場合はCodingInterviewCatの方が取り組みやすいと感じます。自分は今後、NeetCodeにも取り組む予定です。

[書籍] 独学コンピューターサイエンティスト Pythonで学ぶアルゴリズムとデータ構造

Pythonで書かれているため非常に読みやすく、データ構造とアルゴリズムの入門書として優れた一冊でした。各トピックが広く浅く整理されており、詰まることなく最後まで読み切ることができました。

データ構造やアルゴリズムの説明の後に、コーディングテストを意識した問題も掲載されており、学んだ内容をどのように応用するかをイメージしやすい構成になっています。また、図が豊富で探索過程を視覚的に理解できる点も良かったです。 これまでC++ベースの書籍で挫折してきた自分にとって、Pythonで書かれていることは大きなメリットであり、初めて最後まで読破できた書籍でもありました。

[書籍] 問題解決力を鍛える! アルゴリズムとデータ構造

本書はC++で書かれていますが、すでにPythonで基礎を学んでいたため、復習としてスムーズに読み進めることができました。図やイラストが豊富で、探索やデータ構造の遷移が非常に分かりやすく説明されています。 また、計算量やアルゴリズムの背景にある数学的な説明も丁寧で、「なぜそうなるのか」を深く理解したいときに非常に役立ちました(難しい証明部分は適宜飛ばしながら読みました)。

特にグラフアルゴリズムの解説が充実しており、この分野を強化する上で非常に有用でした。一方で、内容はやや高度であるため、初学者が最初の一冊として読むにはハードルが高いと感じました。実際、自分も最初にこの本に取り組んだ際は理解に時間がかかり、学習が進まない原因の一つになっていました。

まとめ

今回、LeetCodeを150問解くことで、データ構造とアルゴリズムに対する知識が増え、自分で使いこなせるレベルになってきました。特に、これまで苦手意識のあったグラフアルゴリズムについても、演習を通じて体系的に理解できました。

振り返ってみると、これまでうまくいかなかった原因は、インプット中心の学習や、自分に合わない教材・環境での演習にありました。一方で、今回のように「わかりやすいWeb教材で学び、LeetCodeで演習し、必要に応じて書籍で補強する」というサイクルを回すことで、効率よく理解を深められることがわかりました。

今後は、今まで解いたLeetCodeの問題の復習や、NeetCodeで新しい分野を学ぶなどにチャレンジしていきたいと思っています。

アルゴリズムとデータ構造に関するC++とPythonについてのまとめ

最近、アルゴリズムとデータ構造に関して学び直しを行っています。Pythonを用いて、LeetCodeの問題を解くことに主軸を置いています。参考に書籍を読むこともありますが、書籍のコードにC++が使われていることが多く、読み解くのに少し時間がかかります。そこで、アルゴリズムとデータ構造に関する書籍に頻出するC++の記法のみを、Pythonと対応付けながらざっと学習したいと考えました。上記のことをChatGPTに依頼したところ、個人的に満足の行くものが出力できたので、本ブログに掲載しておきます。

(ブログに載せておくと目次をつけることで見たいコンテンツにすぐにたどり着けるため、主に個人用途です。)


競プロやアルゴリズム本でよく出る C++の定番記法 を、意味Pythonでの対応 がすぐ分かる形でまとめるね。

最初に結論を言うと、アルゴリズム本を読むために必要なのは、C++を全部覚えることではなくて、

  • 配列・文字列
  • ループ
  • 関数
  • pair / vector
  • sort
  • queue / stack / priority_queue
  • set / map
  • lower_bound / upper_bound
  • bit演算

このあたりを読めるようになることです。 ここを押さえるだけで、かなり読みやすくなる。


1. 入出力

C++

#include <iostream>
using namespace std;

int main() {
    int n;
    cin >> n;
    cout << n << endl;
}

Python

n = int(input())
print(n)

複数入力

C++

int a, b;
cin >> a >> b;

Python

a, b = map(int, input().split())

配列入力

C++

int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) cin >> a[i];

Python

n = int(input())
a = list(map(int, input().split()))

2. 変数宣言

C++

int x = 3;
long long y = 10000000000;
double z = 1.5;
string s = "abc";
bool ok = true;

Python

x = 3
y = 10000000000
z = 1.5
s = "abc"
ok = True

よく見る型

C++

  • int : 普通の整数
  • long long : 大きい整数
  • double : 実数
  • string : 文字列
  • bool : 真偽値

Python は整数がかなり大きくなってもそのまま扱えるので、 C++ の long long を Python では普通の int と考えてよいことが多い。


3. 配列・vector

C++

vector<int> a = {1, 2, 3};
cout << a[0] << endl;
a.push_back(4);

Python

a = [1, 2, 3]
print(a[0])
a.append(4)

長さ n の配列を作る

C++

vector<int> a(n);

これは 0 で初期化された長さ n の配列

Python

a = [0] * n

2次元配列

C++

vector<vector<int>> dp(n, vector<int>(m, 0));

Python

dp = [[0] * m for _ in range(n)]

注意として、Python で次はダメなことが多い。

dp = [[0] * m] * n

これだと内側のリストが同じものを参照してしまう。


4. ループ

for 文

C++

for (int i = 0; i < n; i++) {
    cout << i << endl;
}

Python

for i in range(n):
    print(i)

1 から n まで

C++

for (int i = 1; i <= n; i++) {
}

Python

for i in range(1, n + 1):
    pass

逆順ループ

C++

for (int i = n - 1; i >= 0; i--) {
}

Python

for i in range(n - 1, -1, -1):
    pass

while 文

C++

while (x > 0) {
    x--;
}

Python

while x > 0:
    x -= 1

5. 条件分岐

C++

if (x > 0) {
    cout << "positive" << endl;
} else if (x == 0) {
    cout << "zero" << endl;
} else {
    cout << "negative" << endl;
}

Python

if x > 0:
    print("positive")
elif x == 0:
    print("zero")
else:
    print("negative")

6. 関数

C++

int add(int a, int b) {
    return a + b;
}

Python

def add(a, b):
    return a + b

bool を返す関数

C++

bool is_even(int x) {
    return x % 2 == 0;
}

Python

def is_even(x):
    return x % 2 == 0

7. 文字列

C++

string s = "abc";
cout << s[0] << endl;
cout << s.size() << endl;

Python

s = "abc"
print(s[0])
print(len(s))

部分文字列

C++

string s = "abcdef";
cout << s.substr(2, 3) << endl;  // "cde"

Python

s = "abcdef"
print(s[2:5])  # "cde"

C++ の substr(開始位置, 長さ) と Python の s[l:r] は少し感覚が違うので注意。


8. pair

C++本ではかなりよく出る。

C++

pair<int, int> p = {3, 5};
cout << p.first << " " << p.second << endl;

Python

p = (3, 5)
print(p[0], p[1])

pair の配列

C++

vector<pair<int, int>> vp;
vp.push_back({2, 10});
vp.push_back({1, 20});

Python

vp = []
vp.append((2, 10))
vp.append((1, 20))

9. auto

C++ では型を省略して auto をよく使う。

C++

auto x = 10;
auto p = make_pair(3, 4);

Python

x = 10
p = (3, 4)

Python は基本的に全部これに近い。


10. sort

昇順ソート

C++

sort(a.begin(), a.end());

Python

a.sort()

降順ソート

C++

sort(a.begin(), a.end(), greater<int>());

Python

a.sort(reverse=True)

pair のソート

C++

vector<pair<int, int>> vp = {{2, 3}, {1, 5}, {2, 1}};
sort(vp.begin(), vp.end());

これは first を優先し、同じなら second で昇順

Python

vp = [(2, 3), (1, 5), (2, 1)]
vp.sort()

Python のタプルも同じルールで比較される。


key を使ったソート

C++

sort(v.begin(), v.end(), [](int a, int b) {
    return abs(a) < abs(b);
});

Python

v.sort(key=abs)

11. 最大値・最小値

C++

int x = max(a, b);
int y = min(a, b);

Python

x = max(a, b)
y = min(a, b)

配列の最大最小

C++

int mx = *max_element(a.begin(), a.end());
int mn = *min_element(a.begin(), a.end());

Python

mx = max(a)
mn = min(a)

12. 合計

C++

int sum = accumulate(a.begin(), a.end(), 0);

Python

total = sum(a)

C++ では #include <numeric> が必要なことが多い。


13. 二分探索

これは本当に頻出。

lower_bound

C++

int idx = lower_bound(a.begin(), a.end(), x) - a.begin();

意味:

  • a[idx] >= x となる最初の位置

Python

import bisect
idx = bisect.bisect_left(a, x)

upper_bound

C++

int idx = upper_bound(a.begin(), a.end(), x) - a.begin();

意味:

  • a[idx] > x となる最初の位置

Python

import bisect
idx = bisect.bisect_right(a, x)

14. set

C++

set<int> st;
st.insert(3);
st.insert(1);
st.insert(3);
cout << st.count(3) << endl;

Python

st = set()
st.add(3)
st.add(1)
st.add(3)
print(3 in st)

違い

C++ の set常にソートされた状態。 Python の set順序なし

だから、

C++

set<int> st = {3, 1, 2};
// 取り出すと 1, 2, 3 の順になりやすい

Python

st = {3, 1, 2}
# 順序は保証されない

C++ の set に近いことを Python でやりたいなら、

  • set に入れて
  • 必要時に sorted(st)

とすることが多い。


15. map

C++

map<string, int> mp;
mp["apple"] = 3;
mp["banana"]++;
cout << mp["apple"] << endl;

Python

mp = {}
mp["apple"] = 3
mp["banana"] = mp.get("banana", 0) + 1
print(mp["apple"])

Counter 的な使い方

C++

map<int, int> cnt;
for (int x : a) cnt[x]++;

Python

cnt = {}
for x in a:
    cnt[x] = cnt.get(x, 0) + 1

または

from collections import Counter
cnt = Counter(a)

16. queue

C++

queue<int> q;
q.push(3);
q.push(5);
cout << q.front() << endl;
q.pop();

Python

from collections import deque

q = deque()
q.append(3)
q.append(5)
print(q[0])
q.popleft()

BFS ではこれを使う。


17. stack

C++

stack<int> st;
st.push(3);
st.push(5);
cout << st.top() << endl;
st.pop();

Python

st = []
st.append(3)
st.append(5)
print(st[-1])
st.pop()

Python ではリストを stack として使うことが多い。


18. priority_queue

ヒープ。ダイクストラ法などでよく出る。

C++

priority_queue<int> pq;
pq.push(3);
pq.push(5);
cout << pq.top() << endl;  // 5

これは 最大ヒープ

Python

import heapq

pq = []
heapq.heappush(pq, 3)
heapq.heappush(pq, 5)
print(pq[0])  # 3

Python の heapq最小ヒープ


最小値を取りたい C++ と対応させる

C++

priority_queue<int, vector<int>, greater<int>> pq;

Python

import heapq
pq = []

このときは両方とも最小ヒープとして考えられる。


pair を入れる

C++

priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;
pq.push({dist, v});

Python

import heapq
pq = []
heapq.heappush(pq, (dist, v))

タプル比較で先頭から比較されるので、かなり対応しやすい。


19. foreach のような書き方

C++

for (int x : a) {
    cout << x << endl;
}

Python

for x in a:
    print(x)

pair を同時に受け取る

C++

for (auto [x, y] : vp) {
    cout << x << " " << y << endl;
}

Python

for x, y in vp:
    print(x, y)

20. 添字つきループ

C++

for (int i = 0; i < a.size(); i++) {
    cout << i << " " << a[i] << endl;
}

Python

for i, x in enumerate(a):
    print(i, x)

21. 参照

本でこれが出ると一気に読みづらくなりやすい。

C++

void add_one(int& x) {
    x++;
}

これは x を直接変更する

Python

Python には同じ意味の参照構文はないので、普通は返り値で書く。

def add_one(x):
    return x + 1

ただし、list などのミュータブルなものは中身を直接変えられる。

def push_item(a):
    a.append(1)

22. const

C++

const int INF = 1e9;

Python

INF = 10**9

Python に厳密な const はない。 大文字で「定数っぽい」と表すことが多い。


23. 無限大の表現

C++

const int INF = 1e9;
const long long INF = 1e18;

Python

INF = 10**18

または

INF = float('inf')

ただし競プロでは整数で持つほうが扱いやすいことも多い。


24. 構造体 struct

C++

struct Node {
    int to;
    int cost;
};

Python

簡単にはタプルで済ませることが多い。

node = (to, cost)

あるいは class。

class Node:
    def __init__(self, to, cost):
        self.to = to
        self.cost = cost

アルゴリズムでは Python ではタプルで十分なことが多い。


25. グラフの隣接リスト

C++

vector<vector<int>> graph(n);
graph[u].push_back(v);
graph[v].push_back(u);

Python

graph = [[] for _ in range(n)]
graph[u].append(v)
graph[v].append(u)

重み付きグラフ

C++

vector<vector<pair<int, int>>> graph(n);
graph[u].push_back({v, w});

Python

graph = [[] for _ in range(n)]
graph[u].append((v, w))

26. DP 配列

C++

vector<int> dp(n, INF);
dp[0] = 0;

Python

dp = [INF] * n
dp[0] = 0

2次元DP

C++

vector<vector<int>> dp(n + 1, vector<int>(m + 1, 0));

Python

dp = [[0] * (m + 1) for _ in range(n + 1)]

27. bit演算

AND, OR, XOR

C++

a & b
a | b
a ^ b

Python

a & b
a | b
a ^ b

同じ。


シフト

C++

1 << i

Python

1 << i

これも同じ。


i ビット目が立っているか

C++

if (x & (1 << i)) {
}

Python

if x & (1 << i):
    pass

28. 全探索でよくある bit 列挙

C++

for (int bit = 0; bit < (1 << n); bit++) {
    for (int i = 0; i < n; i++) {
        if (bit & (1 << i)) {
            // i を使う
        }
    }
}

Python

for bit in range(1 << n):
    for i in range(n):
        if bit & (1 << i):
            # i を使う
            pass

29. lambda

C++

auto f = [](int x, int y) {
    return x + y;
};

Python

f = lambda x, y: x + y

ただし Python では複雑な処理なら普通に def を使うほうが読みやすい。


30. 三項演算子

C++

int x = (a > b ? a : b);

Python

x = a if a > b else b

31. よくあるマクロ・別名

本や競プロ解説でかなり出る。

C++

using ll = long long;
using pii = pair<int, int>;

Python

対応する特別な文法はない。普通にそのまま書く。

# ll -> int と考えて読む
# pii -> tuple[int, int] くらいの気持ちで読む

よくある省略

C++

typedef long long ll;
#define rep(i, n) for (int i = 0; i < (n); i++)

これは競プロ特有。

rep(i, n) {
    cout << i << endl;
}

実質こういう意味。

for (int i = 0; i < n; i++) {
    cout << i << endl;
}

Pythonなら普通に

for i in range(n):
    print(i)

32. emplace_backpush_back

C++

v.push_back({x, y});
v.emplace_back(x, y);

アルゴリズム本を読む範囲では、どちらも 「末尾に追加してる」くらいで読んで大丈夫。

Python

v.append((x, y))

33. begin(), end()

C++

sort(a.begin(), a.end());
reverse(a.begin(), a.end());

a 全体に対して処理していると思えばよい。

Python

a.sort()
a.reverse()

34. size()

C++

a.size()
s.size()

Python

len(a)
len(s)

35. empty()

C++

if (q.empty()) {
}

Python

if not q:
    pass

36. first, second

C++

pair<int, int> p = {2, 5};
cout << p.first << p.second << endl;

Python

p = (2, 5)
print(p[0], p[1])

あるいはアンパック。

x, y = p
print(x, y)

37. よくある C++ のアルゴリズムコードを Python に読む例

例1: 配列の和

C++

int sum = 0;
for (int i = 0; i < n; i++) {
    sum += a[i];
}

Python

total = 0
for i in range(n):
    total += a[i]

または

total = sum(a)

例2: 最小値を探す

C++

int ans = INF;
for (int i = 0; i < n; i++) {
    ans = min(ans, a[i]);
}

Python

ans = INF
for i in range(n):
    ans = min(ans, a[i])

例3: BFS

C++

queue<int> q;
q.push(s);
dist[s] = 0;

while (!q.empty()) {
    int v = q.front();
    q.pop();
    for (int nv : graph[v]) {
        if (dist[nv] != -1) continue;
        dist[nv] = dist[v] + 1;
        q.push(nv);
    }
}

Python

from collections import deque

q = deque([s])
dist[s] = 0

while q:
    v = q.popleft()
    for nv in graph[v]:
        if dist[nv] != -1:
            continue
        dist[nv] = dist[v] + 1
        q.append(nv)

例4: ダイクストラ

C++

priority_queue<pair<long long, int>,
               vector<pair<long long, int>>,
               greater<pair<long long, int>>> pq;

pq.push({0, s});
dist[s] = 0;

while (!pq.empty()) {
    auto [d, v] = pq.top();
    pq.pop();
    if (d > dist[v]) continue;

    for (auto [nv, cost] : graph[v]) {
        if (dist[nv] > dist[v] + cost) {
            dist[nv] = dist[v] + cost;
            pq.push({dist[nv], nv});
        }
    }
}

Python

import heapq

pq = [(0, s)]
dist[s] = 0

while pq:
    d, v = heapq.heappop(pq)
    if d > dist[v]:
        continue

    for nv, cost in graph[v]:
        if dist[nv] > dist[v] + cost:
            dist[nv] = dist[v] + cost
            heapq.heappush(pq, (dist[nv], nv))

38. まず読めるようになるための対応表

かなり実用的な最小セットだけ抜くとこうです。

C++ Python
vector<int> a(n) a = [0] * n
a.push_back(x) a.append(x)
a.size() len(a)
sort(a.begin(), a.end()) a.sort()
reverse(a.begin(), a.end()) a.reverse()
pair<int,int> (x, y)
p.first, p.second p[0], p[1]
queue<int> deque
stack<int> list
priority_queue heapq
set<int> set
map<int,int> dict
lower_bound bisect_left
upper_bound bisect_right
for (int i=0; i<n; i++) for i in range(n)
for (auto x : a) for x in a
if (...) continue; if ...: continue

39. 読書中に特に困りやすいポイント

最後に、C++本をPython学習者が読むときのコツを書いておくね。

1. 型はあまり気にしすぎなくてよい

int, long long, pair<int,int> などが並んでいても、 まずは

  • 整数
  • 配列
  • 2つ組

くらいにざっくり読む。


2. vector は Python の list だと思ってよい

ほとんどの場面でそれで十分。


3. pair はタプル

{dist, v}(dist, v) と読めばかなり楽になる。


4. priority_queue だけは注意

C++ はデフォルトで最大ヒープ、Python は最小ヒープ。 ここは混乱しやすい。


5. lower_boundbisect_left

二分探索の章ではほぼこれと思ってよい。


6. 競プロ特有の省略記法は展開して読む

たとえば

rep(i, n)

を見たら、頭の中で

for (int i = 0; i < n; i++)

に展開する。


40. おすすめの読み方

本を読むときは、C++ を Python に逐一完全翻訳しようとすると遅いので、

  1. データ構造だけ対応づける

    • vectorlist
    • pairtuple
    • queuedeque
  2. ループだけ読み替える

    • for (int i = 0; i < n; i++)for i in range(n)
  3. アルゴリズムの本質だけ追う

    • 何を状態にしているか
    • どこで更新しているか
    • 何を最小化 / 最大化しているか

この順で見ると、かなり楽になる。

『実践サイバーセキュリティ入門講座』を読みました

『実践サイバーセキュリティ入門講座 現場に残された痕跡からハッカーの攻撃を暴け』を読みました。本書を手に取った動機は、サイバーセキュリティを「攻撃する側」ではなく「守る側」の視点から体系的に学びたいと感じたからです。これまでにもTryHackMeのような攻撃側の視点に立った学習コンテンツを利用したことはありましたが、防御側としてどのように攻撃の痕跡を追い、解明していくのかを実践的に学べる教材はそれほど多くない印象がありました。そのような中で、本書はBTLO(Blue Team Labs Online)を活用しながら、防御の立場で学習を進められる構成になっており、とても魅力的に感じました。

感想

良かった点は、実際に手を動かしながら学べる点です。特に印象的だったのは、メモリのフォレンジック解析を体験できたことでした。これまで私は、メモリ解析というと専用の物理機器がなければ実施できないものだと思い込んでいました。しかし、本書ではVolatility 3を用いてメモリダンプファイルを解析する手法が紹介されており、PC上で比較的手軽に解析が行えることを知り、驚きがありました。実際にVolatility 3を使って解析を進めると、マシンで起動していたプロセスや開いていたファイルの情報まで確認でき、そこからランサムウェアの侵入経路を推測していく過程は非常にスリリングでした。

また、ディスクのフォレンジック解析ではAutopsyを利用し、PCの利用履歴を明らかにしたり、匿名メッセージアプリであるSignalのデータを復元したりする体験ができました。

少し脱線しますが、廃棄したスマホやPCのデータが専門家によって復元できるケースを見聞きしたことがあります。本書でディスクフォレンジックの基礎に触れたことで、専門家がどのような手順でデータの復旧を行っているのか、完全に理解したわけではないものの、以前より具体的に想像できるようになったと感じています。

一方で注意点として、本書では前提条件として「基本的なコンピューター操作やインターネットの利用経験」が挙げられていますが、実際にはプロセスやメモリといったコンピューターサイエンスの基礎知識もある程度求められると感じました。エンジニアリングの知識があれば理解しやすい内容ですが、完全な初学者にとってはやや難易度が高い部分もあるかもしれません。

最後に

私は将来的にサイバーセキュリティ対策のスペシャリストを担う立場になる予定はありませんが、エンジニアとしてセキュリティ意識を高めることは不可欠だと考えています。本書で得た学びは、その第一歩として非常に有意義なものでした。今後は設計面からセキュリティを学び、防御力の高いソフトウェア開発を行っていきたいです。

『7日間でハッキングをはじめる本』を読みました

本書『7日間でハッキングをはじめる本 TryHackMeを使って身体で覚える攻撃手法と脆弱性』を手に取った動機は、セキュリティ技術を座学だけでなく、実際に手を動かしながら学んでみたいと考えたためです。これまで攻撃に関する用語は知識としては持っていたものの、具体的に体験したことはありませんでした。そのため、ハンズオンで学べるという点に魅力を感じ、本書を読み進めました。

感想

本書の良かった点は、TryHackMe という学習サイトを使用して、攻撃手法を実際に操作しながら学べる構成になっていることです。たとえば、座学でポートスキャンの概念を学んだことはありましたが、nmap コマンドを用いることで想像以上に簡単に実行できてしまうことに驚きました。さらに、nmap コマンドはポートの確認だけでなく脆弱性の検出まで可能であることを知り、攻撃がいかに身近なツールで実現できてしまうのかを実感しました。

また、metasploit を利用して Windows マシンに侵入し、パスワードを窃取する一連の流れを体験したり、Burp を使って通信内容を書き換えたりするなど、実践的な内容が豊富に盛り込まれていました。特に metasploit を使えば、既知の脆弱性に対する攻撃を非常に容易に行えてしまうことがわかりました。

他にも、過去に流出したパスワードを元に作成された rockyou ファイルと呼ばれるパスワードリストを用いてパスワードリスト攻撃を行うなど、総当たり攻撃の現実味を体験できたことは非常に印象的でした。

著者の語り口も特徴的で、「〜を改ざんしてテンションを上げてみましょう」といった表現があり、技術解説にとどまらず、わくわくしながら読み進められる工夫がなされ、楽しみながら学習を続けることができました。

本書とあわせて副読本として『ホワイトハッカー入門 第2版』も読み、攻撃者側の視点に立ち、理論をより広く学ぶことができました。ハンズオンを通して攻撃手法が身についたからこそ、こうした理論面の本も深く吸収できていると感じました。

最後に

今回の読書を通じて、既知のソフトウェアの脆弱性を実際に体験することで、セキュリティパッチが適用された最新版を維持することの重要性も強く実感しました。既知の脆弱性を持つソフトウェアに対する攻撃は非常に容易であり、アップデートを怠ることのリスクは想像以上に大きいと感じました。今後はエンジニアとして、セキュアな設計や実装の原則についても知識を広げていきたいと考えています。

『リレーショナルデータベース入門 』を読みました

バックエンドエンジニアとして業務に携わる中で、データベース分野に強い関心を持つようになり、今後の自分の強みとして伸ばしていきたいと考えています。本業や副業のいずれでも、これまで継続的にDB関連の開発に携わってきたこともあり、より基礎から体系的に理解したいと思い、本書『リレーショナルデータベース入門: データモデル・SQL・管理システム・NoSQL』を手に取りました。

感想

本書は、SQLの使い方といった実践的な操作方法よりも、データベースの設計思想や内部構造に重点が置かれており、集合論やリレーショナル代数といった理論的な内容が丁寧に解説されています。そのため、集合論の記述が多く、ある程度の慣れが必要だと感じました。

また、データベースの歴史についても多く触れられており、現在主流となっているリレーショナルデータベースがどのような背景や経緯を経て発展してきたのかを知ることができた点は印象的でした。技術の進化を文脈込みで理解できるのは、本書ならではの魅力だと思います。

一方で、大学の教科書として使われることを想定された書籍であるため、全体的に文体は硬く感じ、サクサクとは読み進められるものではありませんでした。、また、1つのテーマが1章あたり20〜40ページ程度でまとめられている分、詳細な噛み砕いた説明は少なく、理解が追いつかない箇所もありました。個人的には、各テーマをより易しいレベルから扱っている他書を先に読んだうえで、あらためて本書に戻ってくると、より理解が深まりそうだと感じています。

DBに関するさまざまなトピックを俯瞰するという意味ではとても良い書籍ですが、1つのテーマを深く掘り下げられるわけではなく、かつ平易な説明でもないため、難易度は高めです。ただし、トピック同士の関連性が理論を交えて説明されている点は価値が高く、今後も何度も読み返したい一冊だと感じました。

特に印象に残ったのは、クエリのコスト計算の考え方や、トランザクションにおける多版実行制御(MVCC)の説明です。これらは普段の業務ではブラックボックスとして扱いがちな部分ですが、内部の仕組みを知ることで、設計やパフォーマンスを意識した実装につなげられそうだと感じました。

最後に

本書を通じて、リレーショナルデータベースの理論と歴史について詳しく知ることができました。今回は難しく感じる部分も多かったため、関連する入門書を読んでから再度チャレンジし、設計理論やトランザクション、クエリ最適化といった分野についても、より深く理解していきたいと考えています。

エンジニア6年目 2025年振り返り

2025年は1月に第二子が生まれ、その後は育休を取得していました。本業や副業での稼働はなかったため、今年の振り返りは主に個人学習と育児がメイントピックとなります。

過去の振り返り記事

個人学習

1-7月: 統計

育休期間中は、普段使っている技術や今後使うであろう技術の「基礎」を改めて学び直そうと考えていました。特に近年のAIの台頭を受けて統計学の重要性を強く感じ、学習を始めることにしました。最初に手に取った入門書が非常に面白く、より広く深く学んでみたいと感じたことから、統計検定の学習にも取り組みました。

結果として、3月に統計検定2級、7月に準1級を取得しました。統計学の学習過程では線形代数微分積分の学び直しも行えたため、数式に対する抵抗感が以前よりも小さくなったと感じています。

統計検定の学習については、以下の記事で詳しくまとめています。

yuki0920.hatenablog.jp yuki0920.hatenablog.jp

8-11月: ファイナンス、経済、投資

統計の学習を終えた後は、投資・経済・ファイナンス分野の学習に取り組みました。今後エンジニアとしてさらに価値を高めていくためには、技術力に加えてドメイン知識を深めていくことが重要だと考えています。もともと会計のドメイン知識を持っていることもあり、関連性の高い金融分野に強い興味を持つようになりました。今後は金融の知識も深め、自身の強みの一つとしていきたいと考えています。

こうした背景から、ファイナンスや経済学に関する書籍を集中的に読み進めました。特に印象に残った書籍については、以下の記事で書評としてまとめています。

yuki0920.hatenablog.jp yuki0920.hatenablog.jp

あわせて、個人の資産運用についても改めて真剣に向き合いました。2019年からNISAを活用して投資信託を購入し始め、現在では課税口座も併用し、大半の金融資産を投資信託で運用しています。これまでは経済情勢などを深く意識せず、S&P 500や全世界株式(いわゆるオルカン)に連動する投資信託のみを購入し、結果的に良い成績ではありましたが、これらに集中投資することで、為替リスクなどの様々なリスクを抱えていることも理解しました。そこで、より堅実な資産配分を目指すため、投資についての学習にも力を入れました。

最終的には、S&P 500やオルカン系の商品に加えて、金・債券・日本株式(TOPIX連動)なども組み入れることで、自分なりに納得できる資産配分へとポートフォリオを組み替えることにしました。

12月: 応用情報勉強開始

ビジネス分野の学習に一区切りをつけ、2026年4月に受験予定の応用情報技術者試験の学習を始めました。単に合格するだけであれば1〜2か月の学習でも十分だと思いますが、今回は合格そのものを目的とせず、実務に活かせる知識を身につけることを意識しています。

具体的には、気になったテーマについては専門書を読むなどして深掘りしながら学習を進めています。まだ学習を始めたばかりですが、現時点ではプロジェクトマネジメントやサービスマネジメントといったマネジメント系分野、そしてデータベースに特に面白さを感じています。

2027年度から情報処理技術者試験が再編される予定であるため、今後は応用情報技術者試験自体の価値が変わる可能性もあります。そのため、資格取得をゴールとするのではなく、実務や実生活に活かせる知識を得ることを目的として、引き続き学習と受験に取り組んでいきたいと考えています。

子育て

1月に第二子(娘)が誕生してからの数か月間は、主に夜間のミルクを担当しました。第一子(息子)のときと同様、最初の3か月は睡眠サイクルが整わず大変になることを覚悟していましたが、前回の経験を活かせたこともあり、想像していたよりスムーズに乗り切ることができました。妻と円滑にコミュニケーションを取りながら役割分担できたことや、便利グッズへの投資を惜しまなかったことが大きかったと感じています。特に、ピジョンの「哺乳びんスチーム除菌・乾燥器」は非常に重宝しました。

また、育休中は息子と過ごす時間も大幅に増えました。以前は休日にYouTubeを見せてしまう時間が多かったのですが、徐々にその時間を減らしながら、親子で過ごす時間を大切にするよう心がけています。最近では、絵本を読んだり、一緒にパズルをしたりして過ごすことが増え、成長を間近で感じられる貴重な時間になっています。また、家族で鴨川シーワールドへ行ったことをきっかけに、息子が動物に強く関心を持つようになったため、千葉や東京の動物園や水族館には足繁く通っています。

まとめ

2025年は、子どもたちとのかけがえのない時間を過ごしながら、技術やビジネスの学習にもじっくり取り組むことができた、非常に充実した1年でした。 2026年には復職を予定しているため、仕事も育児も楽しみつつ取り組んでいきたいと思います。

『行動経済学入門』を読みました

行動経済学入門』を読みました。私が本書を手に取った動機は、個人が常に合理的な行動を取ることを前提とする従来の経済学に興味を持ち、学習を進める中で、その前提から逸脱する人間の行動をどのように分析するのかという点についても知りたいと考え、行動経済学に興味を持つようになったためです。これまでにも関連書籍をいくつか読んできましたが、応用例に焦点が当てられているものや結論だけが述べられているものが多く、もう少し理論的な部分まで深掘りして理解したいと感じていました。そこで、よりアカデミック寄りの入門書として本書を選びました。

感想

本書は扱うトピックの幅が広く、体系的に行動経済学を学ぶに適した書籍だと感じました。私は自分のことを比較的合理的に判断するタイプだと思っていましたが、それでも振り返ってみると、ときに非合理な行動を取ってしまうことがあることがわかりました。本書を通じて行動経済学の視点に触れたことで、そうした自分の意思決定を言語化しやすくなり、自分自身への理解が進んだだけでなく、他者の行動や判断についても「なぜそうなるのか」を考える手がかりが増えたように思います。

特に良かったのは、自分がどういうときに合理的な行動から外れた選択をしてしまうのかを内省するきっかけになった点です。 何らかの意思決定をするときに簡略化した思考で判断する手法を「ヒューリスティック」と表現しますが、このヒューリスティックは早い意思決定ができる反面、時としてバイアスを生じさせてしまうことがあります。本書を通してさまざまなタイプのヒューリスティックを知ることで、意思決定の場面では自分が意図しない形でバイアスを持ち込んでしまうことがあり、その影響を受けながら判断している可能性があると分かりました。

最後に

本書を読んで、行動経済学は自身や他者の意思決定を分析するのに有用なフレームワークだと感じました。今後は、本書で得た理論的な理解を土台にしながら、実生活に役立てていきたいです。