首页

▼ 算法合集

▼ 加密算法

▼ 凯撒密码

凯撒密码

▼ Hill密码算法

Hill密码算法

▼ 多项式哈希算法

多项式哈希算法

▼ Rail Fence Cipher

Rail Fence Cipher

▼ Graph算法

▼ 关节点

关节点

▼ Bellman-Ford算法

Bellman-Ford算法

▼ 广度优先搜索

广度优先搜索

▼ GraphBridges

桥接模式

▼ 深度优先搜索

深度优先搜索

▼ 检测循环

检测循环

▼ Dijkstra算法

Dijkstra算法

▼ Eulerian Path

欧拉路径

▼ Floyd-Warshall算法

Floyd-Warshall算法

▼ HamiltonianCycle

Hamiltonian Cycle

▼ Kruskal算法

Kruskal算法

▼ Prim算法

Prim算法

▼ 强连通分量

强连通分量

▼ 拓扑排序

拓扑排序

▼ Travelling Salesman Problem

Travelling Salesman

▼ 图像处理算法

▼ Seam Carving算法

内容感知缩放算法

▼ 链表

▼ 反向遍历

反向遍历

▼ GraphTraversal

GraphTraversal

▼ 数学算法

▼ 二进制浮点数

BinaryFloatingPoint

▼ 位操作

位操作算法

▼ 复数

复数

▼ 欧几里得算法

Euclidean Algorithm

▼ Euclidean Distance

欧几里得距离

▼ 阶乘算法

阶乘算法

▼ 快速幂算法

快速幂算法

▼ Fibonacci数列

斐波那契数列

▼ 傅里叶变换

Fourier变换

▼ Horner法

霍纳法则

▼ 整数划分

整数划分

▼ 判断是否为2的幂

判断是否为2的幂

▼ 最小公倍数

最小公倍数

▼ Liu Hui

Liu Hui

▼ 矩阵

Matrix

▼ Pascal三角形

Pascal三角形

▼ Primality Test

素数测试

▼ 质因数

质因数

▼ 弧度计算

弧度计算

▼ 埃拉托色尼筛法

埃拉托色尼筛法

▼ SquareRoot

SquareRoot

▼ MachineLearning

▼ K均值算法

K均值算法

▼ K近邻算法

K近邻算法

▼ 搜索算法

▼ 二分查找算法

二分查找

▼ 插值搜索算法

插值搜索算法

▼ 跳跃搜索算法

跳跃搜索算法

▼ 线性搜索

线性搜索算法

▼ 集合

▼ 笛卡尔积

笛卡尔积

▼ 组合总和

组合总和

▼ 组合算法

组合算法

▼ Fisher-Yates洗牌算法

Fisher-Yates洗牌算法

▼ 背包问题

背包问题

▼ 最长公共子序列

最长公共子序列

▼ 最长递增子序列

最长递增子序列

▼ 最大子数组

最大子数组

▼ 排列组合

排列组合

▼ 幂集

幂集算法

▼ 最短公共超序列

最短公共超序列

▼ Sorting Algorithms

▼ 冒泡排序

冒泡排序

▼ 桶排序算法

桶排序算法

▼ 计数排序算法

计数排序

▼ 堆排序算法

堆排序

▼ 插入排序

插入排序

▼ 归并排序

归并排序

▼ 快速排序算法

快速排序算法

▼ 基数排序

基数排序

▼ 选择排序算法

选择排序算法

▼ 希尔排序

希尔排序

▼ 统计学

▼ 加权随机

加权随机算法

▼ 字符串算法

▼ Hamming距离

Hamming距离

▼ KnuthMorrisPratt算法

Knuth-Morris-Pratt算法

▼ LevenshteinDistance

Levenshtein距离

▼ 最长公共子串

最长公共子串

▼ 回文检测算法

回文检测算法

▼ Rabin-Karp算法

Rabin-Karp算法

▼ 正则表达式匹配

正则表达式匹配

▼ Z算法

Z算法

▼ Tree Data Structure

▼ 广度优先搜索

广度优先搜索

▼ 深度优先搜索

深度优先搜索

▼ 未分类

▼ 最佳买卖股票时机

最佳买卖股票时机

▼ 汉诺塔算法

HanoiTower

▼ 跳跃游戏算法

跳跃游戏

▼ KnightTour

骑士巡逻

▼ N皇后问题

N皇后问题

▼ 雨水收集

雨水收集

▼ 递归楼梯问题

递归楼梯问题

▼ 方阵旋转

方阵旋转

▼ 独特路径

UniquePaths

▼ 数据结构

▼ BloomFilter算法

布隆过滤器

