参考:
常用公式
求最大公约数
// 求最大公约数
private long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
求最小公倍数
// 求最小公倍数
private long lcm(long a, long b) {
return a * (b / gcd(a, b));
}
快速幂
// 递归写法
public double quickPow(double x, long n) {
if (n == 0) {
return 1.0;
}
double tmp = quickPow(x, n / 2);
if (n % 2 == 0) {
// 偶数
return tmp * tmp;
} else {
// 奇数
return tmp * tmp * x;
}
}
// 迭代写法
public double quickPow(double x, long n) {
double x_contribute = x;
double ans = 1.0;
while (n > 0) {
if (n % 2 == 1) {
ans *= x_contribute;
}
x_contribute *= x_contribute;
n /= 2;
}
return ans;
}
快速乘
快速乘和快速幂的区别就是把乘法换成加法,x^y是y个x相乘,x*y是y个x相加。所以只要会写快速幂那就会写快速乘。
丑数
| 序号 | 题目 | 完成 |
|---|---|---|
| 263. 丑数 | ✅ | |
| 264. 丑数 II | ✅ | |
| 1201. 丑数 III | ✅ | |
| 313. 超级丑数 | ✅ | |
| 剑指 Offer 49. 丑数 | ✅ |
// 自己写了一个,效率一般
class Solution {
public boolean isUgly(int n) {
if (n <= 0) {
return false;
}
List<Integer> factors = new ArrayList<>();
factors.add(2);
factors.add(3);
factors.add(5);
for (int factor : factors) {
n = divide(n, factor);
if (n == 1 || factors.contains(n)) {
return true;
}
}
return false;
}
public int divide(int n, int x) {
// 能够整除,一直除下去,不能够整除,返回自身
return n % x == 0 ? divide(n / x, x) : n;
}
}
// labuladong的写法,更简洁..
class Solution {
public boolean isUgly(int n) {
if (n <= 0) {
return false;
}
while (n % 2 == 0) {
n /= 2;
}
while (n % 3 == 0) {
n /= 3;
}
while (n % 5 == 0) {
n /= 5;
}
return n == 1;
}
}
class Solution {
public int nthUglyNumber(int n) {
int[] ans = new int[n + 1];
int ugly2 = 1;
int ugly3 = 1;
int ugly5 = 1;
int factor2 = 0;
int factor3 = 0;
int factor5 = 0;
// 第一个数字是1
ans[0] = 1;
int i = 1;
while (i <= n) {
ugly2 = ans[factor2] * 2;
ugly3 = ans[factor3] * 3;
ugly5 = ans[factor5] * 5;
if (ugly2 <= ugly3 && ugly2 <= ugly5) {
// 因为不同的路线上的数可能相等,所以需要特殊处理
// 当这个元素已经被加过,直接跳过
if (ans[i - 1] != ugly2) {
ans[i++] = ugly2;
}
factor2++;
} else if (ugly3 <= ugly5) {
if (ans[i - 1] != ugly3) {
ans[i++] = ugly3;
}
factor3++;
} else {
if (ans[i - 1] != ugly5) {
ans[i++] = ugly5;
}
factor5++;
}
}
return ans[n - 1];
}
}
// 看了下labuladong的写法,处理不太一样,整体思路是差不多的
class Solution {
public int nthUglyNumber(int n) {
// 可以理解为三个指向有序链表头结点的指针
int p2 = 1, p3 = 1, p5 = 1;
// 可以理解为三个有序链表的头节点的值
int product2 = 1, product3 = 1, product5 = 1;
// 可以理解为最终合并的有序链表(结果链表)
int[] ugly = new int[n + 1];
// 可以理解为结果链表上的指针
int p = 1;
// 开始合并三个有序链表
while (p <= n) {
// 取三个链表的最小结点
int min = Math.min(Math.min(product2, product3), product5);
// 接到结果链表上
ugly[p] = min;
p++;
// 前进对应有序链表上的指针
// 这边没有用else,如果出现相等的元素,在每一条相等的线上都会走一步
if (min == product2) {
product2 = 2 * ugly[p2];
p2++;
}
if (min == product3) {
product3 = 3 * ugly[p3];
p3++;
}
if (min == product5) {
product5 = 5 * ugly[p5];
p5++;
}
}
// 返回第 n 个丑数
return ugly[n];
}
}
我的思路
把2,3,5换成了a,b,c。
并且只需要被a,b,c整除即可,不需要限制因子只可以为a,b,c,这样简化了问题,就不需要从丑数序列里取因子,直接从1开始递增加1即可。
需要注意1已经不满足条件了。
需要考虑数组越界情况。
不过发现,这样做会超时,看了题解,原来需要用互斥原理。
目标x内能被a,b,c整除的数的个数为:x/a + x/b + x/c - x/lcm_ab - x/lcm_ac - x/lcm_bc + x/lcm_abc
class Solution {
public int nthUglyNumber(int n, int a, int b, int c) {
long left = 0;
long right = min(a,b,c) * n * 1L + 1;
while(left < right){
long mid = left + (right - left)/2;
long count = count(mid, a, b, c);
if(count >= n){
right = mid;
}else{
left = mid + 1;
}
}
return (int)left;
}
public long count(long x, int a, int b, int c){
long lcm_ab = lcm(a,b);
long lcm_bc = lcm(c,b);
long lcm_ac = lcm(a,c);
long lcm_abc = lcm(a,b,c);
return x/a + x/b + x/c - x/lcm_ab - x/lcm_ac - x/lcm_bc + x/lcm_abc;
}
public long lcm(int a, long b) {
return a * 1L* (b / gcd(a, b));
}
public long lcm(int a, int b, int c){
return lcm(a, lcm(b,c));
}
public long gcd(long a, long b) {
return b == 0 ? a : gcd(b, a % b);
}
public int min(int a, int b, int c){
return Math.min(a, Math.min(b,c));
}
}
进制转换
| 序号 | 题目 | 完成 |
|---|---|---|
| 168. Excel表列名称 | ✅ | |
| 171. Excel表列序号 | ✅ |
class Solution {
public String convertToTitle(int columnNumber) {
StringBuilder sb = new StringBuilder();
int p = columnNumber;
while (p > 0) {
p--;
sb.append((char) (p % 26 + 'A'));
p = p / 26;
}
sb.reverse();
return sb.toString();
}
}
class Solution {
public int titleToNumber(String columnTitle) {
char[] chars = columnTitle.toCharArray();
int len = chars.length;
int val = 0;
for (int i = 0; i < len; i++) {
val += (chars[i] - 'A' + 1) * Math.pow(26, len - i - 1);
}
return val;
}
}
平方根
| 序号 | 题目 | 完成 |
|---|---|---|
| 69. x 的平方根 |
// 用二分算法解决了
class Solution {
public int mySqrt(int x) {
int left = 0;
int right = x;
int ans = 0;
while (left <= right) {
int mid = left + (right - left) / 2;
long val = (long) mid * mid;
// 找左边界
if (val == x) {
return mid;
} else if (val < x) {
left = mid + 1;
} else {
right = mid - 1;
// 因为存在不能被整开平方根的情况,我们可以认为是小于目标的第一个数,所以从right大于的时候就慢慢减,直到退出的时候,right就是对应的值
ans = right;
}
}
return right;
}
}
幂
| 序号 | 题目 | 完成 |
|---|---|---|
| 231. 2的幂 | ✅ | |
| 326. 3的幂 | ✅ | |
| 342. 4的幂 | ✅ | |
| 50. Pow(x, n) | ✅ | |
| 372. 超级次方 | ✅ |
2的幂比较容易,因为但凡一个数是2的幂,那么它肯定只有一位为1,用n&(n-1)去除最后一位的1,结果肯定是0。
class Solution {
public boolean isPowerOfTwo(int n) {
if (n < 0) {
return false;
}
return (n & (n - 1)) == 0;
}
}
//方法一,试除法
class Solution {
public boolean isPowerOfThree(int n) {
if (n <= 0) {
return false;
}
while (n % 3 == 0) {
n = n / 3;
}
// 整除到3^0,如果等于1,说明是3的幂,如果不等于1,说明还有其他除数
return n == 1;
}
}
//方法二:不需要循环或整除的方法
class Solution {
public boolean isPowerOfThree(int n) {
if (n <= 0) {
return false;
}
// 1162261467 = 3 ^ 19 ,是符合条件的最大的3的幂,如果n为3的幂,那么n肯定是1162261467的一个约数
// 是它的约数表示可以被它整除
return (1162261467 % n) == 0;
}
}
是2的幂不一定是4的幂,4的幂一定是2的幂。
class Solution {
public boolean isPowerOfFour(int n) {
if (n <= 0) {
return false;
}
while (n % 4 == 0) {
n = n / 4;
}
return n == 1;
}
}
4的幂肯定是只有一个1,并且1出现在偶数位,用两个条件:
- 肯定是2的幂
- 构造一个只有奇数位有1的数
1010 1010 1010 1010 1010 1010 1010 1010,与之想与,得到的如果是0,说明是4的幂。
class Solution {
public boolean isPowerOfFour(int n) {
return n > 0 && (n & (n - 1)) == 0 && (n & 0xaaaaaaaa) == 0;
}
}
//递归
class Solution {
public double myPow(double x, int n) {
if (n == 0) {
return 1.0;
}
long N = n;
if (n < 0) {
return 1.0 / quickPow(x, -N);
}
return quickPow(x, N);
}
public double quickPow(double x, long n) {
if (n == 0) {
return 1.0;
}
double tmp = quickPow(x, n / 2);
if (n % 2 == 0) {
// 偶数
return tmp * tmp;
} else {
// 奇数
return tmp * tmp * x;
}
}
}
//leetcode submit region end(Prohibit modification and deletion)
// 迭代解法
class Solution {
public double myPow(double x, int n) {
if (n == 0) {
return 1.0;
}
long N = n;
if (n < 0) {
return 1.0 / quickPow(x, -N);
}
return quickPow(x, N);
}
public double quickPow(double x, long n) {
double x_contribute = x;
double ans = 1.0;
while (n > 0) {
if (n % 2 == 1) {
ans *= x_contribute;
}
x_contribute *= x_contribute;
n /= 2;
}
return ans;
}
}
要想办法把问题转换为子问题。
class Solution {
int base = 1337;
public int superPow(int a, int[] b) {
int len = b.length;
if (len == 0) {
return 1;
}
int x = b[len - 1];
int[] c = Arrays.copyOfRange(b, 0, len - 1);
return (myPow(a, x) * myPow(superPow(a, c), 10)) % base;
}
public int myPow(int a, int k) {
if (k == 0) return 1;
a %= base;
if (k % 2 == 1) {
// k 是奇数
return (a * myPow(a, k - 1)) % base;
} else {
// k 是偶数
int sub = myPow(a, k / 2);
return (sub * sub) % base;
}
}
}
整除
| 序号 | 题目 | 完成 |
|---|---|---|
| 29. 两数相除 | ✅ |
// 官方题解
class Solution {
public int divide(int dividend, int divisor) {
// 考虑被除数为最小值的情况
if (dividend == Integer.MIN_VALUE) {
if (divisor == 1) {
return Integer.MIN_VALUE;
}
if (divisor == -1) {
return Integer.MAX_VALUE;
}
}
// 考虑除数为最小值的情况
if (divisor == Integer.MIN_VALUE) {
return dividend == Integer.MIN_VALUE ? 1 : 0;
}
// 考虑被除数为 0 的情况
if (dividend == 0) {
return 0;
}
// 一般情况,使用二分查找
// 将所有的正数取相反数,这样就只需要考虑一种情况
boolean rev = false;
if (dividend > 0) {
dividend = -dividend;
rev = !rev;
}
if (divisor > 0) {
divisor = -divisor;
rev = !rev;
}
int left = 1, right = Integer.MAX_VALUE, ans = 0;
while (left <= right) {
// 注意溢出,并且不能使用除法
int mid = left + ((right - left) >> 1);
boolean check = quickAdd(divisor, mid, dividend);
if (check) {
// 说明大于或等于,需要减少
ans = mid;
// 注意溢出
if (mid == Integer.MAX_VALUE) {
break;
}
left = mid + 1;
} else {
right = mid - 1;
}
}
return rev ? -ans : ans;
}
// 快速乘
public boolean quickAdd(int y, int z, int x) {
// x 和 y 是负数,z 是正数
// 需要判断 z * y >= x 是否成立
int result = 0, add = y;
while (z != 0) {
if ((z & 1) != 0) {
// 需要保证 result + add >= x
if (result < x - add) {
return false;
}
result += add;
}
if (z != 1) {
// 需要保证 add + add >= x
if (add < x - add) {
return false;
}
add += add;
}
// 不能使用除法
z >>= 1;
}
return true;
}
}
位运算
| 序号 | 题目 | 完成 |
|---|---|---|
| 318. 最大单词长度乘积 | ✅ |
//leetcode submit region begin(Prohibit modification and deletion)
class Solution {
public int maxProduct(String[] words) {
int len = words.length;
// 用mask数组记录每一个字符串的字符出现情况
int[] mask = new int[len];
for (int i = 0; i < len; i++) {
String word = words[i];
for (char c : word.toCharArray()) {
// mask的低26位,每一位都表示字母是否存在
mask[i] |= 1 << (c - 'a');
}
}
int max = 0;
for (int i = 0; i < len; i++) {
for (int j = i + 1; j < len; j++) {
// 说明两个字母没有相交
if ((mask[i] & mask[j]) == 0) {
max = Math.max(max, words[i].length() * words[j].length());
}
}
}
return max;
}
}
//leetcode submit region end(Prohibit modification and deletion)