作者: CheungQ

MutationObserver网页元素变更监视

MutationObserver接口提供了监视对DOM树所做更改的能力。它被设计为旧的Mutation Events功能的替代品,该功能是DOM3 Events规范的一部分。

构造函数:MutationObserver()

方法:

  • disconnect():解除监视
  • observe():监视某个dom元素的变更
  • takeRecords():从MutationObserver的通知队列中删除所有待处理的通知

代码:

let MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver;
let observer = new MutationObserver(function(mutations) {
    // console.log(mutations)
    mutations.forEach(function(record) {
        if(record.attributeName === "value" ){
            console.log(record.target.getAttribute("id"))
            console.log(record.oldValue)
            console.log(record.target.value)
        }
    });
});
let observerOptions = { attributes: true, childList: true, characterData: true, attributeOldValue :true, attributeFilter:["value"] }
observer.observe(document.getElementById("xxxxx"), observerOptions);

observerOptions中可以配置监听的变更内容。

LeetCode刷题【413】等差数列划分

如果一个数列 至少有三个元素 ,并且任意两个相邻元素之差相同,则称该数列为等差数列。

  • 例如,[1,3,5,7,9][7,7,7,7][3,-1,-5,-9] 都是等差数列。

给你一个整数数组 nums ,返回数组 nums 中所有为等差数组的 子数组 个数。

子数组 是数组中的一个连续序列。

 

示例 1:

输入:nums = [1,2,3,4]
输出:3
解释:nums 中有三个子等差数组:[1, 2, 3]、[2, 3, 4] 和 [1,2,3,4] 自身。

示例 2:

输入:nums = [1]
输出:0

 

提示:

  • 1 <= nums.length <= 5000
  • -1000 <= nums[i] <= 1000
