Dynamic Programming Approach with Visualization
n本の木が横一列に並んでおり、何本かの木を伐採して残った木の高さが単調増加になるようにしたい。 残せる木の最大本数を求める問題です。これは最長増加部分列(Longest Increasing Subsequence, LIS)問題として知られています。
配列 [100, 102, 101, 91, 199] から最長増加部分列を見つける
dp[i] = i番目の木を最後とする最長増加部分列の長さ
1function longestIncreasingSubsequence(n, heights) {2 // dp[i] = i番目の木を最後とする増加部分列の最大長3 // 初期値は全て1(各木単体の部分列)4 const dp = new Array(n).fill(1);56 // 各木について、その木を最後とする最長増加部分列の長さを計算7 for (let i = 1; i < n; i++) {8 // i番目の木より前の全ての木をチェック9 for (let j = 0; j < i; j++) {10 // j番目の木の高さがi番目の木の高さより小さい場合11 if (heights[j] < heights[i]) {12 // j番目の木を最後とする部分列にi番目の木を追加13 dp[i] = Math.max(dp[i], dp[j] + 1);14 }15 }16 }1718 // 全てのdp値の中で最大値を返す19 return Math.max(...dp);20}
より大きなデータセットに対しては、二分探索を使用したO(n log n)のアルゴリズムも実装可能です。
1function longestIncreasingSubsequenceOptimized(n, heights) {2 // tails[i] = 長さi+1の増加部分列の末尾要素の最小値3 const tails = [];45 for (let i = 0; i < n; i++) {6 const height = heights[i];78 // 二分探索で挿入位置を見つける9 let left = 0;10 let right = tails.length;1112 while (left < right) {13 const mid = Math.floor((left + right) / 2);14 if (tails[mid] < height) {15 left = mid + 1;16 } else {17 right = mid;18 }19 }2021 // 挿入または更新22 if (left === tails.length) {23 tails.push(height);24 } else {25 tails[left] = height;26 }27 }2829 return tails.length;30}