LeetCode690,137(只出现一次数字合集),剑指20,有限状态自动机

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode690,137(只出现一次数字合集),剑指20,有限状态自动机相关的知识,希望对你有一定的参考价值。

LeetCode 690 员工的重要性

2021.5.1的每日一题,简单题,思路也比较简单,就是将员工存储起来,然后按照要求查询就行了,因为下属可能还有下属,所以需要递归或者迭代,自己写的代码

class Solution {
    public int getImportance(List<Employee> employees, int id) {
        //先遍历列表存储每个员工的编号和对应的下标,然后查询
        Map<Integer, Integer> map = new HashMap<>();
        for(int i = 0; i < employees.size(); i++){
            map.put(employees.get(i).id, i);
        }
        int res = 0;
        Employee leadall = employees.get(map.get(id));
        //当下属不为空
        Deque<Employee> stack = new LinkedList<>();
        stack.push(leadall);
        while(!stack.isEmpty()){
            Employee lead = stack.pop();
            List<Integer> sub = lead.subordinates;
            res += lead.importance;
            for(int i = 0; i < sub.size(); i++){
                //sub中找下属的id,然后在map中找id的下标,在employees中找下标对应的下属
                Employee temp = employees.get(map.get(sub.get(i)));
                stack.push(temp);
            }
        }
        return res;
    }
}

然后看了解答,发现map中存id和员工更简单;另外,我这里是用的栈,因为我写的时候没有想到什么深度广度,只是觉得栈可以实现。而一般广度有限遍历是用队列,这里没有区别,不过以后还是要注意

LeetCode 137(只出现一次数字合集)

昨天的每日一题,我能想到的只有统计个数,主要学习二进制写法和有限状态自动机,二进制代码如下:

class Solution {
    public int singleNumber(int[] nums) {
        //出现两次异或就行了,三次咋整呢,我想到的只有统计个数
        //看了二进制,直呼好家伙
        //统计每一个数的32个二进制位上的个数,因为非答案数有三个,所以说三个加起来肯定是0或者3
        //而答案数,是1或者0,那么统计所有数的第一位1的个数,然后余3,得到的数就是答案该位是否为1
        int res = 0;
        for(int i = 0; i < 32; i++){
            int total = 0;
            for(int num : nums){
                total += ((num >> i) & 1);
            }
            res = ((total % 3) << i) | res;
        }
        return res;
    }
}

另外就是有限状态自动机了,官解里面叫数字电路的设计
这个也是按位处理,按位统计每个数字该位上的数字,只不过使用状态转移的方法进行统计的,因为每个数字出现的个数是3次,所以3个状态00 01 02,然后每出现一次就进行状态的转移,而只出现了一次的数字就会让该二进制位是1,这样就可以找出这个数字了。很巧妙,感觉一般想不出来
关键问题就是如何写状态转移的代码,昨天在这里卡了很久,之前也学过数电,而且好像还学的不错,但是现在也真是忘了。最后去看了一篇文章,大概了解了一下如何用真值表写出表达式
先列出真值表,这一步应该不难,然后根据真值表中最后结果为1的状态,将前面的状态一个个用表达式写出来,然后相或,也就是+,就可以了。当然也可以根据结果为0的状态写
另外这里有个地方也要注意,就是因为是高位和低位两个状态都要转移,所以同时转移和先转移低位再转移高位的表达式是不同的。同时转移的时候要用临时变量存储
最后输出的时候,高位是0,因此只输出低位就可以

