首页 > 编程语言 >最长递增子序列算法

最长递增子序列算法

时间:2024-03-12 10:01:40浏览次数:24  
标签:nums 复杂度 最长 算法 序列 递增 dp

最长递增子序列算法

最长递增子序列(Longest Increasing Subsequence, LIS)是计算机科学中的一个经典问题,目标是在给定的数列中找到一个非降序排列的子序列,使得该子序列的长度尽可能长。以下是一些解决最长递增子序列问题的算法:

  1. 动态规划法(Dynamic Programming):

    • 初始化一个长度为 n 的数组 dp,其中 dp[i] 表示以原序列中第 i 个元素结尾的最长递增子序列的长度。
    • 遍历输入序列,对于每个元素 a[i],从 dp[0]dp[i-1] 找出所有小于它的值对应的 dp[j],更新 dp[i] 为这些值中的最大值加 1(因为当前元素可以加入到那些子序列末尾形成新的更长递增子序列)。
    • 最后,数组 dp 中的最大值即为原序列的最长递增子序列的长度。
  2. 贪心 + 二分查找优化:

    • 在动态规划的基础上,可以进一步优化时间复杂度至 O(n log n)。
    • 维护一个有序序列(如使用堆或平衡二叉搜索树),用于存储当前递增子序列的末尾元素。
    • 对于每一个新元素,如果它比有序序列的末尾元素大,则将其添加到序列中;否则,用它替换有序序列中第一个大于它的元素,并调整有序序列保持递增。
    • 最后,有序序列的长度就是原序列的最长递增子序列长度。
  3. 最长公共子序列变形法:

    • 将原序列排序,然后计算原序列和排序后的序列之间的最长公共子序列,但这通常不是最有效的解法,因为会改变原序列的相对顺序,可能会导致错误的结果。

在实际编程实现时,动态规划方法更为常用且易于理解,而结合贪心策略和二分查找的优化版本则可以有效降低时间复杂度,适用于大规模数据。

使用动态规划法

以下是一个使用动态规划法(简单朴素实现)在 PHP 中解决最长递增子序列问题的示例代码:

<?php

function longestIncreasingSubsequence($nums) {
    $n = count($nums);
    
    // 初始化 dp 数组,长度为 n+1,dp[0] 作为哨兵值
    $dp = array_fill(0, $n + 1, 1);
    
    // 遍历输入数组
    for ($i = 1; $i <= $n; $i++) {
        for ($j = 0; $j < $i; $j++) {
            // 如果当前元素大于前一个元素,则可以形成更长的递增子序列
            if ($nums[$i - 1] > $nums[$j]) {
                $dp[$i] = max($dp[$i], $dp[$j] + 1);
            }
        }
    }

    // 返回最长递增子序列的长度
    return max($dp);
}

// 测试数据
$nums = [10, 9, 2, 5, 3, 7, 101, 18];
$result = longestIncreasingSubsequence($nums);
echo "The length of the longest increasing subsequence is: ", $result;

?>

这个PHP代码将计算给定数组$nums的最长递增子序列的长度。

复杂度

上面的PHP代码实现的最长递增子序列算法使用了动态规划,其时间复杂度为O(n^2),其中n是输入数组$nums的长度。这是因为有两个嵌套循环,外层循环遍历整个数组,内层循环则对每个元素之前的每个元素进行比较。

空间复杂度方面,该代码使用了一个大小为n+1的一维数组$dp来存储子问题的解,因此空间复杂度为O(n)。

使用贪心策略和二分查找

最长递增子序列问题可以通过结合贪心策略和二分查找进行优化,从而将时间复杂度降低到O(n log n)。

以下是使用PHP实现的优化版本:

<?php

// 定义一个二分查找函数,用于在动态规划数组中找到插入位置
function binarySearch(&$dp, $length, $target) {
    $left = 0;
    $right = $length - 1;

    // 通过二分查找确定目标值应该插入的位置
    while ($left <= $right) {
        $mid = floor(($left + $right) / 2);
        if ($dp[$mid] < $target) { // 目标值大于中间值,则在右半部分继续查找
            $left = $mid + 1;
        } else { // 目标值小于等于中间值,在左半部分或刚好就是中间位置
            $right = $mid - 1;
        }
    }

    // 返回插入位置(左边界+1,因为这里使用的是左闭右开区间)
    return $left;
}

