CS-Notes/docs/notes/Leetcode 题解 - 数学.md

480 lines
14 KiB
Markdown
Raw Normal View History

2019-03-08 21:39:27 +08:00
# 素数分解
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
每一个数都可以分解成素数的乘积例如 84 = 2<sup>2</sup> \* 3<sup>1</sup> \* 5<sup>0</sup> \* 7<sup>1</sup> \* 11<sup>0</sup> \* 13<sup>0</sup> \* 17<sup>0</sup> \* …
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
# 整除
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
 x = 2<sup>m0</sup> \* 3<sup>m1</sup> \* 5<sup>m2</sup> \* 7<sup>m3</sup> \* 11<sup>m4</sup> \* …
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
 y = 2<sup>n0</sup> \* 3<sup>n1</sup> \* 5<sup>n2</sup> \* 7<sup>n3</sup> \* 11<sup>n4</sup> \* …
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
如果 x 整除 yy mod x == 0则对于所有 imi <= ni。
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 最大公约数最小公倍数
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
x  y 的最大公约数为gcd(x,y) =  2<sup>min(m0,n0)</sup> \* 3<sup>min(m1,n1)</sup> \* 5<sup>min(m2,n2)</sup> \* ...
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
x  y 的最小公倍数为lcm(x,y) =  2<sup>max(m0,n0)</sup> \* 3<sup>max(m1,n1)</sup> \* 5<sup>max(m2,n2)</sup> \* ...
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 生成素数序列
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[204. Count Primes (Easy)](https://leetcode.com/problems/count-primes/description/)
2019-03-08 20:31:07 +08:00
埃拉托斯特尼筛法在每次找到一个素数时,将能被素数整除的数排除掉。
```java
2019-03-08 21:39:27 +08:00
public int countPrimes(int n) {
    boolean[] notPrimes = new boolean[n + 1];
    int count = 0;
    for (int i = 2; i < n; i++) {
        if (notPrimes[i]) {
            continue;
        }
        count++;
        //  i * i 开始因为如果 k < i那么 k * i 在之前就已经被去除过了
        for (long j = (long) (i) * i; j < n; j += i) {
            notPrimes[(int) j] = true;
        }
    }
    return count;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
### 最大公约数
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
2019-03-08 20:31:07 +08:00
}
```
最小公倍数为两数的乘积除以最大公约数。
```java
2019-03-08 21:39:27 +08:00
int lcm(int a, int b) {
    return a * b / gcd(a, b);
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 使用位操作和减法求解最大公约数
2019-03-08 20:31:07 +08:00
[编程之美2.7](#)
2019-03-08 21:39:27 +08:00
对于 a  b 的最大公约数 f(a, b),有:
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
- 如果 a  b 均为偶数f(a, b) = 2\*f(a/2, b/2);
- 如果 a 是偶数 b 是奇数f(a, b) = f(a/2, b);
- 如果 b 是偶数 a 是奇数f(a, b) = f(a, b/2);
- 如果 a  b 均为奇数f(a, b) = f(b, a-b);
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
 2 和除 2 都可以转换为移位操作。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int gcd(int a, int b) {
    if (a < b) {
        return gcd(b, a);
    }
    if (b == 0) {
        return a;
    }
    boolean isAEven = isEven(a), isBEven = isEven(b);
    if (isAEven && isBEven) {
        return 2 * gcd(a >> 1, b >> 1);
    } else if (isAEven && !isBEven) {
        return gcd(a >> 1, b);
    } else if (!isAEven && isBEven) {
        return gcd(a, b >> 1);
    } else {
        return gcd(b, a - b);
    }
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
# 进制转换
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 7 进制
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[504. Base 7 (Easy)](https://leetcode.com/problems/base-7/description/)
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public String convertToBase7(int num) {
    if (num == 0) {
        return "0";
    }
    StringBuilder sb = new StringBuilder();
    boolean isNegative = num < 0;
    if (isNegative) {
        num = -num;
    }
    while (num > 0) {
        sb.append(num % 7);
        num /= 7;
    }
    String ret = sb.reverse().toString();
    return isNegative ? "-" + ret : ret;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
Java  static String toString(int num, int radix) 可以将一个整数转换为 radix 进制表示的字符串。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public String convertToBase7(int num) {
    return Integer.toString(num, 7);
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 16 进制
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[405. Convert a Number to Hexadecimal (Easy)](https://leetcode.com/problems/convert-a-number-to-hexadecimal/description/)
2019-03-08 20:31:07 +08:00
```html
Input:
26
Output:
"1a"
Input:
-1
Output:
"ffffffff"
```
负数要用它的补码形式。
```java
2019-03-08 21:39:27 +08:00
public String toHex(int num) {
    char[] map = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
    if (num == 0) return "0";
    StringBuilder sb = new StringBuilder();
    while (num != 0) {
        sb.append(map[num & 0b1111]);
        num >>>= 4; // 因为考虑的是补码形式因此符号位就不能有特殊的意义需要使用无符号右移左边填 0
    }
    return sb.reverse().toString();
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 26 进制
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[168. Excel Sheet Column Title (Easy)](https://leetcode.com/problems/excel-sheet-column-title/description/)
2019-03-08 20:31:07 +08:00
```html
2019-03-08 21:39:27 +08:00
1 -> A
2 -> B
3 -> C
2019-03-08 20:31:07 +08:00
...
2019-03-08 21:39:27 +08:00
26 -> Z
27 -> AA
28 -> AB
2019-03-08 20:31:07 +08:00
```
2019-03-08 21:39:27 +08:00
因为是从 1 开始计算的而不是从 0 开始因此需要对 n 执行 -1 操作。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public String convertToTitle(int n) {
    if (n == 0) {
        return "";
    }
    n--;
    return convertToTitle(n / 26) + (char) (n % 26 + 'A');
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
# 阶乘
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 统计阶乘尾部有多少个 0
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[172. Factorial Trailing Zeroes (Easy)](https://leetcode.com/problems/factorial-trailing-zeroes/description/)
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
尾部的 0  2 * 5 得来2 的数量明显多于 5 的数量因此只要统计有多少个 5 即可。
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
对于一个数 N它所包含 5 的个数为N/5 + N/5<sup>2</sup> + N/5<sup>3</sup> + ...其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5N/5<sup>2</sup> 表示不大于 N 的数中 5<sup>2</sup> 的倍数再贡献一个 5 ...。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int trailingZeroes(int n) {
    return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
如果统计的是 N! 的二进制表示中最低位 1 的位置只要统计有多少个 2 即可该题目出自 [编程之美2.2](#) 。和求解有多少个 5 一样2 的个数为 N/2 + N/2<sup>2</sup> + N/2<sup>3</sup> + ...
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
# 字符串加法减法
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 二进制加法
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[67. Add Binary (Easy)](https://leetcode.com/problems/add-binary/description/)
2019-03-08 20:31:07 +08:00
```html
2019-03-08 21:39:27 +08:00
a = "11"
b = "1"
Return "100".
2019-03-08 20:31:07 +08:00
```
```java
2019-03-08 21:39:27 +08:00
public String addBinary(String a, String b) {
    int i = a.length() - 1, j = b.length() - 1, carry = 0;
    StringBuilder str = new StringBuilder();
    while (carry == 1 || i >= 0 || j >= 0) {
        if (i >= 0 && a.charAt(i--) == '1') {
            carry++;
        }
        if (j >= 0 && b.charAt(j--) == '1') {
            carry++;
        }
        str.append(carry % 2);
        carry /= 2;
    }
    return str.reverse().toString();
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 字符串加法
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[415. Add Strings (Easy)](https://leetcode.com/problems/add-strings/description/)
2019-03-08 20:31:07 +08:00
字符串的值为非负整数。
```java
2019-03-08 21:39:27 +08:00
public String addStrings(String num1, String num2) {
    StringBuilder str = new StringBuilder();
    int carry = 0, i = num1.length() - 1, j = num2.length() - 1;
    while (carry == 1 || i >= 0 || j >= 0) {
        int x = i < 0 ? 0 : num1.charAt(i--) - '0';
        int y = j < 0 ? 0 : num2.charAt(j--) - '0';
        str.append((x + y + carry) % 10);
        carry = (x + y + carry) / 10;
    }
    return str.reverse().toString();
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
# 相遇问题
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 改变数组元素使所有的数组元素都相等
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[462. Minimum Moves to Equal Array Elements II (Medium)](https://leetcode.com/problems/minimum-moves-to-equal-array-elements-ii/description/)
2019-03-08 20:31:07 +08:00
```html
Input:
[1,2,3]
Output:
2
Explanation:
2019-03-08 21:39:27 +08:00
Only two moves are needed (remember each move increments or decrements one element):
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[1,2,3]  =>  [2,2,3]  =>  [2,2,2]
2019-03-08 20:31:07 +08:00
```
每次可以对一个数组元素加一或者减一,求最小的改变次数。
这是个典型的相遇问题,移动距离最小的方式是所有元素都移动到中位数。理由如下:
2019-03-08 21:39:27 +08:00
 m 为中位数。a  b  m 两边的两个元素 b > a。要使 a  b 相等它们总共移动的次数为 b - a这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
设数组长度为 N则可以找到 N/2  a  b 的组合使它们都移动到 m 的位置。
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 解法 1
2019-03-08 20:31:07 +08:00
先排序时间复杂度O(NlogN)
```java
2019-03-08 21:39:27 +08:00
public int minMoves2(int[] nums) {
    Arrays.sort(nums);
    int move = 0;
    int l = 0, h = nums.length - 1;
    while (l <= h) {
        move += nums[h] - nums[l];
        l++;
        h--;
    }
    return move;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 解法 2
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
使用快速选择找到中位数时间复杂度 O(N)
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int minMoves2(int[] nums) {
    int move = 0;
    int median = findKthSmallest(nums, nums.length / 2);
    for (int num : nums) {
        move += Math.abs(num - median);
    }
    return move;
2019-03-08 20:31:07 +08:00
}
2019-03-08 21:39:27 +08:00
private int findKthSmallest(int[] nums, int k) {
    int l = 0, h = nums.length - 1;
    while (l < h) {
        int j = partition(nums, l, h);
        if (j == k) {
            break;
        }
        if (j < k) {
            l = j + 1;
        } else {
            h = j - 1;
        }
    }
    return nums[k];
2019-03-08 20:31:07 +08:00
}
2019-03-08 21:39:27 +08:00
private int partition(int[] nums, int l, int h) {
    int i = l, j = h + 1;
    while (true) {
        while (nums[++i] < nums[l] && i < h) ;
        while (nums[--j] > nums[l] && j > l) ;
        if (i >= j) {
            break;
        }
        swap(nums, i, j);
    }
    swap(nums, l, j);
    return j;
2019-03-08 20:31:07 +08:00
}
2019-03-08 21:39:27 +08:00
private void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
# 多数投票问题
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 数组中出现次数多于 n / 2 的元素
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[169. Majority Element (Easy)](https://leetcode.com/problems/majority-element/description/)
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
先对数组排序最中间那个数出现次数一定多于 n / 2。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int majorityElement(int[] nums) {
    Arrays.sort(nums);
    return nums[nums.length / 2];
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
可以利用 Boyer-Moore Majority Vote Algorithm 来解决这个问题使得时间复杂度为 O(N)。可以这么理解该算法使用 cnt 来统计一个元素出现的次数当遍历到的元素和统计元素不相等时 cnt--。如果前面查找了 i 个元素 cnt == 0说明前 i 个元素没有 majority或者有 majority但是出现的次数少于 i / 2因为如果多于 i / 2 的话 cnt 就一定不会为 0。此时剩下的 n - i 个元素中majority 的数目依然多于 (n - i) / 2因此继续查找就能找出 majority。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int majorityElement(int[] nums) {
    int cnt = 0, majority = nums[0];
    for (int num : nums) {
        majority = (cnt == 0) ? num : majority;
        cnt = (majority == num) ? cnt + 1 : cnt - 1;
    }
    return majority;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
# 其它
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
## 平方数
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[367. Valid Perfect Square (Easy)](https://leetcode.com/problems/valid-perfect-square/description/)
2019-03-08 20:31:07 +08:00
```html
2019-03-08 21:39:27 +08:00
Input: 16
Returns: True
2019-03-08 20:31:07 +08:00
```
平方序列1,4,9,16,..
间隔3,5,7,...
2019-03-08 21:39:27 +08:00
间隔为等差数列使用这个特性可以得到从 1 开始的平方序列。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public boolean isPerfectSquare(int num) {
    int subNum = 1;
    while (num > 0) {
        num -= subNum;
        subNum += 2;
    }
    return num == 0;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 3  n 次方
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[326. Power of Three (Easy)](https://leetcode.com/problems/power-of-three/description/)
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public boolean isPowerOfThree(int n) {
    return n > 0 && (1162261467 % n == 0);
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 乘积数组
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[238. Product of Array Except Self (Medium)](https://leetcode.com/problems/product-of-array-except-self/description/)
2019-03-08 20:31:07 +08:00
```html
2019-03-08 21:39:27 +08:00
For example, given [1,2,3,4], return [24,12,8,6].
2019-03-08 20:31:07 +08:00
```
给定一个数组,创建一个新数组,新数组的每个元素为原始数组中除了该位置上的元素之外所有元素的乘积。
2019-03-08 21:39:27 +08:00
要求时间复杂度为 O(N),并且不能使用除法。
2019-03-08 20:31:07 +08:00
```java
2019-03-08 21:39:27 +08:00
public int[] productExceptSelf(int[] nums) {
    int n = nums.length;
    int[] products = new int[n];
    Arrays.fill(products, 1);
    int left = 1;
    for (int i = 1; i < n; i++) {
        left *= nums[i - 1];
        products[i] *= left;
    }
    int right = 1;
    for (int i = n - 2; i >= 0; i--) {
        right *= nums[i + 1];
        products[i] *= right;
    }
    return products;
2019-03-08 20:31:07 +08:00
}
```
2019-03-08 21:39:27 +08:00
## 找出数组中的乘积最大的三个数
2019-03-08 20:31:07 +08:00
2019-03-08 21:39:27 +08:00
[628. Maximum Product of Three Numbers (Easy)](https://leetcode.com/problems/maximum-product-of-three-numbers/description/)
2019-03-08 20:31:07 +08:00
```html
2019-03-08 21:39:27 +08:00
Input: [1,2,3,4]
Output: 24
2019-03-08 20:31:07 +08:00
```
```java
2019-03-08 21:39:27 +08:00
public int maximumProduct(int[] nums) {
    int max1 = Integer.MIN_VALUE, max2 = Integer.MIN_VALUE, max3 = Integer.MIN_VALUE, min1 = Integer.MAX_VALUE, min2 = Integer.MAX_VALUE;
    for (int n : nums) {
        if (n > max1) {
            max3 = max2;
            max2 = max1;
            max1 = n;
        } else if (n > max2) {
            max3 = max2;
            max2 = n;
        } else if (n > max3) {
            max3 = n;
        }
        if (n < min1) {
            min2 = min1;
            min1 = n;
        } else if (n < min2) {
            min2 = n;
        }
    }
    return Math.max(max1*max2*max3, max1*min1*min2);
2019-03-08 20:31:07 +08:00
}
```