ผลรวมคำนำหน้าและยอดรวมสะสม
สร้างอาร์เรย์ผลรวมคำนำหน้าเพื่อตอบคำถามผลรวมช่วงในเวลา O(1) และประยุกต์เทคนิคนี้กับโจทย์ช่วงย่อย เช่น ช่วงย่อยที่มีผลรวมสูงสุด
ผลรวมคำนำหน้าและยอดรวมสะสม เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
โจทย์ผลรวมช่วง
เมื่อกำหนดอาร์เรย์ nums คุณต้องตอบการสอบถามหลายรายการในรูปแบบว่า ผลรวมของสมาชิกตั้งแต่ดัชนี i ถึงดัชนี j คือเท่าใด การคำนวณแต่ละรายการแบบตรงไปตรงมาใช้เวลา O(n) ดังนั้นการสอบถาม k รายการจึงมีต้นทุน O(n×k) หากใช้อาร์เรย์ผลรวมตั้งแต่ต้น คุณจะคำนวณผลรวมสะสมล่วงหน้าด้วยเวลา O(n) แล้วตอบการสอบถามแต่ละรายการได้ในเวลา O(1) นี่เป็นหนึ่งในเทคนิคการคำนวณล่วงหน้าที่ใช้กันอย่างแพร่หลายที่สุดในการสัมภาษณ์
# Naive: O(n) per query
def range_sum_naive(nums, i, j):
return sum(nums[i:j+1])
nums = [1, 3, 5, 7, 9]
print(range_sum_naive(nums, 1, 3)) # 3+5+7 = 15
print(range_sum_naive(nums, 0, 4)) # 1+3+5+7+9 = 25
# For 1000 queries, this takes 5000 operationsการสร้างอาร์เรย์ผลรวมตั้งแต่ต้น
กำหนดให้ prefix[i] เป็นผลรวมของ nums[0] ถึง nums[i-1] (มีช่องเพิ่มอีกหนึ่งช่อง โดยออฟเซ็ต 1 แบบดัชนีเริ่มจากศูนย์ช่วยให้จัดการกรณีขอบเขตได้ง่ายขึ้น) สร้างอาร์เรย์นี้ด้วยเวลา O(n) โดยวนผ่านข้อมูลเพียงรอบเดียว: prefix[i] = prefix[i-1] + nums[i-1] จากนั้นการสอบถามช่วง sum(i, j) จะกลายเป็น prefix[j+1] - prefix[i] ซึ่งเป็นการลบเพียงครั้งเดียวและใช้เวลา O(1)
def build_prefix(nums):
n = len(nums)
prefix = [0] * (n + 1)
for i in range(n):
prefix[i+1] = prefix[i] + nums[i]
return prefix
def range_sum(prefix, i, j):
return prefix[j+1] - prefix[i] # O(1)
nums = [1, 3, 5, 7, 9]
pre = build_prefix(nums)
print(pre) # [0, 1, 4, 9, 16, 25]
print(range_sum(pre, 1, 3)) # 9 - 1 = 8? Wait: 3+5+7=15
# Hmm: prefix[4]-prefix[1] = 16-1 = 15 correct
print(range_sum(pre, 1, 3)) # 15ผลรวมช่วงย่อยเท่ากับ K
การหาจำนวนช่วงย่อยที่มีผลรวมเท่ากับ k เป็นโจทย์คลาสสิกที่ใช้ตารางแฮชร่วมกับผลรวมตั้งแต่ต้น แนวคิดสำคัญคือ ผลรวมช่วงย่อยตั้งแต่ i ถึง j เท่ากับ prefix[j] - prefix[i-1] หากต้องการให้ค่านี้เท่ากับ k จะได้ว่า prefix[i-1] = prefix[j] - k ขณะสแกนจากซ้ายไปขวาโดยรักษาผลรวมตั้งแต่ต้นสะสมไว้ ให้ค้นหาว่า current_sum - k ปรากฏมาก่อนกี่ครั้ง แล้วนับช่วงย่อยที่ถูกต้องทั้งหมดได้ในเวลา O(n)
from collections import defaultdict
def subarray_sum_k(nums, k):
count = 0
current = 0
freq = defaultdict(int)
freq[0] = 1 # empty prefix
for n in nums:
current += n
count += freq[current - k] # how many prior sums give diff=k
freq[current] += 1
return count
print(subarray_sum_k([1, 1, 1], 2)) # 2
print(subarray_sum_k([1, 2, 3], 3)) # 2 ([1,2] and [3])ผลรวมช่วงย่อยสูงสุดด้วยผลรวมตั้งแต่ต้น
เราสามารถมองปัญหาผลรวมช่วงย่อยสูงสุดเป็นปัญหาผลรวมตั้งแต่ต้นได้ โดยสำหรับแต่ละดัชนี j เราต้องการหาค่าสูงสุดของ prefix[j] - prefix[i] สำหรับ i ทุกค่าที่ < j ค่า i ที่เหมาะสมที่สุดสำหรับแต่ละ j คือผลรวมตั้งแต่ต้นที่มีค่าน้อยที่สุดซึ่งพบมาจนถึงตอนนั้น การสแกนจากซ้ายไปขวาพร้อมติดตาม min_prefix ใช้เวลา O(n) วิธีนี้เทียบเท่ากับอัลกอริทึมของ Kadane เมื่อมองผ่านมุมมองของผลรวมตั้งแต่ต้น
def max_subarray_prefix(nums):
max_sum = float('-inf')
min_pre = 0 # prefix[0] = 0
current = 0
for n in nums:
current += n
max_sum = max(max_sum, current - min_pre)
min_pre = min(min_pre, current)
return max_sum
print(max_subarray_prefix([-2,1,-3,4,-1,2,1,-5,4]))
# 6 (same as Kadane's)
print(max_subarray_prefix([-1,-2,-3]))
# -1ผลรวมตั้งแต่ต้นแบบ 2 มิติสำหรับการสอบถามตาราง
ผลรวมตั้งแต่ต้นสามารถขยายไปใช้กับตาราง 2 มิติได้ กำหนดให้ P[i][j] เป็นผลรวมของสมาชิกทั้งหมดในสี่เหลี่ยมตั้งแต่ (0,0) ถึง (i-1,j-1) สร้างค่าด้วยสูตรการรวมและตัดส่วนซ้ำ: P[i][j] = P[i-1][j] + P[i][j-1] - P[i-1][j-1] + grid[i-1][j-1] จากนั้นการสอบถามผลรวมของสี่เหลี่ยมใด ๆ ตั้งแต่ (r1,c1) ถึง (r2,c2) จะตอบได้ในเวลา O(1) โดยใช้การค้นค่าทั้งสี่ตำแหน่ง
def build_2d_prefix(grid):
R, C = len(grid), len(grid[0])
P = [[0]*(C+1) for _ in range(R+1)]
for r in range(1, R+1):
for c in range(1, C+1):
P[r][c] = (P[r-1][c] + P[r][c-1]
- P[r-1][c-1] + grid[r-1][c-1])
return P
def rect_sum(P, r1, c1, r2, c2):
return P[r2+1][c2+1] - P[r1][c2+1] - P[r2+1][c1] + P[r1][c1]
grid = [[3,0,1,4],[5,6,3,2],[1,2,0,1]]
P = build_2d_prefix(grid)
print(rect_sum(P, 0, 0, 1, 1)) # 3+0+5+6 = 14ผลรวมสะสมสำหรับดัชนีสมดุล
ดัชนีสมดุล คือตำแหน่งที่ผลรวมของสมาชิกทางซ้ายเท่ากับผลรวมทางขวา ให้คำนวณผลรวมทั้งหมดล่วงหน้า จากนั้นสแกนโดยรักษาผลรวมทางซ้ายสะสมไว้ ผลรวมทางขวาคือ total - left_sum - nums[i] ตรวจสอบความเท่ากันในเวลา O(1) ต่อดัชนี จึงใช้เวลา O(n) โดยรวม วิธีนี้แสดงให้เห็นว่าผลรวมสะสมสามารถใช้แทนอาร์เรย์ผลรวมตั้งแต่ต้นสองชุดได้อย่างไร
def find_pivot_index(nums):
total = sum(nums)
left_sum = 0
for i, n in enumerate(nums):
# right_sum = total - left_sum - nums[i]
if left_sum == total - left_sum - n:
return i
left_sum += n
return -1
print(find_pivot_index([1, 7, 3, 6, 5, 6])) # 3
print(find_pivot_index([1, 2, 3])) # -1อาร์เรย์ผลคูณโดยไม่รวมตัวเอง
เมื่อกำหนดอาร์เรย์ ให้ส่งคืนอาร์เรย์ที่สมาชิกแต่ละตัวเป็นผลคูณของสมาชิกตัวอื่นทั้งหมด ห้ามใช้การหาร ให้ใช้ ผลคูณของส่วนก่อนหน้า และ ผลคูณของส่วนถัดไป: result[i] = (ผลคูณของสมาชิกทั้งหมดก่อน i) × (ผลคูณของสมาชิกทั้งหมดหลัง i) สร้างผลคูณของส่วนก่อนหน้าด้วยการวนจากซ้ายไปขวา จากนั้นคูณผลคูณของส่วนถัดไปด้วยการวนจากขวาไปซ้ายโดยใช้ตัวแปรสะสม จึงไม่ต้องใช้อาร์เรย์เพิ่มเติมสำหรับส่วนถัดไป
def product_except_self(nums):
n = len(nums)
result = [1] * n
# Left pass: result[i] = product of nums[:i]
prefix = 1
for i in range(n):
result[i] = prefix
prefix *= nums[i]
# Right pass: multiply in product of nums[i+1:]
suffix = 1
for i in range(n-1, -1, -1):
result[i] *= suffix
suffix *= nums[i]
return result
print(product_except_self([1, 2, 3, 4]))
# [24, 12, 8, 6] O(n) time, O(1) extra spaceผลรวมตั้งแต่ต้นด้วยโมดูโล
โจทย์บางข้อถามหาจำนวนช่วงย่อยที่ผลรวมหารด้วย k ลงตัว โดยใช้ผลรวมตั้งแต่ต้นแบบโมดูโล k: หาก prefix[j] % k == prefix[i] % k แสดงว่า sum(i+1..j) หารด้วย k ลงตัว การใช้ตารางแฮชนับจำนวนแต่ละค่าเศษที่พบขณะสแกนทำให้ใช้เวลา O(n) การกำหนดค่าเริ่มต้นที่สำคัญคือ freq[0] = 1 เพื่อรองรับช่วงย่อยที่เริ่มจากดัชนี 0
from collections import defaultdict
def subarray_div_by_k(nums, k):
freq = defaultdict(int)
freq[0] = 1
current = 0
count = 0
for n in nums:
current = (current + n) % k
count += freq[current]
freq[current] += 1
return count
print(subarray_div_by_k([4, 5, 0, -2, -3, 1], 5))
# 7 (seven subarrays divisible by 5)อาร์เรย์ผลต่างสำหรับการอัปเดตช่วง
อาร์เรย์ผลต่าง เป็นสิ่งตรงข้ามกับผลรวมตั้งแต่ต้น เมื่อกำหนดอาร์เรย์ ให้คำนวณล่วงหน้าว่า diff[i] = nums[i] - nums[i-1] การเพิ่ม x ให้กับช่วง [l, r] ต้องดำเนินการกับอาร์เรย์ผลต่างเพียงสองครั้งในเวลา O(1): diff[l] += x และ diff[r+1] -= x หลังจากอัปเดตทั้งหมดแล้ว ให้สร้างอาร์เรย์ผลลัพธ์กลับคืนด้วยการวนหาผลรวมตั้งแต่ต้นเพียงรอบเดียว วิธีนี้เปลี่ยนการอัปเดตช่วง k ครั้งจาก O(n×k) เป็น O(n + k)
def apply_range_updates(n, updates):
# updates: list of (l, r, val)
diff = [0] * (n + 1)
for l, r, val in updates:
diff[l] += val
diff[r+1] -= val
# Reconstruct with prefix sum
result = []
running = 0
for i in range(n):
running += diff[i]
result.append(running)
return result
# Add 3 to [1,3], add 1 to [0,2]
print(apply_range_updates(5, [(1,3,3),(0,2,1)]))
# [1, 4, 4, 3, 0]ผลรวมตั้งแต่ต้นในโจทย์สัมภาษณ์
ผลรวมตั้งแต่ต้นปรากฏในโจทย์หลายประเภท:
- การสอบถามช่วง — ผลรวมช่วงย่อย ผลรวมสี่เหลี่ยม
- การนับช่วงย่อย — ผลรวมเท่ากับ k หารด้วย k ลงตัว
- โจทย์ผลคูณ — ผลคูณโดยไม่รวมตัวเอง
- สมดุล — หาดัชนีจุดหมุน
- การอัปเดตช่วง — อาร์เรย์ผลต่าง
# Template: prefix sum + hash map for subarray problems
from collections import defaultdict
def subarray_count_template(nums, target):
"""
Count subarrays with property involving prefix sums.
Adapt 'target' and lookup condition for each problem.
"""
freq = defaultdict(int)
freq[0] = 1 # empty prefix at sum=0
current = 0
count = 0
for n in nums:
current += n
count += freq[current - target] # adjust per problem
freq[current] += 1
return count
print(subarray_count_template([1,2,3,2,1], 3)) # 3ผลรวมสะสมและค่าสูงสุดสะสม
นอกเหนือจากผลรวมตั้งแต่ต้นแล้ว โจทย์จำนวนมากยังใช้ ค่าสูงสุดสะสม หรือ ค่าต่ำสุดสะสม ซึ่งรักษาไว้ด้วยตัวแปรเพียงตัวเดียว โจทย์เวลาที่ดีที่สุดในการซื้อหุ้นใช้ราคาต่ำสุดสะสม ส่วนการคำนวณน้ำฝนที่ขังจากด้านซ้ายใช้ความสูงด้านซ้ายที่เป็นค่าสูงสุดสะสม รูปแบบเหล่านี้ต้องสแกนเพียงครั้งเดียวและใช้พื้นที่เพิ่มเติม O(1) จึงเป็นมาตรฐานสูงสุดทั้งด้านประสิทธิภาพของเวลาและพื้นที่
def max_profit(prices):
# Running minimum buy price
min_price = float('inf')
max_prof = 0
for price in prices:
if price < min_price:
min_price = price
elif price - min_price > max_prof:
max_prof = price - min_price
return max_prof
def left_max_array(heights):
# Running max from left for trapping rain water
n = len(heights)
left_max = [0] * n
left_max[0] = heights[0]
for i in range(1, n):
left_max[i] = max(left_max[i-1], heights[i])
return left_max
print(max_profit([7,1,5,3,6,4])) # 5ตรวจสอบความเข้าใจอย่างรวดเร็ว
ตรวจสอบความเข้าใจของคุณเกี่ยวกับแนวคิดโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้
ทบทวนบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า ผลรวมตั้งแต่ต้นเปลี่ยนการสอบถามช่วงที่ใช้เวลา O(n) ให้เป็นการค้นค่าในเวลา O(1) ด้วยการคำนวณผลรวมสะสมล่วงหน้าในการวนผ่านข้อมูล O(n) เพียงครั้งเดียว การใช้ผลรวมตั้งแต่ต้นร่วมกับตารางแฮชช่วยให้แก้โจทย์การนับช่วงย่อยที่มีผลรวมที่กำหนดหรือมีคุณสมบัติหารลงตัวได้ในเวลา O(n) และ อาร์เรย์ผลต่างเป็นสิ่งตรงข้ามกับผลรวมตั้งแต่ต้น โดยช่วยให้อัปเดตช่วงได้ในเวลา O(1) และสร้างผลลัพธ์กลับคืนด้วยการวนหาผลรวมตั้งแต่ต้นเพียงครั้งเดียวในตอนท้าย ถัดไปเราจะจัดการกับเทคนิคตัวชี้สองตัว โดยเริ่มจากตัวชี้ที่ปลายทั้งสองด้าน
คำถามที่พบบ่อย
บทเรียน “ผลรวมคำนำหน้าและยอดรวมสะสม” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ผลรวมคำนำหน้าและยอดรวมสะสม” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ผลรวมคำนำหน้าและยอดรวมสะสม”
สร้างอาร์เรย์ผลรวมคำนำหน้าเพื่อตอบคำถามผลรวมช่วงในเวลา O(1) และประยุกต์เทคนิคนี้กับโจทย์ช่วงย่อย เช่น ช่วงย่อยที่มีผลรวมสูงสุด คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน
คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่
ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน
บทเรียน “ผลรวมคำนำหน้าและยอดรวมสะสม” ใช้เวลานานแค่ไหน
บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย
ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม
ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- พื้นฐานอาร์เรย์และการดำเนินการในที่เดิม
- ผลรวมคำนำหน้าและยอดรวมสะสม
- ตัวชี้สองตัว: จากปลายตรงข้าม
- ตัวชี้สองตัว: ช้าและเร็ว