【数据结构和算法】递增的三元子序列

简介: 给你一个整数数组nums,判断这个数组中是否存在长度为3的递增子序列。如果存在这样的三元组下标(i, j, k)且满足i < j < k,使得nums[i] < nums[j] < nums[k],返回true;否则,返回false。

其他系列文章导航

Java基础合集

数据结构与算法合集

设计模式合集

多线程合集

分布式合集

ES合集


文章目录

其他系列文章导航

文章目录

前言

一、题目描述

二、题解

2.1 方法一:贪心 + 二分

2.2 方法二:贪心(优化)

三、代码

3.1 方法一:贪心 + 二分

3.2 方法二:贪心(优化)

四、复杂度分析

4.1 方法一:贪心 + 二分

4.2 方法二:贪心(优化)


前言

这是力扣的334题,难度为中等,解题方案有很多种,本文讲解我认为最奇妙的两种。


一、题目描述

给你一个整数数组 nums ,判断这个数组中是否存在长度为 3 的递增子序列。

如果存在这样的三元组下标 (i, j, k) 且满足 i < j < k ,使得 nums[i] < nums[j] < nums[k] ,返回 true ;否则,返回 false

示例 1:

输入:nums = [1,2,3,4,5]

输出:true

解释:任何 i < j < k 的三元组都满足题意


示例 2:

输入:nums = [5,4,3,2,1]

输出:false

解释:不存在满足题意的三元组

示例 3:

输入:nums = [2,1,5,0,4,6]

输出:true

解释:三元组 (3, 4, 5) 满足题意,因为 nums[3] == 0 < nums[4] == 4 < nums[5] == 6


