0Pricing
DSA Interview Prep · บทเรียน

การเรียงสับเปลี่ยนและการจัดหมู่

แจกแจงการเรียงสับเปลี่ยนทั้งหมดของรายการทั้งกรณีที่มีและไม่มีสมาชิกซ้ำ พร้อมสร้างการจัดหมู่ขนาด k และรูปแบบผลรวมของการจัดหมู่ทั้งหมด

การเรียงสับเปลี่ยนและการจัดหมู่ เป็นบทเรียน DSA Interview Prep ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน DSA Interview Prep และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

การเรียงสับเปลี่ยนกับการจัดหมู่

การเรียงสับเปลี่ยน คือการจัดวางที่ ลำดับมีความสำคัญ: [1,2,3] และ [3,2,1] ถือว่าแตกต่างกัน จำนวนการเรียงสับเปลี่ยนของสมาชิก n รายการคือ n! การจัดหมู่ คือการเลือกที่ ลำดับไม่มีความสำคัญ: การเลือก {1,2} เหมือนกับ {2,1} จำนวนการจัดหมู่ขนาด k จากสมาชิก n รายการคือ C(n,k) = n! / (k! × (n-k)!) ทั้งสองรูปแบบเป็นพื้นฐานสำคัญของโจทย์สัมภาษณ์ที่เกี่ยวกับการนับ การแจกแจง และการเลือก

import math

# Permutations
n = 4
print(f'Permutations of {n} items: {math.factorial(n)}')
# 4! = 24

# Combinations
for k in range(n+1):
    print(f'C({n},{k}) = {math.comb(n,k)}')
# C(4,0)=1, C(4,1)=4, C(4,2)=6, C(4,3)=4, C(4,4)=1
# Sum = 2^4 = 16 (total subsets)

การสร้างการเรียงสับเปลี่ยนทั้งหมด

ใช้อาร์เรย์บูลีน used เพื่อติดตามว่าสมาชิกใดอยู่ในเส้นทางปัจจุบัน ในแต่ละขั้น ให้ลองสมาชิกทุกตัวที่ยังไม่ได้ใช้ หลังสำรวจเสร็จแล้ว ให้ทำเครื่องหมายสมาชิกนั้นว่าไม่ได้ใช้อีกครั้ง ต่างจาก subsets ตรงที่ไม่มีดัชนี start เพราะการเรียงสับเปลี่ยนสามารถใช้สมาชิกในลำดับใดก็ได้ การเรียกซ้ำจะสิ้นสุดเมื่อ len(path) == n

def permutations(nums):
    result = []
    used = [False] * len(nums)
    def backtrack(path):
        if len(path) == len(nums):
            result.append(list(path))
            return
        for i, num in enumerate(nums):
            if not used[i]:
                used[i] = True         # CHOOSE
                path.append(num)
                backtrack(path)        # EXPLORE
                path.pop()             # UNCHOOSE
                used[i] = False
    backtrack([])
    return result

print(permutations([1, 2, 3]))
# [[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]

การเรียงสับเปลี่ยนแบบสลับค่า

อีกทางเลือกหนึ่งคือ สลับสมาชิกที่ตำแหน่ง start กับสมาชิกแต่ละตัวตั้งแต่ start ถึง n-1 เรียกซ้ำ แล้วสลับกลับ วิธีนี้แก้ไขอาร์เรย์ภายในที่เดิมโดยไม่ต้องใช้อาร์เรย์ used แนวคิดสำคัญคือ ในแต่ละระดับ สมาชิกทุกตัวทางซ้ายของ start จะถูกกำหนดตำแหน่งไว้แล้ว และเราจะเลือกว่าสมาชิกใดไปอยู่ที่ตำแหน่ง start วิธีนี้ใช้หน่วยความจำมีประสิทธิภาพกว่าเล็กน้อย และเป็นพื้นฐานของอัลกอริทึมของฮีป

def permutations_swap(nums):
    result = []
    def backtrack(start):
        if start == len(nums):
            result.append(list(nums))
            return
        for i in range(start, len(nums)):
            nums[start], nums[i] = nums[i], nums[start]  # CHOOSE (swap)
            backtrack(start + 1)                          # EXPLORE
            nums[start], nums[i] = nums[i], nums[start]  # UNCHOOSE (swap back)
    backtrack(0)
    return result

print(permutations_swap([1, 2, 3]))
# Same 6 permutations, different order

การเรียงสับเปลี่ยน II: การจัดการค่าซ้ำ

