价值:记录学习过程的思考,本身就是一场动态规划的前生,记忆化搜索。
Problem: 300. 最长递增子序列
// 状态定义:dp[i] 表示 前i-1个 小于 当前i的最长的递增子序列
// 状态递推:dp[i] = max(dp[i],dp[j])
// 答案:dp[i]+1并且赋值到dp[i]中, 每一步状态都可能是答案,更新每一步 ans,得出答案
//定义状态:dp[i] 表示以i结尾的最长递增子序列长度
//状态递推:dp[i] = max(dp[i],dp[j]+1)
// 答案: 可能不以它结尾,枚举取max
class Solution {
private int[] arr, cache;
public int lengthOfLIS(int[] nums) {
this.arr = nums;
int n = nums.length;
//dp[i] = max(dp[j],dp[i]) ==> ans= ++dp[i];
//回溯
//int res = lengthOfLIS_hui_su(nums);
//记忆化搜索
// cache = new int[n];
// int res = lengthOfLIS_cache(nums);
//递推
int res = dpn(n);
//另一种dp
//dp[i] = max(dp[j]+1,dp[i]) ==> ans = dp[i];
//int res = lengthOfLIS_DP1(nums);
return res;
}
//题意:
// 递增子序列 IS Increasing Subsequence
//最长递增子序列 LIS Longest Increasing Subsequence
//思考本质:
//从数组中选择一些数,且顺序和数组中的顺序是一致的
//【回溯实现】
//集合角度考虑:子序列本质上就是数组的一个子集,那我们就用子集型回溯来思考
// 对于子集型回溯
// 我们有 “选或不选” 和 “枚举选哪个” 这两种思路
// 如果 我们倒着思考,假设nums[n-1]是子序列的最后一个数,
// 思路1“选或不选”,前面的数字需要和nums[n-1]比较大小,所以知道当前下标外,还需要前边数字的下标
// 思路2“枚举选哪个”,我们可以直接枚举前面的比nums[n-1]小的数字,当作子序列的倒数第二个数字,只需要知道当前这个下标
// 综上思路1、思路2,思路2,只需要一个参数下标,更加好写,下面就这样实现思路2
//【====优化思路===】
//O(n^2) 回溯 -> 记忆化搜索 -> 递推
//O(nlogn) 二分+贪心
//【====优化思路===】
//回溯三问:子问题?以nums[i] 结尾的LIS长度
// 当前操作?枚举nums[j]
// 下一个子问题?以nums[j]结尾的LIS长度
//回溯思路2:回溯探索: dfs(i) = max{dfs(j)} + 1, j<i && arr[j] < arr[i]
public int dfs(int i){
int res = 0;
for(int j = 0; j < i; j++){
if(arr[j]<arr[i]){
res = Math.max(dfs(j),res);
}
}
return res+1;
}
//回溯优化思考:再case1中,比如从18开始递归,会用到7,从101开始递归,会用到7,重复子问题
//动态规划来优化
public int dfs_cache(int i){
if(cache[i]>0){
return cache[i];
}
int res = 0;
for(int j = 0; j < i; j++){
if(arr[j]<arr[i]){
res = Math.max(dfs_cache(j),res);
}
}
cache[i] = res+1;
return res+1;
}
//递推:dfs 变为 dp[] ,递归改为循环
// 状态定义:dp[i] 表示 前i-1个 小于 当前i的最长的递增子序列
// 状态递推:dp[i] = max(dp[j],dp[i])
// 答案:dp[i]+1并且赋值到dp[i]中
public int dpn(int n){
int ans = 0;
int[] dp = new int[n];
for(int i = 0; i< n; i++){
for(int j =0 ; j< i; j++){
if(arr[j] < arr[i]){
dp[i] = Math.max(dp[i], dp[j]);
}
}
ans = Math.max(ans,++dp[i]);
}
return ans;
}
public int lengthOfLIS_cache(int[] nums) {
int n = nums.length;
int ans = 0;
//枚举nums[i]作为子数组的结尾数字
for(int i =0 ;i <n;i++){
ans = Math.max(dfs_cache(i),ans);
}
return ans;
}
public int lengthOfLIS_hui_su(int[] nums) {
int n = nums.length;
int ans = 0;
//枚举nums[i]作为子数组的结尾数字
for(int i =0 ;i <n;i++){
ans = Math.max(dfs(i),ans);
}
return ans;
}
//定义状态:dp[i] 表示以i结尾的最长递增子序列长度
//状态递推:dp[i] = max(dp[i],dp[j]+1)
public int lengthOfLIS_DP1(int[] nums) {
int n = nums.length;
int i = 0;
int j = 0;
int[] dp = new int[n];
dp[0] = 1;
for(i = 1; i < n; i++){
dp[i] = 1;
for(j = 0 ; j < i; j++){
if(nums[j] < nums[i]){
dp[i] = Math.max(dp[i],dp[j]+1);
}
}
}
int ans = 0;
for(int t = 0; t<n; t++){
ans = Math.max(ans,dp[t]);
}
return ans;
}
}