优选算法 力扣 611. 有效三角形的个数 双指针降低时间复杂度 贪心策略 C++题解 每日一题

目录

零、题目描述

题目链接:有效三角形的个数

题目描述:

示例 1:

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

输出:3

解释:有效的组合是:

2,3,4 (使用第一个 2)

2,3,4 (使用第二个 2)

2,2,3
示例 2:

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

输出:4
提示:

1 <= nums.length <= 1000

0 <= nums[i] <= 1000

一、为什么这道题值得你花几分钟看懂?

有效三角形的个数问题,是双指针算法在"三重循环优化"中的经典应用。它的价值在于:让你学会如何通过排序和条件转化,将高复杂度的枚举问题降维打击

这道题看似需要枚举所有可能的三元组(暴力解法的思路),但通过三角形三边关系的数学特性,我们能发现一个关键规律:对于排序后的数组,若较小的两边之和大于最大边,则三者可组成三角形。这个规律让双指针有了用武之地------通过固定最大边,用双指针寻找符合条件的较小两边,将时间复杂度从 O(n³) 降至 O(n²)。

搞定这道题,你会掌握:

  • 如何将数学定理转化为算法优化的依据
  • 双指针在"固定一端,搜索两端"场景中的应用
  • 排序在数组优化问题中的辅助作用

这种"用数学简化问题,用指针降低复杂度"的思维,对解决组合计数类问题至关重要。

二、从暴力枚举到双指针:思路的进化

1. 暴力枚举:直观但低效的尝试

最容易想到的思路是:枚举所有可能的三元组组合,判断是否满足三角形三边关系,统计符合条件的个数。

暴力解法思路

cpp 复制代码
// 伪代码
int count = 0;
for (int i = 0; i < n; i++) {
    for (int j = i + 1; j < n; j++) {
        for (int k = j + 1; k < n; k++) {
            if (nums[i] + nums[j] > nums[k] && 
                nums[i] + nums[k] > nums[j] && 
                nums[j] + nums[k] > nums[i]) {
                count++;
            }
        }
    }
}
return count;

问题分析

  • 时间复杂度:O(n³),当 n 为 1000 时,运算次数高达 10⁹,必然超时
  • 空间复杂度:O(1),只使用常数个变量
  • 核心缺陷:存在大量重复判断,且三边关系的三个条件中有冗余

2. 数学简化:三角形条件的优化

三角形三边关系的本质是:任意两边之和大于第三边 。但当数组排序后(假设 a ≤ b ≤ c),这个条件可以简化为:a + b > c

证明

  • 因为 a ≤ b ≤ c,所以 a + c > b(c ≥ b 且 a ≥ 0,故 a + c ≥ b,当 a > 0 时成立)
  • 同理 b + c > a 也一定成立
  • 因此只需判断最小的两边之和是否大于最大边(a + b > c)

这个简化让我们的判断条件从三个减少为一个,为后续优化奠定基础。

3. 双指针:固定最大边,搜索有效对

基于排序和简化的条件,我们可以设计更高效的算法:

  • 先对数组排序(从小到大)
  • 固定最大边 c(即数组中的某个元素 nums[i])
  • 用双指针寻找所有满足 a + b > c 的 (a, b) 对(其中 a 和 b 是 c 左侧的元素,且 a ≤ b < c)

为什么固定最大边?

  • 排序后,数组右侧的元素更大,适合作为最大边 c
  • 固定 c 后,只需在左侧元素中寻找符合条件的 a 和 b,缩小搜索范围

三、双指针实现:固定一端,收缩两端

1. 算法步骤

  1. 对数组 nums 排序(从小到大)
  2. 初始化计数变量 count = 0
  3. 从右向左遍历数组,固定最大边 c = nums[i](i 从 n-1 开始,至少为 2,因为需要三个元素)
  4. 对每个 i,设置双指针:left = 0(指向最小元素),right = i - 1(指向 c 左侧的最大元素)
  5. left < right 时,循环:
    • nums[left] + nums[right] > nums[i]:说明从 left 到 right-1 的所有元素与 right 搭配,都满足和大于 c(因为数组递增),因此计数 count += right - left,并将 right 左移(缩小范围)
    • 否则:说明当前 left 太小,将 left 右移(增大 a 的值)
  6. 遍历结束后,返回 count

2. 代码实现

cpp 复制代码
class Solution {
public:
    int triangleNumber(vector<int>& nums) {
        int answer = 0;
        // 先对数组排序,为双指针创造条件
        sort(nums.begin(), nums.end());
        // 从右向左固定最大边 c = nums[i]
        for (int i = nums.size() - 1; i >= 2; i--) {
            int left = 0;       // 左指针指向最小元素
            int right = i - 1;  // 右指针指向最大边左侧的元素
            // 双指针搜索符合条件的 a 和 b
            while (left < right) {
                // 若 a + b > c,说明 [left, right-1] 与 right 搭配均有效
                if (nums[left] + nums[right] > nums[i]) {
                    answer += right - left;
                    right--;  // 缩小右边界,寻找更小的 b
                } else {
                    left++;   // 增大左边界,寻找更大的 a
                }
            }
        }
        return answer;
    }
};

