leetcode
leetcode 2101 ~ 2150
最长递增子序列 II

最长递增子序列 II

难度:

标签:

题目描述

代码结果

运行时间: 809 ms, 内存: 48.5 MB


/*
 * 思路:
 * 使用Java Stream进行处理,我们需要先对数组进行预处理,以便使用动态规划来求解。
 * 我们可以使用一个数组dp来存储以每个元素结尾的最长递增子序列长度。
 * 由于Stream不适合直接进行动态规划,我们可以将其用作辅助工具来处理部分逻辑。
 */
import java.util.Arrays;

public class Solution {
    public int longestSubsequence(int[] nums, int k) {
        int n = nums.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        for (int i = 1; i < n; i++) {
            int finalI = i;
            dp[i] = Arrays.stream(dp, 0, i)
                .filter(j -> nums[finalI] > nums[j] && nums[finalI] - nums[j] <= k)
                .map(j -> dp[j] + 1)
                .max()
                .orElse(1);
        }
        return Arrays.stream(dp).max().orElse(1);
    }
}

解释

方法:

本题解采用的是基于归并排序的分治策略来处理问题,并结合动态规划(DP)和双端队列(deque)优化查找过程。首先,使用一个DP数组dp来保存以每个元素结尾的最长子序列的长度。同时,数组p用于存储nums中元素的索引,按照元素值进行排序,对于相同值的情况按照索引逆序排序。通过分治方法,对索引数组p进行递归划分,对每个子数组,利用双端队列dq维护可能的子序列候选。在处理过程中,如果当前元素索引小于等于中点,更新队列;如果大于中点,通过队列来更新dp数组。整体算法通过不断的分治和合并来构建可能的最长递增子序列。最终返回dp数组中的最大值,即为所求的最长子序列长度。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
在使用归并排序的分治策略时,您是如何确保在子数组中维持元素的原始顺序?
在这个算法中,归并排序的分治策略并不是直接应用于原数组nums,而是应用于索引数组p。通过对索引数组p进行排序和分治,我们实际上是按照nums中的元素值的大小进行操作,同时保留了元素的索引信息。这样,即使在分治处理过程中,元素的相对顺序是根据它们的值和索引来确定的,原数组nums的物理顺序不会被改变,从而确保了元素的原始顺序在逻辑上是通过索引和值的排序来维持的。
🦆
您提到使用了一个双端队列来优化查找过程,请问这样做主要解决了什么问题?
双端队列的使用主要是为了高效地维护和更新动态规划中的状态。在处理每个子数组时,需要频繁地添加元素、移除元素以及获取最大值操作。使用双端队列,可以在O(1)时间复杂度内完成这些操作:队列的两端都可以进行元素的添加和移除,这使得在遍历过程中可以快速地更新dp数组。此外,双端队列帮助维持了一个有序的候选序列,这对于快速查找和更新满足条件的最长递增子序列长度至关重要。
🦆
在分治法中,对于元素索引的排序和逆序排序有何作用,它们是如何影响算法性能的?
在这个问题中,元素索引的排序按照元素值进行正序排序,对于相同元素值的情况则按照索引进行逆序排序。这样的排序策略有两个作用:首先,正序排序确保我们可以根据元素值的大小来分治和合并,这是递增子序列的基础;其次,逆序排序处理相同值的元素时,能够保证在动态规划过程中,较后面的元素不会错误地参考到前面的元素,从而避免重复计算和错误更新,这对于保持算法的正确性和提高效率是非常重要的。
🦆
您如何处理元素值相同但索引不同的情况?排序时对这种情况有什么特别的考虑吗?
在元素值相同的情况下,通过将索引以逆序的方式排序,算法特别处理了这种情况。这种排序方式的目的是确保在我们的动态规划过程中,后出现的索引(即物理位置上较后的元素)在先出现的索引之前被处理。这有助于防止在更新dp值时的冲突,确保每个元素在计算其最长递增子序列长度时,不会被同值的前面的元素影响,这对于保持算法的逻辑正确性和性能优化都是必要的。

相关问题