Faisalaffan Blog
Faisalaffan Blog
Rekayasa Perangkat Lunak5 menit baca

Best Time to Buy and Sell Stock: Dari Brute Force ke One-Pass O(n)

Menguasai soal klasik coding interview 'Best Time to Buy and Sell Stock' dengan penjelasan step-by-step โ€” mulai dari pendekatan brute force paling naif, mengenali pola, sampai ke solusi one-pass O(n) yang optimal dengan sliding window / two-pointer. Lengkap dengan visualisasi array, analisis kompleksitas, dan intuisi di balik setiap keputusan.

Faisal Affan
Faisal Affan
Diterbitkan
Best Time to Buy and Sell Stock: Dari Brute Force ke One-Pass O(n)

Rahasia dari banyak soal array: "What if I track the minimum so far while scanning?" โ€” pola ini muncul di mana-mana, dari stock trading sampai rainwater trapping.

Soal

Diberikan array prices di mana prices[i] adalah harga saham pada hari ke-i. Tugas kita: cari maximum profit dari satu kali transaksi โ€” beli di satu hari, jual di hari setelahnya. Kalau tidak ada profit yang mungkin (harga selalu turun), return 0.

Constraints:

  • 1 <= prices.length <= 10โต
  • 0 <= prices[i] <= 10โด

Contoh dari soal:

Plaintext
Input:  prices = [7, 1, 5, 3, 6, 4]
Output: 5

Beli hari ke-2 (harga = 1), jual hari ke-5 (harga = 6), profit = 6 - 1 = 5.

Plaintext
Input:  prices = [7, 6, 4, 3, 1]
Output: 0

Tidak ada transaksi yang menghasilkan profit โ€” harga cuma turun terus.

Intuisi Awal โ€” Brute Force

Pikiran pertama yang muncul: coba semua kemungkinan beli-dan-jual.

Go
func maxProfit(prices []int) int {
    maxProfit := 0
    n := len(prices)
    for buy := 0; buy < n; buy++ {
        for sell := buy + 1; sell < n; sell++ {
            profit := prices[sell] - prices[buy]
            if profit > maxProfit {
                maxProfit = profit
            }
        }
    }
    return maxProfit
}

Logikanya sederhana: untuk setiap hari buy, coba semua hari sell setelahnya. Hitung profit, ambil yang terbesar.

Kompleksitas: O(nยฒ) โ€” nested loop penuh. Untuk n = 10โต, ini jalan ~10ยนโฐ iterasi. Tidak lolos.

Observasi Kunci

Perhatikan ulang contoh: [7, 1, 5, 3, 6, 4]

Untuk setiap calon hari jual, profit maksimum didapat kalau kita beli di harga terendah sebelum hari itu.

Hari ke- Price Min harga sebelum hari ini Profit jika jual hari ini
1 7 tidak ada (belum bisa jual) -
2 1 7 1 - 7 = -6 โ†’ max(sebelumnya, 0) = 0
3 5 1 (update setelah lihat 1) 5 - 1 = 4
4 3 1 3 - 1 = 2
5 6 1 6 - 1 = 5
6 4 1 4 - 1 = 3

Maksimum profit: max(0, 4, 2, 5, 3) = 5. Cocok dengan output.

Pola: Track Minimum Sambil Scan

Dari tabel di atas kita sadar: kita tidak perlu nested loop. Cukup sekali jalan dari kiri ke kanan, sambil mencatat dua hal:

  1. Harga terendah yang pernah kita lihat sejauh ini (min_price)
  2. Profit maksimum yang bisa kita dapat jika jual di harga saat ini (max_profit)

Di setiap langkah i:

  • Profit jika jual sekarang = prices[i] - min_price
  • Update max_profit jika profit ini lebih besar
  • Update min_price jika prices[i] lebih kecil dari min_price

Solusi Optimal โ€” One-Pass O(n)

Go
func maxProfit(prices []int) int {
    minPrice := math.MaxInt32
    maxProfit := 0

    for _, price := range prices {
        if price < minPrice {
            minPrice = price
        } else {
            profit := price - minPrice
            if profit > maxProfit {
                maxProfit = profit
            }
        }
    }

    return maxProfit
}

Atau versi yang lebih ringkas:

Go
func maxProfit(prices []int) int {
    minPrice := math.MaxInt32
    maxProfit := 0

    for _, price := range prices {
        minPrice = min(minPrice, price)
        maxProfit = max(maxProfit, price-minPrice)
    }

    return maxProfit
}

