算法题 递增的三元子序列

334. 递增的三元子序列

问题描述

给定一个整数数组 nums,判断这个数组中是否存在长度为 3 的递增子序列(即存在下标 i < j < k 使得 nums[i] < nums[j] < nums[k])。要求算法时间复杂度为 O(n),空间复杂度为 O(1)。

示例

输入:nums = [1,2,3,4,5]
输出:true   // 任何三个连续元素都满足

输入:nums = [5,4,3,2,1]
输出:false  // 没有递增的三元组

输入:nums = [2,1,5,0,4,6]
输出:true   // [1,5,6] 或 [0,4,6]

算法思路

贪心策略(双指针)

  1. 维护两个变量
    • first:当前找到的最小候选值(作为三元组的第一个元素)
    • second:当前找到的次小候选值(作为三元组的第二个元素,且满足 first < second
  2. 遍历数组
    • 若当前数 num > second → 找到三元组,返回 true
    • num <= first → 更新 first = num(尝试更小的起点)
    • first < num <= second → 更新 second = num(优化中间值)
  3. 核心思想
    • 通过不断降低 firstsecond 的值,为后续元素创造更大的匹配空间
    • 即使更新了 first,之前合法的 second 仍然有效(隐含保留了历史信息)

代码实现

class Solution {
    public boolean increasingTriplet(int[] nums) {
        // 初始化两个候选值为最大值
        int first = Integer.MAX_VALUE;
        int second = Integer.MAX_VALUE;
        
        for (int num : nums) {
            if (num > second) {
                // 找到大于 second 的数,形成三元组
                return true;
            } else if (num > first) {
                // 更新次小候选值(保证 first < second)
                second = num;
            } else {
                // 更新最小候选值(可能破坏 first < second 关系,但后续会修复)
                first = num;
            }
        }
        return false;
    }
}

算法分析

  • 时间复杂度:O(n)
    仅需遍历数组一次,每个元素处理时间为 O(1)
  • 空间复杂度:O(1)
    仅使用两个额外变量

算法过程

nums = [2,1,5,0,4,6]

  1. 初始:first = ∞, second = ∞
  2. num=2 → 更新 first=2(路径:2
  3. num=1 → 更新 first=1(路径:1
  4. num=5 → 更新 second=5(路径:1→5
  5. num=0 → 更新 first=0(路径:0
  6. num=4 → 更新 second=4(路径:0→4
  7. num=66>4 → 找到三元组 0→4→6,返回 true

关键点

  1. 贪心策略有效性

    • 更新 first 不会破坏已存在的 secondsecond 隐含了比它小的历史值)
    • 示例:当 first=1 更新为 first=0 后,second=5 仍然有效(隐含了 1→5 的路径)
  2. 等号处理

    • 严格递增要求:遇到相等值时跳过更新(如 num <= first 包含等号)
    • 示例:[1,1,1] 不会触发任何更新
  3. 候选值关系

    • 总保持 first <= second(可能暂时不满足 first < second,但遇到中间值会修复)
    • 示例:当 first=0 时,遇到 4 会修复为 first=0 < second=4

边界处理

边界情况处理方式
数组长度小于 3直接返回 false
全递减序列持续更新 first,不会触发 true
重复元素等号确保跳过重复值
三元组出现在序列末尾遍历到最后才触发 num > second 的条件

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 标准递增
    System.out.println(solution.increasingTriplet(new int[]{1,2,3}));    // true
    
    // 三元组不连续
    System.out.println(solution.increasingTriplet(new int[]{2,1,5,0,4,6})); // true
    
    // 全递减
    System.out.println(solution.increasingTriplet(new int[]{5,4,3,2,1}));   // false
    
    // 有重复值
    System.out.println(solution.increasingTriplet(new int[]{1,1,1,1,1}));   // false
    
    // 最小值在末尾
    System.out.println(solution.increasingTriplet(new int[]{2,3,1,4}));     // true (2->3->4)
    
    // 边界长度
    System.out.println(solution.increasingTriplet(new int[]{1,2}));          // false
}

常见问题

  1. 为什么更新 first 后不会丢失历史信息?
    虽然 first 被更新为更小的值,但 second 仍然保存着之前有效的中间值。当后续遇到比 second 大的数时,隐含的历史小值(可能是更新前的 first)仍然与 second 构成二元组。

  2. 遇到重复元素如何处理?
    条件 num <= firstnum <= second 中的等号确保重复元素不会破坏候选关系,且不会形成非严格递增序列。

  3. 算法是否总能找到最长的递增序列?
    不是,该算法仅检测是否存在三元组,不保证找到最长序列(如 [1,3,2,4] 可能检测 1→3→41→2→4)。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值