Tag Archives: 数论

LeetCode Count Primes

204. Count Primes

Count the number of prime numbers less than a non-negative number, n.

Example:

Input: 10
Output: 4
Explanation: There are 4 prime numbers less than 10, they are 2, 3, 5, 7.

本题要求从1~n共有多少个素数。Hint里一步步深入写得非常详细了,简要概括一下,有两种方法,一种是常规的从1~n一个个判断是否为非数,另一种是比较巧妙的Hash方法。 常规方法。首先看看判断一个数n是否为素数的方法,因为如果n为合数,则n可以分解为p×q,又n=p×q=(√n)×(√n),假设p是较小数的话,则p≤(√n)。所以我们只需要从2~(√n)判断能否被n整除,时间复杂度为O(n0.5)。从1~n都需要判断是否为素数,所以总的时间复杂度为O(n1.5)。 完整代码如下:

class Solution {
private:
    bool isPrime(int x)
    {
        if (x <= 1)
            return false;
        for (int i = 2; i * i <= x; ++i) {
            if (x % i == 0)
                return false;
        }
        return true;
    }
public:
    int countPrimes(int n)
    {
        int ans = 0;
        for (int i = 1; i < n; ++i) {
            if (isPrime(i))
                ++ans;
        }
        return ans;
    }
};

本代码提交TLE,在n很大的时候无法通过测试。

后来看了Hint,解法还是很巧妙的。如果一个数n是素数,则n的倍数肯定不是素数了,比如2是素数,则4=2*2、6=2*3、8=2*4…这些数肯定就不是素数了。所以我们可以建立一个1~n的Hash表,表示一个数是否为素数。初始的时候Hash[1~n]=true。然后从2开始,如果Hash[i]为true,说明i是素数,则2*i, 3*i,…都不是素数了,即Hash[2*i], Hash[3*i]..=false。如果Hash[i]为false,说明i不是素数,则i可以分解为i=p*q,使得p(或q)为素数,则之前测试p时,已经把p的所有倍数都置为false了,而i的倍数肯定也是p的倍数,所以不需要对i的倍数再置false了。

循环的终止条件是i>(√n),和第一种解法的分析一样,如果某个合数x大于(√n),则其肯定有一个素因子i是小于(√n)的,所以在测试i时,就已经把x置为false了。

另外对于素数i,我们之前分析的是要把所有2*i, 3*i…都置为false。其实这里也可以优化,我们可以从i*i开始置为false,而不需要每次都从2*i开始。比如i=5,常规是要把所有2*5, 3*5, 4*5, 5*5,…都置为false,但其实2*5, 3*5, 4*5都已经被之前的素数2或3置为false了。所以每次我们只需要从i*i开始,下标以i递增的置为false就好。(从i*i到i*i+(i-1)之间的合数被比i小的合数置为false了)总的来说,这一题加速的技巧很多,完整代码如下:

class Solution {
public:
    int countPrimes(int n)
    {
        vector<bool> mark(n, true);
        for (int i = 2; i * i < n; ++i) {
            if (!mark[i])
                continue;
            for (int j = i * i; j < n; j += i) {
                mark[j] = false;
            }
        }
        int ans = 0;
        for (int i = 2; i < n; ++i)
            ans += (mark[i] ? 1 : 0);
        return ans;
    }
};

本代码提交AC,用时29MS。

LeetCode Factorial Trailing Zeroes

172. Factorial Trailing Zeroes

Given an integer n, return the number of trailing zeroes in n!.

Example 1:

Input: 3
Output: 0
Explanation: 3! = 6, no trailing zero.

Example 2:

Input: 5
Output: 1
Explanation: 5! = 120, one trailing zero.

Note: Your solution should be in logarithmic time complexity.


本题要求n的阶乘结果中末尾有多少个数字0。暴力方法就是先把n!求出来,然后不断除10算末尾0的个数,但是暴力方法无法满足log(n)的时间复杂度。 参考网上的题解:http://www.geeksforgeeks.org/count-trailing-zeroes-factorial-number/,分析得还是很详细的。 要产生一个尾数0,必须是10=2*5,所以算n!中min(2的素因子,5的素因子),就有多少个0。简单考察一下5!= (2 * 2 * 2 * 3 * 5),11!= (2 8 * 34 * 52 * 7),即n!中2的素因子个数肯定要比5的素因子个数要多,这个也是可以证明的。比如从5k+1到5(k+1),只多了一个5的素因子, 但是[5k+1, 5k+2]至少多了一个2的素因子,从[5k+3, 5k+4]又至少多了一个2的素因子。所以2的素因子要多于5的素因子。
下面就把问题转换为求n!中5的素因子的个数。

