[LeetCode]-数学-创新互联

前言

记录 LeetCode 刷题中遇到的数学相关题目

创新互联专注于昭阳企业网站建设,响应式网站建设,成都做商城网站。昭阳网站建设公司,为昭阳等地区提供建站服务。全流程按需求定制开发,专业设计,全程项目跟踪,创新互联专业和态度为您提供的服务204. 计数质数

质数是数论中很重要的一部分,应该也是大多数人刚接触数论时接触到第一个知识点。下面根据官方题解总结一下几种判断质数或者筛质数的方法:

  1. 最原始的做法。质数的 定义 就是,在大于 1 的自然数中,除了 1 和它本身以外不再有其他因数的自然数。所以对于每个数 x,我们可以从小到大枚举 [2,x−1] 中的每个数 y,判断 y 是否为 x 的因数,只要枚举到有一个是,那就说明 x 不是质数。这种方法,对于判断 n 个数有多少质数的情况,时间复杂度为 O(n²)。这种方法有一个优化的地方在于,不需要判断 [2,x−1],判断 [ 2 , x ] [2,\sqrt{x}] [2,x ​] 即可
  2. 埃氏筛:如果 x 是质数,那么大于 x 的 x 的倍数 2x,3x,… 一定不是质数,所以可以从小到大遍历每个数,如果这个数为质数,则将其所有的倍数都标记为合数 (除了该质数本身)。这个方法也可以优化:如果 x 是质数,只需从 x * x 开始标记为合数即可,因为从 2x 开始到 x * x 之间的合数一定在枚举到比 x 小的质数时就已经筛过了
  3. 线性筛:相较于 埃氏筛,在枚举时多维护一个当前得到的质数集合。与 埃氏筛 不同的是,合数标记过程 不再仅当 x 为质数时才进行,而是对每个整数 x 都进行。对于整数 x,我们不再标记其所有的倍数,而是只 标记质数集合中的数与 x 相乘得到的数为合数,也就是对于每一个 x,枚举质数集合,将每个质数与 x 的积标为合数,且在发现 x 能整除某个质数的时候结束本次标记过程

下面是线性筛的做法:

public int countPrimes(int n) {Listprimes = new ArrayList(); //质数集合
    int[] isPrime = new int[n];                      //标记质数
    Arrays.fill(isPrime, 1);
    for (int i = 2; i< n; ++i) {if (isPrime[i] == 1) {primes.add(i);
        }
        //注意如果 i * primes.get(j) >= n 直接跳出循环
        for (int j = 0; j< primes.size() && i * primes.get(j)< n; ++j) {isPrime[i * primes.get(j)] = 0;
            //遇到能整除的质数结束标记过程
            if (i % primes.get(j) == 0) {break;
            }
        }
    }
    return primes.size();
}
326. 3 的幂

根据数据范围,n 不大于 231- 1 的大取值为 319= 1162261467,那么如果 n 是 3 的幂,即 n = 3x,那么一定满足 n * 3k= 319,其中 0<= k<= 19,亦即 319% n = 0,所以只需判断这个等式是否成立即可。还要判断 n 是否大于 0:

public boolean isPowerOfThree(int n) {return n >0 && 1162261467 % n == 0;
}
1262. 可被三整除的大和

