news 2026/6/9 16:08:01

从零开始写算法——贪心篇2:买卖股票的最佳时间 + 划分字母区间

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从零开始写算法——贪心篇2:买卖股票的最佳时间 + 划分字母区间

在算法中,贪心算法 (Greedy Algorithm)往往是一个让人又爱又恨的话题。爱它是因为代码通常很短,恨它是因为“当前最优选择会导致全局最优”这个逻辑有时候很难一眼看穿。

今天我们通过两道经典的 LeetCode 题目——121. 买卖股票的最佳时机763. 划分字母区间,来感受一下贪心算法如何在“一次遍历”中解决问题。

这两个题目虽然场景完全不同,但核心思想是通用的:维护一个关键的状态变量(最小值或最远边界),在遍历过程中不断更新这个状态,从而得到最终答案。


Part 1:买卖股票的最佳时机 —— 维护“历史最低点”

1. 题目核心

给定一个数组prices,它的第i个元素是一支给定股票第i天的价格。如果你最多只允许完成一笔交易(即买入和卖出一支股票一次),设计一个算法来计算你所能获取的最大利润。

2. 贪心策略

我们要赚钱,核心逻辑非常简单:“低买高卖”。 但是我们不能穿越时空,我们只能在当前这一天决定:

  1. 如果在今天卖出,我能赚多少?(取决于之前的最低价是多少)。

  2. 今天是不是一个新的“历史最低点”?如果是,那以后如果想买,肯定按今天的价格买更划算。

所以,我们需要维护一个变量min_price,代表过去几天(包括今天)出现的最低股价

3. 代码实现 (C++)

你的代码中有一个非常有意思的注释,关于“先算利润还是先更新最小值”的讨论,这体现了很好的思考深度。

C++代码实现:

class Solution { public: int maxProfit(vector<int>& prices) { // 思路就是我们通过一次遍历,但是这个遍历中我们去维护最小值,以及更新答案 // min_price 初始化为第一天的价格 int ans = 0, min_price = prices[0]; for (int x : prices) { /* 这里有一个细节逻辑: 我们是先计算 ans = max(ans, x - min_price),再更新 min_price。 这意味着我们计算的是:【今天的价格 - 之前几天的最低价】。 如果你反过来,先更新 min_price,再算 ans, 那么计算的就是:【今天的价格 - 包括今天在内的历史最低价】。 如果今天就是最低价,利润算出来是 0,反正 ans 初始化也是 0, 所以这两种写法对最终结果 ans 没有影响。 */ ans = max(ans, x - min_price); min_price = min(min_price, x); } return ans; } };

4. 复杂度分析

  • 时间复杂度:O(N)

    • 我们只用了一个for循环遍历数组prices,每个元素只访问了一次。

  • 空间复杂度:O(1)

    • 我们只使用了ansmin_price两个整数变量,不需要额外的数组空间。


Part 2:划分字母区间 —— 维护“最远边界”

1. 题目核心

给你一个字符串s。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。 例如:ababcbacadefegdehijhklija出现在最前面,也出现在下标 8 的位置。那么第一个片段至少要切在下标 8 之后,否则a就跨越两个片段了。

2. 贪心策略

这道题的贪心逻辑在于:一旦由于某个字母(比如 'a')被迫扩大了区间,这个区间内的所有其他字母(比如 'b', 'c')也得被包进来。

我们可以把这个问题分解为两个步骤:

  1. 预处理:每个人都想知道自己“最后出现的位置”在哪里。我们可以先遍历一遍,记下每个字母最后一次出现的下标。

  2. 合并区间

    • 我们维护一个end指针,表示当前片段至少要延伸到的位置

    • 当我们遍历到一个字符c时,看看它的最后出现位置是不是比当前的end还远?如果是,为了包住这个c,我们的片段必须延长(end变大)。