举一个例子20!=1*2*3*4*5*6*7*8*9*10*11*12*13*14*15*16*17*18*19*20,能够分解出5的素因子的数只有5,10,15,20,一共4=floor(20/5)。所以20!=2432902008176640000有4个尾数0。所以n!中5的素因子的个数是floor(n/5)?

且慢,如果n=25,floor(n/5)=5,但是25=5*5能分解出两个5的素因子,所以25!中应该有六个5的素因子。所以每增加一个25的倍数的数,5的素因子的个数加1;同理每增加一个125的倍数的数,5的素因子要加2,但因为125的倍数的数肯定是25的倍数的数,所以我们在求25的倍数的数的时候已经求过一次125倍数的数的个数了,结果就是5的个数=floor(n/5)+floor(n/25)+floor(n/125)+…
完整代码如下,注意i要是long long类型的,否则可能溢出。

class Solution {
public:
    int trailingZeroes(int n)
    {
        int ans = 0;
        for (long long i = 5; n / i >= 1; i *= 5) {
            ans += n / i;
        }
        return ans;
    }
};

本代码提交AC,用时3MS。
还有一种解法,思路是一样的:

class Solution {
public:
    int trailingZeroes(int n)
    {
        int ans = 0;
        while (n > 0) {
            ans += n / 5;
            n /= 5;
        }
        return ans;
    }
};

第一种解法是固定分子不变,第二种解法是固定分母不变,结果是一样的。

LeetCode Valid Perfect Square

LeetCode Valid Perfect Square Given a positive integer num, write a function which returns True if num is a perfect square else False. Note: Do not use any built-in library function such as sqrt. Example 1:

Input: 16
Returns: True
Example 2:
Input: 14
Returns: False

本题要判断一个数是否是完全平方数。有很多种解法,现列举如下: 解法1。牛顿法。牛顿法本是用来求解函数f(x)=0的根的,可以用来开方。$$f(x)=x^2-num$$等于0的正根就是num的根号。下面就是牛顿法求解f(x)=0的根的更新公式,$$x_{n+1}$$比$$x_n$$更接近f(x)=0的真实根。初始的时候可以随便代一个$$x_0$$到公式中。 对于函数$$f(x)=x^2-num$$,$$f'(x)=2x$$,所以牛顿递推式为$$!x_{n+1}=x_n-\frac{x_n^2-num}{2x_n}=(x_n+num/x_n)/2$$ 所以我们可以初始代入$$x_n=num$$,然后不断用牛顿法,直到x*x<=num时,如果x*x==num,则num为完全平方数,否则不是。 关于牛顿法用于开放的原理介绍,果壳网上有个人介绍得很详细。 代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long x = num; while (x*x > num) { x = (x + num / x) / 2; } return x*x == num; } }; [/cpp] 本代码提交AC,用时0MS。 解法2。对于num,如果$$x=\frac{num}{2}$$的平方依然大于num,则$$x=\frac{x}{2}$$,如果x的平方依然大于num,则继续对x除以2,不断除,直到x平方小于等于num时,遍历[x,2*x]之间的数,看看x*x==num是否成立,如果成立,说明num是完全平方数,否则不是。这种解法在高级算法里好像讲过。完整代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { if (num == 1)return true; long long x = num >> 1; while (x*x > num)x >>= 1; for (int y = x; y <= (x << 1); ++y) { if (y*y == num)return true; } return false; } }; [/cpp] 本代码提交AC,用时3MS。 解法3。观测下面的等式发现完全平方数都是由连续奇数相加而来,所以我们也可以把连续奇数加起来,直到超过num时,看看和与num是否相等。 1 = 1 4 = 1 + 3 9 = 1 + 3 + 5 16 = 1 + 3 + 5 + 7 25 = 1 + 3 + 5 + 7 + 9 36 = 1 + 3 + 5 + 7 + 9 + 11 …. 1+3+…+(2n-1) = (2n-1 + 1)n/2 = n*n 代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long sum = 1; for (int i = 3; sum < num; i += 2) sum += i; return sum == num; } }; [/cpp] 本代码提交AC,用时3MS。 解法4。二分搜索。l=0,r=num,mid=(l+r)/2,根据mid*mid和num的大小关系一步步缩小范围。复杂度为O(lgn),完整代码如下: [cpp] class Solution { public: bool isPerfectSquare(int num) { long long l = 0, r = num; while (l <= r) { long long mid = (l + r) / 2; long long prod = mid*mid; if (prod == num)return true; if (prod < num)l = mid + 1; else r = mid – 1; } return l*l == num; } }; [/cpp] 本代码提交AC,用时0MS。 注意这个题的代码中,为了防止int越界,都需要用long long。 参考: http://bookshadow.com/weblog/2016/06/26/leetcode-valid-perfect-square/ http://www.cnblogs.com/grandyang/p/5619296.html]]>

