跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++算法

C++ 算法实战:排序子序列、消减整数与最长上升子序列

C++ 算法实战涵盖三个典型题目。排序子序列通过遍历判断单调性划分;消减整数利用贪心策略最大化每次减法幅度;最长上升子序列采用动态规划结合二分查找将复杂度优化至 O(n log n)。代码实现包含完整逻辑与边界处理,适合笔试面试复习。

孤勇者发布于 2026/3/26更新于 2026/6/1021 浏览
C++ 算法实战:排序子序列、消减整数与最长上升子序列

一、排序子序列

题目解析

排序子序列划分示例

所谓排序子序列,是指数组中一段连续的元素,它们要么是非递增的,要么是非递减的。给定一个数组,我们需要判断它最少可以划分为多少个这样的排序子序列。

例如:1 2 3 2 2 1 可以划分为 1 2 3 和 2 2 1 两个排序子序列。

算法思路

这道题的核心在于如何贪心地确定分割点。

暴力解法 从索引 0 开始遍历,寻找一段满足条件的连续子序列,直到条件不再满足(即发现不单调),此时计数加一并结束当前段;接着从该位置继续向后寻找下一段。

贪心优化 当遇到数值相同的区域时,由于非递增或非递减都允许相等元素存在,这段相等的序列既可以归入前一段,也可以归入后一段。因此,在遍历时遇到相等值,直接跳过即可,无需立即决定归属。

这里有两个边界情况需要注意:

  1. 开头相等:如果数组起始位置就是相等序列,无法判断后续是递增还是递减。处理策略是直接跳过这些相等元素,因为它们总能被合并到后续的单调序列中。
  2. 结尾元素:如果最后一个元素无法与前一段组成单调序列,则它单独构成一个子序列。

消减整数过程演示

代码实现

#include <iostream>
using namespace std;

const int N = 1e5 + 10;
int arr[N];
int n;

int main() {
    cin >> n;
    for (int i = 0; i < n; i++) cin >> arr[i];

    int ret = 0;
    for (int i = 0; i < n; i++) {
         (i == n - ) {
            
            ret++;
            ;
        }
         (arr[i] < arr[i + ]) {
            
             (i < n && arr[i] <= arr[i + ]) i++;
            ret++;
        }   (arr[i] > arr[i + ]) {
            
             (i < n && arr[i] >= arr[i + ]) i++;
            ret++;
        }  {
            
             (i < n && arr[i] == arr[i + ]) i++;
        }
    }
    cout << ret << endl;
     ;
}
if
1
// 数组最后一个位置,必须计入
break
if
1
// 非递减序列
while
1
else
if
1
// 非递增序列
while
1
else
// 开头位置的相等子序列,直接跳过
while
1
return
0

二、消减整数

题目解析

最长上升子序列状态更新

给定一个正整数 H,从 1 开始进行减法操作。第一次减去 1,之后每一次减去的数要么是上一次减去的数 x,要么是 2*x。目标是求将 H 减到 0 所需的最少次数。

简单来说,每次操作可以选择减去 x 或 2x,其中 x 初始为 1,随后根据选择更新。

算法思路

暴力解法 尝试所有可能的组合,直到 H 减到 0 或小于 0。这种方法时间复杂度和空间复杂度都过高,且容易陷入死循环或无法恰好减到 0 的情况。

贪心优化 既然要求最少次数,直觉上我们应该尽可能多地减去较大的数。但前提是减完后剩余部分能被整除,否则可能无法精确减到 0。

具体策略如下:

  1. 首先执行一次减法,H 变为 H-1,已用次数为 1,当前基数 x=1。
  2. 在后续步骤中,检查剩余的 H 是否能被 2*x 整除。
    • 如果能整除,说明减去 2*x 是可行的,且能更快接近 0。此时我们将 x 翻倍,然后减去新的 x。
    • 如果不能整除,说明减去 2*x 会导致余数无法被后续操作消除,只能减去当前的 x。

这个逻辑保证了每一步都在'可行'的前提下最大化了减去的幅度。

贪心策略选择示意

代码实现

#include <iostream>
using namespace std;

