首页 > 编程语言 >java面试算法:设计搜索输入框的输入提示功能

java面试算法:设计搜索输入框的输入提示功能

时间:2023-06-14 15:04:00浏览次数:38  
标签:node java 面试 TrieNode ArrayList public 输入框 字符串 节点

我们使用搜索引擎时,需要在搜索框输入关键字,当你在框中输入头几个字符时,搜索框会出现一个下拉框,里面包含着以当前输入字符为前缀的字符串,如果里面包含你想要输入的内容,那么你就可以直接选取,而不必要把关键字的所有字符依次输入,这种功能极大的提高了搜索体验。

java面试算法:设计搜索输入框的输入提示功能_子节点

本次算法题的题目是,你如何设计该功能。例如给你一组关键词:to, tea, ted, in, inn. 当用户输入’t’的时候,你要把以’t’开头的所有单词返回给用户。

这道算法题的解决涉及到一种树形结构叫字典树,其具体结构如下:

java面试算法:设计搜索输入框的输入提示功能_java_02

当给定一个字符t时,我们从树的根节点走到左边以字符t开头的子树,这颗子树的叶子节点就是我们要返回给用户的所有单词。假设单词是有26个小写字母组成的,那么字典树的每个节点最多可以有26个孩子节点。我们先看看树节点如何定义:

import java.util.ArrayList;
import java.util.HashMap;


public class TrieNode {
   private HashMap<Byte, TrieNode> map = new HashMap<Byte, TrieNode>();
   private String s = "";

   public void setString(String str) {
       this.s = str;
   }

   public String getString() {
       return s;
   }


   public TrieNode  nextNode(Byte b) {
       if (map.get(b) == null) {
           TrieNode n = new TrieNode();
           map.put(b, n);
       }

       return map.get(b);
   }

   public TrieNode getNode(Byte b) {
       return map.get(b);
   }

   public ArrayList<TrieNode> getAllNextNodes() {
       ArrayList<TrieNode> arr = new ArrayList<TrieNode>();
       for (char c = 'a'; c <= 'z'; c++) {
           if (map.get((byte)c) != null) {
               arr.add(map.get((byte)c));
           }
       }

       return arr;
   }
}

TrieNode 类中有个map,它的作用在于指向孩子节点,给定一个字符,从该哈希表中得到的节点,就是以该字符开头的子多叉树的根节点。如果节点是叶子节点,那么该节点就包含了其对应的字符串,例如上图中的编号为3,4,5,7,9,11,12,15。从根节点开始抵达到叶子节点的每一条路径都对应一个字符,所有这些字符组合起来就是叶子节点对应的字符串。

注意到,拥有相同祖先的叶子节点所包含的字符串都有着相同的前缀,例如节点“tea”, “ted”,”ten”,他们的父节点是”te”,从根节点要抵达这些叶子节点,那必须先从根节点到达节点”te”。

我们再看看给定一组字符串,如何根据字符串构造一颗字典树,代码如下:

import java.util.ArrayList;
import java.util.Stack;


public class TrieBuilder {
    private TrieNode root = null;
    private Stack<TrieNode> stack = new Stack<TrieNode>();

    public void addWord(String s) {
        if (root == null) {
            root = new TrieNode();
        }

        TrieNode node = root;
        int l = 0;
        while (l < s.length()) {
            node = node.nextNode((byte)s.charAt(l));
            l++;
        }

        node.setString(s);
    }

    private void addNodeListToStack(ArrayList<TrieNode> nodes) {
        for (int i = 0; i < nodes.size(); i++) {
            stack.push(nodes.get(i));
        }
    }

    public ArrayList<String> getAllWordsByPrefix(String prefix) {
        int l = 0;
        TrieNode node = root;

        while (l < prefix.length() ) {
            node =  node.getNode((byte)prefix.charAt(l));
            if (node == null) {
                break;
            }
            l++;
        }

        if (node == null) {
            return null;
        }


        addNodeListToStack(node.getAllNextNodes());
        ArrayList<String> allWords = new ArrayList<String>();
        while (stack.empty() == false) {
            TrieNode n = stack.pop();
            if (n.getString().isEmpty() == false) {
                allWords.add(n.getString());
            }

            addNodeListToStack(n.getAllNextNodes());
        }

        return allWords;

    }
}

addWords接口根据给定字符串构造相应节点,根据字符串中的每个字符,调用TrieNode的nextNode接口,它从节点的map中查看是否有对应于给定字符的子节点,如果没有,则构造一个新节点,加入当前节点的map.

addNodeListToStack把给定节点的所有子节点加入堆栈,例如对于节点”te”,其有三个子节点:“tea”,”ted”,”ten”, 着三个节点将会被压到堆栈上。

getAllWordsByPrefix根据前缀字符串,找到所有已该字符串为前缀的关键字,例如给定前缀字符串为”t”, 那么该函数先从多叉树中找到以t开头的子树,然后把其所有子节点压入堆栈,根据图中所示多叉树,以t开头的子树,其根节点含有两个子节点,分别是”to”, 和 “te”, 于是代码把这两个节点压入堆栈,然后再从堆栈弹出,弹出时查看该节点是否含有关键词字符串,当子节点”to”被弹出堆栈时,它含有关键词”to”,那么代码把这个关键词加入队列,当节点”te”被弹出堆栈时,该节点不含有关键词,于是代码继续把该节点的所有子节点”tea”,”ted”,”ten”压入堆栈,然后再把堆栈中的节点弹出,做相同操作,当堆栈所有节点处理完毕后,拥有给定字符串为前缀的所有关键字字符串就包含在一个队列中了。

最后我们看看主入口的代码:

import java.util.ArrayList;


