Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
179 changes: 179 additions & 0 deletions 08_Dynamic Programming/300. Longest Increasing Subsequence.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
<問題>
https://leetcode.com/problems/longest-increasing-subsequence/description/

# step1

5分程度答えを見ずに考えて、手が止まるまでやってみる。
何も思いつかなければ、答えを見て解く。
ただし、コードを書くときは答えを見ないこと。
正解したら一旦OK。
思考過程もメモしてみる。

```c++
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> length(nums.size(), 1);
for (int i = 0; i < nums.size(); i++ ) {
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
length[i] = max(length[i], length[j] + 1);
}
}
}
return *max_element(length.begin(), length.end());
}
};
```

【考えたこと】
- 計算量:O(N^2) 空間:O(N)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

C++ で時間計算量が O(N^2) のプログラムで N=2500 の入力データを入力して実行するとき、おおよそどれくらいの時間がかかるか推定できますか?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

C++の1秒間の実行ステップ数を1.010^8とすると、
計算にかかる時間=2500
2500/10^8〜0.0625(s)
と考えております。
以下を参考にしました。
hroc135/leetcode#9 (comment)

ちなみになのですが、C++の1秒間の実行ステップ数を、
ネットで調べてもなかなか見つけられないのは、調べる力の問題なのでしょうか。
それらしき値を見つけても、ブログだったり、知恵袋でしか、
見つけられず本当に合っているのか不安になることが多いです...

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

正しいと思います。

ちなみになのですが、C++の1秒間の実行ステップ数を、
ネットで調べてもなかなか見つけられないのは、調べる力の問題なのでしょうか。

自分の場合は大学の先輩から教えてもらったように思います。書籍やネット上の確度の高い情報で見た記憶はありません。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

実際のところ、いろいろな事情があるので桁くらいしか合わないものです。
CPU のインストラクションごとにクロック数が異なり、最近はマイクロコードになって並列して実行できたりできなかったりするので精度良く見積もることは困難です。
https://discord.com/channels/1084280443945353267/1218823752243089408/1244470338562293882
https://discord.com/channels/1084280443945353267/1253694251271852095/1273238168170266686
https://discord.com/channels/1084280443945353267/1200089668901937312/1235490680592273410

- 漸化式にするとLength(i)=max( Length(k)+1, Length(i) ) (k<i1を満たす全てのk)
- for文でi=0から埋めていくイメージ
- 再帰でも解けるはず。step2で解く。

# step2
他の方が描いたコードを見て、参考にしてコードを書き直してみる。
参考にしたコードのリンクは貼っておく。
読みやすいことを意識する。
他の解法も考えみる。

計算量:O(N)
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これは自乗でしょうか。

N:sの文字サイズ

```c++
class Solution {
private:
// rightより前の部分配列で、nums[right]よりも小さい値で終わるシーケンスの個数を返す。
int get_maxlength_ended_at_index(int right, const vector<int>& nums,
const vector<int>& maxlengths) {
int max_length = 0;
for (int i = 0; i < right; i++) {
if (nums[i] < nums[right]) {
max_length = max(max_length, maxlengths[i]);
}
}
return max_length;
}
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> maxlengths(nums.size(), 1);
for (int i = 0; i < nums.size(); i++ ) {
int max_length = get_maxlength_ended_at_index(i, nums, maxlengths);
maxlengths[i] = max_length + 1;
}
return *max_element(maxlengths.begin(), maxlengths.end());
}
};
```

- 動的計画法については、以下動画がわかりやすかった.

本問のsubproblemは、「要素iまでで連続する要素の最大長さ」

https://www.youtube.com/watch?v=OQ5jsbhAv_M

- 命名方法・関数化

> lengthsの意味がパズルになっているからだと思いました。
> 日本語で長々と書くと、「lengths[i] とは、仮に nums[i] がシーケンスの最後であると仮定した場合に可能な、最も長いシーケンスの長さ」ですよね。
> まあ、「長さ(複数)」であることには間違いないですが、「長さ」とだけいわれて、ああ「仮に nums[i] がシーケンスの最後であると仮定した場合に可能な、最も長いシーケンスの長さ」ってことね、とならず、それを推測するパズルになっています。
> これを前提とすると、内側のループは、関数にすることができるはずで、「i よりも左で、nums[i] 未満で終わる最大のシーケンスの長さ」を返させればいいですね

https://github.com/hayashi-ay/leetcode/pull/27/files#diff-b7fbb0dce1473afc0264185268f1a1ef6d682a3a8c997d43bc8bdd636a66ce4aR177

上のコメントを見て、関数化した。引数が多くなっているが大丈夫??

get_max_length_of_lis_before_indexは、rightより前の部分配列で、nums[right]よりも小さい値で終わるシーケンスの個数

length[i]はiで終わるシーケンスの最大要素数

関数の命名方法が難しそう。一旦コメントを記載することにした。



以下、再帰でも書きました。

```c++
class Solution {
private:
map<int, int> length;
int get_max_length_of_lis_before_index(const vector<int>& nums, int n) {
if (length.contains(n)) {
return length[n];
}
int max_length = 1;
for (int i = 0; i < n; i++) {
if (nums[i] < nums[n]) {
max_length = max(get_max_length_of_lis_before_index(nums, i) + 1, max_length);
}
}
length[n] = max_length;
return max_length;
}
public:
int lengthOfLIS(vector<int>& nums) {
int max_length;
for (int i = 0; i < nums.size(); i++) {
max_length = get_max_length_of_lis_before_index(nums, i);
}
return max_length;
}
};
```


- 0(NlogN)の計算量のアルゴリズム(2分探索)もあるよう。step4で追加したい。
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

https://discord.com/channels/1084280443945353267/1200089668901937312/1209534429593210932
このあたりから参照です。

このあたりの解法、お互いに移り変われるところがあり、解法を考えるときに局所的な変形などをしています。
https://discord.com/channels/1084280443945353267/1206101582861697046/1209027377397506109
https://discord.com/channels/1084280443945353267/1210494002277908491/1215698534855090207


https://github.com/sakupan102/arai60-practice/pull/32#pullrequestreview-2081534548

- 類題

https://discord.com/channels/1084280443945353267/1201211204547383386/1241981541802840228

- セグメントツリーでの解き方もあるが、ソフトウェアエンジニアの常識ではないとのこと。


- 参考

https://github.com/seal-azarashi/leetcode/pull/28

https://github.com/TORUS0818/leetcode/pull/33/files

https://github.com/Ryotaro25/leetcode_first60/pull/34#discussion_r1743810106




# step3

今度は、時間を測りながら、もう一回、書きましょう。書いてアクセプトされたら文字消してもう一回書く。これを10分以内に一回もエラーを出さずに書ける状態になるまで続ける。3回続けてそれができたらその問題はOK。

実施しました。


```c++
class Solution {
private:
// rightより前の部分配列で、nums[right]よりも小さい値で終わるシーケンスの個数を返す。
int get_maxlength_ended_at_index(int right, const vector<int>& nums,
const vector<int>& maxlengths) {
int max_length = 0;
for (int i = 0; i < right; i++) {
if (nums[i] < nums[right]) {
max_length = max(max_length, maxlengths[i]);
}
}
return max_length;
}
public:
int lengthOfLIS(vector<int>& nums) {
vector<int> maxlengths(nums.size(), 1);
for (int i = 0; i < nums.size(); i++ ) {
int max_length = get_maxlength_ended_at_index(i, nums, maxlengths);
maxlengths[i] = max_length + 1;
}
return *max_element(maxlengths.begin(), maxlengths.end());
}
};
```