腾讯笔试题

第1题

实现一个循环队列。
没什么好说的,看基本功(但是我太紧张了,实现对了,面试官让我测试一下,我测试的时候写了个死循环,唉命苦)。我使用的链表实现的,既有单链表也有双链表。

public class QueueTest {
    private DNode head;
    private DNode tail;
    public QueueTest() {
        head = new DNode(-1);
        tail = head;
        head.pre = tail;
        head.next = tail;
    }

    public void push(int val) {
        DNode DNode = new DNode(val);
        tail.next = DNode;
        DNode.pre = tail;

        tail = DNode;
        tail.next = head;
        head.pre = tail;
    }

    public boolean isEmpty() {
        return head == tail;
    }

    public void pop() {
        if (!isEmpty()) {
            head.next = head.next.next;
            head.next.pre = head;
            tail = head.pre;
        }
    }
    public int peek() {
        if (!isEmpty()) {
            return head.next.data;
        }
        return -1;
    }
    public String toString() {
        if (isEmpty()) return "[]";
        StringBuilder sb = new StringBuilder();
        sb.append("[");
        for (DNode p = head.next; p != head; p = p.next) {
            sb.append(p.data);
            sb.append(", ");
        }
        sb.deleteCharAt(sb.lastIndexOf(","));
        sb.append("]");
        return sb.toString();
    }



    public static void main(String[] args) {
//        QueueTest queue = new QueueTest();
//        System.out.println(queue.isEmpty());
//        queue.push(1);
//        System.out.println(queue.isEmpty());
//        System.out.println(queue);
//        queue.push(2);
//        System.out.println(queue);
//
//        queue.push(3);
//        System.out.println(queue);
//
//        DNode p = queue.head.next;
//        while (p != queue.head) {
//            System.out.println(p.data);
//            p = p.next;
//        }
//        System.out.println(p.next.data);
//        while (!queue.isEmpty()) {
//            System.out.println(queue.peek());
//            queue.pop();
//        }

        QueueDemo que = new QueueDemo();
        System.out.println(que.isEmpty());
        que.push(1);
        System.out.println(que);
        que.push(2);
        System.out.println(que);
        que.push(3);
        System.out.println(que);
        System.out.println(que.peek());
        System.out.println(que.isEmpty());
        que.pop();
        System.out.println(que.peek());
        que.pop();
        System.out.println(que.peek());
        que.pop();
        System.out.println(que.peek());
        System.out.println(que.isEmpty());
        System.out.println(que);

    }


}
class DNode {
    public int data;
    public DNode next;
    public DNode pre;

    public DNode(int val) {
        data = val;
        next = null;
        pre = null;
    }
}

class Node {
    int val;
    Node next;

    Node(int val) {
        this.val = val;
    }
}

class QueueDemo {
    Node head;
    Node tail;
    QueueDemo() {
        head = new Node(-1);
        head.next = head;
        tail = head.next;
    }

    public boolean isEmpty() {
        return head == tail;
    }

    public void push(int val) {
        Node node = new Node(val);

        tail.next = node;
        node.next = head;
        tail = node;
    }

    public int peek() {
        if (!isEmpty()) {
            return head.next.val;
        }
        return -1;
    }

    public void pop() {
        if (!isEmpty()) {
            head.next = head.next.next;
            if (head.next == head) tail = head;
        }
    }

    public String toString() {
        if (isEmpty()) return "[]";
        StringBuilder sb = new StringBuilder();
        sb.append("[ ");
        Node p = head.next;
        while (p != head) {
            sb.append(p.val);
            sb.append(", ");
            p = p.next;
        }
        sb.deleteCharAt(sb.lastIndexOf(","));
        sb.append("]");
        return sb.toString();

    }
}

第2题

