ผลรวมสองค่าและรูปแบบหลากหลาย
แก้โจทย์ผลรวมสองค่า สามค่า สี่ค่า และผลรวมสองค่าในอาร์เรย์ที่เรียงแล้วด้วยแผนผังแฮชและตัวชี้สองตัว พร้อมเปรียบเทียบต้นทุนด้านเวลาและพื้นที่
ผลรวมสองค่าและรูปแบบหลากหลาย เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
ผลรวมสองค่า: โจทย์สัมภาษณ์คลาสสิก
LeetCode 1 “ผลรวมสองค่า”: เมื่อกำหนดอาร์เรย์ที่ไม่ได้เรียงลำดับและค่าเป้าหมาย ให้คืนค่าดัชนีขององค์ประกอบสองรายการที่รวมกันได้ค่าเป้าหมาย วิธีลองครบทุกคู่ซึ่งมีความซับซ้อน O(n²) จะตรวจสอบทุกคู่ วิธีที่เหมาะสมที่สุดซึ่งมีความซับซ้อน O(n) ใช้แฮชแมป: สำหรับแต่ละองค์ประกอบ x ให้ตรวจสอบว่า target - x มีอยู่ในแมปแล้วหรือไม่ หากมี ให้คืนค่าคู่ดัชนี หากไม่มี ให้จัดเก็บ x และดัชนีของมันไว้ในแมป
โจทย์ผลรวมสองค่ามักเป็นโจทย์แรก ๆ ในการสัมภาษณ์ — การรู้วิธีนี้อย่างแม่นยำแสดงให้เห็นว่าคุณพร้อมก้าวไปสู่โจทย์ที่ยากขึ้น
def twoSum(nums, target):
seen = {} # val -> index
for i, x in enumerate(nums):
complement = target - x
if complement in seen:
return [seen[complement], i]
seen[x] = i
return []
print(twoSum([2, 7, 11, 15], 9)) # [0, 1]
print(twoSum([3, 2, 4], 6)) # [1, 2]
print(twoSum([3, 3], 6)) # [0, 1]เหตุใดแฮชแมปจึงใช้แก้โจทย์ผลรวมสองค่าได้
แฮชแมปจะจัดเก็บองค์ประกอบทุกตัวที่พบจนถึงขณะนั้น เมื่อประมวลผลองค์ประกอบ x หาก target - x อยู่ในแมป องค์ประกอบทั้งสองก็เป็นคู่ที่ถูกต้อง สิ่งสำคัญคือจะตรวจสอบค่าคู่เติมเต็มก่อนจัดเก็บ x เสมอ เพื่อป้องกันกรณีที่องค์ประกอบหนึ่งตัวถูกจับคู่กับตัวเอง (เช่น หาก x == target/2 การตรวจสอบในแมปจะเกิดขึ้นก่อนจัดเก็บ x ดังนั้นจะไม่ตรงกัน เว้นแต่จะมีค่าซ้ำกันสองตัว)
# Trace two-sum on [2, 7, 11, 15], target=9
nums, target = [2, 7, 11, 15], 9
seen = {}
for i, x in enumerate(nums):
complement = target - x
print(f'i={i} x={x} complement={complement} seen={seen}')
if complement in seen:
print(f' Found: indices [{seen[complement]}, {i}]')
break
seen[x] = iผลรวมสองค่าบนอาร์เรย์ที่เรียงลำดับ (ตัวชี้สองตัว)
หากอาร์เรย์เรียงลำดับอยู่แล้วและคุณต้องการดัชนีของค่า (ไม่ใช่ดัชนีเดิม) ให้ใช้เทคนิคตัวชี้สองตัว โดยเริ่มตัวชี้ซ้ายและตัวชี้ขวาจากปลายตรงข้ามกัน หากผลรวมเท่ากับเป้าหมาย ให้คืนผลลัพธ์ หากผลรวมน้อยเกินไป ให้เลื่อนตัวชี้ซ้ายไปทางขวา หากผลรวมมากเกินไป ให้เลื่อนตัวชี้ขวาไปทางซ้าย วิธีนี้ใช้เวลา O(n) และพื้นที่ O(1) ซึ่งดีกว่าวิธีใช้แฮชแมปเมื่ออาร์เรย์เรียงลำดับแล้วและหน่วยความจำมีจำกัด
def twoSumSorted(numbers, target):
lo, hi = 0, len(numbers) - 1
while lo < hi:
s = numbers[lo] + numbers[hi]
if s == target:
return [lo + 1, hi + 1] # 1-indexed as per LeetCode 167
elif s < target:
lo += 1
else:
hi -= 1
return []
print(twoSumSorted([2, 7, 11, 15], 9)) # [1, 2]
print(twoSumSorted([2, 3, 4], 6)) # [1, 3]
print(twoSumSorted([-1, 0], -1)) # [1, 2]ผลรวมสามค่า (LeetCode 15)
LeetCode 15 “ผลรวมสามค่า”: ค้นหาชุดสามค่าที่ไม่ซ้ำกันทั้งหมดซึ่งมีผลรวมเป็นศูนย์ เรียงลำดับอาร์เรย์ กำหนดองค์ประกอบทีละตัว และใช้ตัวชี้สองตัวกับอาร์เรย์ย่อยที่เหลือซึ่งเรียงลำดับแล้ว ข้ามค่าที่ซ้ำกันเพื่อหลีกเลี่ยงชุดสามค่าที่ซ้ำกัน เวลา: O(n²) — เหมาะสมที่สุดสำหรับโจทย์นี้ เนื่องจากตัวผลลัพธ์เองอาจมีชุดสามค่าจำนวน O(n²) ชุด
def threeSum(nums):
nums.sort()
result = []
for i in range(len(nums) - 2):
if i > 0 and nums[i] == nums[i-1]: # skip duplicates
continue
lo, hi = i + 1, len(nums) - 1
while lo < hi:
s = nums[i] + nums[lo] + nums[hi]
if s == 0:
result.append([nums[i], nums[lo], nums[hi]])
while lo < hi and nums[lo] == nums[lo+1]: lo += 1
while lo < hi and nums[hi] == nums[hi-1]: hi -= 1
lo += 1; hi -= 1
elif s < 0:
lo += 1
else:
hi -= 1
return result
print(threeSum([-1, 0, 1, 2, -1, -4])) # [[-1,-1,2],[-1,0,1]]
print(threeSum([0, 0, 0, 0])) # [[0,0,0]]ผลรวมสี่ค่า (LeetCode 18)
LeetCode 18 “ผลรวมสี่ค่า”: ค้นหาชุดสี่ค่าที่ไม่ซ้ำกันทั้งหมดซึ่งมีผลรวมเท่ากับเป้าหมาย ต่อยอดจากผลรวมสามค่าโดยกำหนดองค์ประกอบสองตัวด้วยลูปซ้อนกันสองชั้น (ข้ามค่าที่ซ้ำกัน) จากนั้นใช้ตัวชี้สองตัวกับอาร์เรย์ย่อยด้านใน เวลา: O(n³) สำหรับผลรวม k ค่าโดยทั่วไป รูปแบบคือเรียกซ้ำ k-2 ครั้ง แล้วใช้ตัวชี้สองตัว ทำให้ใช้เวลา O(n^(k-1))
def fourSum(nums, target):
nums.sort()
n, result = len(nums), []
for i in range(n - 3):
if i > 0 and nums[i] == nums[i-1]:
continue
for j in range(i+1, n-2):
if j > i+1 and nums[j] == nums[j-1]:
continue
lo, hi = j+1, n-1
while lo < hi:
s = nums[i]+nums[j]+nums[lo]+nums[hi]
if s == target:
result.append([nums[i],nums[j],nums[lo],nums[hi]])
while lo < hi and nums[lo] == nums[lo+1]: lo += 1
while lo < hi and nums[hi] == nums[hi-1]: hi -= 1
lo += 1; hi -= 1
elif s < target: lo += 1
else: hi -= 1
return result
print(fourSum([1,0,-1,0,-2,2], 0))
# [[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]ผลรวมสองค่าที่ใกล้เป้าหมายที่สุด
รูปแบบที่พบบ่อยคือ ค้นหาคู่ที่มีผลรวมใกล้ค่าเป้าหมายที่สุด (ไม่จำเป็นต้องเท่ากับเป้าหมายพอดี) ให้เรียงลำดับอาร์เรย์และใช้ตัวชี้สองตัว ติดตามผลรวมที่ใกล้ที่สุดที่พบจนถึงขณะนั้น และปรับปรุงค่าเมื่อพบคู่ที่มีผลต่างสัมบูรณ์จากเป้าหมายน้อยกว่า วิธีนี้มีความซับซ้อน O(n log n) และทำได้ตรงไปตรงมาหลังจากเรียงลำดับแล้ว
def twoSumClosest(nums, target):
nums.sort()
lo, hi = 0, len(nums) - 1
best = float('inf')
best_pair = None
while lo < hi:
s = nums[lo] + nums[hi]
if abs(s - target) < abs(best - target):
best = s
best_pair = (nums[lo], nums[hi])
if s < target:
lo += 1
elif s > target:
hi -= 1
else:
return best_pair # exact match
return best_pair
print(twoSumClosest([1, 3, 4, 7, 10], 15)) # (7, 10) => 17, closest to 15
print(twoSumClosest([2, 5, 8, 11], 10)) # (2, 8) => 10, exact!ผลรวมสองค่าที่มีหลายคู่ (ทุกคู่)
หากต้องการค้นหาคู่ทั้งหมดที่มีผลรวมเท่ากับเป้าหมาย ให้เรียงลำดับอาร์เรย์และใช้ตัวชี้สองตัวเพื่อรวบรวมคู่ทั้งหมด หลังจากพบคู่ที่ถูกต้อง ให้ข้ามค่าที่ซ้ำกันจากปลายทั้งสองด้านก่อนดำเนินการต่อ วิธีนี้ใช้เวลา O(n log n) สำหรับการเรียงลำดับ และ O(n) สำหรับการสแกน รวมเป็น O(n log n) โดยรวม การใช้แฮชแมปเพื่อรวบรวมคู่ก็ทำได้เช่นกัน แต่ต้องระมัดระวังเรื่องค่าซ้ำ
def twoSumAllPairs(nums, target):
nums.sort()
lo, hi = 0, len(nums) - 1
pairs = []
while lo < hi:
s = nums[lo] + nums[hi]
if s == target:
pairs.append((nums[lo], nums[hi]))
while lo < hi and nums[lo] == nums[lo+1]: lo += 1
while lo < hi and nums[hi] == nums[hi-1]: hi -= 1
lo += 1; hi -= 1
elif s < target:
lo += 1
else:
hi -= 1
return pairs
print(twoSumAllPairs([1,1,2,3,4,4,5], 5)) # [(1,4),(1,4)-deduped,(2,3)]
# After duplicate-skipping: [(1,4),(2,3)]นับคู่ที่มีผลรวมน้อยกว่า k
อีกรูปแบบหนึ่งคือการนับว่ามีกี่คู่ที่มีผลรวมน้อยกว่า k ให้เรียงลำดับอาร์เรย์และใช้ตัวชี้สองตัว เมื่อ nums[lo] + nums[hi] < k คู่ทั้งหมด (lo, lo+1), (lo, lo+2), ..., (lo, hi) จะเป็นคู่ที่ถูกต้อง — มีทั้งหมด hi - lo คู่ ให้เลื่อน lo ไปข้างหน้า มิฉะนั้นให้ลดช่วง hi เวลารวมคือ O(n log n) สำหรับการเรียงลำดับ และ O(n) สำหรับการนับ
def countPairsLessThan(nums, k):
nums.sort()
lo, hi = 0, len(nums) - 1
count = 0
while lo < hi:
if nums[lo] + nums[hi] < k:
count += hi - lo # all (lo, lo+1)...(lo, hi) are valid
lo += 1
else:
hi -= 1
return count
print(countPairsLessThan([1, 3, 7, 11, 12], 10)) # (1,3),(1,7),(3,7) => 3
print(countPairsLessThan([3, 5, 2, 3], 7)) # (2,3),(2,3) => 2... verifyผลรวมสองค่าด้วยแฮชแมป: การจัดการค่าซ้ำ
เมื่อค่าเดียวกันสามารถปรากฏได้หลายครั้งและคุณต้องการนับจำนวนคู่ที่ถูกต้อง (ไม่ใช่เพียงตรวจว่ามีอยู่หรือไม่) ให้จัดเก็บจำนวนความถี่ไว้ในแมป สำหรับคู่ที่องค์ประกอบทั้งสองมีค่าเท่ากัน จำนวนคู่จากความถี่ f คือ f*(f-1)//2 สำหรับคู่ที่องค์ประกอบมีค่าต่างกัน ให้คูณความถี่ขององค์ประกอบทั้งสองเข้าด้วยกัน วิธีนี้ช่วยให้นับคู่ที่ถูกต้องทั้งหมดได้ใน O(n)
from collections import Counter
def countTwoSumPairs(nums, target):
freq = Counter(nums)
count = 0
seen = set()
for x in freq:
y = target - x
if y in freq and (x, y) not in seen:
if x == y:
count += freq[x] * (freq[x] - 1) // 2
else:
count += freq[x] * freq[y]
seen.add((x, y))
seen.add((y, x))
return count
print(countTwoSumPairs([1,1,2,3,4,4,3], 4))
# Pairs summing to 4: (1,3)x2x2=4, (0+more)...การสังเกตรูปแบบต่าง ๆ ของผลรวมสองค่า
รูปแบบผลรวมสองค่าปรากฏในโจทย์หลายรูปแบบ ควรสังเกตเมื่อโจทย์ขอให้ค้นหาองค์ประกอบสองตัวขึ้นไปที่มีความสัมพันธ์เชิงตัวเลขบางอย่าง (ผลรวม ผลคูณ หรือผลต่าง) กลยุทธ์หลักคือกำหนดองค์ประกอบหนึ่งตัว แล้วค้นหาค่าคู่เติมเต็มในโครงสร้างที่คำนวณเตรียมไว้ (แฮชแมป หรืออาร์เรย์ที่เรียงลำดับแล้วร่วมกับตัวชี้) ต่อยอดเป็นผลรวม k ค่าโดยกำหนดองค์ประกอบ k-2 ตัวด้วยลูปซ้อนกัน แล้วใช้กรณีฐาน
# Summary of approaches by scenario
scenarios = [
('Unsorted array, any indices, one pair', 'hash map O(n) time O(n) space'),
('Sorted array, any indices, one pair', 'two pointers O(n) time O(1) space'),
('All unique pairs summing to target', 'sort + two pointers O(n log n)'),
('Three numbers summing to zero (3-sum)', 'sort + fix + two pointers O(n^2)'),
('k numbers summing to target (k-sum)', 'sort + k-2 loops + two pointers O(n^(k-1))')
]
for scenario, approach in scenarios:
print(f'{scenario}\n => {approach}\n')การสื่อสารในการสัมภาษณ์สำหรับโจทย์ผลรวมสองค่า
เมื่อพบโจทย์ผลรวมสองค่าในการสัมภาษณ์ ให้พูดอธิบายแนวคิดของคุณไปด้วยว่า “ฉันต้องการตัวเลขสองตัวที่รวมกันได้ค่าเป้าหมาย สำหรับตัวเลขแต่ละตัว x ฉันต้องตรวจสอบว่า target-x มีอยู่หรือไม่ ฉันตอบการตรวจสอบนี้ได้ใน O(1) ด้วยแฮชแมป ทำให้ใช้เวลารวม O(n) และใช้พื้นที่ O(n) อีกทางเลือกหนึ่ง หากอาร์เรย์เรียงลำดับแล้ว ฉันสามารถใช้ตัวชี้สองตัวโดยใช้พื้นที่ O(1) ได้” ควรกล่าวถึงทั้งสองแนวทาง และสอบถามว่ามีข้อจำกัดด้านหน่วยความจำหรือไม่ก่อนเลือกวิธี
แบบทดสอบสั้น ๆ
ทดสอบความเข้าใจแนวคิดเรื่องโครงสร้างข้อมูลและอัลกอริทึม — การเตรียมตัวสัมภาษณ์เขียนโปรแกรมจากบทเรียนนี้
สรุปบทเรียน
ในบทเรียนนี้ คุณได้เรียนรู้ว่า การหาผลรวมของสองจำนวนใช้แฮชแมปเพื่อตรวจสอบว่ามีส่วนเติมเต็มอยู่หรือไม่ในเวลา O(1) จึงมีเวลารวมเป็น O(n) สำหรับอาร์เรย์ที่เรียงลำดับแล้ว การใช้ตัวชี้สองตัวจะใช้พื้นที่ O(1) และ การหาผลรวมของสามจำนวนและสี่จำนวนสามารถลดรูปเป็นการหาผลรวมของสองจำนวนได้ด้วยการเรียงลำดับและลูปซ้อนกัน โดยใช้เวลา O(n²) และ O(n³) ตามลำดับ ต่อไปเราจะศึกษารูปแบบการนับความถี่และการจัดกลุ่มด้วย defaultdict และ Counter
คำถามที่พบบ่อย
บทเรียน “ผลรวมสองค่าและรูปแบบหลากหลาย” ฟรีหรือไม่
ใช่ — ข้อความเต็มของ “ผลรวมสองค่าและรูปแบบหลากหลาย” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน
คุณจะเรียนรู้อะไรในบทเรียน “ผลรวมสองค่าและรูปแบบหลากหลาย”
แก้โจทย์ผลรวมสองค่า สามค่า สี่ค่า และผลรวมสองค่าในอาร์เรย์ที่เรียงแล้วด้วยแผนผังแฮชและตัวชี้สองตัว พร้อมเปรียบเทียบต้นทุนด้านเวลาและพื้นที่ คุณปฏิบัติ 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- ภายในฟังก์ชันแฮชและการจัดการการชนกัน
- ผลรวมสองค่าและรูปแบบหลากหลาย
- การนับความถี่และการจัดกลุ่ม
- ลำดับต่อเนื่องที่ยาวที่สุดและแคช LRU