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

การค้นหาคำนำหน้าและ Starts-With

เพิ่มเมธอด starts_with ที่คืนค่า true หากมีคำที่แทรกไว้คำใดใช้คำนำหน้าที่กำหนดร่วมกัน และใช้เมธอดนี้สร้างคำแนะนำเติมคำอัตโนมัติ

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

พลังของการสืบค้นคำนำหน้า

ข้อได้เปรียบที่สำคัญของ Trie เหนือตารางแฮชคือ การสืบค้นคำนำหน้า ที่มีประสิทธิภาพ การสืบค้นคำนำหน้าสามารถตอบคำถามว่า 'มีคำที่จัดเก็บไว้กี่คำซึ่งขึ้นต้นด้วยคำนำหน้านี้' 'มีคำที่จัดเก็บไว้ทั้งหมดใดบ้างที่ขึ้นต้นด้วยคำนำหน้านี้' หรือเพียง 'มีคำใด ๆ ที่ขึ้นต้นด้วยคำนำหน้านี้หรือไม่' การสืบค้นเหล่านี้ใช้เวลา O(p) โดย p คือความยาวคำนำหน้า และไม่ขึ้นกับจำนวนคำทั้งหมดที่จัดเก็บไว้ จึงทำให้ Trie เหมาะสำหรับ autocomplete และคำแนะนำการค้นหา

เมธอด starts_with