    • 何时收网?当遍历下标i追上了end时,说明这一段里所有出现过的字母,它们的最后一次出现位置都在i之前(或就是i)。此刻我们可以安全地切一刀!

3. 代码实现 (C++)

这一段代码完美诠释了“二次遍历”的思想。

C++代码实现:

class Solution { // 思路: 同一个字母最多出现在一个片段中,意味这个区间要包含所有这个字母,所以需要对区间进行划分 // 先计算每个字母对应下标,然后划分出区间,然而区间内的其他字母也会有自己对应的区间,此时应该进行合并区间 // 也就是我们目标两次遍历,第一次预处理,第二次进行划分。 public: vector<int> partitionLabels(string s) { int n = s.size(); // last 数组用来充当哈希表,记录每个字符最后出现的下标 // 大小为 26,对应 'a'-'z' int last[26]; for (int i = 0; i < n; ++i) { last[s[i] - 'a'] = i; // 不断更新,最后留下的就是最后一次出现的下标 } vector<int> ans; int start = 0, end = 0; // 第二次遍历:根据最远边界进行切割 for (int i = 0; i < n; ++i) { // 贪心核心:end 必须能包住当前字符 s[i] 的最后出现位置 end = max(end, last[s[i] - 'a']); // 如果当前的遍历下标 i 追上了 end,说明当前片段可以结束了 if(end == i) { ans.push_back(end - start + 1); // 计算当前片段长度 start = i + 1; // 更新下一个片段的起点 } } return ans; } };

4. 复杂度分析

  • 时间复杂度:O(N)

    • 虽然有两个for循环,但它们是并列的,不是嵌套的。

    • 第一个循环遍历N次构建last数组。

    • 第二个循环遍历N次进行切割。

    • 总操作次数是2N,在渐进复杂度中依然是 O(N)。

  • 空间复杂度:O(1)

    • 虽然我们开辟了一个last数组,但它的大小固定为 26(字符集大小),不随字符串长度N变化。在算法分析中,常数大小的空间视为 O(1)。

    • (注:返回值的ans数组通常不计入算法的空间复杂度,因为它用于存储结果)。


总结

这两道题虽然外表不同,但本质上都是线性扫描 + 状态更新

  1. 买卖股票:我们在扫描中更新最小值 (min),一旦遇到高价就计算利润。

  2. 划分字母:我们在扫描中更新最远边界 (end),一旦到达边界就进行切割。

这种“走一步看一步”且只需一次(或两次)遍历的方法,正是贪心算法在处理线性数据结构时的强大之处。掌握这种思维,能让你在面试中快速写出 O(N) 的高效代码。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/5 15:41:30

【小程序毕设源码分享】基于springboot+小程序的汽车服务企业客户评价APP的设计与实现(程序+文档+代码讲解+一条龙定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/5 21:11:49

【小程序毕设全套源码+文档】基于Android的地球村共享书屋平台的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍&#xff1a;✌️码农一枚 &#xff0c;专注于大学生项目实战开发、讲解和毕业&#x1f6a2;文撰写修改等。全栈领域优质创作者&#xff0c;博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围&#xff1a;&am…

作者头像 李华
网站建设 2026/6/5 19:51:19

Redis Zset的实现为什么用跳表,而不用平衡树?

之前写过一篇 Redis 数据类型的底层数据结构的实现&#xff0c;其中提到&#xff0c;ZSet 对象的底层数据结构实现之一是跳表。 然后&#xff0c;有读者就问&#xff1a;为什么不使用平衡树&#xff08;如红黑树、AVL 树&#xff09;&#xff1f; 我们先来了解下跳表&#xf…

作者头像 李华
网站建设 2026/6/5 19:43:25

2026之初凭这份Java面试突击指南,斩获9张大厂Offer

今年金三银四快要到了&#xff0c;不知道大家都拿到Offer没有&#xff0c;如果没有的话&#xff0c;希望大家不要怪LZ凡尔赛了&#xff08;手动狗头&#xff09;。LZ截止今天为止已经收到了第9家公司的Offer&#xff0c;这张的Offer的话给到28k*14薪。由于个人原因&#xff0c;…

作者头像 李华
网站建设 2026/6/5 19:53:09

开盘即罄的深层解读:招商林屿缦岛如何重塑城北改善格局

2026年2月8日&#xff0c;招商林屿缦岛用一场“开盘即罄”的市场表现&#xff0c;不仅创造了自身的销售纪录&#xff0c;更在更深层次上影响了城北改善市场的格局。这次热销事件&#xff0c;需要被放置在更广阔的市场背景中进行解读&#xff0c;才能理解其真正的意义与影响。林…

作者头像 李华
网站建设 2026/6/5 20:59:11

挑战一篇文章带你图解Spring事务拆解底层源码!

下面我会简单介绍一下 Spring 事务的基础知识&#xff0c;以及使用方法&#xff0c;然后直接对源码进行拆解。不 BB&#xff0c;上文章目录。1.1. 项目准备需要搭建环境的同学&#xff0c;代码详见&#xff1a;https://github.com/lml200701158/program_demo/tree/main/spring-…

作者头像 李华