int main() {
    int t;
    cin >> t;
    while (t--) {
        int n;
        cin >> n;
        n--; // 第一次减去 1
        int ret = 1, x = 1;
        while (n) {
            if (n % (2 * x) == 0) {
                x *= 2;
            }
            n -= x;
            ret++;
        }
        cout << ret << endl;
    }
    return 0;
}

三、最长上升子序列 (二)

题目解析

给定一个数组,找到其中最长的严格上升子序列的长度。严格上升意味着新数组中的元素必须严格大于前一个元素。

算法思路

动态规划思路 直观的想法是记录每个位置作为结尾的最长上升子序列长度。但这需要 O(n^2) 的时间复杂度,对于大数据量不够高效。

核心优化 我们不需要记录所有的子序列,只需要记录长度为 n 的子序列中,末尾元素最小的那个值。为什么?因为末尾元素越小,后续接上更大元素的概率就越大,越容易形成长度更长的序列。

假设我们维护一个数组 dp,其中 dp[i] 表示长度为 i+1 的上升子序列的最小末尾元素。这个 dp 数组本身也是严格递增的。

二分查找 当遍历到一个新元素 e 时:

  1. 如果 e 大于 dp 中所有元素,说明它可以接在最长子序列后面,形成更长的序列,直接追加到 dp 末尾。
  2. 如果 e 不能延长最长序列,但它比某个长度的子序列末尾小,我们可以替换掉那个位置的末尾元素,从而让该长度的子序列变得更'优'。

由于 dp 数组是有序的,我们可以使用二分查找来定位应该替换的位置。我们要找的是第一个大于等于 e 的元素位置,将其替换为 e。

排序子序列划分示例

代码实现

class Solution {
public:
    int dp[100001];
    int pos = 0;

    int LIS(vector<int>& a) {
        for (auto& e : a) {
            if (pos == 0 || e >= dp[pos]) {
                dp[++pos] = e;
            } else {
                int l = 1, r = pos;
                while (l < r) {
                    int mid = l + (r - l) / 2;
                    if (dp[mid] >= e) {
                        r = mid;
                    } else {
                        l = mid + 1;
                    }
                }
                dp[l] = e;
            }
        }
        return pos;
    }
};

目录

  1. 一、排序子序列
  2. 题目解析
  3. 算法思路
  4. 代码实现
  5. 二、消减整数
  6. 题目解析
  7. 算法思路
  8. 代码实现
  9. 三、最长上升子序列 (二)
  10. 题目解析
  11. 算法思路
  12. 代码实现
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • C++ TCP Socket 网络编程基础与封装实战
  • Nginx 重载时 Worker 进程关闭机制解析
  • Dify 工作流发布为 MCP Server 实战指南
  • C++ 从零实现 TCP Socket 网络通信工具
  • 2023 年入职或转行网络安全职业规划指南
  • Android IM 即时通讯应用开发实战:基于 Smack 与 Openfire
  • 前端安全实战:如何避免 JavaScript 代码中的常见漏洞
  • ERNIE-4.5-0.3B 超轻量模型部署与性能实测指南
  • DeepSeek 使用完全指南:10 个高效对话与隐藏技巧
  • Kokoro-TTS跨平台C++移植实战:从Windows到嵌入式终端
  • AI Agent开发第86课-讲透知识图谱Neo4j在构建Agent时到底怎么用(一)
  • macOS Tahoe 26.2 更新详解:12 项关键改进与体验优化
  • 2024 大模型落地应用案例集:娱乐、视频与游戏行业精选
  • Python 面向对象编程入门:初识对象
  • 使用 Bright Data Web Scraper API + Python 抓取 Glassdoor 数据实战
  • FLASH 坏块监测系统算法题解
  • PlotDigitizer 图表数据自动化提取实战指南
  • LoRA 微调 LLaMA 类模型:原理与实战指南
  • 计算机视觉基础与实战应用指南
  • 基于 AI Agent 的通用米家智能家居控制方案

相关免费在线工具

  • 加密/解密文本

    使用加密算法(如AES、TripleDES、Rabbit或RC4)加密和解密文本明文。 在线工具,加密/解密文本在线工具,online

  • Gemini 图片去水印

    基于开源反向 Alpha 混合算法去除 Gemini/Nano Banana 图片水印,支持批量处理与下载。 在线工具,Gemini 图片去水印在线工具,online

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online