四、代码解析与复杂度分析

代码走读(示例1:nums = [2,2,3,4]

  1. 排序后:[2,2,3,4]
  2. 固定最大边 i=3(值为4):
    • left=0(值为2),right=2(值为3)
    • 2 + 3 > 4 → 有效,计数 answer += 2-0=2(即 (2,3) 和 (2,3) 两个组合 注:用的不同的2)
    • right-- → right=1
    • left=0 < right=1,判断 2 + 2 = 4 → 不满足,left++ → left=1
    • left 不再小于 right,结束本轮
  3. 固定最大边 i=2(值为3):
    • left=0,right=1(值为2)
    • 2 + 2 > 3 → 有效,计数 answer += 1-0 = 1
    • right-- → right=0,循环结束
  4. 总计数为 2 + 1 = 3,与示例结果一致

复杂度分析

复杂度类型 具体值 说明
时间复杂度 O(n²) 排序耗时 O(n log n),外层循环 O(n),内层双指针循环 O(n),总体由 O(n²) 主导
空间复杂度 O(log n) 排序算法的栈空间开销(取决于具体实现)

五、常见问题与注意事项

1. 为什么排序后能保证正确性?

排序后,我们固定最大边 c,并在左侧寻找 a 和 b(a ≤ b < c)。此时只需判断 a + b > c,因为:

  • 由于 a ≤ b ≤ c,a + c > b 和 b + c > a 必然成立
  • 所有可能的三元组都会被枚举,因为我们遍历了所有可能的最大边 c

2. 数组中存在 0 会影响结果吗?

不会。因为 0 无法参与组成三角形(例如 0,0,0 或 0,1,1 都不满足两边之和大于第三边),但算法会自动过滤这些情况:

  • 若 a=0,b 为正数,c ≥ b,则 a + b = b ≤ c → 不满足条件,left 会右移
  • 最终 0 不会被计入有效计数

3. 双指针的移动逻辑为什么正确?

  • nums[left] + nums[right] > c 时:因为数组递增,left 到 right-1 之间的所有元素与 right 搭配,和都会大于 c(例如 left+1 对应的元素 ≥ left,故 left+1 + right ≥ left + right > c),因此直接计数 right - left
  • nums[left] + nums[right] ≤ c 时:当前 left 太小,即使与最大的 b(right)搭配都不满足,因此必须右移 left 以增大 a 的值

六、举一反三

这道题的"固定一端 + 双指针"思路可推广到:

  • 三数问题:如寻找三数之和为目标值、三数乘积最大等
  • 区间计数问题:如统计数组中满足某种条件的区间对数量

下一题预告LeetCode LCR 179. 查找总价格为目标值的两个商品!这道题是经典的"两数之和"变种,会让你看到双指针在"有序数组中寻找目标对"的妙用------如何通过首尾指针的移动,快速定位和为目标值的两个元素,将时间复杂度从 O(n²) 优化到 O(n)。明天的解析会带你拆解其中的逻辑,让你进一步熟悉双指针的灵活应用!

最后欢迎大家在评论区分享你的代码或思路,咱们一起交流探讨~ 🌟 要是有大佬有更精妙的思路或想法,恳请在评论区多多指点批评,我一定会虚心学习,并且第一时间回复交流哒!

✨ 如果你觉得这些思路对你有启发,不妨动动手指:

  • 🌟 点个赞,让这份思考被更多人看见
  • ⭐ 收个藏,下次遇到类似问题能快速找回灵感
  • 👀 加个关注,后续更新的解题干货绝对不会让你迷路

毕竟,在算法的世界里,互相照亮的每一步,都走得更有力量呀~这是封面原图,咱们下篇题解再见!😉

相关推荐
我要学习别拦我~19 分钟前
逻辑回归建模核心知识点梳理:原理、假设、评估指标与实战建议
算法·机器学习·逻辑回归
KotlinKUG贵州41 分钟前
贪心算法:从“瞎蒙”到稳赚
算法·kotlin
重生之我是Java开发战士1 小时前
【C语言】动态内存管理详解
c语言·开发语言·算法
墨染点香1 小时前
LeetCode 刷题【31. 下一个排列】
算法·leetcode·职场和发展
wrynhyxa1 小时前
力扣热题100——子串
算法·leetcode·哈希算法
Moonbit2 小时前
MoonBit Pearls Vol.03:01背包问题
后端·算法·编程语言
啊阿狸不会拉杆2 小时前
《算法导论》第 2 章 - 算法基础
数据结构·c++·算法·排序算法
啊阿狸不会拉杆2 小时前
《算法导论》第 4 章 - 分治策略
开发语言·数据结构·c++·算法·排序算法
2501_924731992 小时前
智慧能源场景设备缺陷漏检率↓76%:陌讯多模态融合检测方案实战解析
大数据·人工智能·算法·目标检测·计算机视觉·视觉检测
白葵新2 小时前
C#案例实战
c++·python·算法·计算机视觉·c#