เมื่ออินพุตมีค่าซ้ำ (เช่น [1, 1, 2]) แนวทางใช้อาร์เรย์ติดตามสมาชิกที่ใช้แล้วจะสร้างการเรียงสับเปลี่ยนซ้ำ วิธีแก้คือเรียงลำดับอาร์เรย์ก่อน จากนั้นข้ามค่าซ้ำหากสมาชิกที่เหมือนกันก่อนหน้าไม่ได้ถูกใช้ในการเรียกซ้ำครั้งนี้ เงื่อนไขคือ if i > 0 and nums[i] == nums[i-1] and not used[i-1]: continue วิธีนี้บังคับให้เลือกค่าซ้ำจากซ้ายไปขวาเสมอ

def permutations_unique(nums):
    nums.sort()
    result = []
    used = [False] * len(nums)
    def backtrack(path):
        if len(path) == len(nums):
            result.append(list(path))
            return
        for i in range(len(nums)):
            if used[i]: continue
            # Skip if this num is a duplicate and the previous dup was not used
            if i > 0 and nums[i] == nums[i-1] and not used[i-1]:
                continue
            used[i] = True
            path.append(nums[i])
            backtrack(path)
            path.pop()
            used[i] = False
    backtrack([])
    return result

print(permutations_unique([1, 1, 2]))
# [[1,1,2],[1,2,1],[2,1,1]] — 3, not 6

การเรียงสับเปลี่ยนถัดไป (ตามลำดับพจนานุกรม)

การเรียงสับเปลี่ยนถัดไป (LeetCode 31) จะแปลงอาร์เรย์ให้เป็นการเรียงสับเปลี่ยนถัดไปที่มีค่ามากขึ้นตามลำดับพจนานุกรม โดยแก้ไขภายในที่เดิม อัลกอริทึมมีขั้นตอนดังนี้: (1) หาดัชนีขวาสุด i ที่ nums[i] < nums[i+1] (2) หาดัชนีขวาสุด j ที่ nums[j] > nums[i] (3) สลับ nums[i] กับ nums[j] (4) กลับลำดับส่วนท้ายหลังดัชนี i หากไม่มี i ดังกล่าว ให้กลับลำดับอาร์เรย์ทั้งหมด (วนกลับไปเป็นการเรียงสับเปลี่ยนที่เล็กที่สุด)

def next_permutation(nums):
    n = len(nums)
    # Step 1: find rightmost i where nums[i] < nums[i+1]
    i = n - 2
    while i >= 0 and nums[i] >= nums[i+1]:
        i -= 1
    if i >= 0:
        # Step 2: find rightmost j where nums[j] > nums[i]
        j = n - 1
        while nums[j] <= nums[i]:
            j -= 1
        # Step 3: swap
        nums[i], nums[j] = nums[j], nums[i]
    # Step 4: reverse suffix after i
    nums[i+1:] = nums[i+1:][::-1]
    return nums

print(next_permutation([1, 2, 3]))  # [1,3,2]
print(next_permutation([3, 2, 1]))  # [1,2,3] (wraps)
print(next_permutation([1, 1, 5]))  # [1,5,1]

การย้อนกลับสำหรับการจัดหมู่ k รายการ

สร้างการจัดหมู่ของสมาชิก k รายการจากสมาชิก n รายการทั้งหมด (LeetCode 77) ใช้ดัชนีเริ่มต้นเหมือนกับ subsets เพื่อหลีกเลี่ยงการทำ revisiting สมาชิกและรักษาลำดับที่เรียงแล้ว ตัดกิ่งเมื่อมีสมาชิกเหลือน้อยกว่า k - len(path) รายการ: if len(nums) - i + 1 < k - len(path): break วิธีนี้เทียบเท่ากับ combine(n, k) ก่อนหน้านี้ แต่ทำงานกับอาร์เรย์จริง

def combinations(nums, k):
    result = []
    def backtrack(start, path):
        if len(path) == k:
            result.append(list(path))
            return
        for i in range(start, len(nums)):
            # Pruning: not enough elements left
            if len(nums) - i < k - len(path):
                break
            path.append(nums[i])
            backtrack(i + 1, path)
            path.pop()
    backtrack(0, [])
    return result

print(combinations([1,2,3,4,5], 3))
# 10 combinations: C(5,3)
import math
print(math.comb(5,3))  # 10

ผลรวมของการจัดหมู่: ใช้ซ้ำได้ไม่จำกัด

ผลรวมของการจัดหมู่ (LeetCode 39) อนุญาตให้ใช้ตัวเลขแต่ละตัวซ้ำได้ไม่จำกัด ความแตกต่างจากการจัดหมู่ทั่วไปคือ แทนที่จะเลื่อน start ไปที่ i+1 ให้ส่งค่า i (ดัชนีเดิม) เพื่อให้ใช้สมาชิกปัจจุบันซ้ำได้ การตัดกิ่งทำดังนี้: หากเป้าหมายที่เหลือเป็น 0 ให้บันทึกเส้นทาง หากมีค่าติดลบ ให้หยุด การเรียงลำดับช่วยให้ยุติการค้นหาได้ก่อน เมื่อสมาชิกตัวเลือกที่เหลือทั้งหมดมีค่ามากกว่าเป้าหมายที่เหลือ