remain 数组中,remain[0] 记录被遍历过的元素中,被三除后余 0 的元素大和;remain[[1] 记录被三除后余 1 的元素大和;remain[2] 记录被三除后余 2 的元素大和。当把所有元素遍历完后,remain[0] 就是本题答案

public int maxSumDivThree(int[] nums) {int len = nums.length;
    int[] remain = new int[3];
    int a,b,c;
    for(int i = 0; i< len; i++){a = remain[0] + nums[i];
        b = remain[1] + nums[i];
        c = remain[2] + nums[i];
        remain[a % 3] = Math.max(remain[a % 3], a);
        remain[b % 3] = Math.max(remain[b % 3], b);
        remain[c % 3] = Math.max(remain[c % 3], c);
    }
    return remain[0];
}
171. Excel表列序号

字符串转化为数字,相当于 26 进制转化为十进制。
但十进制中没有 0,‘A’ 对应的是 1 而不是 0,所以将字母转化为数字时还需要加一,即 字母 - ‘A’ + 1 而不是 字母 - ‘A’

public int titleToNumber(String columnTitle) {int len = columnTitle.length();
    int ans = 0;
    for(int i = 0; i< len; i++){ans = ans * 26 + (columnTitle.charAt(i) - 'A' + 1);
    }
    return ans;
}
168. Excel表列名称

171 题的思路逆过来即可。一开始是没做 171 题先做了 168 题,对这个 “cn–” 语句不能理解。然后就先做了 171 题,171 中的 “columnTitle.charAt(i) - ‘A’ + 1” 还是挺好理解的

cn 每次减一后对 26 取余得到的就是 171 中 " columnTitle.charAt(i) - ‘A’ ",所以再加 ‘A’ 得到的就是对应位上的字母。以此类推,将所有字母使用 StringBuilder 连接起来。连接的顺序是从低位到高位, reverse 成高位到低位即可

public String convertToTitle(int cn) {StringBuilder sb = new StringBuilder();
    while (cn >0) {cn--;
        sb.append((char)(cn % 26 + 'A'));
        cn /= 26;
    }
    return sb.reverse().toString();
}
292. Nim 游戏

如果轮到我的时候剩下 0 个石头,那我必输;如果剩下 1 个,那我可以拿 1 个,轮到对面没有石头拿,对面必输;如果剩下 2 个,那我可以拿 2 个,轮到对面没有石头拿,对面必输;如果剩下 3 个,那我可以拿 3 个,轮到对面没有石头拿,对面必输;如果剩下 4 个,那我不管拿 1 个还是 2 个还是 3 个,剩下的石头对面都可以一次性拿完,再次轮到我没有石头拿,我必输;如果剩下 5 个,我可以只拿 1 个,剩下 4 个给对面,跟剩下 4 个给我一样,对面必输…可以发现规律,如果 n 取余 4 为 0,那么我必输

public boolean canWinNim(int n) {return n % 4 != 0;
}
470. 用 Rand7() 实现 Rand10()

randX() 可以得到 [1,X],那么 randX() - 1 为 [0,X - 1],(randX() - 1) * Y 为 [0,(X - 1)Y],那么 (randX() - 1) * Y + randY() 即为 [1,X*Y]

故已有 randX(),randY(),可以通过 (randX() - 1) * Y + randY() 得到 randX*Y

所以对于这道题,一开始已有 rand7,可以得到 rand49,如果结果在 [1,40] 中,就可以取余 10 然后加 1 得到 rand10。如果结果在 [41,49],就可以减去 40,得到 [1,9],即 rand9,可以跟 rand7 得到 rand63。同理如果结果在 [1,60] 可以得到 rand10,否则就是得到 rand3,可以跟 rand7 得到 rand21。此时如果结果在 [1,20] 可以得到 rand10,否则只剩下 1 这一个结果,无法继续得到新的 rand,只能从头开始循环

public int rand10() {while(true) {int rand7_1 = rand7();
        int rand7_2 = rand7();
        int r1 = (rand7_1 - 1) * 7 + rand7_2; //rand 49
        if(r1<= 40) return r1 % 10 + 1; //rand 10

        r1 -= 40; //rand 9
        int rand7_3 = rand7();
        int r2 = (r1 - 1) * 7 + rand7_3; //rand 63
        if(r2<= 60) return r2 % 10 + 1; //rand 10

        r2 -= 10; //rand 3
        int rand7_4 = rand7();
        int r3 = (r2 - 1) * 7 + rand7_4; //rand 21
        if(r3<= 20) return r3 % 10 + 1; 
        
        //此时r3 - 20是1,说明后续无法继续去找rand X*Y了,只能重新开始循环
    }
}
50. Pow(x, n)

计算 xn,如果是按照 n 个 x 相乘的思路,复杂度为 O(n);而快速幂基于分治的思想,xn会等于 xn/2的平方,因此可以先计算出 xn/2然后进行一次平方运算得到 xn,同理,xn/2又等于 xn/4,因此可以先计算出 xn/4然后进行一次平方运算得到 xn/2,以此类推,因此总的复杂度为 O(logn),从而减少运算时间

要注意的是,如果 n 为奇数,那么 xn应该等于 xn/2* xn/2* x,因此需要判断进行平方根的平方运算时需不需要多乘一个 x

class Solution {public double myPow(double x, int n) {return n >= 0 ? quickPow(x, n) : 1.0 / quickPow(x, -n);
    }
    public double quickPow(double x, long n) {if (n == 0) {return 1.0;
        }
        //计算平方根
        double tmp = quickPow(x,n / 2);
        return n % 2 == 0 ? tmp * tmp : tmp * tmp * x;
    }
}
剑指 Offer 62. 圆圈中最后剩下的数字

约瑟夫环。参考题解。
我们知道最后剩下的数字在最后一个数组中下标肯定为 0,由于在上一轮要删除第 m 个数,而且不是最后剩下这个数,所以最后剩下这个数在上一轮,也就是倒数第二个数组中的前面一定有 m 个数,它才不会被删掉,所以它在倒数第二个数组中的下标就是 (0 + m) % 2,0 + m 表示前面有 m 个数,但由于前面不一定有 m 个数,所以我们要把数组看成循环数组,所以求下标时要取余数组大小,倒数第二个数组的大小为 2 ,所以取余 2。得到的就是最后剩下的这个数在倒数第二个数组中的下标,以此类推,推算到第一个数组,也即原数组,就能得到最后剩下的数字在原数组中的下标,从而找到这个数

public int lastRemaining(int n, int m) {int res = 0;
    for(int i = 2;i<= n;i++){res = (res + m) % i;
    }
    return res;
}

你是否还在寻找稳定的海外服务器提供商?创新互联www.cdcxhl.cn海外机房具备T级流量清洗系统配攻击溯源,准确流量调度确保服务器高可用性,企业级服务器适合批量采购,新人活动首月15元起,快前往官网查看详情吧


标题名称:[LeetCode]-数学-创新互联
网页链接:http://azwzsj.com/article/jsehh.html