提示:

    • 1 <= nums.length <= 5 * 105
    • -231 <= nums[i] <= 231 - 1

    进阶:你能实现时间复杂度为 O(n) ,空间复杂度为 O(1) 的解决方案吗?


    二、题解

    题目要我们判断是否存在长度为 3 的上升子序列,问题可以转换为求 nums 的最长上升子序列的长度。

    如果 nums 的最长上升子序列长度大于等于 3,那么原问题答案为 True,否则为 False。

    2.1 方法一:贪心 + 二分

    思路与算法:

    简单来说,就是在遍历每个数 nums[i] 的同时,维护一个具有单调性的 f[ ] 数组,其中 f[len]=x 代表长度为 len 的最长上升子序列最小结尾元素为 x,可以证明 f 数组具有单调性,因此每次可以通过二分找到小于 nums[i] 的最大下标,来作为 nums[i] 的前一个数。

    综上,我们求得最长上升子序列的最大长度,然后和 3 比较即可得出答案。

    2.2 方法二:贪心(优化)

    方法二达到了进阶的要求!

    思路与算法:

    我们可以对 f 数组进行优化:使用有限变量进行替换(将 f 数组的长度压缩为 2),数组含义不变,f[1]=x 代表长度为 1 的上升子序列最小结尾元素为 x,f[2]=y 代表长度为 2 的上升子序列的最小结尾元素为 y。

    从前往后扫描每个 nums[i],每次将 nums[i] 分别与 f[1]] 和 f[2] 进行比较,如果能够满足 nums[i]>f[2],代表 nums[i] 能够接在长度为 2 的上升子序列的后面,直接返回 True,否则尝试使用 nums[i] 来更新 f[1] 和 f[2]。

    这样,我们只使用了有限变量,每次处理 nums[i] 只需要和有限变量进行比较。


    三、代码

    3.1 方法一:贪心 + 二分

    Java版本:

    class Solution {
        public boolean increasingTriplet(int[] nums) {
            int n = nums.length, ans = 1;
            int[] f = new int[n + 1];
            Arrays.fill(f, Integer.MAX_VALUE);
            for (int i = 0; i < n; i++) {
                int t = nums[i];
                int l = 1, r = i + 1;
                while (l < r) {
                    int mid = l + r >> 1;
                    if (f[mid] >= t) {
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                }
                f[r] = t;
                ans = Math.max(ans, r);
            }
            return ans >= 3;
        }
    }

    image.gif

    C++版本:

    #include <vector>
    #include <climits>
    using namespace std;
    class Solution {
    public:
        bool increasingTriplet(vector<int>& nums) {
            int n = nums.size(), ans = 1;
            vector<int> f(n + 1, INT_MAX);
            for (int i = 0; i < n; i++) {
                int t = nums[i];
                int l = 1, r = i + 1;
                while (l < r) {
                    int mid = l + (r - l) / 2;
                    if (f[mid] >= t) {
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                }
                f[r] = t;
                ans = max(ans, r);
            }
            return ans >= 3;
        }
    };

    image.gif

    Python版本:

    def increasingTriplet(nums):
        n = len(nums)
        ans = 1
        f = [float('inf')] * (n + 1)
        for i in range(n):
            t = nums[i]
            l, r = 1, i + 1
            while l < r:
                mid = (l + r) // 2
                if f[mid] >= t:
                    r = mid
                else:
                    l = mid + 1
            f[r] = t
            ans = max(ans, r)
        return ans >= 3

    image.gif

    3.2 方法二:贪心(优化)

    Java版本:

    class Solution {
        public boolean increasingTriplet(int[] nums) {
            int n = nums.length;
            long[] f = new long[3];
            f[1] = f[2] = Integer.MAX_VALUE;
            for (int t : nums) {
                if (f[2] < t) {
                    return true;
                } else if (f[1] < t && t < f[2]) {
                    f[2] = t;
                } else if (f[1] > t) {
                    f[1] = t;
                }
            }
            return false;
        }
    }

    image.gif

    C++版本:

    class Solution {
    public:
        bool increasingTriplet(vector<int>& nums) {
            int n = nums.size();
            long long f[3] = {INT_MAX, INT_MAX, INT_MAX};
            for (int t : nums) {
                if (f[2] < t) {
                    return true;
                } else if (f[1] < t && t < f[2]) {
                    f[2] = t;
                } else if (f[1] > t) {
                    f[1] = t;
                }
            }
            return false;
        }
    };

    image.gif

    Python版本:

    class Solution:
        def increasingTriplet(self, nums: List[int]) -> bool:
            n = len(nums)
            f = [float('inf'), float('inf'), float('inf')]
            for t in nums:
                if f[2] < t:
                    return True
                elif f[1] < t < f[2]:
                    f[2] = t
                elif f[1] > t:
                    f[1] = t
            return False

    image.gif


    四、复杂度分析

    4.1 方法一:贪心 + 二分

      • 时间复杂度:O(nlog⁡n)
      • 空间复杂度:O(n)

      4.2 方法二:贪心(优化)

        • 时间复杂度:O(n)
        • 空间复杂度:O(1)
        目录
        相关文章
        |
        1月前
        |
        存储 人工智能 算法
        数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
        这篇文章详细介绍了Dijkstra和Floyd算法,这两种算法分别用于解决单源和多源最短路径问题,并且提供了Java语言的实现代码。
        69 3
        数据结构与算法细节篇之最短路径问题:Dijkstra和Floyd算法详细描述,java语言实现。
        |
        1月前
        |
        机器学习/深度学习 存储 缓存
        数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
        文章主要介绍了排序算法的分类、时间复杂度的概念和计算方法,以及常见的时间复杂度级别,并简单提及了空间复杂度。
        25 1
        数据结构与算法学习十:排序算法介绍、时间频度、时间复杂度、常用时间复杂度介绍
        |
        30天前
        |
        存储 算法 Java
        Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性
        Java Set因其“无重复”特性在集合框架中独树一帜。本文解析了Set接口及其主要实现类(如HashSet、TreeSet)如何通过特定数据结构和算法确保元素唯一性,并提供了最佳实践建议,包括选择合适的Set实现类和正确实现自定义对象的hashCode()与equals()方法。
        32 4
        |
        1月前
        |
        算法
        动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
        这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
        65 0
        动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
        |
        1月前
        |
        搜索推荐 算法
        数据结构与算法学习十四:常用排序算法总结和对比
        关于常用排序算法的总结和对比,包括稳定性、内排序、外排序、时间复杂度和空间复杂度等术语的解释。
        19 0
        数据结构与算法学习十四:常用排序算法总结和对比
        |
        1月前
        |
        存储 缓存 分布式计算
        数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
        这篇文章是关于数据结构与算法的学习指南,涵盖了数据结构的分类、数据结构与算法的关系、实际编程中遇到的问题以及几个经典的算法面试题。
        29 0
        数据结构与算法学习一:学习前的准备,数据结构的分类,数据结构与算法的关系,实际编程中遇到的问题,几个经典算法问题
        |
        1月前
        |
        机器学习/深度学习 存储 算法
        【数据结构与算法基础】——算法复杂度
        【数据结构与算法基础】——算法复杂度
        |
        1月前
        |
        机器学习/深度学习 搜索推荐 算法
        探索数据结构:初入算法之经典排序算法
        探索数据结构:初入算法之经典排序算法
        |
        1月前
        |
        算法 Java 索引
        数据结构与算法学习十五:常用查找算法介绍,线性排序、二分查找(折半查找)算法、差值查找算法、斐波那契(黄金分割法)查找算法
        四种常用的查找算法:顺序查找、二分查找(折半查找)、插值查找和斐波那契查找,并提供了Java语言的实现代码和测试结果。
        19 0
        |
        1月前
        |
        存储 算法 Java
        数据结构和算法--分段树
        数据结构和算法--分段树
        16 0