动态规划-子序列问题2


1. 最长定差子序列(1218)

题目描述:
在这里插入图片描述

状态表示:
根据题目要求以及做题经验可以设置一个数组dp,将dp[i]表示为以arr数组的i位置为结尾的等差子序列的最大长度。
状态转移方程:
这题在子序列问题中算是经典的一个问题,状态转移方程就是在dp[i]-dp[j]==difference时,dp[i[=dp[j]+1,这里的j就是从0~i-1的一个值,用于在i的循环中加上一个循环来遍历子序列的多种可能。具体可以看代码,逻辑相对简单。
初始化:
初始化还是一样,因为子序列的长度都是至少为1,所以先将dp数组的所有元素赋为1。
填表顺序:
从左至右。
返回值:
dp数组中的最大值。
代码如下:

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        int n = arr.length;
        int[] dp = new int[n];

        for (int i = 0; i < n; i++) {
            dp[i] = 1;
        }

        int max = 1;
        for (int i = 1; i < n; i++) {

            for (int j = i - 1; j >= 0; j--) {
                if (arr[i] - arr[j] == difference) {

                    dp[i] = dp[j] + 1;
                    break;
                }
            }
            max = Math.max(max, dp[i]);
        }

        return max;
    }
}

优化:
理论上以上的代码可以解决上述问题,但是在leetcode的提交过程中出现提交超时的情况,所以可以使用另一种思想来编写代码。直接遍历一遍arr数组来完成动态规划。

class Solution {
    public int longestSubsequence(int[] arr, int difference) {
        Map<Integer, Integer> hash = new HashMap<>();

        int ret = 1;
        for (int n : arr) {

            hash.put(n, hash.getOrDefault(n - difference, 0) + 1);
            ret = Math.max(ret, hash.get(n));

        }
        return ret;
    }
}

要理解上述代码的意思,就得先理解第一段代码中的一个点,那就是对于i位置,要得到最长的等差子序列的长度不需要去遍历i位置前的每一个元素,只需要去遍历到第一个满足条件的j下标即可,因为后面的子序列长度不可能会超过j’位置的长度。
优化后的代码就使用到了这种思想,使用了哈希来存对应的数值对,遍历arr,对于每个值a存入(a,满足以a为结尾的等差子序列最大长度),当然这里()中的第二个值有几种特殊情况,第一种例如a是第一个数,那么肯定构不成等差子序列第二项就赋为1。第二种情况,对于a-difference,在哈希表中搜索不到这样的值,因为我们遍历数组是从前往后遍历的,所以这种情况就说明了对于a没有满足条件的等差子序列,那么a对应的第二项就是赋为1。在正常情况下,就是a-difference能在哈希表中搜索到,那么就将a对应的第二个值赋为a-difference在哈希表中对应的第二个值+1。
有人可能会问,为什么使用a-difference对应的第二值更新的a对应的第二值就是最大的值,因为我们是从前往后遍历arr的,然后hash中的特性就是当key相同时会将对应的value值赋为后续加入的key对应的value,因为后续加入的a-difference对应的value肯定比前面的a-difference的value大,也就是后续加入的a-difference对应的等差子序列的长度肯定比前面的a-difference的等差子序列大,所以这样更新值自然正确。

题目链接

优化后时间复杂度:O(N)
优化后空间复杂度:O(N)

2. 最长的斐波那契子序列的长度(873)

题目描述:
在这里插入图片描述

状态表示:
这题的状态表示比较特殊,因为单独使用dp[i]无法解题,因为使用二位数组进行状态表示,设置dp[i][j]为以arr数组中的i和j元素为结尾时的最长的斐波那契子序列的长度。
状态转移方程:
状态转移方程和前面做过的子序列问题还是相似的,就是两层的循环再去寻找满足条件的第三个元素,这里的条件就是斐波那契数列的条件,当元素存在并且在arr数组中的下标为k时,那么dp[i][j]=dp[k][i]+1。
初始化:
初始化的话,因为固定住了两个尾部元素所以长度至少是为2的,所以直接将dp这个二维数组的每个元素都赋为2。但是如果最终数组中是没有斐波那契数列的话,那么就不能返回2,因为题目要求只能要么返回大于等于3这种有斐波那契子序列的情况,要么返回0,所以在返回结果那里要多加一个判断。前面我们将dp数组全赋为2单纯是为了计算。
填表顺序:
从上到下,从左至右。
代码如下:

