月度归档: 2022年6月

LeetCode刷题【1409】查询带键的排列

给你一个待查数组 queries ,数组中的元素为 1m 之间的正整数。 请你根据以下规则处理所有待查项 queries[i](从 i=0i=queries.length-1):

  • 一开始,排列 P=[1,2,3,...,m]
  • 对于当前的 i ,请你找出待查项 queries[i] 在排列 P 中的位置(下标从 0 开始),然后将其从原位置移动到排列 P 的起始位置(即下标为 0 处)。注意, queries[i]P 中的位置就是 queries[i] 的查询结果。

请你以数组形式返回待查数组  queries 的查询结果。

 

示例 1:

输入:queries = [3,1,2,1], m = 5
输出:[2,1,2,1] 
解释:待查数组 queries 处理如下:
对于 i=0: queries[i]=3, P=[1,2,3,4,5], 3 在 P 中的位置是 2,接着我们把 3 移动到 P 的起始位置,得到 P=[3,1,2,4,5] 。
对于 i=1: queries[i]=1, P=[3,1,2,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,3,2,4,5] 。 
对于 i=2: queries[i]=2, P=[1,3,2,4,5], 2 在 P 中的位置是 2,接着我们把 2 移动到 P 的起始位置,得到 P=[2,1,3,4,5] 。
对于 i=3: queries[i]=1, P=[2,1,3,4,5], 1 在 P 中的位置是 1,接着我们把 1 移动到 P 的起始位置,得到 P=[1,2,3,4,5] 。 
因此,返回的结果数组为 [2,1,2,1] 。  

示例 2:

输入:queries = [4,1,2,2], m = 4
输出:[3,1,2,0]

示例 3:

输入:queries = [7,5,5,8,3], m = 8
输出:[6,5,0,7,5]

 

提示:

  • 1 <= m <= 10^3
  • 1 <= queries.length <= m
  • 1 <= queries[i] <= m
