1.题目链接:
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
子序列 是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例 如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。
示例 1:
输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。示例 2:
输入:nums = [0,1,0,3,2,3]
输出:4
示例 3:
输入:nums = [7,7,7,7,7,7,7]
输出:1
提示:
1 <= nums.length <= 2500
-10^4 <= nums[i] <= 10^4
3. 解法(暴搜 -> 记忆化搜索 -> 动态规划):
算法思路:
暴搜:
a. 递归含义:给 dfs 一个使命,给他一个数 i ,返回以 i 位置为起点的最长递增子序列的长度;
b. 函数体:遍历 i 后面的所有位置,看看谁能加到 i 这个元素的后面。统计所有情况下的最大值。
c. 递归出口:因为我们是判断之后再进入递归的,因此没有出口~
记忆化搜索:
a. 加上一个备忘录;
b. 每次进入递归的时候,去备忘录里面看看;
c. 每次返回的时候,将结果加入到备忘录里面。
动态规划:
a. 递归含义 -> 状态表示;
b. 函数体 -> 状态转移方程;
c. 递归出口 -> 初始化。
Java算法代码:
class Solution {
public int lengthOfLIS(int[] nums) {
int n = nums.length;
int [] dp = new int[n];
int ret = 0;
Arrays.fill(dp,1);
//填表顺序:从后往前
for(int i = n-1; i>=0;i--){
for(int j = i + 1; j < n; j++){
if(nums[j] > nums[i])
dp[i] = Math.max(dp[i] , dp[j] + 1);
}
ret = Math.max(ret,dp[i]);
}
return ret;
// 记忆化搜索
int ret = 0;
int n = nums.length;
int [] memo = new int[n];
for(int i = 0; i < n; i++)
ret = Math.max(ret,dfs(i,nums,memo));
return ret;
}
public int dfs(int pos, int[] nums, int[] memo){
if(memo[pos] != 0) return memo[pos];
int ret = 1;
for(int i = pos + 1; i < nums.length ; i++)
if(nums[i] > nums[pos])
ret = Math.max(ret,dfs(i,nums,memo) + 1);
memo[pos] = ret;
return ret;
}
}
运行结果:
递归展开:
这里的动态规划,是从后往前(里面是从前往后,这里的max函数,已经是来更新正确的)
然后后面的暴力搜索:从前往后(每次固定一个数),利用结果携带的值来更新正确的值。
这里引入记忆化搜索优化,会使得,前面已经记录的值,直接使用。不用再次递归。
逻辑展开:
需要注意的是,当笔者使用动态规划得到的dp数组中的值和memo中的值是一样的。
得到这个结果的时候,就应该意识到一些事情。----谜题解开了
---------------------------------------------------------------------------------------------------------------------------------
记住,相信你的递归函数,它可以做到!
记住,不理解时候,去尝试手动展开!
记住,逻辑展开(你不可能对所有的题目都进行手动展开)!