计算字符串表达式的值:里面有括号,加号,和乘号,就这三中操作符。
因为最近在看jvm,知道jvm是用操作数栈实现的,但是我没有细想,只想到了用中缀表达式来求解。但是这个平时没接触过,所以吭哧了半个小时也没搞定,总之一个小时两道题,我是一道也没做出来。说难吧,这也不难啊,但是短时间想完美解决,平时肯定是要做过这种题的。昨晚在床上想想想,最后给想通了,今天上午就给实现了。

  1. 即先将字符串表达式转换为一个中缀表达式二叉树,叶子结点是操作数,分支节点是操作符。
    这一步要计算每一个操作符的优先级,找优先级最低的,如果遇到左括号,则括号中的操作符优先级直接提升一个base(10),遇到右括号降一个base(10),这样括号内的操作符一定大于括号外的,操作符被包裹的括号层数越多,其优先级越高。
    找到优先级最低的作为根,然后将字符串表达式一分为二,继续递归建树,
    如果表达式中没有操作符了,说明这就是一个叶子结点,然后将其周围的括号扒掉,求其值;
  2. 然后计算表达式树的值。如果中缀表达式树已经建好,这一步就比较简单了,就是如果是叶子结点直接返回其值;
    如果是分支结点,递归求其左子树的值和右子树的值,然后看该分支的操作符是加还是乘,求对应的结果返回即可

代码:

public class StringToResultTest {
    //先递归建一个中缀表达式树
    public TreeNode toInTree(String string) {

        //找到树的根。根不能在括号里面,并且优先级最低
        int base = 0;//基础优先级,没遇到一个左括号,加10,遇到一个右括号-10
        int root_ind = -1;//记录优先级最低的操作符,将其作为根
        int root_p = Integer.MAX_VALUE;
        for (int i = 0; i < string.length(); i++) {
            char ch = string.charAt(i);
            int pror = 0;
            if (ch == '(') base += 10;
            if (ch == ')') base -= 10;
            if (ch == '+') {
                pror = base + 1;//计算该字符的优先级
                if (pror < root_p) {
                    root_ind = i;
                    root_p = pror;
                }
            }
            if (ch == '*') {
                pror = base + 2;//计算该字符的优先级
                if (pror < root_p) {
                    root_ind = i;
                    root_p = pror;
                }
            }
        }
        if (root_ind != -1) {//说明找到了一个根,其位置在root_ind,这是一个分支结点
            char op = string.charAt(root_ind);
            int val = op == '+' ? 0 : 1;//加是0,乘法是1
            TreeNode root = new TreeNode(val);
            root.left = toInTree(string.substring(0, root_ind));
            root.right = toInTree(string.substring(root_ind+1));
            return root;
        } else { //说明没有根了,也就是没有表达式,则将该字符串求值即可,这是一个叶子结点
            //剥除左括号
            int l = string.lastIndexOf('(');
            if (l != -1) {
                string = string.substring(l+1);
            }
            //剥除右括号
            int r = string.indexOf(')');
            if (r != -1) {
                string = string.substring(0, r);
            }
            return new TreeNode(Integer.valueOf(string));
        }
    }

    public int toResult(TreeNode root) {
        if (root != null) {
            if (root.left == null && root.right == null) {//叶子结点肯定是操作数
                return root.val;
            }else {
                int left_ = toResult(root.left);
                int right_ = toResult(root.right);
                if (root.val == 0) {
                    return left_ + right_;
                }else return left_ * right_;
            }
        }
        return 0;
    }

    public static void main(String[] args) {
        StringToResultTest re = new StringToResultTest();
        String string = "((5*2)+10)*(30+50*10+(2*5+10))";
        TreeNode root = re.toInTree(string);
        System.out.println(re.toResult(root));
    }
}

class TreeNode {
    int val;//记录值,如果是叶子结点,就代表该结点的值;如果是分支结点,就表示操作符,0:+;1:*
    TreeNode left;
    TreeNode right;

    TreeNode(int val) {
        this.val = val;
        left = null;
        right = null;
    }
}

上面的方法有一个缺点就是复杂度有点高,差的时候表达式树深接近n,每一次都要遍历一遍字符串求根,复杂度又是n,所以是n平方,好的时候也得nlogn。

