June 1st 2023
Problem Description permalink
You are given an array prices where prices[i] is the price of a given stock on the ith day.
You want to maximize your profit by choosing a single day to buy one stock and choosing a different day in the future to sell that stock.
Return the maximum profit you can achieve from this transaction. If you cannot achieve any profit, return 0.
Example 1: Input: prices = [7,1,5,3,6,4] Output: 5 Explanation: Buy on day 2 (price = 1) and sell on day 5 (price = 6), profit = 6-1 = 5. Note that buying on day 2 and selling on day 1 is not allowed because you must buy before you sell.
Example 2: Input: prices = [7,6,4,3,1] Output: 0 Explanation: In this case, no transactions are done and the max profit = 0.
Solution (1) permalink
// Util functions
function expect(description: string, test: boolean) {
if (test) {
console.log(`✅ ${description}`)
} else {
console.log(`❌ ${description}`)
}
}
// day is a human day (so the first day is day 1)
function maxProfitFromBuyingAtMoment(prices: number[], day: number): number {
const pricesAfterPurchase = prices.slice(day)
const highestValueAfterPurchase = Math.max(...pricesAfterPurchase)
return Math.max(highestValueAfterPurchase - prices[day - 1], 0)
}
function maxProfit(prices: number[]): number {
const profitsByDay = prices
.map((_: number, index: number) => {
return maxProfitFromBuyingAtMoment(prices, index + 1)
})
return Math.max(...profitsByDay, 0)
};
// Test the max profit for each day
expect(
"The profit from buying on Day 1 in [7, 1, 5, 3, 6, 4] is 0",
maxProfitFromBuyingAtMoment([7, 1, 5, 3, 6, 4], 1) == 0)
expect(
"The profit from buying on Day 2 in [7, 1, 5, 3, 6, 4] is 5",
maxProfitFromBuyingAtMoment([7, 1, 5, 3, 6, 4], 2) == 5)
// Test finding the max profit in the set
expect(
"The max profit in maxProfit([7, 1, 5, 3, 6, 4] is 5",
maxProfit([7, 1, 5, 3, 6, 4]) === 5)
expect(
"The max profit in maxProfit([7,6,4,3,1] is 0",
maxProfit([7, 6, 4, 3, 1]) === 0)
Discussion (1) permalink
This is a brute force solution that compares every moment when a stock could have been bought or sold. I wasn't happy with this, though, I knew I wanted to find local maxima and minima for a quicker solution.
In this was a project that I was actually shipping I might have shipped this version depending on my dataset because it's probably good enough for most things. If this ran as a batch job overnight and wasn't something humans waited on then this is good enough- it's clear and it works.
Solution (2) permalink
// Util functions
function expect(description: string, test: boolean) {
if (test) {
console.log(`✅ ${description}`)
} else {
console.log(`❌ ${description}`)
}
}
function arrayEqual(a: number[], b: number[]): boolean {
if (a.length !== b.length) {
return false
}
for (var i = 0; i < a.length; i++) {
if (a[i] !== b[i]) {
return false
}
}
return true
}
/*
// Need to find local minima and local maxima
[0, 1, 2, 4, 8, 1]
// Store 0
[0, 1] // going up
[1, 2] // going up
[2, 4] // going up
[4, 8] // going up
[8, 1] // going down (store 8 and 1)
*/
enum PriceDirection {
Up = 1,
Down = 2
}
// moment is a computer moment (first element is 0)
function maxProfitFromBuyingAtMoment(prices: number[], moment: number): number {
const pricesAfterPurchase = prices.slice(moment)
const highestValueAfterPurchase = Math.max(...pricesAfterPurchase)
return Math.max(highestValueAfterPurchase - prices[moment], 0)
}
function simplifyPriceLine(prices: number[]): number[] {
var keyPriceMoments: number[] = [prices[0]]
var movementDirection: PriceDirection | null = null
for (var i = 1; i < prices.length; i++) {
const previous = prices[i - 1];
const now = prices[i];
const todaysMovement = now > previous ? PriceDirection.Up : PriceDirection.Down
// console.log([previous, now], todaysMovement == 1 ? "Up" : "Down")
if (movementDirection !== null && todaysMovement != movementDirection) {
keyPriceMoments.push(previous)
}
movementDirection = todaysMovement;
}
// Always append the last one, this is slightly extra work
keyPriceMoments.push(prices[prices.length - 1])
return keyPriceMoments
}
function maxProfit(prices: number[]): number {
const simplifiedPriceLine = simplifyPriceLine(prices)
const profitsForKeyMoments = simplifiedPriceLine
.map((_: number, index: number) => {
return maxProfitFromBuyingAtMoment(simplifiedPriceLine, index)
})
return Math.max(...profitsForKeyMoments, 0)
};
// Test simplifying the price line
expect("Simplifying [1, 2, 4, 2, 5, 7, 2, 4, 9, 0, 9]",
arrayEqual(
simplifyPriceLine([1, 2, 4, 2, 5, 7, 2, 4, 9, 0, 9]),
[1, 4, 2, 7, 2, 9, 0, 9]))
// Test the max profit for each moment
expect(
"The profit from buying on Day 1 in [7, 1, 5, 3, 6, 4] is 0",
maxProfitFromBuyingAtMoment([7, 1, 5, 3, 6, 4], 0) == 0)
expect(
"The profit from buying on Day 2 in [7, 1, 5, 3, 6, 4] is 5",
maxProfitFromBuyingAtMoment([7, 1, 5, 3, 6, 4], 1) == 5)
// // Test finding the max profit in the set
expect(
"The max profit in maxProfit([7, 1, 5, 3, 6, 4] is 5",
maxProfit([7, 1, 5, 3, 6, 4]) === 5)
expect(
"The max profit in maxProfit([7,6,4,3,1] is 0",
maxProfit([7, 6, 4, 3, 1]) === 0)
expect(
"The profit in maxProfit([1,2,4,2,5,7,2,4,9,0,9]) is 9",
maxProfit([1, 2, 4, 2, 5, 7, 2, 4, 9, 0, 9]) == 9
)
Discussion (2) permalink
I like that this verion has more tests. I like that the function for simplifying the price line is broken out. This version isn't guaranteed to be faster, if the data is up and down every-other moment then the performance of this is exactly the same as the other version.