// 使用贪心策略和二分查找优化的最长递增子序列算法
function longestIncreasingSubsequenceOptimized($nums) {
    $n = count($nums);

    // 初始化 dp 数组,长度为 n+1,前面填充 0,实际只用到前 n 个元素存储 LIS 的长度
    $dp = array_fill(0, $n + 1, 0);

    // tail 数组用来存储每个长度对应的LIS末尾元素,保持单调递增顺序
    $tail = array();

    // 遍历输入数组
    for ($i = 0; $i < $n; $i++) {
        // 通过二分查找确定当前数字 nums[$i] 在 dp 数组中的插入位置
        $pos = binarySearch($dp, $i, $nums[$i]);

        // 更新 dp 数组对应位置的值为当前遍历到的数,表示以这个数结尾的递增子序列长度
        $dp[$pos] = $nums[$i];

        // 更新 tail 数组,确保它始终包含各个长度递增子序列的最后一个元素,并且有序
        if ($pos == count($tail)) { // 当 pos 大于 tail 数组长度时,表示需要新增一个元素
            $tail[] = $nums[$i];
        } else {
            $tail[$pos] = $nums[$i]; // 替换 tail 数组中的某个元素,保持递增顺序
        }
    }

    // 返回最长递增子序列的长度,即 tail 数组的长度
    return count($tail);
}

// 测试数据
$nums = [10, 9, 2, 5, 3, 7, 101, 18];
$result = longestIncreasingSubsequenceOptimized($nums);
echo "The length of the longest increasing subsequence is: ", $result;

?>

在这个优化版本中,我们维护了一个单调递增数组$tail来记录当前已找到的所有递增子序列的最后一个元素,并且用一个辅助函数binarySearch()进行二分查找以快速定位新元素应插入的位置。

这样在遍历整个输入数组时,可以达到O(n log n)的时间复杂度。同时注意,这个版本只返回了最长递增子序列的长度,如果需要找出具体的递增子序列,还需要额外存储路径信息。

找出具体的递增子序列

为了找出最长递增子序列的具体元素,我们需要在实现中额外存储路径信息。这里提供一个基于上述优化算法的PHP代码示例,同时返回最长递增子序列的长度和具体序列:

<?php

function binarySearch(&$dp, $length, $target) {
    $left = 0;
    $right = $length - 1;

    while ($left <= $right) {
        $mid = floor(($left + $right) / 2);
        if ($dp[$mid] < $target) {
            $left = $mid + 1;
        } else {
            $right = $mid - 1;
        }
    }

    return $left;
}

function longestIncreasingSubsequenceOptimized($nums) {
    $n = count($nums);
    $dp = array_fill(0, $n + 1, 0);
    $tail = array(); // 存储每个长度对应的LIS末尾元素
    $prevIndices = array_fill(0, $n + 1, null); // 存储前驱节点信息,用于回溯构建 LIS

    for ($i = 0; $i < $n; $i++) {
        $pos = binarySearch($dp, $i, $nums[$i]);
        $dp[$pos] = $nums[$i];
        $prevIndices[$pos] = $i; // 记录当前位置的前驱节点(原数组中的索引)

        // 更新 tail 数组,确保有序性
        if ($pos == count($tail)) {
            $tail[] = $nums[$i];
        } else {
            $tail[$pos] = $nums[$i];
        }
    }

    // 回溯构建最长递增子序列
    $lis = [];
    $idx = count($tail) - 1; // 最后一个有效位置对应最长递增子序列的最后一个元素
    while ($idx >= 0) {
        $lis[] = $nums[$prevIndices[$idx]]; // 添加当前元素到 LIS
        $idx = $prevIndices[$idx]; // 移动到前一个元素的位置
    }

    // 反转 LIS 以得到正确的顺序
    $lis = array_reverse($lis);

    // 返回最长递增子序列的长度及具体序列
    return [
        'length' => count($lis),
        'sequence' => $lis,
    ];
}

// 测试数据
$nums = [10, 9, 2, 5, 3, 7, 101, 18];
$result = longestIncreasingSubsequenceOptimized($nums);
echo "The length of the longest increasing subsequence is: ", $result['length'];
echo "\nThe longest increasing subsequence is: ", implode(', ', $result['sequence']);

?>

这段代码在计算最长递增子序列长度的同时,通过$prevIndices数组记录了每个位置的前驱节点,最后根据这些信息进行回溯,构造出具体的最长递增子序列。



欢迎关注公-众-号【TaonyDaily】、留言、评论,一起学习。

