笔试面试中常见数学算法题

素数分解

每一个数都可以分解成素数的乘积,例如 84 = 22 31 50 71 110 130 170 * …

整除

令 x = 2m0 3m1 5m2 7m3 11m4 * …

令 y = 2n0 3n1 5n2 7n3 11n4 * …

如果 x 整除 y(y mod x == 0),则对于所有 i,mi <= ni。

最大公约数或最小公倍数

最大公约数

greatest common divisor,gcd

辗转相除法

int gcd(int a, int b) {
    return b == 0 ? a : gcd(b, a % b);
}

最小公倍数

least common multiple, lcm

int lcm(int a, int b) {
    return a * b / gcd(a, b);
}

204. 计数质数

统计所有小于非负整数 n 的质数的数量。

示例:

输入: 10
输出: 4
解释: 小于 10 的质数一共有 4 个, 它们是 2, 3, 5, 7 。

埃拉托斯特尼筛法:

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

进制转换

504. 七进制数

难度:简单

给定一个整数,将其转化为7进制,并以字符串形式输出。

示例 1:

输入: 100
输出: "202"

示例 2:

输入: -7
输出: "-10"

使用常规的取余,得到最后一位。

public String convertToBase7 (int num) {
    if (num == 0) {
        return "0";
    }
    boolean isNegative = num < 0;
    if (isNegative) {
        num = -num;
    }
    StringBuilder sb = new StringBuilder();
    while (num != 0) {
        sb.append(num % 7);
        num /= 7;
    }
    String ans = sb.reverse().toString();
    return isNegative ? "-" + ans : ans;
}

405. 数字转换为十六进制数

难度:简单

给定一个整数,编写一个算法将这个数转换为十六进制数。对于负整数,我们通常使用 补码运算 方法。
注意:

  1. 十六进制中所有字母( af )都必须是小写。
  2. 十六进制字符串中不能包含多余的前导零。如果要转化的数为0,那么以单个字符 ‘0’ 来表示;对于其他情况,十
    六进制字符串中的第一个字符将不会是0字符。
  3. 给定的数确保在32位有符号整数范围内。
  4. 不能使用任何由库提供的将数字直接转换或格式化为十六进制的方

示例 1:

输入:
26

输出:
"1a"

示例 2:

输入:
-1

输出:
"ffffffff"

与二进制数0b1111相与,得到最后一位,0b开头的数用来表示二进制数。

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;
    }
    return sb.reverse().toString();
}

168. Excel表列名称

难度:简单

给定一个正整数,返回它在 Excel 表中相对应的列名称。

例如,

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

示例 1:

输入: 1
输出: "A"

示例 2:

输入: 28
输出: "AB"

示例 3:

输入: 701
输出: "ZY"

实际上就是一个26进制的问题。因为是从1开始的,所以要将初始的 n 减一处理。

public String convertToTitle (int n) {
    if (n == 0) {
        return "";
    }
    n--;
    return convertToTitle(n / 26) + (char) (n % 26 + 'A');
}

阶乘

172. 阶乘后的零

难度:简单

给定一个整数 n,返回 n! 结果尾数中零的数量。

示例 1:

输入: 3
输出: 0
解释: 3! = 6, 尾数中没有零。

示例 2:

输入: 5
输出: 1
解释: 5! = 120, 尾数中有 1 个零.

说明: 你算法的时间复杂度应为 O(log n) 。


尾部的0肯定是2*5造成的,并且阶乘中,2的数量肯定比5多,所以只需要统计5的个数即可。

对于一个数 N,它所包含 5 的个数为:N/5 + N/5^2^ + N/5^3^ + …,其中 N/5 表示不大于 N 的数中 5 的倍数贡献一个 5,N/5^2^ 表示不大于 N 的数中 5^2^ 的倍数再贡献一个 5 …。

比如说26,小于 26 的数中:

5的倍数有 5,10,15,20,25,所以 N/5 = 5,贡献了 5个5 ;

5^2^的倍数有25,所以N/5^2^=1,再贡献1个5;

总共就是6个5。

递归法:

public int trailingZeroes (int n) {
    return n == 0 ? 0 : n / 5 + trailingZeroes(n / 5);
}