LeetCode Single Number III

260. Single Number III 260. Single Number III

Given an array of numbers nums, in which exactly two elements appear only once and all the other elements appear exactly twice. Find the two elements that appear only once.

Example:

Input:  [1,2,1,3,2,5]
Output: [3,5]

Note:

  1. The order of the result is not important. So in the above example, [5, 3] is also correct.
  2. Your algorithm should run in linear runtime complexity. Could you implement it using only constant space complexity? 260. Single Number III

本题是LeetCode Single Number的升级版,即数组中有两个只出现一次的数,其他的数都出现了两次,现在要把这两个只出现一次的数找出来。
直接用LeetCode Single Number异或的方法是不行的,因为虽然出现两次的数异或等于0了,但是有两个只出现一次的数a和b,得到的结果是它们的异或a^b。
如果有办法把数组中的数分成两堆,a和b在不同的堆里,并且每堆除了a或b,其他数也都恰好出现了两次,即如果3出现两次,则两个3要么都在a堆中,要么都在b堆中。这样我们就可以对两个堆分别异或,找到每堆中只出现一次的数a和b。
那么根据a^b的结果能否把数分成两堆呢,答案是可以的。异或的规则是相同为0,不同为1。既然a和b分别只出现了1次,那么a和b肯定是不同的两个数,所以他们的异或结果的二进制中肯定有一位是1,记这一位为x,那么a和b在x位的二进制肯定是不一样的,这就找到了a和b的区别。
所以我们可以把数组中的所有数按x位为0或1分成两堆,这样就达到了开头的目的,然后利用LeetCode Single Number的方法,在两堆进行异或,最终就能找到a和b了。完整代码如下:

class Solution {
public:
    vector<int> singleNumber(vector<int>& nums)
    {
        int flag = 0;
        for (int i = 0; i < nums.size(); ++i) {
            flag ^= nums[i];
        }
        int mask = 1;
        for (int i = 0; i < 32; ++i) {
            if ((flag & mask) != 0)
                break;
            mask <<= 1;
        }
        vector<int> ans(2, 0);
        for (int i = 0; i < nums.size(); ++i) {
            if (nums[i] & mask) {
                ans[0] ^= nums[i];
            }
            else {
                ans[1] ^= nums[i];
            }
        }
        return ans;
    }
};

本代码提交AC,用时16MS。

二刷。相同思路,很简洁的代码,对于n,直接n&(-n)就能得到其最后一位1的位置,请看面试笔试宝典第三版P97,代码如下:

class Solution {
public:
	vector<int> singleNumber(vector<int>& nums) {
		int c = 0, n = nums.size();
		for (int i = 0; i < n; ++i) {
			c ^= nums[i];
		}
		c = c & (-c); // 得到c最后一个1
		int a = 0, b = 0;
		for (int i = 0; i < n; ++i) {
			if ((nums[i] & c) == 0) { // 注意加括号,位运算优先级低于==
				a ^= nums[i];
			}
			else {
				b ^= nums[i];
			}
		}
		return { a,b };
	}
};

本代码提交AC,用时20MS。

LeetCode Excel Sheet Column Number

171. Excel Sheet Column Number

Given a column title as appear in an Excel sheet, return its corresponding column number.

For example:

    A -> 1
    B -> 2
    C -> 3
    ...
    Z -> 26
    AA -> 27
    AB -> 28 
    ...

Example 1:

Input: "A"
Output: 1

Example 2:

Input: "AB"
Output: 28

Example 3:

Input: "ZY"
Output: 701

LeetCode Excel Sheet Column Title的反过程,要把字符串转换为数字,比前一题简单,扫一遍字符串,和’A’作差,然后结果不断乘26。完整代码如下:

class Solution {
public:
    int titleToNumber(string s)
    {
        int ans = 0;
        for (int i = 0; i < s.size(); i++) {
            ans = ans * 26 + (s[i] – ‘A’) + 1;
        }
        return ans;
    }
};

本代码提交AC,用时3MS。

LeetCode Excel Sheet Column Title

168. Excel Sheet Column Title

Given a positive integer, return its corresponding column title as appear in an Excel sheet.

For example:

    1 -> A
    2 -> B
    3 -> C
    ...
    26 -> Z
    27 -> AA
    28 -> AB 
    ...

Example 1:

Input: 1
Output: "A"

Example 2:

Input: 28
Output: "AB"

Example 3:

Input: 701
Output: "ZY"

本题要求把数字转换为Excel中的列的名称,规则就是题中给出的例子。 可以看到是26进制,比如1048,除以26余数为8,商为40,则最后一个字母就是’A’+8-1=’H’;然后用40继续循环,40除以26余数为14,商为1,则倒数第二个字母为’A’+14-1=’N’,第一个字母为’A’。 需要注意能整除26的情况,比如26,余数为0,此时应该是字母’Z’,然后在n/=26的基础上,把n减1。完整代码如下:

class Solution {
public:
    string convertToTitle(int n)
    {
        string ans = "";
        while (n > 0) {
            int r = n % 26;
            char c = r – 1 + ‘A’;
            n /= 26;
            if (r == 0) {
                ans = ‘Z’ + ans;
                n–;
            }
            else {
                ans = c + ans;
            }
        }
        return ans;
    }
};

本代码提交AC,用时0MS。

LeetCode Single Number

136. Single Number

Given a non-empty array of integers, every element appears twice except for one. Find that single one.

Note:

Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory?

Example 1:

Input: [2,2,1]
Output: 1

Example 2:

Input: [4,1,2,1,2]
Output: 4

已知一个数组中除了一个数字,其他数字都出现了两次,现在需要找出这个只出现一次的数字。要求线性时间复杂度,且不使用额外的空间。 这是一个位运算的题,我们知道两个相同的数按位异或之后结果为0,因为异或操作是相同为0,不同为1。所以可以把数组中的所有数字都进行异或运算,那么所有出现两次的数字异或之后都等于0,最终只剩下出现次数为1次的那个数字。思路还是很巧妙的,完整代码如下:

class Solution {
public:
    int singleNumber(vector<int>& nums)
    {
        int ans = 0;
        for (int i = 0; i < nums.size(); i++) {
            ans ^= nums[i];
        }
        return ans;
    }
};

本代码提交AC,用时16MS。

LeetCode Majority Element II

229. Majority Element II2

Given an integer array of size n, find all elements that appear more than ⌊ n/3 ⌋ times.

Note: The algorithm should run in linear time and in O(1) space.

Example 1:

Input: [3,2,3]
Output: [3]

Example 2:

Input: [1,1,1,3,3,2,2,2] Output: [1,2]29. Majority Element II

本题要求一个数组中出现次数大于 ⌊ n/3 ⌋的所有数字,上一题LeetCode Majority Element是求数组中出现次数大于 ⌊ n/2 ⌋的数字,而且保证数组中有且仅有一个这样的众数。 在本题中,并不一定存在符合题意的众数存在,如果存在,最多有2个这样的众数,可以用反证法,如果有3个这样的众数,那么每个元素的出现次数最多是n/3,不可能超过。 思路还可以用上一题的摩尔投票法,即我们假设最多存在两个这样的众数,且随便设置这两个不同的众数为0和1,然后用摩尔投票法过一遍数组,看看最终记录的众数是哪两个。 最后再过一遍数组,统计一下这两个数的次数是否超过n/3。 完整代码如下:

class Solution {
public:
    vector<int> majorityElement(vector<int>& nums)
    {
        int num1 = 0, num2 = 1, cnt1 = 0, cnt2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == num1) { // (1)
                cnt1++;
            }
            else if (nums[i] == num2) { // (2)
                cnt2++;
            }
            else if (cnt1 == 0) { // (3)
                num1 = nums[i];
                cnt1++;
            }
            else if (cnt2 == 0) { // (4)
                num2 = nums[i];
                cnt2++;
            }
            else {
                cnt1–;
                cnt2–;
            }
        }
        vector<int> ans;
        cnt1 = 0;
        cnt2 = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == num1)
                cnt1++;
            else if (nums[i] == num2)
                cnt2++;
        }
        if (cnt1 > nums.size() / 3)
            ans.push_back(num1);
        if (cnt2 > nums.size() / 3)
            ans.push_back(num2);
        return ans;
    }
};