Related Topics
  • 树状数组
  • 数组
  • 模拟

  • 👍 39
  • 👎 0
  • 树状数组实现
    思路同官方题解

    1. 树状数组的长度为queries.length + m,把原先的m序列在树状数组中后面部分构件起来
    2. 这样移动某个数字到前面的时候,就等于把树状数组后面的这个数字,往前面部分移动,需要在树状数组中对后面的key进行减1,并在前面移动到的新位置加1
    3. 这样需要维护一个数组记录每个数字当前所在的索引下标,移动到前面的时候更新索引下标为前面的值
    4. 每次查询当前位置前面的前缀和即为前面有多少个数字,并需要减1,因为包含了当前数字
    class Solution {
        public int[] processQueries(int[] queries, int m) {
            int l = queries.length + m;
            FenwickTree fenwickTree = new FenwickTree(l);
            int[] numPos = new int[m+1];
            for (int i = queries.length + 1; i <= l; i++) {
                fenwickTree.add(i,1);
                numPos[i - queries.length] = i;
            }
    
            int[] res = new int[queries.length];
            for (int i = 0; i < queries.length; i++) {
                int num = queries[i];
                res[i] = fenwickTree.query(numPos[num]) - 1;
                fenwickTree.add(numPos[num],-1);
                numPos[num] = queries.length - i;
                fenwickTree.add(numPos[num],1);
            }
            return res;
        }
    
        class FenwickTree{
            int[] arr;
    
            public FenwickTree(int n){
                arr = new int[n+1];
            }
    
            public int lowBit(int i){
                return i & (-i);
            }
    
            public void add(int idx , int num){
                while (idx < arr.length){
                    arr[idx] += num;
                    idx += lowBit(idx);
                }
            }
    
            public int query(int idx){
                int sum = 0;
                while (idx > 0){
                    sum += arr[idx];
                    idx -= lowBit(idx);
                }
                return sum;
            }
    
        }
    }

    LeetCode刷题【1310】子数组异或查询

    有一个正整数数组 arr,现给你一个对应的查询数组 queries,其中 queries[i] = [Li, Ri]

    对于每个查询 i,请你计算从 Li 到 Ri 的 XOR 值(即 arr[Li] xor arr[Li+1] xor ... xor arr[Ri])作为本次查询的结果。

    并返回一个包含给定查询 queries 所有结果的数组。

     

    示例 1:

    输入:arr = [1,3,4,8], queries = [[0,1],[1,2],[0,3],[3,3]]
    输出:[2,7,14,8] 
    解释:
    数组中元素的二进制表示形式是:
    1 = 0001 
    3 = 0011 
    4 = 0100 
    8 = 1000 
    查询的 XOR 值为:
    [0,1] = 1 xor 3 = 2 
    [1,2] = 3 xor 4 = 7 
    [0,3] = 1 xor 3 xor 4 xor 8 = 14 
    [3,3] = 8
    

    示例 2:

    输入:arr = [4,8,2,10], queries = [[2,3],[1,3],[0,0],[0,3]]
    输出:[8,0,4,4]
    

     

    提示:

    • 1 <= arr.length <= 3 * 10^4
    • 1 <= arr[i] <= 10^9
    • 1 <= queries.length <= 3 * 10^4
    • queries[i].length == 2
    • 0 <= queries[i][0] <= queries[i][1] < arr.length
    Related Topics
    • 位运算
    • 数组
    • 前缀和

  • 👍 146
  • 👎 0
  • 树状数组
    基本是模板套用,不过思路要转变下

    原本的前缀和套用为异或操作,本质没有改变

    class Solution {
        public int[] xorQueries(int[] arr, int[][] queries) {
            FenwickTree fenwickTree = new FenwickTree(arr.length);
            for (int i = 0; i < arr.length; i++) {
                fenwickTree.add(i+1, arr[i]);
            }
            int[] ans = new int[queries.length];
            for (int i = 0; i < queries.length; i++) {
                ans[i] = fenwickTree.query(queries[i][0]) ^ fenwickTree.query(queries[i][1]+1);
            }
            return ans;
        }
    
        class FenwickTree{
            int[] arr;
    
            public FenwickTree(int n){
                arr = new int[n+1];
            }
    
            public int lowBit(int i){
                return i & ( -i );
            }
    
            public void add(int idx, int num){
                while (idx < arr.length){
                    arr[idx] ^= num;
                    idx += lowBit(idx);
                }
            }
    
            public int query(int idx){
                int res = 0;
                while (idx > 0 ){
                    res ^= arr[idx];
                    idx -= lowBit(idx);
                }
                return res;
            }
        }
    }

    LeetCode刷题【面试题 10.10】数字流的秩

    假设你正在读取一串整数。每隔一段时间,你希望能找出数字 x 的秩(小于或等于 x 的值的个数)。请实现数据结构和算法来支持这些操作,也就是说:

    实现 track(int x) 方法,每读入一个数字都会调用该方法;

    实现 getRankOfNumber(int x) 方法,返回小于或等于 x 的值的个数。

    注意:本题相对原题稍作改动

    示例:

    输入:
    ["StreamRank", "getRankOfNumber", "track", "getRankOfNumber"]
    [[], [1], [0], [0]]
    输出:
    [null,0,null,1]
    

    提示:

    • x <= 50000
    • track 和 getRankOfNumber 方法的调用次数均不超过 2000 次
    Related Topics
    • 设计
    • 树状数组
    • 二分查找
    • 数据流

  • 👍 32
  • 👎 0
  • 树状数组结构直接套模板使用

    在该题中,树状数组对应下标表意为当前下标数字的出现次数

    1. track(int x) 方法,每读入一个数字都会调用该方法, 等价于在给树状数组对应下标数值加1,即当前下标出现次数加1
    2. getRankOfNumber(int x) 方法,返回小于或等于 x 的值的个数,等价于求下标0到当前下标的前缀和,即从0到当前下标的所有数字出现次数之和

    代码

    class StreamRank {
    
        private FenwickTree fenwickTree;
    
    
        public StreamRank() {
            fenwickTree = new FenwickTree(50001);
        }
    
        public void track(int x) {
            fenwickTree.add(x+1,1);
        }
    
        public int getRankOfNumber(int x) {
            return fenwickTree.query(x+1);
        }
    
    
        class FenwickTree{
            int[] arr;
            public FenwickTree(int num){
                arr = new int[num+1];
            }
    
            int lowBit(int x){
                return x & ( -x );
            }
    
            public void add(int idx, int num){
                while (idx < arr.length){
                    arr[idx] += num;
                    idx += lowBit(idx);
                }
            }
    
            public int query(int idx){
                int sum = 0;
                while (idx > 0){
                    sum += arr[idx];
                    idx -= lowBit(idx);
                }
                return sum;
            }
        }
    }

    LeetCode刷题【400】第 N 位数字

    给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字。

     

    示例 1:

    输入:n = 3
    输出:3
    

    示例 2:

    输入:n = 11
    输出:0
    解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。
    

     

    提示:

    • 1 <= n <= 231 - 1
    Related Topics
    • 数学
    • 二分查找

  • 👍 324
  • 👎 0
  • 剑指Offer 44 题 数字序列中某一位的数字

    解题思路

    按图分析

    1. 0-9的时候都可以直接返回自己
    2. 10-99一共有90个数字,每个数字两个字符
    3. 100-999一共有900个数字,每个数字三个字符
    4. 1000-9999一共有9000个数字,每个数字四个字符
    5. 后续雷同

    按照这个规律分析,我们只需从头开始,依次从头开始

    1. 减去10个字符
    2. 减去90 * 2 个字符
    3. 减去900 * 3个字符
    4. 减去9000 * 4个字符
    5. 如果不够减了则从这一档的1 x 10^pow开始,

    问题

    1. 运算中间的值
     cur + 9 * powOf10 * ( pow+1 ) 

    会超过int型上限,所以最简单的处理办法中间的运算过程用了long型数据

    1. 取最后结果的某一位数字也可以用数学方法,不过偷懒了下直接转字符串了

    代码

    class Solution {
        public int findNthDigit(int n) {
            if (n < 10){
                return n;
            }
            long cur = 10;
            long powOf10 = 10;
            long pow = 1;
            while (cur + 9 * powOf10 * ( pow+1 )< n){
                cur += 9 * powOf10 * (pow+1);
                powOf10 *= 10;
                pow++;
            }
            long order = powOf10 + (n-cur) / (pow+1);
            long idx = (n - cur) % (pow+1);
            String orderStr = Long.toString(order);
            return orderStr.charAt((int)idx)-'0';
        }
    }

    LeetCode刷题【307】区域和检索 – 数组可修改

    给你一个数组 nums ,请你完成两类查询。

    1. 其中一类查询要求 更新 数组 nums 下标对应的值
    2. 另一类查询要求返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的  ,其中 left <= right

    实现 NumArray 类:

    • NumArray(int[] nums) 用整数数组 nums 初始化对象
    • void update(int index, int val)nums[index] 的值 更新val
    • int sumRange(int left, int right) 返回数组 nums 中索引 left 和索引 right 之间( 包含 )的nums元素的  (即,nums[left] + nums[left + 1], ..., nums[right]

     

    示例 1:

    输入:
    ["NumArray", "sumRange", "update", "sumRange"]
    [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
    输出:
    [null, 9, null, 8]
    
    解释:
    NumArray numArray = new NumArray([1, 3, 5]);
    numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9
    numArray.update(1, 2);   // nums = [1,2,5]
    numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8
    

     

    提示:

    • 1 <= nums.length <= 3 * 104
    • -100 <= nums[i] <= 100
    • 0 <= index < nums.length
    • -100 <= val <= 100
    • 0 <= left <= right < nums.length
    • 调用 updatesumRange 方法次数不大于 3 * 104 
    Related Topics
    • 设计
    • 树状数组
    • 线段树
    • 数组

  • 👍 521
  • 👎 0
  • 树状数组套模板

    class NumArray {
    
        private FenwickTree fenwickTree;
    
        public NumArray(int[] nums) {
            fenwickTree = new FenwickTree(nums.length);
            for (int i = 0; i < nums.length; i++) {
                fenwickTree.add(i+1,nums[i]);
            }
        }
    
        public void update(int index, int val) {
            fenwickTree.add(index+1,val - fenwickTree.queryIndex(index+1));
        }
    
        public int sumRange(int left, int right) {
            return fenwickTree.rangeSum(left,right+1);
        }
    
    
        class FenwickTree{
            int[] arr;
            public FenwickTree(int n){
                arr = new int[n+1];
            }
    
            public int lowBit(int x){
                return x & (- x);
            }
    
            public void add(int idx, int num){
                while (idx < arr.length){
                    arr[idx] += num;
                    idx += lowBit(idx);
                }
            }
    
            public int querySum(int idx){
                int res = 0;
                while (idx > 0){
                    res += arr[idx];
                    idx -= lowBit(idx);
                }
                return res;
            }
    
            public int queryIndex(int idx){
                return querySum(idx) - querySum(idx-1);
            }
    
            public int rangeSum(int left, int right){
                if (left > right){
                    int tmp = right;
                    right = left;
                    left = tmp;
                }
                return querySum(right) - querySum(left);
            }
        }
    }

    LeetCode刷题【468】验证IP地址

    给定一个字符串 queryIP。如果是有效的 IPv4 地址,返回 "IPv4" ;如果是有效的 IPv6 地址,返回 "IPv6" ;如果不是上述类型的 IP 地址,返回 "Neither"

    有效的IPv4地址“x1.x2.x3.x4” 形式的IP地址。 其中 0 <= xi <= 255 且 xi 不能包含 前导零。例如: “192.168.1.1” 、 “192.168.1.0” 为有效IPv4地址, “192.168.01.1” 为无效IPv4地址; “192.168.1.00”“192.168@1.1” 为无效IPv4地址。

    一个有效的IPv6地址 是一个格式为“x1:x2:x3:x4:x5:x6:x7:x8” 的IP地址,其中:

    • 1 <= xi.length <= 4
    • xi 是一个 十六进制字符串 ,可以包含数字、小写英文字母( 'a''f' )和大写英文字母( 'A''F' )。
    • 在 xi 中允许前导零。

    例如 "2001:0db8:85a3:0000:0000:8a2e:0370:7334""2001:db8:85a3:0:0:8A2E:0370:7334" 是有效的 IPv6 地址,而 "2001:0db8:85a3::8A2E:037j:7334""02001:0db8:85a3:0000:0000:8a2e:0370:7334" 是无效的 IPv6 地址。

    示例 1:

    输入:queryIP = "172.16.254.1"
    输出:"IPv4"
    解释:有效的 IPv4 地址,返回 "IPv4"

    示例 2:

    输入:queryIP = "2001:0db8:85a3:0:0:8A2E:0370:7334"
    输出:"IPv6"
    解释:有效的 IPv6 地址,返回 "IPv6"

    示例 3:

    输入:queryIP = "256.256.256.256"
    输出:"Neither"
    解释:既不是 IPv4 地址,又不是 IPv6 地址

    提示:

    • queryIP 仅由英文字母,数字,字符 '.'':' 组成。

    Related Topics

    • 字符串

    👍 200👎 0

    拆解方法 & Java的split的坑
    先说坑
    示例"1,2,".split(",").length 这个分出来结果是[1,2],长度不是3,末尾的 , 会被省略

    然后本题思路

    1. 字符串长度小于7个的直接NEITHER
    2. 接下来只要预判前5个字符,根据是否出现.或者:来区分用IPv4还是IPv6的方法去校验
    3. 分别根据IPv4还是IPv6的区别使用.或者:来拆分,这里会遇到split方法的坑
    4. 再接下来校验拆分后每一段的规则
    5. IPv4的长度为0-3个字符,每个字符只能是数字,不能有先导0,最终数字要小于256
    6. IPv6的长度为0-4个字符,每个字符只能是0-9或者A-F或者A-F

    每个方法各自封装处理

    代码

    class Solution {
    
        String IPV4 = "IPv4";
        String IPV6 = "IPv6";
        String NEITHER = "Neither";
    
        public String validIPAddress(String queryIP) {
            if (queryIP.length() < 7){
                return NEITHER;
            }
            for (int i = 0; i < 6; i++) {
                if (queryIP.charAt(i) == '.'){
                    return isIpv4(queryIP);
                }
                if (queryIP.charAt(i) == ':'){
                    return isIpv6(queryIP);
                }
            }
            return NEITHER;
        }
    
        /**
         * 验证IPv4类型的IP字符串
         * @param queryIP
         * @return
         */
        public String isIpv4(String queryIP){
            String[] ipv4Nums = queryIP.split("\\.");
            if (ipv4Nums.length != 4 || queryIP.charAt(queryIP.length()-1) == '.'){
                return NEITHER;
            }
    
            for (String ipv4Num : ipv4Nums) {
                if (!isIpv4Num(ipv4Num)){
                    return NEITHER;
                }
            }
            return IPV4;
        }
    
        /**
         * 验证IPv4的每一段
         * @param ipv4NumStr
         * @return
         */
        public boolean isIpv4Num(String ipv4NumStr){
            //单个长度大于3个 或者为空
            if (ipv4NumStr.length()>3 || ipv4NumStr.length()==0){
                return false;
            }
            //长度大于1的首个字符不能为0
            if (ipv4NumStr.length()>1 && ipv4NumStr.charAt(0) == '0'){
                return false;
            }
            int num = 0;
            for (int i = 0; i < ipv4NumStr.length(); i++) {
                int tmp = ipv4NumStr.charAt(i) - '0';
                if (tmp < 0 || tmp > 9){
                    return false;
                }
                num *= 10;
                num += tmp;
            }
            return num < 256;
        }
    
    
        /**
         * 验证IPv6的字符串
         * @param queryIP
         * @return
         */
        public String isIpv6(String queryIP){
            String[] ipv6Nums = queryIP.split("\\:");
            if (ipv6Nums.length != 8 || queryIP.charAt(queryIP.length()-1) == ':'){
                return NEITHER;
            }
            for (String ipv6Num : ipv6Nums) {
                if (!isIpv6Num(ipv6Num)){
                    return NEITHER;
                }
            }
            return IPV6;
        }
    
        /**
         * 验证IPv6的每一段
         * @param ipv6NumStr
         * @return
         */
        public boolean isIpv6Num(String ipv6NumStr){
            if (ipv6NumStr.length() > 4 || ipv6NumStr.length() < 1){
                return false;
            }
            for (int i = 0; i < ipv6NumStr.length(); i++) {
                char c = ipv6NumStr.charAt(i);
                if (!(c - '0' >=0 && c - '9' <= 0)&&
                    !(c - 'A' >=0 && c - 'F' <= 0)&&
                    !(c - 'a' >=0 && c - 'f' <= 0)){
                    return false;
                }
            }
            return true ;
        }
    }

    LeetCode刷题【786】第 K 个最小的素数分数

    给你一个按递增顺序排序的数组 arr 和一个整数 k 。数组 arr1 和若干 素数  组成,且其中所有整数互不相同。

    对于每对满足 0 <= i < j < arr.lengthij ,可以得到分数 arr[i] / arr[j]

    那么第 k 个最小的分数是多少呢?  以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j]

     

    示例 1:

    输入:arr = [1,2,3,5], k = 3
    输出:[2,5]
    解释:已构造好的分数,排序后如下所示: 
    1/5, 1/3, 2/5, 1/2, 3/5, 2/3
    很明显第三个最小的分数是 2/5
    

    示例 2:

    输入:arr = [1,7], k = 1
    输出:[1,7]
    

     

    提示:

    • 2 <= arr.length <= 1000
    • 1 <= arr[i] <= 3 * 104
    • arr[0] == 1
    • arr[i] 是一个 素数i > 0
    • arr 中的所有数字 互不相同 ,且按 严格递增 排序
    • 1 <= k <= arr.length * (arr.length - 1) / 2
    Related Topics
    • 数组
    • 二分查找
    • 排序
    • 堆(优先队列)

  • 👍 221
  • 👎 0
  • 优先队列两种实现

    首先,直接上来一个优先队列,自定义排序。

    为了实现这个自定义的排序,我们需要记录几个东西

    1. 分子
    2. 分母
    3. 为了便于比较,相除后的小数值也可以事先计算好,比较的时候就直接比较这个数字即可
    4. 把所有的都放进去之后,我们就开始从头部取出K个即可
    5. 最终得到第K个的分子分母即可

    理解起来也相对比较简单

    代码

    class Solution {
        public int[] kthSmallestPrimeFraction(int[] arr, int k) {
            PriorityQueue<double[]> queue = new PriorityQueue<>(new Comparator<double[]>() {
                @Override
                public int compare(double[] o1, double[] o2) {
                    if (o1[2] >= o2[2]){
                        return 1;
                    }
                    return -1;
                }
            });
            for (int i : arr) {
                for (int j : arr) {
                    queue.offer(new double[]{(double)i,(double)j,((double)i)/j});
                }
            }
            while (--k > 0){
                queue.poll();
            }
            return new int[]{(int)queue.peek()[0],(int)queue.peek()[1]};
        }
    }

    写完这个之后思考一下,我们真的需要把所有的值都算一遍全都存进优先队列么?答案是否定的,

    1. 我们可以把数据分别按照分母分成arr[]数组长度个分组,
    2. 分别维护一个指针,指向当前对应的分子,
    3. 这样我们的优先队列只要维护比较arr[]数组长度个值即可,每当从队列头部取出一个对象时,只需把分子位置往后挪动一位,重新放入队列
    4. 如果分子指针位置已经到达了arr[]数组尾部,则不再需要往队列中添加

    代码

    class Solution {
        public int[] kthSmallestPrimeFraction(int[] arr, int k) {
            PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    if (((double)arr[o1[0]])/arr[o1[1]] >= ((double)arr[o2[0]])/arr[o2[1]]){
                        return 1;
                    }
                    return -1;
                }
            });
            for (int i = 0; i < arr.length; i++) {
                queue.offer(new int[]{0,i});
            }
            while (--k > 0) {
                int[] top = queue.poll();
                if (top[0] < arr.length){
                    top[0]++;
                    queue.offer(top);
                }
            }
            return new int[]{arr[queue.peek()[0]],arr[queue.peek()[1]]};
        }
    
    }