def combination_sum(candidates, target):
    candidates.sort()
    result = []
    def backtrack(start, path, remaining):
        if remaining == 0:
            result.append(list(path))
            return
        for i in range(start, len(candidates)):
            c = candidates[i]
            if c > remaining: break  # all remaining are too big
            path.append(c)
            backtrack(i, path, remaining - c)  # reuse allowed: pass i, not i+1
            path.pop()
    backtrack(0, [], target)
    return result

print(combination_sum([2, 3, 6, 7], 7))
# [[2,2,3],[7]]

ผลรวมของการจัดหมู่ II: ไม่ใช้ซ้ำและมีค่าซ้ำในอินพุต

ผลรวมของการจัดหมู่ II (LeetCode 40) ใช้ตัวเลขแต่ละตัวได้ไม่เกินหนึ่งครั้ง แต่อินพุตอาจมีค่าซ้ำ เทคนิคที่ใช้ร่วมกันมีสองอย่าง: เลื่อน start ไปที่ i+1 (ไม่ใช้ซ้ำ) และข้ามค่าซ้ำในระดับเดียวกัน (if i > start and nums[i] == nums[i-1]: continue) หลังจากเรียงลำดับแล้ว วิธีนี้รวมการจัดการค่าซ้ำจาก subsets II เข้ากับข้อจำกัดไม่ใช้ซ้ำของการจัดหมู่

def combination_sum_ii(candidates, target):
    candidates.sort()
    result = []
    def backtrack(start, path, remaining):
        if remaining == 0:
            result.append(list(path))
            return
        for i in range(start, len(candidates)):
            if candidates[i] > remaining: break
            # Skip duplicates at same level
            if i > start and candidates[i] == candidates[i-1]:
                continue
            path.append(candidates[i])
            backtrack(i + 1, path, remaining - candidates[i])  # no reuse: i+1
            path.pop()
    backtrack(0, [], target)
    return result

print(combination_sum_ii([10,1,2,7,6,1,5], 8))
# [[1,1,6],[1,2,5],[1,7],[2,6]]

การจัดหมู่ตัวอักษรจากหมายเลขโทรศัพท์

การจัดหมู่ตัวอักษร (LeetCode 17) จับคู่ตัวเลขแต่ละหลักกับตัวอักษรบนแป้นกดโทรศัพท์ และสร้างการจัดหมู่ตัวอักษรที่เป็นไปได้ทั้งหมดสำหรับสตริงตัวเลขที่กำหนด นี่เป็นโจทย์การย้อนกลับ โดยในแต่ละตำแหน่ง เราจะเลือกตัวอักษรหนึ่งตัวจากการจับคู่ของตัวเลขหลักนั้นแล้วเรียกซ้ำต่อ สำหรับสตริงความยาว n ที่แต่ละหลักมีตัวอักษรเฉลี่ย k ตัว ความซับซ้อนด้าน time คือ O(kⁿ)

def letter_combinations(digits):
    if not digits: return []
    phone = {
        '2': 'abc', '3': 'def', '4': 'ghi', '5': 'jkl',
        '6': 'mno', '7': 'pqrs', '8': 'tuv', '9': 'wxyz'
    }
    result = []
    def backtrack(index, path):
        if index == len(digits):
            result.append(''.join(path))
            return
        for letter in phone[digits[index]]:
            path.append(letter)
            backtrack(index + 1, path)
            path.pop()
    backtrack(0, [])
    return result

print(letter_combinations('23'))
# ['ad','ae','af','bd','be','bf','cd','ce','cf']

การเปรียบเทียบการเรียงสับเปลี่ยนและการจัดหมู่

ความแตกต่างเชิงโครงสร้างที่สำคัญมีดังนี้: การเรียงสับเปลี่ยน — ไม่มีดัชนีเริ่มต้น ใช้อาร์เรย์ used หรือการสลับค่าเพื่อป้องกันการใช้ซ้ำ ต้นไม้มีตัวเลือก n รายการในแต่ละระดับ และมีใบไม้ทั้งหมด n! ใบ การจัดหมู่ — ใช้ดัชนีเริ่มต้นเพื่อบังคับลำดับ มีใบไม้ C(n,k) ใบ ผลรวมของการจัดหมู่ — ไม่เลื่อนดัชนีเริ่มต้นเพื่อให้ใช้ซ้ำได้ และตัดกิ่งตามเป้าหมาย การจับคู่โจทย์ใหม่กับหนึ่งในสามรูปแบบนี้ช่วยให้เลือกแม่แบบที่ถูกต้องได้ทันที

