Skip to content

Latest commit

 

History

History
174 lines (142 loc) · 6.51 KB

20201102.md

File metadata and controls

174 lines (142 loc) · 6.51 KB

Algorithm

208. Implement Trie (Prefix Tree)

Description

Implement a trie with insert, search, and startsWith methods.

Example:

Trie trie = new Trie();

trie.insert("apple");
trie.search("apple");   // returns true
trie.search("app");     // returns false
trie.startsWith("app"); // returns true
trie.insert("app");   
trie.search("app");     // returns true

Note:

  • You may assume that all inputs are consist of lowercase letters a-z.
  • All inputs are guaranteed to be non-empty strings.

Solution

class Trie {
    TrieNode root;
    /** Initialize your data structure here. */
    public Trie() {
        root=new TrieNode();
    }

    /** Inserts a word into the trie. */
    public void insert(String word) {
      TrieNode temp= root;
      for(char c: word.toCharArray()){
          int i=c-'a';
          if(temp.children[i]==null)
          {
            TrieNode newNode=new TrieNode();
            temp.children[i]=newNode;  
          }
          temp=temp.children[i];
        }
        temp.end=true;
    }

    /** Returns if the word is in the trie. */
    public boolean search(String word) {
        TrieNode temp= root;
        for(char c: word.toCharArray()){
             int i=c-'a';
             if(temp.children[i]==null){
                 return false;
             }
             temp=temp.children[i];       
        }
        return temp.end;
    }

    /** Returns if there is any word in the trie that starts with the given prefix. */
    public boolean startsWith(String prefix) {
        TrieNode temp= root;
        for(char c: prefix.toCharArray()){
             int i=c-'a';
             if(temp.children[i]==null){
                 return false;
             }
             temp=temp.children[i];   
        }
        return temp!=null;
    }
    static class TrieNode{
        TrieNode[] children=new TrieNode[26];
        boolean end=false;
    }
}

/**
 * Your Trie object will be instantiated and called as such:
 * Trie obj = new Trie();
 * obj.insert(word);
 * boolean param_2 = obj.search(word);
 * boolean param_3 = obj.startsWith(prefix);
 */

Discuss

字典树主要有如下三点性质:

  1. 根节点不包含字符,除根节点意外每个节点只包含一个字符。

  2. 从根节点到某一个节点,路径上经过的字符连接起来,为该节点对应的字符串。

  3. 每个节点的所有子节点包含的字符串不相同。

字母树的插入(Insert)、删除( Delete)和查找(Find)都非常简单,用一个一重循环即可,即第i 次循环找到前i 个字母所对应的子树,然后进行相应的操作。实现这棵字母树,我们用最常见的数组保存(静态开辟内存)即可,当然也可以开动态的指针类型(动态开辟内存)。至于结点对儿子的指向,一般有三种方法:

1、对每个结点开一个字母集大小的数组,对应的下标是儿子所表示的字母,内容则是这个儿子对应在大数组上的位置,即标号;

2、对每个结点挂一个链表,按一定顺序记录每个儿子是谁;

3、使用左儿子右兄弟表示法记录这棵树。

三种方法,各有特点。第一种易实现,但实际的空间要求较大;第二种,较易实现,空间要求相对较小,但比较费时;第三种,空间要求最小,但相对费时且不易写。

我们这里只来实现第一种方法,这种方法实现起来简单直观,字母的字典树每个节点要定义一个大小为 26 的子节点指针数组,然后用一个标志符用来记录到当前位置为止是否为一个词,初始化的时候讲 26 个子节点都赋为空。那么 insert 操作只需要对于要插入的字符串的每一个字符算出其的位置,然后找是否存在这个子节点,若不存在则新建一个,然后再查找下一个。查找词和找前缀操作跟 insert 操作都很类似,不同点在于若不存在子节点,则返回 false。查找次最后还要看标识位,而找前缀直接返回 true 即可。代码如下:

Review

快速排序

public class Sort {
    public static void main(String[] args) {
        Sort sort = new Sort();
        System.out.println("sort:");
        int[] a = new int[]{1, 8, 2, 9, 6, 7, 5, 0, 4, 3};
        sort.quickSort(a, 0, a.length-1);
        System.out.println(Arrays.toString(a));
    }

    private void quickSort(int[] a, int start, int end){
        if(start<end){
            // 选取第一个节点为标示,第一次遍历让比pivot小的数字都移到左边,比pivot大的都移到右边
            int pivot = a[start];
            int i = start;
            int j = end;
            while(i<j){
                // 从后往前看,如果i<j,并且a[j]比pivot大,那么j继续往前移动
                while(i<j&&a[j]>pivot){
                    j--;
                }
                // 如果i<j,防止数组越界,同时也说明上述while循环结束的条件是a[j]<pivot
                if(i<j){
                    // 此时将a[i]与a[j]交换一下,同时把pivot移动到右边,这时候pivot右侧的数字全部比pivot大
                    a[i] = a[j];
                    a[j] = pivot;
                    // a[i]是之前的a[j],已经在pivot左侧了,所以不用继续比较,往后移动一个i
                    i++;
                }
                // 从前往后看,如果i<j, 并且a[i]<pivot,那么i继续往右移动
                while(i<j&&a[i]<pivot){
                    i++;
                }
                // 如果i<j,防止数组越界,同时也说明上述while循环结束的条件是a[i]>pivot
                if(i<j){
                    // 此时将a[j]与a[i]交换一下,因此当前的a[j]比pivot大,同时把pivot移动到左边,这时候pivot左侧的数字全部比pivot小
                    a[j] =a[i];
                    a[i] = pivot;
                    // a[j]是之前的a[i],已经在pivot右侧了,而且刚才比较发现比pivot大,所以不用继续比较,往前移动一个j
                    j--;
                }
            }
            // 最后跳出循环不在移动的时候,说明i=j移到了中间部分,且左侧的值都比pivot小,且右侧的值都比pivot大,此时再给a[i]赋值pivot,可省略这一步
            a[i]= pivot;
            // 递归执行,i左侧
            quickSort(a, start, i-1);
            // 递归执行,i右侧
            quickSort(a,i+1,end);
        }
    }
}

Tip

Share