公众号

Don’t reinvent the wheel, library code is there to help.

文章来源:刘俊涛的博客


若有帮助到您,欢迎点赞、转发、支持,您的支持是对我坚持最好的肯定(_)

标签:nums,复杂度,最长,算法,序列,递增,dp
From: https://www.cnblogs.com/lovebing/p/18067673

相关文章

  • 【算法】【线性表】【链表】合并 K 个升序链表
    1 题目给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。示例1:输入:lists=[[1,4,5],[1,3,4],[2,6]]输出:[1,1,2,3,4,4,5,6]解释:链表数组如下:[1->4->5,1->3->4,2->6]将它们合并到一个有序链表中得到。1-......
  • 第六节:动态规划相关(不同路径、礼物最大价值、最长递增子序列)
    一.        二.        三.         !作       者:Yaopengfei(姚鹏飞)博客地址:http://www.cnblogs.com/yaopengfei/声     明1:如有错误,欢迎讨论,请勿谩骂^_^。声     明2:原创博客请在转载......
  • [积跬致远]Vol.1 版本兼容地启动sequence+Shell中的$0、$1、$2的含义+$cast 与类型转
    版本兼容地启动sequenceuvm从1.1d到1.2再到IEEE1800.2,有了很多变化。尤其是从1.1d到1.2,在objection的使用上有了一些关键性变化。在uvm进入到1.2后,starting_phase不在推荐使用。更为重要的是,不仅仅是不再推荐,而且如果以defaultsequence的方式启动以后,defaultsequence被启动以......
  • 代码随想录算法训练营第六天| 242. 有效的字母异位词
    242.有效的字母异位词https://leetcode.cn/problems/valid-anagram/description/publicbooleanisAnagram(Strings,Stringt){char[]sChar=s.toCharArray();char[]tChar=t.toCharArray();Arrays.sort(sChar);Arrays.sort(tChar......
  • php反序列化和redis未授权
    1、centos系统安装http,php,redis服务更新yum源httpphpredis2、使用redis未授权漏洞写入phpinfo3、配合gopher协议ssrf漏洞向服务器写入任意数据4、演示ssh免密码登录5、画图解释PHP反序列化漏洞的原理6、预习一下java反序列化漏洞,包括继承,重写等概念......
  • m基于深度学习的32QAM调制解调系统相位检测和补偿算法matlab仿真
    1.算法仿真效果matlab2022a仿真结果如下:  2.算法涉及理论知识概要        随着通信技术的飞速发展,高阶调制格式如32QAM(32-QuadratureAmplitudeModulation,32进制正交幅度调制)在高速数据传输中得到了广泛应用。然而,由于信道失真、噪声干扰等因素,接收端往往面......
  • RC4算法:流密码算法的经典之作
    一、RC4算法的起源与演变RC4算法是由著名密码学家RonRivest在1987年设计的一种流密码算法,其名字来源于RivestCipher4。RC4算法简单高效,被广泛应用于数据加密和网络安全领域。尽管RC4算法在早期被广泛使用,但随着时间的推移,一些安全性问题逐渐暴露,导致其在一些场景下被取代......
  • 代码随想录算法训练营第四十一天 | 416. 分割等和子集,● 01背包问题,你该了解这些! 滚
     46.携带研究材料(第六期模拟笔试)时间限制:5.000S空间限制:128MB题目描述小明是一位科学家,他需要参加一场重要的国际科学大会,以展示自己的最新研究成果。他需要带一些研究材料,但是他的行李箱空间有限。这些研究材料包括实验设备、文献资料和实验样本等等,它们各自占据......
  • 代码随想录算法训练营第四十三天|● 1049. 最后一块石头的重量 II ● 494. 目标和
    最后一块石头的重量 II 题目链接:1049.最后一块石头的重量II-力扣(LeetCode)思路:尽可能将石头分成重量相近的两堆,结果一定最小,因此问题可以转换为分割子集。dp[i]的含义是背包容量为i的背包能装下的最大重量,由于题目中最大重量是15000,所以我们申请15001的vector。注意,结果不......
  • 算法题 - Pop Sequence
    PopSequence(25)GivenastackwhichcankeepMnumbersatmost.PushNnumbersintheorderof1,2,3,...,Nandpoprandomly.Youaresupposedtotellifagivensequenceofnumbersisapossiblepopsequenceofthestack.Forexample,ifMis5and......