starts_with(prefix) จะส่งกลับค่าจริงหากมีคำที่จัดเก็บไว้คำใดขึ้นต้นด้วยคำนำหน้าที่กำหนด ให้เดินตาม Trie โดยใช้อักขระแต่ละตัวของคำนำหน้า หากสามารถเดินตามอักขระทั้งหมดได้โดยไม่พบเส้นเชื่อมที่หายไป แสดงว่าคำนำหน้านั้นมีอยู่และมีคำอย่างน้อยหนึ่งคำขึ้นต้นด้วยคำนำหน้านี้ การใช้งานเหมือนกับ search ทุกประการ ยกเว้นว่าจะส่งกลับค่าจริงทันทีที่เดินครบ และไม่ตรวจสอบ is_end

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def starts_with(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children:
                return False
            node = node.children[c]
        return True

t = Trie()
for w in ['hello','help','world','word']:
    t.insert(w)
print(t.starts_with('hel'))   # True
print(t.starts_with('wor'))   # True
print(t.starts_with('xyz'))   # False

Autocomplete: การค้นหาคำทั้งหมดที่มีคำนำหน้า

หากต้องการใช้งาน autocomplete ให้เดินไปยังโหนดปลายของคำนำหน้า จากนั้นทำ DFS หรือ BFS จากโหนดนั้นเพื่อรวบรวมคำทั้งหมดที่แตกแขนงออกไป เติมคำนำหน้าไว้ด้านหน้าของส่วนต่อท้ายแต่ละส่วนที่รวบรวมได้ เพื่อสร้างคำเต็มกลับคืนมา การดำเนินการนี้ใช้เวลา O(p + W) โดย W คือจำนวนอักขระทั้งหมดในคำที่ตรงกัน

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def autocomplete(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node.children:
                return []
            node = node.children[c]
        # DFS from prefix end node
        results = []
        def dfs(n, path):
            if n.is_end:
                results.append(prefix + path)
            for char, child in n.children.items():
                dfs(child, path + char)
        dfs(node, '')
        return results

t = Trie()
for w in ['apple','app','application','apply','apt']:
    t.insert(w)
print(t.autocomplete('app'))  # ['app','apple','apply','application']

การส่งคืนคำแนะนำที่เรียงลำดับแล้ว

สำหรับ autocomplete ที่เรียงลำดับแล้ว ให้เดินผ่านโหนดลูกตามลำดับตัวอักษรระหว่างทำ DFS โดยวนซ้ำผ่าน sorted(node.children.items()) เนื่องจากโหนดลูกจัดเก็บอยู่ในพจนานุกรม วิธีนี้จึงเพิ่มค่าใช้จ่าย O(ALPHABET_SIZE × depth) แต่รับประกันผลลัพธ์ตามลำดับพจนานุกรม Trie ที่ใช้อาร์เรย์จะเดินผ่านโหนดลูกตามลำดับตัวอักษรอยู่แล้ว เพราะดัชนี 0-25 เรียงลำดับไว้

def dfs_sorted(node, prefix, results):
    if node.is_end:
        results.append(prefix)
    for char in sorted(node.children.keys()):  # alphabetical order
        dfs_sorted(node.children[char], prefix + char, results)

print('Iterating children in sorted order gives lex-sorted suggestions')

คำแนะนำ autocomplete อันดับต้น ๆ จำนวน k รายการ

สำหรับ คำแนะนำอันดับต้น ๆ จำนวน k รายการตามความถี่ ให้เพิ่มตัวนับในแต่ละโหนดเพื่อบันทึกจำนวนครั้งที่มีการค้นหาคำซึ่งสิ้นสุดที่โหนดนั้น ระหว่างรวบรวมคำแนะนำ ให้ใช้ฮีปค่าสูงสุดขนาด k วิธีนี้ลดผลลัพธ์จาก DFS ที่มีขนาด O(W) ให้เหลือ O(k) โดยไม่ต้องสร้างรายการผลลัพธ์ที่ตรงกันทั้งหมดไว้ในหน่วยความจำ เครื่องมือค้นหาในโลกจริงจะผสานการเดินตามคำนำหน้าใน Trie เข้ากับข้อมูลความถี่ เพื่อสร้างคำแนะนำที่รวดเร็วและเกี่ยวข้อง

การใช้งาน Trie สำหรับ LeetCode 208

โจทย์ LeetCode 208 'สร้าง Trie (ต้นไม้คำนำหน้า)' ต้องการสิ่งต่อไปนี้โดยตรง ได้แก่ insert(word) search(word) ที่ส่งกลับค่าบูลีนว่าตรงกันทั้งหมดหรือไม่ และ startsWith(prefix) ที่ส่งกลับค่าบูลีนว่าตรงกับคำนำหน้าหรือไม่ นี่คือการใช้งาน Trie แบบมาตรฐาน โปรดจำไว้ว่า search ต้องมี is_end=True ส่วน startsWith ต้องการเพียงให้เส้นทางคำนำหน้ามีอยู่

class Trie:
    def __init__(self):
        self.root = {}
    
    def insert(self, word):
        node = self.root
        for c in word:
            if c not in node:
                node[c] = {}
            node = node[c]
        node['#'] = True  # '#' marks word end
    
    def search(self, word):
        node = self.root
        for c in word:
            if c not in node: return False
            node = node[c]
        return '#' in node
    
    def startsWith(self, prefix):
        node = self.root
        for c in prefix:
            if c not in node: return False
            node = node[c]
        return True

t = Trie()
t.insert('apple')
print(t.search('apple'))      # True
print(t.search('app'))        # False
print(t.startsWith('app'))   # True

การใช้ '#' เป็นเครื่องหมายจุดสิ้นสุด (Trie แบบพจนานุกรม)

ทางลัดที่เรียบง่ายคือจัดเก็บ Trie เป็นพจนานุกรมซ้อนกัน โดยใช้คีย์พิเศษสำหรับทำเครื่องหมาย เช่น '#' เพื่อระบุจุดสิ้นสุดของคำ จึงไม่จำเป็นต้องมีคลาส TrieNode วิธีนี้กระชับและเหมาะกับการสัมภาษณ์ แต่จะอ่านเข้าใจยากกว่าวัตถุ TrieNode ที่ระบุอย่างชัดเจนเล็กน้อย ทั้งสองรูปแบบยอมรับได้ โดยแบบพจนานุกรมเขียนได้เร็วกว่าเมื่อมีเวลาจำกัด

การหาคำนำหน้าร่วมที่ยาวที่สุดด้วย Trie

หากต้องการหาคำนำหน้าร่วมที่ยาวที่สุดของรายการสตริง ให้ insert สตริงทั้งหมดลงใน Trie จากนั้นเดินจากรากตามเส้นทางเดียวที่มีอยู่ต่อไป ตราบใดที่ (1) โหนดปัจจุบันมีโหนดลูกเพียงหนึ่งโหนด และ (2) โหนดนั้นยังไม่ใช่จุดสิ้นสุดของคำ ให้หยุดเมื่อเงื่อนไขใดเงื่อนไขหนึ่งไม่เป็นจริง เส้นทางที่เดินตามคือคำนำหน้าร่วมที่ยาวที่สุด

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

def longest_common_prefix(words):
    root = TrieNode()
    for word in words:
        node = root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    prefix = []
    node = root
    while len(node.children) == 1 and not node.is_end:
        char, node = next(iter(node.children.items()))
        prefix.append(char)
    return ''.join(prefix)

print(longest_common_prefix(['flower','flow','flight']))  # 'fl'
print(longest_common_prefix(['dog','racecar','car']))     # ''

ปัญหาการแทนที่คำ

การแทนที่คำ (LeetCode 648): เมื่อกำหนดพจนานุกรมคำรากและประโยค ให้แทนที่แต่ละคำในประโยคด้วยคำรากที่ตรงกันและสั้นที่สุดจากพจนานุกรม ให้ insert คำรากทั้งหมดลงใน Trie สำหรับแต่ละคำในประโยค ให้เดินตาม Trie จนพบจุดสิ้นสุดของคำราก แล้วส่งคืนคำรากนั้นเป็นคำแทนที่ หากไม่มีคำรากใดตรงกัน ให้คงคำเดิมไว้ วิธีนี้ใช้เวลา O(จำนวนอักขระทั้งหมด) เมื่อเทียบกับการลองทุกกรณีที่ใช้เวลา O(n × m)

class TrieNode:
    def __init__(self):
        self.children = {}
        self.is_end = False

def replaceWords(dictionary, sentence):
    root = TrieNode()
    for word in dictionary:
        node = root
        for c in word:
            if c not in node.children:
                node.children[c] = TrieNode()
            node = node.children[c]
        node.is_end = True
    
    def find_root(word):
        node = root
        for i, c in enumerate(word):
            if c not in node.children: break
            node = node.children[c]
            if node.is_end:
                return word[:i+1]
        return word
    
    return ' '.join(find_root(w) for w in sentence.split())

print(replaceWords(['cat','bat','rat'], 'the cattle was rattled by the battery'))

ปัญหาคู่ผลรวมจากแผนที่

ผลรวมจากแผนที่ (LeetCode 677): insert คู่คีย์กับค่า และส่งกลับผลรวมของค่าทั้งหมดที่คีย์มีคำนำหน้าที่กำหนด เพิ่มฟิลด์ val ให้แต่ละ TrieNode สำหรับ insert ให้เดินไปยังจุดสิ้นสุดแล้วกำหนดค่า สำหรับการสืบค้นผลรวม ให้เดินไปยังโหนดปลายของคำนำหน้าแล้วคำนวณผลรวม DFS ของฟิลด์ val ทั้งหมดที่อยู่ด้านล่าง หรืออีกทางหนึ่ง ให้จัดเก็บผลรวมสะสมในแต่ละโหนดระหว่างการ insert เพื่อให้การสืบค้นใช้เวลา O(p)

การนำการเติมคำอัตโนมัติที่มีผลลัพธ์จำกัดไปใช้งาน

ในระบบเติมคำอัตโนมัติที่ใช้งานจริง การคืนคำ ทั้งหมด ที่มีคำนำหน้าเดียวกันนั้นไม่เหมาะสมเมื่อมีคำที่ตรงกันหลายพันคำ แต่ให้ใช้ ฮีปค่าสูงสุดขนาด k ระหว่างการเดินผ่านด้วย DFS โดยเก็บคำที่มีคะแนนสูงสุด k คำที่พบจนถึงขณะนั้นไว้ หากกิ่งของ DFS ไม่สามารถมีคำที่อยู่ในกลุ่ม k อันดับแรกได้ ก็ให้หยุดกิ่งนั้นก่อนเวลาอันควร (ตัดกิ่งด้วยขอบเขตบนของคะแนน) วิธีนี้ใช้เวลา O(p + k × log k) ต่อการค้นหาหนึ่งครั้งสำหรับคำแนะนำ k คำ ซึ่งดีกว่าการรวบรวมผลลัพธ์ที่ตรงกันทั้งหมดมาก

ตรวจสอบความเข้าใจอย่างรวดเร็ว

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

สรุปบทเรียน

ในบทเรียนนี้ คุณได้เรียนรู้ว่า การตรวจสอบว่าขึ้นต้นด้วยคำนำหน้าจะเดินผ่านเส้นทางของคำนำหน้าและคืนค่าเป็นจริงหากเส้นทางนั้นมีอยู่ — ไม่จำเป็นต้องตรวจสอบจุดสิ้นสุดคำ การเติมคำอัตโนมัติด้วย DFS จะรวบรวมคำทั้งหมดจากโหนดปลายคำนำหน้าโดยเติมอักขระขณะเดินลงไป และ การเพิ่มจำนวนหรือค่าให้กับโหนดช่วยให้สามารถค้นหาผลรวมและคำแนะนำ k อันดับแรกได้ บทถัดไป เราจะเพิ่มการจับคู่ด้วยอักขระตัวแทนและนิพจน์ประจำให้กับไทร

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

บทเรียน “การค้นหาคำนำหน้าและ Starts-With” ฟรีหรือไม่

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

คุณจะเรียนรู้อะไรในบทเรียน “การค้นหาคำนำหน้าและ Starts-With”

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

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

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

บทเรียน “การค้นหาคำนำหน้าและ Starts-With” ใช้เวลานานแค่ไหน

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

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

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

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

  1. คลาส TrieNode: แทรกและค้นหา
  2. การค้นหาคำนำหน้าและ Starts-With
  3. การค้นหาแบบไวลด์การ์ดและนิพจน์ประจำใน Trie
  4. การค้นหาคำ II: Trie + การย้อนกลับบนกริด
← กลับไปที่ DSA Interview Prep