迭代法:

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

字符串加法减法

67. 二进制求和

难度:简单

给定两个二进制字符串,返回他们的和(用二进制表示)。

输入为非空字符串且只包含数字 10

示例 1:

输入: a = "11", b = "1"
输出: "100"

示例 2:

输入: a = "1010", b = "1011"
输出: "10101"

分别取两个指针,从字符串的末尾往头部遍历,使用常规的除法和取余,将结果加入到StringBuffer

public String addBinary(String a , String b)
{
    StringBuffer sb = new StringBuffer();
    int i = a.length() - 1, j = b.length() - 1;
    int carry = 0;
    while (i >= 0 || j >= 0)
    {
        int sum = carry;
        if (i >= 0)
            sum += a.charAt(i--) - '0';
        if (j >= 0)
            sum += b.charAt(j--) - '0';
        sb.append(sum % 2);
        carry = sum / 2;

    }
    if (carry != 0)
        sb.append(carry);
    return sb.reverse().toString();

}

415. 字符串相加

难度:简单

给定两个字符串形式的非负整数 num1num2 ,计算它们的和。

注意:

  1. num1num2 的长度都小于 5100.
  2. num1num2 都只包含数字 0-9.
  3. num1num2 都不包含任何前导零。
  4. 你不能使用任何內建 BigInteger 库, 也不能直接将输入的字符串转换为整数形式。

与上一题类似,但是这一题是十进制。

分别取两个指针,从字符串的末尾往头部遍历,使用常规的除法和取余,将结果加入到StringBuffer

public String addStrings (String num1, String num2) {
    StringBuilder sb = new StringBuilder();
    int carry = 0;
    int i = num1.length() - 1, j = num2.length() - 1;
    while (i >= 0 || j >= 0 || carry == 1) {
        int x = i < 0 ? 0 : num1.charAt(i--) - '0';
        int y = j < 0 ? 0 : num2.charAt(j--) - '0';
        sb.append((x + y + carry) % 10);
        carry = (x + y + carry) / 10;
    }
    return sb.reverse().toString();
}

相遇问题

462. 最少移动次数使数组元素相等 II

难度:中等

给定一个非空整数数组,找到使所有数组元素相等所需的最小移动数,其中每次移动可将选定的一个元素加1或减1。 您可以假设数组的长度最多为10000。

例如:

输入:
[1,2,3]

输出:
2

说明:
只有两个动作是必要的(记得每一步仅可使其中一个元素加1或减1): 

[1,2,3]  =>  [2,2,3]  =>  [2,2,2]

将每个数移动到中位数是步数最少的。

设 m 为中位数。a 和 b 是 m 两边的两个元素,且 b > a。要使 a 和 b 相等,它们总共移动的次数为 b - a,这个值等于 (b - m) + (m - a),也就是把这两个数移动到中位数的移动次数。

public int minMoves2 (int[] nums) {
    Arrays.sort(nums);
    int ans = 0;
    int l = 0, r = nums.length - 1;
    while (l < r) {
        ans += nums[r] - nums[l];
        l++;
        r--;
    }
    return ans;
}

多数投票问题

169. 求众数

难度:简单

给定一个大小为 n 的数组,找到其中的众数。众数是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。

你可以假设数组是非空的,并且给定的数组总是存在众数。

示例 1:

输入: [3,2,3]
输出: 3

示例 2:

输入: [2,2,1,1,1,2,2]
输出: 2

方法一:

排序数组,正中间那个数就是众数。

public int majorityElement(int[] nums) {
    Arrays.sort(nums);
    return nums[nums.length / 2];
}

方法二:Boyer-Moore 投票算法

设置一个计数器,和一个候选者。

遍历数组,如果当前元素跟候选者相同,计数器加一,否则减一。

当计数器为0时,候选者变成下一元素。

遍历完成后,候选者就是我们找的众数。

public int majorityElement(int[] nums) {
    int majority = nums[0];
    int count = 0;
    for (int num : nums) {
        if (count == 0) {
            majority = num;
        }
        count += (num == majority ? 1 : -1);
    }
    return majority;
}