public class BinaryTree {
   public static void main(String[] s) {
       String[] dictionary = new String[]{"tea", "to", "ted", "ten", "A", "in", "inn"};
       TrieBuilder tb = new TrieBuilder();
       for (int i = 0; i < dictionary.length; i++) {
           tb.addWord(dictionary[i]);
       }

       ArrayList<String> prefixWords = tb.getAllWordsByPrefix("t");
       System.out.println("words with prefix of t are:");
       for (int i = 0; i < prefixWords.size(); i++) {
           System.out.print(prefixWords.get(i) + " ");
       }
   }
}

代码先构造一组关键词,然后根据关键词字符串,调用TrieBuilder的addWords接口,构造给定字典树,最后通过前缀字符串”t”,找到所有以字符t开头的关键字,上面代码运行后结果如下:

words with prefix of t are:
to ten ted tea

从结果判定,我们代码的实现能满足题目要求的效果。算法的运行效率与给定前缀的关键字长度有关,如果关键字的最大长度为n, 那么算法的效率就是O(n),字典树的空间效率也跟关键字的长度相关,如果关键字的最大长度为s,那么算法的空间复杂度为O(s).

更详细的讲解以及代码调试演示,请参看视频。

更多技术信息,包括操作系统,编译器,面试算法,机器学习,人工智能,请关照我的公众号:

java面试算法:设计搜索输入框的输入提示功能_搜索_03


标签:node,java,面试,TrieNode,ArrayList,public,输入框,字符串,节点
From: https://blog.51cto.com/u_16160261/6477205

相关文章

  • 微软亚洲工程院面试题:寻找两个二叉树节点的最近祖先
    给定一颗二叉树,并指定二叉树中任意两个节点,要求找出这两个节点在二叉树中的最近祖先,假定二叉树每个节点都有一个指向其父节点的指针,图中没有画出来,要求算法的空间复杂度必须是O(1),时间复杂度为O(h)。例如给定下面二叉树:如果指定的两个节点是401和257,那么他们的最近祖先就是节点1......
  • JavaScript 动态编辑元素某属性值(例如:元素div的class属性)
    元素<divclass="h5-box-search-itemusimglistnodisplay"id="usimglist"></div>(满足条件)动态更新div元素的class属性值://获取目标容器letusimglist=document.getElementById('usimglist');//获取其class的属性值letclassinfo=usimglist.ge......
  • Java8-Consumer的使用场景
    Java8的Consumer比较抽象。结合几个例子来看看常用的使用场景有以下几个:把方法作为函数的入参Java8中可以使用Consumer来实现在函数的入参中传递方法,这个如果熟悉js的话可能会比较好理解一些。在某些情况下,不能直接使用某个对象的方法,需要把方法传递到另一个函数里面去执行,那么......
  • Java8-Predicate 策略模式的替代品消灭 if else
    使用策略模式消灭ifelse,可以利用Java8的新特性来实现策略模式。利用Java8的Predicate消灭ifelse。首先定义一个map,key是不同的服务代码,value是需要做校验的条件,然后针对不同的服务代码做校验。当然Supplier、Consumer都可以做类似的实现。//定义校验的策略映射关系staticM......
  • Java8-并行流的使用
    Java8中的并行流使用publicclassStreamTest{publicList<Person>constructPersons(){List<Person>persons=newArrayList<Person>();for(inti=0;i<5;i++){Personp=newPerson(i,"name"+......
  • JavaScript中数组(Array)与对象(Object)中的检索方式
    这里只是要说明一点,数组(Array)和对象(Object)都可以用[...]的方式来进行检索[...]中包含的需要是一个表达式,这个表达式的值最终会以字符串的形式被使用因为不论是数组(Array)还是对象(Object),他们都是以键值对的形式存储内容的,而所有的键的数据类型都是字符串(Array好像不是,但是先这样......
  • 编译原理动手实操,用java实现编译器-算术表达式及其语法解析器的实现
     本节代码下载地址:http://pan.baidu.com/s/1sjWiwPn代码的理解和运行是吃透编译原理的关键,如果我们看的是爱情动作片,自然选择无码的好,但如果看得是计算机课程,则必须有码,无码的计算机理论都是耍流氓。当前,java所实现的简易编译器目的是将一条或一组含有加号和乘号的算术表达式编译......
  • java构建TCP/IP协议:DNS,域名解析协议的基本原理介绍
    从本节开始,我们研究和实现一个体系较为复杂的协议,也就是域名解析协议,简写为DNS。该协议几乎也是我们”日用而不知“的幕后英雄,没有它肯定就没有现在的互联网繁荣。当我们在浏览器上输入网址,例如www.baidu.com时,浏览器先通过DNS协议找到与该网址对应的IP地址,然后再使用IP去向服务器......
  • java构建TCP/IP协议:DNS,域名解析协议系统的运行流程
    DNS协议的运转需要客户端和服务器进行交互。由于服务器端需要存储大量的域名信息,同时每天需要应答海量的解析请求,因此它的设计必须遵循分布式系统。客户端向一台服务器请求解析服务时,对方可能没有相应的域名信息,于是它会向上一层查询,获得拥有给定域名信息的服务器,然后把对应服务器......
  • 算法面试:根据前序遍历结果序列和中序遍历结果序列重构二叉树
    对不同结构的二叉树进行前序,后序或中序遍历时,得到的结果可以是相同的,例如:上面三种结构的二叉树在进行前序遍历时,得到的序列都是:{3,4,5},但如果给定两种遍历序列,例如再加上中序遍历序列:{4,3,5},那么二叉树的结构就可以唯一确定了,满足这两种遍历序列的二叉树只能是中间那颗二叉树。于......