当然了还有一种是使用双栈,一个操作数栈,一个操作符栈。双栈的做法只需要遍历一遍字符串,复杂度仅为n。
思路:

  1. 遇到一个操作数就进操作数栈;
  2. 遇到一个(,就直接进操作符栈;
  3. 遇到一个),就开始依次弹出操作符栈中的操作符,直到遇到第一个(并将其弹出为止;
  4. 遇到一个+,则开始依次弹出操作符栈中的操作符,直到遇到第一个(为止,然后将+进操作符栈;
  5. 遇到一个×,则开始依次弹出操作符栈中的操作符,直到遇到一个+(为止(或者这么说吧,因为栈中也就它们三个,如果栈顶也是乘号,就弹出直到栈顶不是乘号为止),然后将×入操作符栈。
  6. 每一次弹出一个操作符,都取出操作数栈顶的两个数,进行计算,并保存到操作数栈顶中;
  7. 最后操作符栈为空,操作数栈仅剩一个数,这个数就是最后的结果。

代码:

public int toResult(String expression) {
    Stack<Integer> temp = new Stack<>();//保存中间结果的操作数栈

     //操作符栈,其中存的是操作符的优先级。
     //0表示右括号
     //1表示加号
     //2表示乘号
     //3表示左括号
     Stack<Integer> oper = new Stack<>();

     //保存对应的优先级
     Map<Character, Integer> priority = new HashMap<>();
     priority.put(')', 0);
     priority.put('+', 1);
     priority.put('*', 2);
     priority.put('(', 3);

     boolean flag = true;//表明开始了一个新的数字子串,因为我想实现不仅仅只能支持个位数的计算
     int start = 0;
     for (int i = 0; i < expression.length(); i++) {
         char ch = expression.charAt(i);
         if (priority.containsKey(ch)) {//操作符
             if (!flag) {//说明前面的子串是操作数
                 flag = true;//意味着可能出现新的操作数
                 temp.push(Integer.valueOf(expression.substring(start, i)));
                 System.out.println(Integer.valueOf(expression.substring(start, i)));
             }

             int p = priority.get(ch);
             if (p == 0) {//优先级最低,它会一直让操作符栈里的操作符出栈直到遇到左括号为止
                 while (oper.peek() != 3) {
                     int op = oper.pop();
                     int a = temp.pop();
                     int b = temp.pop();
                     int resl = op == 1 ? a + b : a * b;
                     temp.push(resl);
                 }
                 oper.pop();
             }else if (p == 1) {//加号
                 while (!oper.empty() && oper.peek() != 3) {
                     int op = oper.pop();
                     int a = temp.pop();
                     int b = temp.pop();
                     int resl = op == 1 ? a + b : a * b;
                     temp.push(resl);
                 }
                 oper.push(1);
             }else if (p == 2) {//乘号
                 while (!oper.empty() && oper.peek() == 2) {//只有当栈顶同样是乘号时才出栈计算
                     oper.pop();
                     int a = temp.pop();
                     int b = temp.pop();
                     int resl = a * b;
                     temp.push(resl);
                 }
                 oper.push(2);
             }else {//左括号直接进栈
                 oper.push(3);
             }
         }else {//出现操作数,我们需要判断操作数的位数,将其截取出来
             if (flag) {//开始出现新的操作数
                 flag = false;
                 start = i;//记录操作数的起点
             }
         }

     }
     if (!flag) {//说明还有最后一个操作数
         temp.push(Integer.valueOf(expression.substring(start)));
     }
     while (!oper.empty()) {
         int op = oper.pop();
         int a = temp.pop();
         int b = temp.pop();
         int resl = op == 1 ? a + b : a * b;
         temp.push(resl);
     }

     return temp.pop();
}

第3题

刚刚我同学面的,45分钟一道题。给定一个字符串表示的一元一次方程,方程只有一个未知量x,方程里面没有括号,仅仅有加号和减号。
我刚做了一下,还行,搞出来了。
例如方程:5x-3+5-4x=4-6x-8+3x
思路:

  1. 首先肯定是合并同类项了。我将一个带符号的数字或者x项看成是一个完整的单元,则上式可以分为5x, -3, +5, -4x, =4, -6x, -8, +3x这些单元,可以看到,单元(除了最后一个)的结尾都是以一个加减等号结束的;
  2. 因此我先确定了单元的结尾,然后逆序往前找单元的开头,发现单元(除了第一个)的开头也是一个加减等号;
  3. 当确定了单元之后,就根据单元是x项或是常数项来进行合并同类项的操作。
  4. 在进行系数或常数项值的解析的时候,要注意有一个项是以=开头的。然后如果是x或者-x这种“没有系数”的也要注意一下;
  5. 还有就是最后一项要特别对待。
    代码:
/**
 * 解一个只含有加减和x的方程
 * @param func 方程的表达式
 * @return 解方程的结果
 */
public double solveFunc(String func) {
    int count_x = 0;//x的系数
    int num = 0;//方程等号右边的数,方程结果为 x * count_x = num;

    Set<Character> oper = new HashSet<>();
    oper.add('-');
    oper.add('+');
    oper.add('=');

    boolean isEquals = false;//标记等号是否出现

    for (int i = 0; i < func.length(); i++) {
        char ch = func.charAt(i);
        //因为发现每一个单元(除了最后一个)都是以加减等分割的,所以先求出分割符,然后逆方向找到单元的开头
        //这样就确定了一个单元,然后再对单元的具体情况进行分析
        if (oper.contains(ch) || i == func.length()-1) {//出现了分割符,或者到了最后一个单元
            int end = i == func.length()-1 ? i+1 : i;//最后一个单元要特殊一点,假定以length长度为结尾
            int start = end - 1;

            //找到单元的开头。单元的开头要么是三种符号种的一个,要么就是第一个单元,可能没有符号
            while (start > -1 && !oper.contains(func.charAt(start))) start--;
            if (start == -1) start = 0;
            String ele = func.substring(start, end);
            System.out.println(ele);

            //找到单元后开始解析单元
            //单元是x表达式
            if (ele.endsWith("x")) {
                int val = 0;
                if (ele.charAt(0) == '=') {
                    String sub = ele.substring(1, ele.length()-1);
                    //解析系数值
                    if (sub.equals("")) val = 1;
                    else if (sub.equals("+")) val = 1;
                    else if (sub.equals("-")) val = -1;
                    else val = Integer.valueOf(sub);
                }else {
                    String sub = ele.substring(0, ele.length()-1);
                    //解析系数值
                    if (sub.equals("")) val = 1;
                    else if (sub.equals("+")) val = 1;
                    else if (sub.equals("-")) val = -1;
                    else val = Integer.valueOf(sub);
                }
                System.out.println(val);
                if (isEquals) count_x -= val;
                else count_x += val;
                System.out.println(count_x);

            }else {
                //单元也可能是数字表达式
                int val = 0;
                if (ele.charAt(0) == '=') {
                    //解析系数值
                    val = Integer.valueOf(ele.substring(1));
                }else {
                    val = Integer.valueOf(ele);
                }

                System.out.println(val);
                if (isEquals) num += val;
                else num -= val;
                System.out.println(num);
            }
            if (ch == '=') isEquals = true;//出现了等号,等号右边的则需要变号
            System.out.println("是否出现了等号: " + isEquals);
        }
    }

    System.out.println(count_x + "x = " + num);
    if (count_x == 0) return Double.MAX_VALUE;
    else return (double) num / count_x;

}

总结

总的来说,腾讯出的题确实有水平,第一题是考察基本功的,正常情况下,都应该能做出来;
第2题也不是难的让你没有思路(不像力扣上面的难题,没有思路不看答案压根就想不出来也做不出来),这道题就是那种有了思路觉得简单但是越想细节越多,再加上时间紧张,就容易考虑步骤出错的那种。如果平时做过这种题,可能会心理上轻松一些,做起来更顺手。
字符串,数组,链表,以及栈队列,还有二叉树算是最容易考的了。反而比较令人望而生畏的图,考的较少。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值