Bagaimana dengan case tidak ada profit? Kalau harga selalu turun (contoh 2: [7, 6, 4, 3, 1]), maka price - min_price akan selalu negatif. Tapi max_profit diinisialisasi 0, dan max(0, negatif) = 0. Jadi return 0 โ€” sesuai requirement.

Visualisasi

Plaintext
prices = [7, 1, 5, 3, 6, 4]

i=0: price=7  โ†’ min_price=7    profit=N/A   max_profit=0
i=1: price=1  โ†’ min_price=1    profit=N/A   max_profit=0
i=2: price=5  โ†’ min_price=1    profit=4     max_profit=4  โœ“
i=3: price=3  โ†’ min_price=1    profit=2     max_profit=4
i=4: price=6  โ†’ min_price=1    profit=5     max_profit=5  โœ“ (new max!)
i=5: price=4  โ†’ min_price=1    profit=3     max_profit=5

Return 5

Kompleksitas

Aspek Nilai
Waktu O(n) โ€” satu kali traversal array
Memori O(1) โ€” hanya dua variabel (min_price, max_profit)

Untuk n = 10โต, solusi ini selesai dalam <1ms di bahasa apapun.

Kenapa Pola Ini Penting

Pola "track minimum dari kiri sambil scan" adalah building block untuk banyak variasi soal:

  • Best Time to Buy and Sell Stock II โ€” boleh transaksi berkali-kali
  • Best Time to Buy and Sell Stock III โ€” maksimum dua transaksi
  • Trapping Rain Water โ€” track max dari kiri dan kanan
  • Maximum Subarray (Kadane) โ€” track current_sum dan max_sum

Semuanya berangkat dari ide yang sama: maintain state optimal dari data yang sudah kamu lewati, update state saat kamu maju.

Intisari

Best profit comes from buying at the lowest price seen so far and selling at the current price. Track both in one pass.

Soal ini mengajarkan bahwa brute force bisa runtuh hanya dengan satu observasi sederhana. Selalu tanya sebelum menulis nested loop: "Apa yang sebenarnya perlu saya ingat dari data yang sudah saya lewati?"

Artikel terkait

Rekayasa Perangkat Lunak

Search & Catalog 15k SKU Q-Commerce: Relevan + In-Stock Real-time

System design search dan catalog untuk quick commerce dengan 15.000+ SKU. Elasticsearch full-text search dengan multi-match query dan typo tolerance (fuzziness), filter ketersediaan per-hub via Redis pipeline query-time filtering, ranking berbasis relevansi, ketersediaan, margin, dan popularitas, index update via Kafka streaming, dan autocomplete handler. Implementasi Golang lengkap dengan worker concurrency pattern untuk index sync.
Faisal Affan
Faisal Affan
Diterbitkan

Real-time Order Tracking Q-Commerce: Driver Bergerak di Peta Live

System design real-time order tracking untuk quick commerce. Driver GPS ingestion via WebSocket dengan throttling dan validasi, Kalman filter untuk position smoothing, Redis pub/sub fan-out by order ID untuk scale ribuan driver concurrent, dan Server-Sent Events (SSE) push ke user client. Implementasi Golang dengan pattern WebSocket goroutine pool, pub/sub broker, dan concurrent connection management.
Faisal Affan
Faisal Affan
Diterbitkan
Rekayasa Perangkat Lunak
Real-time Inventory Dark Store: Cegah Oversell di Q-Commerce

Real-time Inventory Dark Store: Cegah Oversell di Q-Commerce

Desain inventory per-hub untuk Astro (instant grocery). Reservation pattern dengan Redis DECRBY atomic + TTL, async sync ke Postgres, stock partitioning untuk hot SKU, reaper job untuk reservation leak, dan cycle counting untuk stok fisik vs sistem. Implementasi Golang lengkap.
Faisal Affan
Faisal Affan
Diterbitkan

Promo & Voucher Engine Q-Commerce: Rule Engine Anti-Abuse

System design promo engine untuk quick commerce. Rule engine dengan kondisi komposable (min transaksi, first-N-order, area, kategori) ke efek (discount, free ongkir), atomic counter untuk limit penggunaan Redis Lua, anti-fraud device fingerprinting, stackable vs eksklusif promo resolution, dan budget cap. Implementasi Golang lengkap dengan composable condition interface, AND/OR composite tree, concurrent-safe redeem, serta Kafka event streaming untuk audit trail.
Faisal Affan
Faisal Affan
Diterbitkan