class Solution {
    public int lenLongestFibSubseq(int[] arr) {
        int n = arr.length;

        int[][] dp = new int[n][n];
        Map<Integer, Integer> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            map.put(arr[i], i);
        }

        for (int i = 0; i < n; i++) {
            for (int j = 0; j < n; j++) {
                dp[i][j] = 2;
            }
        }

        int ret = 2;
        for (int j = 2; j < n; j++) {
            for (int i = 1; i < j; i++) {
                int temp = arr[j] - arr[i];
                if (map.containsKey(temp) && temp < arr[i]) {
                    dp[i][j] = dp[map.get(temp)][i] + 1;
                }

                ret = Math.max(ret, dp[i][j]);
            }
        }

        return ret == 2 ? 0 : ret;
    }
}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

3. 最长等差数列(1027)

题目描述:
在这里插入图片描述

状态表示:
根据上一题的经验,我们就可以设置二维数组dp,并且使用dp[i][j]来表示以i,j位置元素为结尾的最长等差子序列。
状态转移方程:
状态转移方程也是类似的,就是先固定住后两个元素位置,然后搜寻是否有满足等差数列的第一个元素,如果有则dp[i][j]=dp[k][i]+1,但是这里要注意一点,因为本题给的nums数组不是严格递增,所以可能会出现多种情况,所以不能直接一次性将nums送到map中。为了避免nums中的值相同等问题,可以先固定住下标i,然后递增j,然后先仅仅向map中添加i位置之前元素的数对,因为此时的dp[i][j]的运算只涉及到这些,通过这种方法可以有效处理问题。
初始化:
初始化也是一样将二维数组中的所有值赋为2。
填表顺序:
从上到下,从左至右。
代码如下:

class Solution {
    public int longestArithSeqLength(int[] nums) {
        int n = nums.length;

        int[][] dp = new int[n][n];

        for (int i = 0; i < n; i++) {
            Arrays.fill(dp[i], 2);
        }

        Map<Integer, Integer> map = new HashMap<>();
        map.put(nums[0], 0);

        int ret = 2;
        for (int i = 1; i < n; i++) {
            for (int j = i + 1; j < n; j++) {
                int temp = 2 * nums[i] - nums[j];
                if (map.containsKey(temp)) {
                    dp[i][j] = dp[map.get(temp)][i] + 1;
                    ret = Math.max(ret, dp[i][j]);
                }

            }
            map.put(nums[i], i);
        }

        return ret;
    }

}

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

4. 等差数列划分 II - 子序列(446)

题目描述:
在这里插入图片描述

状态表示:
根据前几题的经验,设置二维数组dp[i][j]为以i和j位置元素为结尾时的等差子序列的个数。
状态转移方程:
状态转移方程还是类似的思想,使用两层循环固定住后两个元素的下标然后去寻找满足条件的第一个元素。如果满足条件那么dp[i][j]+=dp[k][i]。不过要注意一个点,就是因为这里是计算等差子序列的个数,所以所有满足条件的第一个元素都要加上。代码中的实现就是,使用哈希表来记录同意数值的不同下标,这样在循环中对于满足条件并且下标小于i的dp值就可以直接加上。具体看代码。
初始化:
初始化因为两个元素无法构成题目要求的等差子序列,所以dp数组全赋为0即可。
填表顺序:
从左至右,从上到下。
返回值:
返回dp数组中的值的和(部分经过处理的和)。
代码如下:

public int numberOfArithmeticSlices(int[] nums) {
        int n = nums.length;

        Map<Integer, List<Integer>> map = new HashMap<>();
        for (int i = 0; i < n; i++) {
            int temp = nums[i];
            if (!map.containsKey(temp)) {
                map.put(temp, new ArrayList<Integer>());
            }
            map.get(temp).add(i);
        }

        int[][] dp = new int[n][n];

        int sum = 0;
        for (int j = 2; j < n; j++) {
            for (int i = 1; i < j; i++) {
                int temp = 2 * nums[i] - nums[j];
                if (map.containsKey(temp)) {
                    for (int a :
                            map.get(temp)) {
                        if (a < i) {
                            dp[i][j] += dp[a][i];
                        }
                    }
                }
                sum += dp[i][j];
            }
        }

        return sum;
    }

题目链接
时间复杂度:O(N^2)
空间复杂度:O(N^2)

  • 21
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值