Notes & software courses · Free to learn
Aph's Blog
On this page

Prefix Sum — พื้นฐาน & แนวคิด

👋 อ่านฟรีทั้งหมดบน Aph's Blog — เนื้อหาภาษาไทย ทำตามทีละหน้าใน sidebar ได้เลย หากมีข้อเสนอแนะหรืออยากให้เพิ่มหัวข้อไหน บอกได้เสมอ

เก็บผลรวมสะสมไว้ล่วงหน้า เพื่อตอบผลรวมของช่วงใด ๆ ได้ในเวลา O(1)

Prefix Sum (ผลรวมสะสม) เป็นเทคนิคง่าย ๆ แต่ทรงพลังมาก ไอเดียคือ ถ้าเราถูกถามผลรวมของ range (ช่วง) ต่าง ๆ ใน array (ลิสต์) บ่อย ๆ แทนที่จะบวกใหม่ทุกครั้ง เรา precompute (เตรียมล่วงหน้า) prefix sum ไว้ แล้วตอบคำถามได้ทันทีในเวลาคงที่ นอกจากนี้ตัว prefix sum เองก็มีประโยชน์ในโจทย์ที่ต้องรู้ค่าสะสม ณ แต่ละจุด เช่น altitude (ระดับความสูง) ที่ไต่ขึ้นไปได้ หรือหา pivot (จุดสมดุล) ของ array

แนวคิดของหัวข้อนี้

สมมติเรามี array nums แล้วอยากรู้ผลรวมของ range nums[i..j] (รวมทั้งสองปลาย) ถ้าถามครั้งเดียวก็บวกตรง ๆ ได้ แต่ถ้าถูกถามหลาย ๆ range การบวกใหม่ทุกครั้งจะเสียเวลา O(n) ต่อ query (คำถาม) รวมแล้วช้า

ทริกคือสร้าง array prefix ที่เก็บผลรวมสะสมตั้งแต่ต้นจนถึงแต่ละ index (ตำแหน่ง) โดยนิยมทำให้ prefix ยาวกว่า nums หนึ่งช่อง (มีช่อง 0 นำหน้า) เพื่อให้สูตรสวย: prefix[k] = ผลรวมของ nums[0..k-1]

ทำไมมันเร็วขึ้น? เพราะเราจ่ายเวลา O(n) แค่ครั้งเดียวตอน build (สร้าง) prefix จากนั้นทุก range query ตอบได้ในเวลา O(1) ด้วยการลบกันแค่ครั้งเดียว ผลรวม range = ผลรวมสะสมถึงปลายขวา ลบ ผลรวมสะสมก่อนถึงปลายซ้าย

python
nums = [3, 1, 4, 1, 5]

# สร้างผลรวมสะสม โดยให้ prefix[0] = 0 นำหน้า
prefix = [0] * (len(nums) + 1)
for i in range(len(nums)):
    prefix[i + 1] = prefix[i] + nums[i]

# prefix = [0, 3, 4, 8, 9, 14]

# ผลรวมของช่วง nums[i..j] (รวมสองปลาย) = prefix[j + 1] - prefix[i]
# เช่น ผลรวมของ nums[1..3] = 1 + 4 + 1 = 6
i, j = 1, 3
print(prefix[j + 1] - prefix[i])  # 6
จำสูตรนี้ไว้

sum(nums[i..j]) = prefix[j+1] - prefix[i] เมื่อ prefix มีช่อง 0 นำหน้า การมีช่อง 0 นำหน้าช่วยไม่ให้ต้องเขียน edge condition (เงื่อนไขพิเศษ) ตอน i = 0 และบ่อยครั้งเราไม่ต้องสร้าง array prefix ทั้งก้อนด้วยซ้ำ ใช้ตัวแปรเดียว accumulate (บวกสะสม) ไปก็พอ

พร้อมแล้วไปต่อ

หมวดนี้มี 2 ข้อ ทั้งสองข้อเป็น prefix sum แบบสะสมด้วยตัวแปรเดียว กดถัดไปเริ่มข้อแรกได้เลย