注意代码中的(1)(2)判断必须在(3)(4)判断前面,因为如果遇到[8,8,7,7,7]这样的样例,如果(3)(4)在前面的话,num1和num2就都会被赋值为8,导致丢掉了8这个结果。还有我们一开始必须给num1和num2随便设置不同的两个数。
本代码提交AC,用时16MS。

LeetCode Majority Element

169. Majority Element

Given an array of size n, find the majority element. The majority element is the element that appears more than ⌊ n/2 ⌋ times.

You may assume that the array is non-empty and the majority element always exist in the array.

Example 1:

Input: [3,2,3]
Output: 3

Example 2:

Input: [2,2,1,1,1,2,2]
Output: 2

要求出一个数组中的多数元素,这里的多数元素是指出现次数多于数组长度的一半。 很有意思的一个题目,前几天听旁边一个组的同学面试回来就说被问到了这个题。 常规思路是把数组排序,然后取⌊ n/2 ⌋这个元素,这种思路的算法复杂度为$O(nlgn)$的代码如下:

class Solution {
public:
    int majorityElement(vector<int>& nums)
    {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};

本代码提交AC,用时52MS。
还有另一种巧妙的思路,假设一个数组是[1,1,2,2,1,1,3,1],多数元素是1,如果从数组中同时删除元素1和2,剩余的数组[1,2,1,1,3,1]中,多数元素还是1。也就是说从数组中删除两个不同的元素,剩余数组中的多数元素和原数组中的多数元素是一样的。这种算法好像叫做摩尔投票法
根据这种思路,我们定义两个变量ans和cnt,初始时cnt=0。当cnt==0时,此时是原始数组或者删除了两个不同的元素。以后每次,遇到和ans相同的元素,cnt++,否则cnt–。cnt–就相当于删掉两个不同的元素。
这种思路只需要遍历一遍原数组,时间复杂度是$O(n)$,完整代码如下:

class Solution {
public:
    int majorityElement(vector<int>& nums)
    {
        int ans, cnt = 0;
        for (int i = 0; i < nums.size(); i++) {
            if (cnt == 0) {
                ans = nums[i];
                cnt++;
            }
            else {
                if (nums[i] == ans)
                    cnt++;
                else
                    cnt–;
            }
        }
        return ans;
    }
};

本代码提交AC,用时22MS。果然比之前的快很多。

LeetCode Pascal’s Triangle II

119. Pascal’s Triangle II

Given a non-negative index k where k ≤ 33, return the kth index row of the Pascal’s triangle.

Note that the row index starts from 0.


In Pascal’s triangle, each number is the sum of the two numbers directly above it.

Example:

Input: 3
Output: [1,3,3,1]

Follow up:

Could you optimize your algorithm to use only O(k) extra space?


本题在LeetCode Pascal’s Triangle的基础上,要求只用O(k)空间复杂度,输出杨辉三角的第k行结果。
关键在于只能用O(k)的空间。我们要算第k行的结果,只要知道第k-1行结果就可以了,所以可以用O(k)的空间保留前一行的结果。每计算到一行就交换一下ans和tmp,完整代码如下:

class Solution {
public:
    vector<int> getRow(int rowIndex)
    {
        vector<int> ans(rowIndex + 1, 1), tmp(rowIndex + 1, 1);
        if (rowIndex == 0 || rowIndex == 1)
            return ans;
        for (int row = 2; row <= rowIndex; row++) {
            for (int col = 1; col < row; col++) {
                tmp[col] = ans[col – 1] + ans[col];
            }
            swap(ans, tmp);
        }
        return ans;
    }
};

本代码提交AC,用时0MS。

其实这题不需要tmp数组也行,就是每行算的时候从后往前算,因为ans[col]只会用到ans[col]和ans[col-1],所以从后往前填数组,即使覆盖了之前的结果,也对前面的计算没有影响。
这种思路在DP解背包问题中很常见,完整代码如下:

class Solution {
public:
    vector<int> getRow(int rowIndex)
    {
        vector<int> ans(rowIndex + 1, 1);
        if (rowIndex == 0 || rowIndex == 1)
            return ans;
        for (int row = 2; row <= rowIndex; row++) {
            for (int col = row – 1; col > 0; col–) {
                ans[col] += ans[col – 1];
            }
        }
        return ans;
    }
};

本代码提交AC,用时0MS。