数学运算问题

197 阅读3分钟

参考:

  1. 微软面试题解析:丑数系列算法
  2. 常用的位操作
  3. 如何高效进行模幂运算

常用公式

微信图片_20230526100327.jpg

求最大公约数

// 求最大公约数
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^yyx相乘,x*yyx相加。所以只要会写快速幂那就会写快速乘。

丑数

微信图片_20230510142458.png

序号题目完成
263. 丑数
264. 丑数 II
1201. 丑数 III
313. 超级丑数
剑指 Offer 49. 丑数

263. 丑数

// 自己写了一个,效率一般
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;
    }
}

264. 丑数 II

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];
    }
}

1201. 丑数 III

我的思路

把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表列序号

168. 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();
    }
}

171. Excel表列序号

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 的平方根

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. 超级次方

231. 2的幂

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;
    }
}

326. 3的幂

//方法一,试除法
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;
    }
}

342. 4的幂

是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出现在偶数位,用两个条件:

  1. 肯定是2的幂
  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;
    }
}

50. Pow(x, n)

//递归
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;
    }
}

372. 超级次方

要想办法把问题转换为子问题。

参考labuladong的解法

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. 最大单词长度乘积

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)