▼ 不相交集数据结构

Disjoint Set

▼ 双向链表

双向链表

▼ Graph

Graph算法

▼ 哈希表

哈希表

▼ Heap数据结构

Heap数据结构

▼ 链表

链表

▼ LRU缓存

LRU缓存

▼ 优先队列

优先队列

▼ 队列

队列

▼ 栈结构

栈结构

▼ Tree Data Structure

树结构

▼ AVL树

AVL树

▼ 二叉搜索树

二叉搜索树

▼ Fenwick树

Fenwick树

▼ 红黑树

红黑树

▼ 线段树

SegmentTree

▼ Trie数据结构

Trie数据结构

最佳买卖股票时间

任务描述

假设有一个数组 prices,其中第 i 个元素是股票在第 i 天的价格。

找出最大利润。你可以进行任意次数的交易(即,买一次并卖出一次股票多次)。

注意:你不能同时进行多次交易(即,在再次购买股票之前必须先卖出股票)。

示例 #1

输入: [7, 1, 5, 3, 6, 4]
输出: 7

解释:在第 2 天买入(价格 1)并在第 3 天卖出(价格 5),利润为 5-1=4。然后在第 4 天买入(价格 3)并在第 5 天卖出(价格 6),利润为 6-3=3

示例 #2

输入: [1, 2, 3, 4, 5]
输出: 4

解释:在第 1 天买入(价格 1)并在第 5 天卖出(价格 5),利润为 5-1=4。注意,你不能在第 1 天买入,然后在第 2 天买入然后卖出它们,因为你在同一时间进行了多次交易。你必须先卖出再买入。

示例 #3

输入: [7, 6, 4, 3, 1]
输出: 0

解释:在这种情况下,没有进行任何交易,即最大利润为 0

可能的解决方案

分治法 O(2^n)

我们可以尝试所有可能的买卖组合,并通过应用 分治法 找出最有利可图的一种。

假设我们有价格数组 [7, 6, 4, 3, 1],我们正处于第 1 天的交易中(在数组的开始)。此时,我们可以说整体最大利润将是以下两个值的 最大值

  1. 选项 1: 留住这笔钱 → 利润将等于从购买/卖出剩余股票中获得的利润 → keepProfit = profit([6, 4, 3, 1])
  2. 选项 2: 在当前价格时买入/卖出 → 在这种情况下,利润将等于从购买/卖出剩余股票中获得利润加上(或减去,取决于我们是卖出还是买入)当前股票价格 → buySellProfit = -7 + profit([6, 4, 3, 1])

整体利润将等于 → overallProfit = Max(keepProfit, buySellProfit)

正如您所看到的,profit([6, 4, 3, 1]) 任务以相同的递归方式解决。

见完整代码示例在 dqBestTimeToBuySellStocks.js

时间复杂度

如您所见,每个递归调用都会产生 2 个更多的递归分支。递归的深度将是 n(价格数组的大小),因此时间复杂度将等于 O(2^n)

如您所见,这非常低效。例如,对于仅仅 20 个价格,递归调用的数量将在 2M 左右!

额外空间复杂度

如果我们避免在递归函数调用之间克隆价格数组,并将指针用于数组,则额外空间复杂度将与递归深度成正比:O(n)

峰谷法 O(n)

如果我们绘制价格数组(即 [7, 1, 5, 3, 6, 4]),我们可以注意到感兴趣的点是连续的谷底和峰值

峰谷法

图像来源:LeetCode

因此,如果我们将在价格上升时跟踪增长,并且在价格下降之前立即卖出股票,我们将获得最大利润(记住,我们在谷底以低价购买了股票)。

见完整代码示例在 peakvalleyBestTimeToBuySellStocks.js

时间复杂度

由于该算法只需遍历价格数组一次,时间复杂度将等于 O(n)

额外空间复杂度

除了价格数组本身之外,该算法消耗常数量的内存。因此,额外空间复杂度为 O(1)

累加器法 O(n)

甚至存在更简单的解决方案。假设我们有这样的价格数组 [1, 7, 2, 3, 6, 7, 6, 7]

简单的一次遍历

图像来源:LeetCode

您可能会注意到,我们甚至不需要跟踪不断增长的房价。相反,我们可以简单地添加图表中所有增长段的房价差,最终总和为可能的最大利润,

见完整代码示例在 accumulatorBestTimeToBuySellStocks.js

时间复杂度

由于该算法只需遍历价格数组一次,时间复杂度将等于 O(n)

额外空间复杂度

除了价格数组本身之外,该算法消耗常数量的内存。因此,额外空间复杂度为 O(1)

参考资料