其他

367. 有效的完全平方数

难度:简单

给定一个正整数 num,编写一个函数,如果 num 是一个完全平方数,则返回 True,否则返回 False。

说明:不要使用任何内置的库函数,如 sqrt

示例 1:

输入:16
输出:True

示例 2:

输入:14
输出:False

生成序列的方法:

完全平方数序列:1,4,9,16,25…

它们的间隔:1,3,5,7,9…

public boolean isPerfectSquare (int num) {
    int delta = 1;
    while (num > 0) {
        num -= delta;
        delta += 2;
    }
    return num == 0;
}

二分查找法:

如果一个数N是完全平方数,那么在小于 N 的数中,肯定存在 sqrt(N),用二分查找找这个数,如果找到了,N就是完全平方数,找不到就不是。

public boolean isPerfectSquare (int num) {
    long left = 1, right = num;
    if (num == 1) {
        return true;
    }
    while (left <= right) {
        long mid = left + (right - left) / 2;
        if (mid * mid == num) {
            return true;
        } else if (mid * mid > num) {
            right = mid - 1;
        } else {
            left = mid + 1;
        }
    }
    return false;
}

238. 除自身以外数组的乘积

难度:中等

给定长度为 n 的整数数组 nums,其中 n > 1,返回输出数组 output ,其中 output[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积。

示例:

输入: [1,2,3,4]
输出: [24,12,8,6]

说明:不要使用除法,且在 O(n) 时间复杂度内完成此题。

进阶:
你可以在常数空间复杂度内完成这个题目吗?( 出于对空间复杂度分析的目的,输出数组不被视为额外空间。)


一个数对应的output = 这个数 左边的数的乘积 * 这个数 右边的数的乘积

public int[] productExceptSelf (int[] nums) {
    int n = nums.length;
    int left = 1;
    int[] ans = new int[n];
    Arrays.fill(ans, 1);
    for (int i = 1; i < n; i++) {
        left *= nums[i - 1];
        ans[i] *= left;
    }
    int right = 1;
    for (int i = n - 2; i >= 0; i--) {
        right *= nums[i + 1];
        ans[i] *= right;
    }
    return ans;
}

628. 三个数的最大乘积

难度:简单

给定一个整型数组,在数组中找出由三个数组成的最大乘积,并输出这个乘积。

示例 1:

输入: [1,2,3]
输出: 6

示例 2:

输入: [1,2,3,4]
输出: 24

注意:

  1. 给定的整型数组长度范围是[3,104],数组中所有的元素范围是[-1000, 1000]。
  2. 输入的数组中任意三个数的乘积不会超出32位有符号整数的范围。

给定的数组中可能负数。

假如对数组排序,最大三个数的乘积,要么是最大的三个数相乘,要么是最小的两个数(两个负数)乘最大的一个数(正数)。

public int maximumProduct (int[] nums) {
    int max1 = Integer.MIN_VALUE;
    int max2 = Integer.MIN_VALUE;
    int max3 = Integer.MIN_VALUE;
    int min1 = Integer.MAX_VALUE;
    int min2 = Integer.MAX_VALUE;
    for (int num : nums) {
        if (num > max1) {
            max3 = max2;
            max2 = max1;
            max1 = num;
        } else if (num > max2) {
            max3 = max2;
            max2 = num;
        } else if (num > max3) {
            max3 = num;
        }

        if (num < min1) {
            min2 = min1;
            min1 = num;
        } else if (num < min2) {
            min2 = num;
        }
    }
    return Math.max(max1 * max2 * max3, min1 * min2 * max1);
}

 上一篇
常见排序算法题总结 常见排序算法题总结
常用排序算法总结 冒泡排序比较相邻的两个元素,如果前一个元素比后一个元素大,则交换它们的位置。第一趟做完后,最后一个元素肯定是数组中最大的元素。 开始第二趟比较,第二趟比较把最后一个元素排除在外。 重复以上步骤,直到没有一对元素需要比较。
2019-09-16
下一篇 
哈希表常见题目总结 哈希表常见题目总结
1. 两数之和难度:简单 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。 示例:
2019-09-16
  目录