Related Topics
  • 数组
  • 动态规划
  • \n
  • 👍 286
  • 👎 0
  • 题解

    8月10日每日一题

    这题比较简单,题目中已经说明了,给出的int[] nums必定是符合条件的分段等差数列,所以,我们其实要做的就是找出各个分段之间的界限,分别求出各个分段的子序列数量并求和。

    那么。如果想要在一次遍历中完成这样的操作,我们需要记录几个关键的信息

    1. 当前位置和上一个位置的数字的差值(i位置和i-1位置的差值)
    2. 上一个位置和上上一个的差值(i-1和i-2位置)
    3. 如果这两个值相等则表示当前数字仍然在同一个等差区间之中,如果不等,则表示已经进入了一个新的等差区间
    4. 还有一个重要的信息,就是当前等差区间的开始位置

    接下来分情况讨论。

    • 如果不等于:表明当前位置是处于两个不同等差分段相接的部分。需要更新差值信息和分段起始位置。

    按照逻辑来说,当前位置应当是分段起始位置。比如[1,2,3,8,9,10]的数组,当我们到达数字8的索引3的时候,知道了起始位置为3。8和前面的值3的差值为5,而当我们到达数字9的时候,9和8的差值为1,是不是应该重新记录分段开始位置呢?显然是错误的,本题中要求子序列最短长度为3,所以只有到达9的时候我们才会知道分段起始位置应该为8所在的位置。

    如果9后面的值不是10,那么分段起始位置会重新刷新,如果是10,那么满足了上面的i – (i-1) 等于 (i-1)-(i-2)位置的值,则恰好分段开始位置为8所在的位置。

    • 如果是值等于的情况

    本来写了长度判断,但是再看了下,能进入这个条件的,必定是长度至少为3的等差分段区间。然后求出当前区间的子序列个数最终求和。

    我们来看下,设等差区间的长度为k

    当k=3的时候,子序列个数为1

    当k=4的时候,子序列个数为1(自身4个)+2(长度为3的2个子序列)

    当k=5的时候,子序列个数为1(自身5个)+2(长度为4的2个子序列)+3(长度为3的3个子序列)

    当k=6的时候,子序列个数为1(自身6个)+2(长度为5的2个子序列)+3(长度为4的3个子序列)+4(长度为3的4个子序列)

    所以,其实很明白了,就是从1开始到指定数字的求和公式n(n+1)/2

    但是这边的合是一次性的,而我们的计算是遍历中求的,不可能当我们还在i点的时候就知道有当前等差分段区间终止位置为i+i’位置,(当然也可以选择在找到3个连续的等差数字的之后往后探测找到结尾位置)。所以我们在遍历中需要在n位置得到总数后,再减去前面一次多加了的,即(n(n+1)/2)-(n(n-1)/2)= n。

    这样就很简单明了了,当差区间的长度k=3的时候只需总数+1,k=4的时候只需总数再加2,k=5的之后总数再加3,以此类推。

    代码:

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int numberOfArithmeticSlices(int[] nums) {
            if (nums.length<3){
                return 0;
            }
            int dis = 0;
            int sum = 0;
            int lastDis = nums[1]-nums[0];
            int disIndexStart = 0;
            for (int i = 2; i < nums.length; i++) {
                dis = nums[i]-nums[i-1];
                if (dis != lastDis){
                    lastDis = dis;
                    disIndexStart = i-1;
                    continue;
                }else{
                    //if (i-disIndexStart >= 2){
                        //超过三个了
                        sum+=i-disIndexStart-1;
                    //}
                }
            }
            return sum;
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)
    解答成功:
    执行耗时:0 ms,击败了100.00% 的Java用户
    内存消耗:36 MB,击败了87.74% 的Java用户

    LeetCode刷题【面试题04.06】 后继者

    题目:力扣挂了。。蛋疼

    题目内容后续再补上Html版的

    设计一个算法,找出二叉搜索树中指定节点的“下一个”节点(也即中序后继)。 
    如果指定节点没有对应的“下一个”节点,则返回null。 
    示例 1: 
    输入: root = [2,1,3], p = 1
    
      2
     / \
    1   3
    
    输出: 2
    
     示例 2:
    
     输入: root = [5,3,6,2,4,null,null,1], p = 6
    
          5
         / \
        3   6
       / \
      2   4
     /
    1
    
    输出: null
     Related Topics 树 深度优先搜索 二叉搜索树 二叉树
     👍 71 👎 0

    题解

    按照题意,找的就是二叉树按照中序遍历,排在目标节点后面一位的数字,这个节点可能是目标节点的右子节点,也还有可能是目标节点的父节点(当目标节点作为父节点的左子节点,且没有右子节点的时候)。

    所以按照题意,直接DFS写个中序遍历,找到目标节点后,标记下已经找到目标节点,而在遍历下一个节点之前判断下,是否已经找到目标节点,如果已经找到了目标节点,则此时需要遍历的节点便是后继者节点。

    class Solution {
    
        private TreeNode current;
        private TreeNode follower;
    
        public TreeNode inorderSuccessor(TreeNode root, TreeNode p) {
            midFetch(root,p);
            return follower;
        }
    
        private void midFetch(TreeNode root , TreeNode p){
            if (root==null)return;
            if (follower!=null){
                return ;
            }
            midFetch(root.left,p);
            if (follower!=null){
                return ;
            }
            if (current!=null && current.val == p.val ){
                follower = root;
                return ;
            }
            current = root;
            midFetch(root.right,p);
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    题中左子节点遍历完成之后需要判断下follower!=null,这里一开始没注意,踩了个坑,在官方给出的测试用例中

    测试用例:[5,3,6,2,4,null,null,1]
    1

    如果没有此处的判断,则会跑出错误的答案。按照规则,应当是每遍历一个节点之后都需要判断。

    所以左子节点遍历后需要判断一下,root节点遍历的时候需要判断一下,右子节点遍历的时候也需要判断一下,但是因为右子节点已经在方法结尾,所以省去判断。

    LeetCode刷题【190】 颠倒二进制位

    颠倒给定的 32 位无符号整数的二进制位。

     

    提示:

    • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
    • 在 Java 中,编译器使用二进制补码记法来表示有符号整数。因此,在上面的 示例 2 中,输入表示有符号整数 -3,输出表示有符号整数 -1073741825

     

    进阶:
    如果多次调用这个函数,你将如何优化你的算法?

     

    示例 1:

    输入: 00000010100101000001111010011100
    输出: 00111001011110000010100101000000
    解释: 输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596     因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000

    示例 2:

    输入:11111111111111111111111111111101
    输出:10111111111111111111111111111111
    解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
         因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。

    示例 1:

    输入:n = 00000010100101000001111010011100
    输出:964176192 (00111001011110000010100101000000)
    解释:输入的二进制串 00000010100101000001111010011100 表示无符号整数 43261596 因此返回 964176192,其二进制表示形式为 00111001011110000010100101000000

    示例 2:

    输入:n = 11111111111111111111111111111101
    输出:3221225471 (10111111111111111111111111111111)
    解释:输入的二进制串 11111111111111111111111111111101 表示无符号整数 4294967293,
         因此返回 3221225471 其二进制表示形式为 10111111111111111111111111111111 。

     

    提示:

    • 输入是一个长度为 32 的二进制字符串
    Related Topics
  • 位运算
  • 分治
  • \n
  • 👍 420
  • 👎 0
  • 题解

    头皮发麻。需要重新复习下二进制运算的相关知识

    与&:0&0=0 0&1=0 1&0=0 1&1=1

    或|:0|0=0 0|1=1 1|0=1 1|1=1

    异或^:0^0=0 0^1=1 1^0=1 1^1=0

    取反~:~1=0 ~0=1

    左移<<:左边的二进制位丢弃,右边补0

    右移>>:正数左补0,负数左补1,右边丢弃

    无符号左移<<<:左边的二进制位丢弃,右边补0

    无符号右移>>>:忽略符号位,空位都以0补齐

    按照题意,我们需要把原本32位二进制数的第0位移动到第31位,第1位移动到第30位,第2位移动到第29位,依次处理

    public int reverseBits(int n) {
            int rev = 0;
            for (int i = 0; i < 32; i++) {
                //tmp = n 与上 1
                //实际操作为
                // 00000000000000000000001010101110
                // 与上
                // 00000000000000000000000000000001
                // 的操作
                //结果为 n的最末尾数, 因为与后面的数字  前面都为0,最末尾为1,
                //根据与运算的规则 结果为
                // 00000000000000000000000000000001
                // 或者
                // 00000000000000000000000000000000
                int tmp = n&1;
                //把上面的结果tmp左移到左边对应的(31-i)位置
                //结果为
                // 10000000000000000000000000000000
                // 或者
                // 00000000000000000000000000000000
                tmp = tmp << (31-i);
                //初始的rev为
                // 00000000000000000000000000000000
                // 和tmp进行或运算 ,就可以将原来的末位二进制数移动到rev顺序的对应位置
                rev = rev|tmp;
                // 再把n往右无符号右移一位,去除掉了刚刚运算过的末位数
                n = n>>>1;
            }
    
            return rev;
        }

    emm还有一个,可以看下java.lang.Integer#reverse的方法

    /**
         * Returns the value obtained by reversing the order of the bits in the
         * two's complement binary representation of the specified {@code int}
         * value.
         *
         * @param i the value to be reversed
         * @return the value obtained by reversing order of the bits in the
         *     specified {@code int} value.
         * @since 1.5
         */
        public static int reverse(int i) {
            // HD, Figure 7-1
            i = (i & 0x55555555) << 1 | (i >>> 1) & 0x55555555;
            i = (i & 0x33333333) << 2 | (i >>> 2) & 0x33333333;
            i = (i & 0x0f0f0f0f) << 4 | (i >>> 4) & 0x0f0f0f0f;
            i = (i << 24) | ((i & 0xff00) << 8) |
                ((i >>> 8) & 0xff00) | (i >>> 24);
            return i;
        }

    LeetCode刷题【313】 超级丑数

    超级丑数 是一个正整数,并满足其所有质因数都出现在质数数组 primes 中。

    给你一个整数 n 和一个整数数组 primes ,返回第 n超级丑数

    题目数据保证第 n超级丑数32-bit 带符号整数范围内。

     

    示例 1:

    输入:n = 12, primes = [2,7,13,19]
    输出:32 
    解释:给定长度为 4 的质数数组 primes = [2,7,13,19],前 12 个超级丑数序列为:[1,2,4,7,8,13,14,16,19,26,28,32] 。

    示例 2:

    输入:n = 1, primes = [2,3,5]
    输出:1
    解释:1 不含质因数,因此它的所有质因数都在质数数组 primes = [2,3,5] 中。
    
     

    提示:

    • 1 <= n <= 106
    • 1 <= primes.length <= 100
    • 2 <= primes[i] <= 1000
    • 题目数据 保证 primes[i] 是一个质数
    • primes 中的所有值都 互不相同 ,且按 递增顺序 排列
    Related Topics
  • 数组
  • 哈希表
  • 数学
  • 动态规划
  • 堆(优先队列)
  • \n
  • 👍 193
  • 👎 0
  • 题解

    8月9日每日一题,解法思路同前面的264题LeetCode刷题【264】 丑数 II,考查多指针的用法。原本的264题中是给定了固定的3个丑数2,3,5,本题中的丑数范围为输入的数组范围,那么同样的解法,每个数字维护一个自己对应的指针位置。

    如果说一上来就做这题感觉理不清的话,可以试着先去做下LeetCode刷题【264】 丑数 II题,再回来看这题就非常明朗了

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int nthSuperUglyNumber(int n, int[] primes) {
            int[] point = new int[primes.length];
            int[] res = new int[n];
            res[0] = 1;
            int k = 0;
            while (k<n-1){
                k++;
                int num = Integer.MAX_VALUE;
                for (int i = 0;i < primes.length;i++) {
                    num = Math.min(num,primes[i] * res[point[i]]);
                }
                for (int i = 0;i < primes.length;i++) {
                    if (primes[i] * res[point[i]] == num) {
                        point[i]++;
                    }
                }
                res[k] = num;
            }
            return res[k];
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    LeetCode刷题【264】 丑数 II

    给你一个整数 n ,请你找出并返回第 n丑数

    丑数 就是只包含质因数 23 和/或 5 的正整数。

     

    示例 1:

    输入:n = 10
    输出:12
    解释:[1, 2, 3, 4, 5, 6, 8, 9, 10, 12] 是由前 10 个丑数组成的序列。
    

    示例 2:

    输入:n = 1
    输出:1
    解释:1 通常被视为丑数。
    

     

    提示:

    • 1 <= n <= 1690
    Related Topics
  • 哈希表
  • 数学
  • 动态规划
  • 堆(优先队列)
  • \n
  • 👍 710
  • 👎 0
  • 题解

    先说明一个概念

    每个合数都可以写成几个质数(也可称为素数)相乘的形式 ,这几个质数就都叫做这个合数的质因数。如果一个质数是某个数的因数,那么就说这个质数是这个数的质因数;而这个因数一定是一个质数。(来自于百度百科)

    举例

    • 1没有质因子。
    • 5只有1个质因子,5本身。(5是质数)
    • 6的质因子是2和3。(6 = 2 × 3)
    • 2、4、8、16等只有1个质因子:2。(2是质数,4 =2²,8 = 2³,如此类推)
    • 10有2个质因子:2和5。(10 = 2 × 5)

    所以我们知道了,所求数字即为,给定的2、3、5相互组合相乘得到的结果,可以重复相乘,即可以使2x2x2x3x3x5x5x5这样的组合。

    而题中所求的第n位即是将所得结果按照从小到大的顺序有序排列的第n位数字,且根据质因数的概念,第一位一定是数字1。

    那么,我们可以按照题意按部就班的来求解,结果集的第一位是1,那么结果集此时是【1】

    来到第二位。可以是前面的结果集第一位的1×2,也可以是1×3,也可以是1×5,但是按照从小到大的顺序,应该是1×2,此时结果集为【1,2】

    来到第三位,因为前面的1×2已经算过,可以直接忽略,那么我们要比较的就是2×2(此处的2是前面得到1×2的结果的2,不是自然数递增的2),和上次剩下的1×3,1×5,显然结果应该是3,此时的结果集【1,2,3】

    来到第四位需要比较的是2×2,2×3,1×5,显然应该是4,此时的结果集为【1,2,3,4】

    后面依次:

    第五位 3×2,2×3,1×5,结果集【1,2,3,4,5

    第六位 3×2,2×3,2×5,结果集是【1,2,3,4,5,6】,而此时的3×2和2×3都能得到6这个结果,显然是重复的,应该只记一次

    第七位 4×2,3×3,2×5,结果集是【1,2,3,4,5,6,8

    第八位 5×2,3×3,2×5,结果集【1,2,3,4,5,6,8,9

    第九位 5×2,4×3,2×5,结果集【1,2,3,4,5,6,8,9,10

    所以自己手动算了这么多步骤,规律应该可以看出来了,数字2,3,5分别在结果集中维护了一个指针,一开始都指向了索引0。结果集中第一位索引0上的值为1。

    然后每次迭代找出2,3,5与索引位置上的值相乘的乘积中最小的一个,将这个结果放入结果集中的下一位,并将当前对应数字的指针位置沿着索引往后移动一位。如果有多个数与索引位置的乘积相等都为最小值,那么这些数字的指针都往后移动一位。

    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int nthUglyNumber(int n) {
            int[] res = new int[n];
            res[0] = 1;
            int p2 = 0, p3 = 0, p5 =0;
            int k = 1;
            while (k < n){
                int num = Math.min(Math.min(res[p2]*2,res[p3]*3),res[p5]*5);
                if (res[p2]*2==num){
                    p2++;
                }
                if (res[p3]*3==num){
                    p3++;
                }
                if (res[p5]*5==num){
                    p5++;
                }
                res[k] = num;
                k++;
            }
            return res[k-1];
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)

    当前题目中固定了丑数范围为2,3,5,更多的非固定丑数范围的题目详见LeetCode刷题【313】 超级丑数

    新更新内容:

    重新做了LeetCode刷题【263】丑数,并再经过了前一段时间动态规划相关题目的练习之后,得到新的理解与启示

    class Solution {
        public int nthUglyNumber(int n) {
            int[] dp = new int[n];
            dp[0] = 1;
            int[] numArr = new int[]{2,3,5};
            int[] pArr = new int[3];
            for (int i = 1; i < n; i++) {
                dp[i] = Math.min(Math.min(dp[pArr[0]]*numArr[0],dp[pArr[1]]*numArr[1]),dp[pArr[2]]*numArr[2]);
                for (int p = 0; p < pArr.length; p++) {
                    if (dp[i] == dp[pArr[p]]*numArr[p]){
                        pArr[p] = pArr[p]+1;
                    }
                }
            }
            return dp[n-1];
        }
    }

    代码可能大同小异,不过在理解上,

    以一个现有的丑数序列为例[1,2,3,4,5,6,8,9,10,12],一开始其实只有一个初始的索引0位置的1。根据丑数的特性,我们知道丑数F(n)一定能由之前的某个丑数乘以2或者乘以3或者乘以5得到,那么究竟取哪个呢,因为要保持递增性,所以应该是较小的。

    而有了这个理解之后,在去看LeetCode刷题【313】 超级丑数的代码其实很容易从上面的新的代码能修改得到

    LeetCode刷题【847】访问所有节点的最短路径

    存在一个由 n 个节点组成的无向连通图,图中的节点按从 0n - 1 编号。

    给你一个数组 graph 表示这个图。其中,graph[i] 是一个列表,由所有与节点 i 直接相连的节点组成。

    返回能够访问所有节点的最短路径的长度。你可以在任一节点开始和停止,也可以多次重访节点,并且可以重用边。

     

    示例 1:

    输入:graph = [[1,2,3],[0],[0],[0]]
    输出:4
    解释:一种可能的路径为 [1,0,2,0,3]

    示例 2:

    输入:graph = [[1],[0,2,4],[1,3,4],[2],[1,2]]
    输出:4
    解释:一种可能的路径为 [0,1,4,2,3]
    

     

    提示:

    • n == graph.length
    • 1 <= n <= 12
    • 0 <= graph[i].length < n
    • graph[i] 不包含 i
    • 如果 graph[a] 包含 b ,那么 graph[b] 也包含 a
    • 输入的图总是连通图
    Related Topics
  • 位运算
  • 广度优先搜索
  • 动态规划
  • 状态压缩
  • \n
  • 👍 190
  • 👎 0
  • 题解

    
    import java.util.LinkedList;
    import java.util.Queue;
    
    //leetcode submit region begin(Prohibit modification and deletion)
    class Solution {
        public int shortestPathLength(int[][] graph) {
            int length = graph.length;
    
            Queue<int[]> queue = new LinkedList<>();
            boolean[][] visited = new boolean[length][1<<length];
            for (int i = 0; i < length; i++) {
                //  出发点,经过状态(状态压缩),步数
                queue.offer(new int[]{i,1<<i,0});
                visited[i][1<<i] = true;
            }
            while (!queue.isEmpty()){
                int[] current = queue.poll();
                int index = current[0];
                int status = current[1];
                int steps = current[2];
    
                if (status == (1 << length)-1){
                    //所有点都被访问过了
                    return steps;
                }
                for (int nextIndex : graph[index]) {
                    int nextIndexStatus = status | (1 << nextIndex);
                    if (!visited[nextIndex][nextIndexStatus]) {
                        queue.offer(new int[]{nextIndex, nextIndexStatus, steps + 1});
                        visited[nextIndex][nextIndexStatus] = true;
                    }
    
                }
    
    
    
            }
    
            return 0;
    
    
        }
    }
    //leetcode submit region end(Prohibit modification and deletion)