On this page
ข้อ 19 · LC724 Find Pivot Index (หา pivot index) 🟢
หา index ที่ผลรวมฝั่งซ้ายเท่ากับฝั่งขวา โดยไม่บวกซ้ำทุกครั้ง
โจทย์ (LC724): กำหนด array จำนวนเต็ม nums ให้คำนวณหา pivot index (ดัชนีจุดสมดุล) ของ array นี้ pivot index คือ index ที่ผลรวมของตัวเลขทั้งหมดที่อยู่ทางซ้ายของ index นั้น (strictly) เท่ากับผลรวมของตัวเลขทั้งหมดที่อยู่ทางขวาของ index นั้น (strictly) ถ้า index อยู่ริมซ้ายสุดของ array ให้ถือว่าผลรวมฝั่งซ้ายเป็น 0 เพราะไม่มีสมาชิกอยู่ทางซ้ายเลย กรณีริมขวาสุดก็เช่นเดียวกัน ให้ return pivot index ที่อยู่ซ้ายสุด ถ้าไม่มี index แบบนี้เลยให้ return -1
- Input:
- nums = [1, 7, 3, 6, 5, 6]
- Output:
- 3
- Explanation:
- pivot index คือ 3: ผลรวมซ้าย = 1 + 7 + 3 = 11 เท่ากับผลรวมขวา = 5 + 6 = 11
- Input:
- nums = [1, 2, 3]
- Output:
- -1
- Explanation:
- ไม่มี index ไหนใน array นี้ที่ทำให้ผลรวมสองฝั่งสมดุลกันได้
- Input:
- nums = [2, 1, -1]
- Output:
- 0
- Explanation:
- pivot index คือ 0: ผลรวมซ้ายว่างจึงเป็น 0 เท่ากับผลรวมขวา = 1 + (-1) = 0
- 1 <= nums.length <= 10^4
- -1000 <= nums[i] <= 1000
ต้อง return index ซ้ายสุดที่สมดุล และฝั่งที่ว่าง (ก่อนตัวแรกหรือหลังตัวสุดท้าย) ถือว่ามีผลรวมเป็น 0
แนวทาง — ต้องใช้อะไร & คิดยังไง
ใช้ไอเดีย prefix sum (ผลรวมสะสม) แต่ครั้งนี้เราสนใจผลรวมสองฝั่งของแต่ละ index วิธี naive (ตรงตัว) คือทุก index ก็บวกฝั่งซ้ายใหม่และฝั่งขวาใหม่ ซึ่งกลายเป็น O(n^2) ช้าเกินไปเมื่อ array ยาว
ทริกคือรู้ผลรวมทั้งหมด total ไว้ก่อน แล้ว accumulate (สะสม) เฉพาะฝั่งซ้าย left_sum ระหว่าง iterate (เดิน) เมื่ออยู่ที่ index i ผลรวมฝั่งขวาหาได้ทันทีจาก total - left_sum - nums[i] ไม่ต้องบวกฝั่งขวาใหม่เลย
- คำนวณ total = ผลรวมทั้ง array (หนึ่งรอบ)
- initialize (ตั้งค่าเริ่มต้น) left_sum = 0 เก็บผลรวมของตัวที่อยู่ทางซ้ายของ i
- iterate (วน) ทีละ index i พร้อมค่า n = nums[i]: คำนวณ right_sum = total - left_sum - n
- ถ้า left_sum == right_sum แปลว่าเจอ pivot return i ทันที (ซ้ายสุดโดยธรรมชาติ)
- ถ้ายังไม่สมดุล บวก n เข้า left_sum แล้วไปตัวถัดไป
- จบ loop ยังไม่เจอ return -1
ลำดับสำคัญมาก ต้องเช็คสมดุล ก่อน แล้วค่อยบวก n เข้า left_sum เพราะตอนอยู่ที่ index i ตัว nums[i] ยังไม่นับเป็นฝั่งซ้าย ถ้าบวกก่อนเช็คจะได้คำตอบผิด
ไล่ทีละสเต็ป
iterate nums = [1, 7, 3, 6, 5, 6] ซึ่ง total = 28
| i | n | left_sum (ก่อนเช็ค) | right_sum | สมดุล? |
|---|---|---|---|---|
| 0 | 1 | 0 | 28-0-1 = 27 | ไม่ |
| 1 | 7 | 1 | 28-1-7 = 20 | ไม่ |
| 2 | 3 | 8 | 28-8-3 = 17 | ไม่ |
| 3 | 6 | 11 | 28-11-6 = 11 | ใช่ → คืน 3 |
▶ เฉลยละเอียด (ลองเองก่อนนะ)
def pivot_index(nums):
total = sum(nums) # ผลรวมทั้งหมด (จ่าย O(n) ครั้งเดียว)
left_sum = 0 # ผลรวมของตัวที่อยู่ทางซ้ายของ i
for i, n in enumerate(nums):
# ผลรวมขวา = total - ฝั่งซ้าย - ตัวปัจจุบัน
right_sum = total - left_sum - n
if left_sum == right_sum: # เช็คก่อนบวก n
return i # เจอ pivot ซ้ายสุด
left_sum += n # เลื่อน i ไปขวา ตัวปัจจุบันจึงกลายเป็นฝั่งซ้าย
return -1 # ไล่จนจบไม่เจอ
print(pivot_index([1, 7, 3, 6, 5, 6])) # 3
print(pivot_index([1, 2, 3])) # -1
print(pivot_index([2, 1, -1])) # 03
-1
0หัวใจคือเราไม่อยากคำนวณผลรวมซ้าย-ขวาใหม่ทุก index (จะกลายเป็น O(n^2)) จึงเก็บ left_sum ที่ accumulate (สะสม) มาจากซ้าย ส่วนผลรวมขวาคำนวณได้จาก total ที่รู้ล่วงหน้า ลบด้วยฝั่งซ้ายและตัวปัจจุบันออก จึงเหลือแค่การ iterate รอบเดียว
edge case (กรณีขอบ) ที่ต้องระวังคือ pivot อยู่ที่หัวหรือท้าย array เช่น [2, 1, -1] ตอบ 0 เพราะฝั่งซ้ายว่าง (ผลรวม 0) เท่ากับฝั่งขวา 1+(-1) = 0 โค้ดนี้รองรับเพราะ left_sum เริ่มที่ 0 อยู่แล้ว ถ้าเผลอสลับไปบวก n ก่อนเช็ค index 0 จะไม่มีทางถูกตรวจในสภาพฝั่งซ้ายว่าง
Time O(n) หา total หนึ่งรอบ แล้ว iterate อีกหนึ่งรอบ · Space O(1) ใช้ตัวแปรสะสมเท่านั้น
เมื่อโจทย์ถามความสัมพันธ์ระหว่าง ผลรวมฝั่งซ้าย กับ ฝั่งขวา ที่แต่ละจุด ให้รู้ total ก่อนแล้ว accumulate ฝั่งเดียว ฝั่งตรงข้ามได้มาจากการลบ เทคนิคนี้เปลี่ยน O(n^2) ให้เหลือ O(n) ในหลายโจทย์