class Solution {
    public int singleNumber(int[] nums) {
        //看了半天,终于差不多能看懂个状态机的大概了,什么个意思呢
        //就是对于每一个数,也是按位处理,就和刚刚的二进制是一样的
        //但是吧,这里有变化的地方就是统计每位的个数的方法
        //状态机使用三个数00 01 02 来统计每一位的个数,因为二进制数02使用10表示的,所以用两个数ab分别代表高位和低位
        //三个状态循环转变,00->01->10->00
        //那么如何转变呢,这就可以写出一个真值表,然后总结出转变的公式
        //这样,32位就可以同时进行计算了
        //主要还是推转变公式
        //对于低位来说,当高位为1,碰到0或者1都是0;高位为0,碰到1,变1,碰到0不变
        //写成公式就是b = b ^ x & ~a (当a等于1时,就是0,;当a等于0时,看左边一项)
        //再看高位,此时低位已经发生了变化,因此要在低位变化的基础上计算高位,这个想不通了
        //此时如果b是1,那么之前b可能是0或者1,不管x是0还是1,此时a不变,都是0;如果b此时是0,那么之前a可能是0或者1,
        //如果x是0,那么b就是00变00或者10变10,那么a就是0或者1,如果x是1,那么b就是01变10,那么a就是1


        //这个是同时变化的,用临时变量存储
        //主要是列出真值表,然后根据结果为1的情况写出表达式
        int a = 0, b = 0;
        for (int num : nums) {
            int aNext = (~a & b & num) | (a & ~b & ~num), bNext = ~a & (b ^ num);
            a = aNext;
            b = bNext;
        }
        return b;

		//这个理解过程是先计算低位,再根据变化后的低位计算高位
        int a = 0, b = 0;
        for(int num : nums){
            a = a ^ num & ~b;
            b = b ^ num & ~a;
        }
        return a;
    }
}


剑指Offer20 表示数值的字符串

下午正好看到这个题了,发现又是有限状态自动机,巧了
这个题状态其实好想多了,就是按照题目的描述,把每一步都定义一个状态,然后根据组成一个数的规则,进行状态的转移。遍历所给的字符串,根据定义的规则进行状态转移,如果不能转移或者到最后不是一个数值的结尾,那么就说明不能表示数值,false
这里直接复制了Krahets,K神的代码,学习一下,加了点注释

class Solution {
    //很有意思的一道题目,今天上午刚用了状态机,就看到这道题目了,说白了,就是定义状态,然后根据遍历到的字符进行状态的转移
    public boolean isNumber(String s) {
        Map[] states = {
            new HashMap<>() {{ put(' ', 0); put('s', 1); put('d', 2); put('.', 4); }}, // 0. 表示空格后面可以是空格,+-,数字,小数点
            new HashMap<>() {{ put('d', 2); put('.', 4); }},                           // 1. 表示+-后面可以跟数字和小数点
            new HashMap<>() {{ put('d', 2); put('.', 3); put('e', 5); put(' ', 8); }}, // 2. 小数点前的数字
            new HashMap<>() {{ put('d', 3); put('e', 5); put(' ', 8); }},              // 3. 小数点
            new HashMap<>() {{ put('d', 3); }},                                        // 4. 空格直接跟小数点和小数点后的数字
            new HashMap<>() {{ put('s', 6); put('d', 7); }},                           // 5. e
            new HashMap<>() {{ put('d', 7); }},                                        // 6. e后面的+-号
            new HashMap<>() {{ put('d', 7); put(' ', 8); }},                           // 7. 整数数字
            new HashMap<>() {{ put(' ', 8); }}                                         // 8. 最后的空格
        };
        //表示第几个状态,刚开始定义为空格
        int p = 0;
        char t;
        for(char c : s.toCharArray()) {
            //遍历字符,用t表示当前状态是什么,d是数字,s是+-符号,e是阶次
            if(c >= '0' && c <= '9') t = 'd';
            else if(c == '+' || c == '-') t = 's';
            else if(c == 'e' || c == 'E') t = 'e';
            else if(c == '.' || c == ' ') t = c;
            else t = '?';
            //如果当前所在的状态中有这个字符,那么就进行转移,没有就返回false
            if(!states[p].containsKey(t)) return false;
            p = (int)states[p].get(t);
        }
        //最终以这几个状态结尾的就是一个可以表示数值的字符串
        return p == 2 || p == 3 || p == 7 || p == 8;
    }
}


另外,还有两种思路,就是剑指Offer提供的思路和逐一判断的思路,其实剑指Offer也相当于逐一判断,这里就不贴了

以上是关于LeetCode690,137(只出现一次数字合集),剑指20,有限状态自动机的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode 137.只出现一次的数字 II

LeetCode 137.只出现一次的数字 II

[LeetCode] 137. 只出现一次的数字 II

LeetCode 137. 只出现一次的数字 II

LeetCode 137 Single Number II(只出现一次的数字 II)(*)

LeetCode137只出现一次的数字——位运算