selected_coding_interview/docs/300. 最长递增子序列.md
状态定义:
nums 以 $nums[i]$ 结尾的最长子序列长度。转移方程: 设 $j∈[0,i)$,考虑每轮计算新 $dp[i]$ 时,遍历 $[0,i)$ 列表区间,做以下判断:
1. 情况 下计算出的 $dp[j] + 1$ 的最大值,为直到 $i$ 的最长上升子序列长度(即 $dp[i]$ )。实现方式为遍历 $j$ 时,每轮执行 $dp[i] = max(dp[i], dp[j] + 1)$。dp[i] = max(dp[i], dp[j] + 1) for j in [0, i)。初始状态:
返回值:
<,,,,,,,,,>
# Dynamic programming.
class Solution:
def lengthOfLIS(self, nums: List[int]) -> int:
if not nums: return 0
dp = [1] * len(nums)
for i in range(len(nums)):
for j in range(i):
if nums[j] < nums[i]: # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)
// Dynamic programming.
class Solution {
public int lengthOfLIS(int[] nums) {
if(nums.length == 0) return 0;
int[] dp = new int[nums.length];
int res = 0;
Arrays.fill(dp, 1);
for(int i = 0; i < nums.length; i++) {
for(int j = 0; j < i; j++) {
if(nums[j] < nums[i]) dp[i] = Math.max(dp[i], dp[j] + 1);
}
res = Math.max(res, dp[i]);
}
return res;
}
}
降低复杂度切入点: 解法一中,遍历计算 $dp$ 列表需 $O(N)$,计算每个 $dp[k]$ 需 $O(N)$。
设计思路:
算法流程:
状态定义:
转移方程: 设 $res$ 为 $tails$ 当前长度,代表直到当前的最长上升子序列长度。设 $j∈[0,res)$,考虑每轮遍历 $nums[k]$ 时,通过二分法遍历 $[0,res)$ 列表区间,找出 $nums[k]$ 的大小分界点,会出现两种情况:
初始状态:
返回值:
<,,,,,,,,,>
# Dynamic programming + Dichotomy.
class Solution:
def lengthOfLIS(self, nums: [int]) -> int:
tails, res = [0] * len(nums), 0
for num in nums:
i, j = 0, res
while i < j:
m = (i + j) // 2
if tails[m] < num: i = m + 1 # 如果要求非严格递增,将此行 '<' 改为 '<=' 即可。
else: j = m
tails[i] = num
if j == res: res += 1
return res
// Dynamic programming + Dichotomy.
class Solution {
public int lengthOfLIS(int[] nums) {
int[] tails = new int[nums.length];
int res = 0;
for(int num : nums) {
int i = 0, j = res;
while(i < j) {
int m = (i + j) / 2;
if(tails[m] < num) i = m + 1;
else j = m;
}
tails[i] = num;
if(res == j) res++;
}
return res;
}
}