写在前面

感觉这道题很适合记录一下,打破了我对 $dfs$ 的思维范式~

想不通的点

  • 递归的传参怎样设置?
  • 递归的 $base$ $case$ 怎么写?
  • 如何进入下一个递归,递归入口在哪?
  • 在 $dfs$ 的过程中,如果中间值不满足非递减特性了怎么办?最终返回的答案应该是什么?
  • 如何设置 $memo$ 数组?

记忆化 dfs

为了更加容易 $1:1$ 改写成 $DP$, 这里递推写成从后往前的。

  • 思考1:递归应该传入哪些参数
    • 除去必要的数组信息:$num1$, $nums2$
    • 题目要求的是以索引 $i$ 结尾的最长递减子序列,$dfs$ 函数自然需要这样一个参数,从而可以从 $i-1$ 的位置转移到 $i$ 的位置;另外,需要知道 $i$ 位置选择了什么数据(来自$nums1$ 还是 $nums2$);这里也是 $memo$ 写法的重要思考依据
  • 思考2:递归的 $base$ $case$ 应该怎么写
    • 因为是从右往左遍历的,因此左边界,就是 $i==0$ 的情况,因为一个数也是一个子数组,长度为 $1$,因此应该返回 $1$。
  • 思考3:如何进入下一个递归入口

    • 根据题意判断入口,也就是可以分解成子问题的入口~
    • 因为 $i-1$ 可以选择的数有两个,可以来自 $nums1$,也可以来自 $nums2$,因此应该分情况讨论:

      • 如果不存在比 $i$ 位置选择的数,也就是找不到满足非递减特性的数字了,应该返回多少?(应该返回1,而不是零,因为其本身可以作为一个子数组
      • 如果 $nums1$ 满足,可以选择,怎么写递归?
      • 如果 $nums2$ 也满足,可以选择,应该怎么写递归?
      • 最后的返回值如何确定?(取三个可能中的最大值

        int dfs(vector<int>& nums1, vector<int>& nums2,int i,int it,vector<vector<int>>& memo){
        if(i==0)
        return 1;
        int res=1;
        if(memo[it][i]!=-1)
        return memo[it][i];
        if(nums1[i-1]<= (it==0 ? nums1[i] : nums2[i]))
        res=dfs(nums1,nums2,i-1,0,memo)+1;
        if(nums2[i-1]<= (it==1 ? nums2[i] : nums1[i]))
        res=max(res,dfs(nums1,nums2,i-1,1,memo)+1);
        memo[it][i]=res;
        return res;
        }
  • 思考4:在 $dfs$ 的过程中,如果中间值不满足非递减特性了怎么办?最终返回的答案应该是什么?

    • 一般的 $dfs$ 都是直接计算出 $dfs(i)$ 即可得出最终结果
    • 但是,这里可以前面半段的满足,后半段满足,这样应该怎么考虑?也就是可能是中间位置的组合数作为末尾得到的最长,呢么自然就能想到:
      • 枚举所有可能:因为最长非递减子数组是整体组合数组的一部分,每一个组合元素都有可能是作为最长非递减子数字的末尾
    • 最终的返回答案:每一个位置都可能作为最长非递减子数组的末尾,而且每个位置有两种选择。
  • 思考5:如何设置 $memo$ 数组?

    • 很明显,我们需要枚举每个位置,同时需要递归,很显然有很多重叠子问题(或者说状态位置
    • 思考有哪些状态位置:对于每个位置,每个位置有两种状态(也就是选择了哪个数组里的数),因此可以考虑使用两行 $m$ 列来存储 $memo$,总共也就这么多状态。
    • 这里和传入参数的写法是联动的,在上述代码中,使用 $int=0或1$ 来代表选择的数组数,这样 $memo$ 数组也就很明晰了。

总结一下🌏

  1. $dfs$ 的传入参数往往是和状态位置相关的,而且一般来说都是从后往前思考,这样也方便翻译成 DP
  2. 注意枚举的使用,有的时候就是要枚举的
  3. 不一定 $dfs(末尾状态 i)$ 就是返回结果,本题中每个状态都有可能是返回结果。不要陷入思维定式 !!
  4. $memo$ 数组的设计和状态的定义密切相关,本题中就是索引位置及对应选择的数。索引位置有 $m$ 个,每个位置都有两种可能(避免使用值来作为传入参数,传入 $flag$ 反而简单明晰)。

贴个代码吧

就不 $1:1$ 翻译成递推了~

class Solution {
public:
    int maxNonDecreasingLength(vector<int>& nums1, vector<int>& nums2) {
        int m=nums1.size();
        int res=0;
        vector<vector<int>> memo(2,vector<int>(m,-1));
        // 枚举所有位置的可能
        for(int i=0;i<m;i++){
            // 每个位置又包含两种可能
            int res1=dfs(nums1,nums2,i,0,memo);
            int res2=dfs(nums1,nums2,i,1,memo);
            // 最终返回值是这些中的最大值
            res=max({res,res1,res2});
        }
        return res;
    }
    // dfs(i) 表示以 nums[i] 结尾的最长非递减数组的长度,传入参数有两个,位置以及选择的数来自谁
    int dfs(vector<int>& nums1, vector<int>& nums2,int i,int it,vector<vector<int>>& memo){
        if(i==0)
            return 1;  
        // 传入的状态参数也对应着memo的维度
        if(memo[it][i]!=-1)
            return memo[it][i];
        // 枚举递归入口
        int res=1;  
        if(nums1[i-1]<= (it==0 ? nums1[i] : nums2[i]))
            res=dfs(nums1,nums2,i-1,0,memo)+1;
        if(nums2[i-1]<= (it==1 ? nums2[i] : nums1[i]))
            res=max(res,dfs(nums1,nums2,i-1,1,memo)+1);
        memo[it][i]=res;
        return res;
    }
};

$\cdots$ 什么都略懂一点,生活更多彩一些 $\cdots$

$\cdots$ end $\cdots$