# Pattern summary:
# Permutations: for i in range(n); if not used[i]; no start advancement
# Combinations: for i in range(start, n); advance start → i+1
# Combo Sum (reuse): for i in range(start, n); advance start → i (same)

# Quick reference:
import math
n = 5
print(f'Perm({n})   = n! = {math.factorial(n)}')
print(f'Comb({n},2) = C(n,k) = {math.comb(n,2)}')
print(f'Comb({n},3) = {math.comb(n,3)}')
# Also: subsets = sum(C(n,k) for k=0..n) = 2^n
print(f'Subsets({n}) = 2^n = {2**n}')

ความซับซ้อนและเคล็ดลับการสัมภาษณ์

ความซับซ้อนด้านเวลาในการแจกแจง: การเรียงสับเปลี่ยน O(n × n!), การจัดหมู่ O(k × C(n,k)), ผลรวมของการจัดหมู่ O(n^(T/min_val)) การใช้หน่วยความจำคือ O(n) สำหรับความลึกของการเรียกซ้ำ บวก O(ผลลัพธ์) สำหรับคำตอบ เคล็ดลับสำคัญ: (1) ต้องทำให้ชัดเจนเสมอว่าลำดับมีความสำคัญหรือไม่ (การเรียงสับเปลี่ยนหรือการจัดหมู่) (2) กล่าวถึงการจัดการค่าซ้ำก่อนที่จะถูกถาม (3) ระบุเงื่อนไขการตัดกิ่งอย่างชัดเจนเสมอ (4) สำหรับ n ที่มีขนาดใหญ่ ให้ชี้ว่าผลลัพธ์เองมีขนาดแบบเอ็กซ์โพเนนเชียล — อัลกอริทึมนี้จึงเหมาะสมที่สุดสำหรับโจทย์ดังกล่าว

import math

# Complexity for n=10
n = 10
print(f'Permutations(10): {math.factorial(n):,} results')
print(f'Combinations(10,5): {math.comb(n,5):,} results')
print(f'Subsets(10): {2**n:,} results')

# For interview: state which pattern
# 'This is a combinations problem because order doesnt matter'
# 'I will use a start index to avoid revisiting elements'
# 'Pruning: when sum exceeds target, break (after sorting)'

ตรวจสอบความเข้าใจ

ทดสอบความเข้าใจแนวคิดเรื่องโครงสร้างข้อมูล & อัลกอริทึม — การเตรียมตัวสัมภาษณ์การเขียนโปรแกรมจากบทเรียนนี้

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า: permutations ใช้อาร์เรย์ used และไม่มีดัชนีเริ่มต้น เพื่อสร้างการจัดวาง n! รูปแบบ combinations ใช้ดัชนีเริ่มต้นที่เลื่อนไปข้างหน้าเพื่อหลีกเลี่ยงการใช้ซ้ำ และสร้างการเลือก C(n,k) รูปแบบ และ ค่าซ้ำในปัญหาทั้งสองประเภทจัดการได้ด้วยการเรียงลำดับและข้ามค่าที่ซ้ำกันในระดับการเรียกซ้ำเดียวกัน บทถัดไปเราจะประยุกต์ใช้การย้อนกลับกับปัญหา N-ควีนส์ และสำรวจการเผยแพร่ข้อจำกัด

คำถามที่พบบ่อย

บทเรียน “การเรียงสับเปลี่ยนและการจัดหมู่” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การเรียงสับเปลี่ยนและการจัดหมู่” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส DSA Interview Prep ให้อัปเกรดเป็น CoddyKit PRO คอร์ส DSA Interview Prep มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การเรียงสับเปลี่ยนและการจัดหมู่”

แจกแจงการเรียงสับเปลี่ยนทั้งหมดของรายการทั้งกรณีที่มีและไม่มีสมาชิกซ้ำ พร้อมสร้างการจัดหมู่ขนาด k และรูปแบบผลรวมของการจัดหมู่ทั้งหมด คุณปฏิบัติ DSA Interview Prep ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน DSA Interview Prep หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน DSA Interview Prep บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “การเรียงสับเปลี่ยนและการจัดหมู่” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน DSA Interview Prep นี้ได้ไหม

ได้ บทเรียน DSA Interview Prep ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. แม่แบบการย้อนรอย: เลือก สำรวจ ยกเลิกการเลือก
  2. เซตย่อยและเพาเวอร์เซต
  3. การเรียงสับเปลี่ยนและการจัดหมู่
  4. N-ควีนและการเผยแพร่ข้อจำกัด
← กลับไปที่ DSA Interview Prep