简介
数学类型题里,有简单的九年义务教育类数学知识点,比如求矩形面积,还有求斜率等。也有比较高级的数学知识点,比如巴什博奕、格雷编码这类题,像这类涉及高级数学知识点的题,我个人建议是了解多做,但不用纠结。这类题你如果知道这种知识点,代码就很简单,但是不知道怎么都做不出来。其实考的已经不是代码算法问题了,而更多的是数学了,如果你是面试官的话可能也不会想出这种背答案类型的题,没有筛选意义。当然学有余力的情况也可以多学多了解,武装到牙齿。
理论基础
数学是利用符号语言研究数量、结构、变化以及空间等概念的一门学科,从某种角度看属于形式科学的一种。数学透过抽象化和逻辑推理的使用,由计数、计算、量度和对物体形状及运动的观察而产生。数学家们拓展这些概念,为了公式化新的猜想以及从选定的公理及定义中建立起严谨推导出的定理。
基础数学的知识与运用是个人与团体生活中不可或缺的一环。 对数学基本概念的完善,早在古埃及、美索不达米亚及古印度内的古代数学文本便可观见,而在古希腊那里有更为严谨的处理。从那时开始,数学的发展便持续不断地小幅进展,至 16 世纪的文艺复兴时期,因为新的科学发现和数学革新两者的交互,致使数学的加速发展,直至今日。数学并成为许多国家及地区的教育范畴中的一部分。
今日,数学使用在不同的领域中,包括 科学、工程、医学、经济学和金融学等。数学对这些领域的应用通常被称为应用数学,有时亦会激起新的数学发现,并导致全新学科的发展,例如物理学的实质性发展中建立的某些理论激发数学家对于某些问题的不同角度的思考。数学家也研究纯数学,就是数学本身的实质性内容,而不以任何实际应用为目标。虽然许多研究以纯数学开始,但其过程中也发现许多应用之处。
计算机的尽头是数学,数学的尽头是哲学,哲学的尽头是神学。
解题心得
- 数学和计算机联系紧密,用对数学公式,对提升时间复杂度大有帮助。
- 数学类型里,涉及到高级数学知识点的背答案题,不用过多纠结,了解即可,不会成为面试重点。
- 要学会用计算机语言表达数学公式。
- 学有余力再把涉及高级数学知识的题好好背一背。
算法题目
7. 整数反转
题目解析:直接转成范围更大的long,反转后再强转输出。
代码如下:
/**
* 数学
*/
class Solution {
public int reverse(int x) {
long ret = 0;
while (x != 0) {
int pop = x % 10;
x /= 10;
ret = ret * 10 + pop;
}
if (ret > Integer.MAX_VALUE || ret < Integer.MIN_VALUE) return 0;
return (int) ret;
}
}
9. 回文数
题目解析:整数取反后,再比较两值是否相等即可。
代码如下:
/**
* 数学
*/
class Solution {
public boolean isPalindrome(int x) {
// 如果为负数,刚一定不是回文,直接返回false
if (x < 0) {
return false;
}
int reverseVal = 0;
int val = x;
// 对值进行反转
while (val != 0) {
int pop = val % 10;
val /= 10;
reverseVal = reverseVal * 10 + pop;
}
if (reverseVal == x) {
return true;
} else {
return false;
}
}
}
50. Pow(x, n)
题目解析:折半计算 x^n = (x2)(n/2) 大大减少运算量。
代码如下:
/**
* 数学
*/
class Solution {
public double myPow(double x, int n) {
double res = 1.0D;
// i每次操作都除2,不用一直相乘
// i = 1 时,i /= 2 就等于0了
for (int i = n; i != 0; i /= 2) {
if (i % 2 != 0) {
res *= x;
}
x *= x;
}
// 根据n的正负性,返回正数,还是倒数
return n < 0 ? 1 / res : res;
}
}
60. 排列序列
题目解析:回溯和递归会超时,主要是用数学方法优化,数字是从1开始的连续自然数,排序结果可推。表达式为:index = k / (n-1)! 此时 index 即为剩余候选数字的索引。
代码如下:
/**
* 数学
*/
class Solution {
public String getPermutation(int n, int k) {
// 用StringBuilder,比直接用String快1ms,多击败50%选手
StringBuilder res = new StringBuilder();
// n为0,直接返回
if (n == 0) {
return res.toString();
}
// 声明候选数字组
LinkedList<Integer> nums = new LinkedList();
for (int i = 1; i <= n; i++) {
nums.add(i);
}
k = k - 1;
// 查找候选数字
while (n != 0) {
// 计算需要第几个侯选值
int index = k / helper(n - 1);
res.append(nums.remove(index));
// 计算k剩余值
k %= helper(n - 1);
n--;
}
return res.toString();
}
// 实现阶乘
private int helper(int n) {
int sum = 1;
for (int i = n; i > 0; i--) {
sum *= i;
}
return sum;
}
}
66. 加一
题目解析:递归依次加一即可。
代码如下:
/**
* 数学
*/
class Solution {
public int[] plusOne(int[] digits) {
int[] res = helper(digits, digits.length - 1);
return res;
}
// 递归加一
public int[] helper(int[] digits, int index) {
// 如果进位到首位,直接新建数组返回
if (index == -1) {
int[] temp = new int[digits.length + 1];
temp[0] = 1;
return temp;
}
// 不等于9就加1,等于9就进一位
if (digits[index] != 9) {
digits[index] = digits[index] + 1;
return digits;
} else {
digits[index] = 0;
digits = helper(digits, index - 1);
return digits;
}
}
}
69. x 的平方根
题目解析:披着数学的外衣,用二分法。
代码如下:
/**
* 数学
*/
class Solution {
public int mySqrt(int x) {
// 特殊值处理
if (x == 0 || x == 1) {
return x;
}
int left = 0;
int right = x;
int mid = 0;
// 二分法
while (left <= right) {
mid = left + (right - left) / 2;
if (mid > x / mid) {
right = mid - 1;
} else if (mid < x / mid) {
left = mid + 1;
} else {
return mid;
}
}
// 否则返回r
return right;
}
}
149. 直线上最多的点数
题目解析:固定一点,找其他点和这个点组成直线,统计他们的斜率,最多次数的斜率即为我们的答案。
代码如下:
/**
* 数学 + 哈希表
*/
class Solution {
public int maxPoints(int[][] points) {
int n = points.length;
if (n <= 2) {
return n;
}
int ret = 0;
for (int i = 0; i < n; i++) {
if (ret >= n - i || ret > n / 2) {
break;
}
Map<Integer, Integer> map = new HashMap<Integer, Integer>();
for (int j = i + 1; j < n; j++) {
int x = points[i][0] - points[j][0];
int y = points[i][1] - points[j][1];
if (x == 0) {
y = 1;
} else if (y == 0) {
x = 1;
} else {
if (y < 0) {
x = -x;
y = -y;
}
int gcdXY = gcd(Math.abs(x), Math.abs(y));
x /= gcdXY;
y /= gcdXY;
}
int key = y + x * 20001;
map.put(key, map.getOrDefault(key, 0) + 1);
}
int maxn = 0;
for (Map.Entry<Integer, Integer> entry: map.entrySet()) {
int num = entry.getValue();
maxn = Math.max(maxn, num + 1);
}
ret = Math.max(ret, maxn);
}
return ret;
}
public int gcd(int a, int b) {
return b != 0 ? gcd(b, a % b) : a;
}
}
168. Excel表列名称
题目解析:该整数减1,然后按照26进制处理即可,因为二十六进制从0开始的。
代码如下:
/**
* 数学
*/
class Solution {
public String convertToTitle(int columnNumber) {
StringBuffer sb = new StringBuffer();
while (columnNumber != 0) {
columnNumber--;
sb.append((char)(columnNumber % 26 + 'A'));
columnNumber /= 26;
}
return sb.reverse().toString();
}
}
171. Excel 表列序号
题目解析:二十六进制转十进制。
代码如下:
/**
* 数学
*/
class Solution {
public int titleToNumber(String columnTitle) {
int number = 0;
int multiple = 1;
for (int i = columnTitle.length() - 1; i >= 0; i--) {
int k = columnTitle.charAt(i) - 'A' + 1;
number += k * multiple;
multiple *= 26;
}
return number;
}
}
172. 阶乘后的零
题目解析:可得三点结论:一、只需要找因子2,5的个数,就能确定0的个数。二、2的个数比5多,所以只要找因子5的个数就行。三、每间隔 5 个数有一个数可以被 5 整除, 然后在这些可被 5 整除的数中,每间隔5个数又有一个可以被 25 整除,故要再除一次,… 直到结果为0,表示没有能继续被5整除的数了。
代码如下:
/**
* 数学
*/
class Solution {
public int trailingZeroes(int n) {
int count = 0;
while(n >= 5) {
count += n / 5;
n /= 5;
}
return count;
}
}
204. 计数质数
题目解析:筛选法:如果x是质数,那么大于x的x的倍数 2x, 3x … 一定不是质数,做标记处理。
代码如下:
/**
* 数学
*/
class Solution {
public int countPrimes(int n) {
int[] isPrime = new int[n];
// 初始化
Arrays.fill(isPrime, 1);
int res = 0;
for (int i = 2; i < n; ++i) {
if (isPrime[i] == 1) {
res += 1;
if ((long) i * i < n) {
for (int j = i * i; j < n; j += i) {
// 合数标记为 0
isPrime[j] = 0;
}
}
}
}
return res;
}
}
223. 矩形面积
题目解析:几何:总面积 = 单个面积1 + 单个面积2 - 重叠面积。
代码如下:
/**
* 数学
*/
class Solution {
public int computeArea(int ax1, int ay1, int ax2, int ay2, int bx1, int by1, int bx2, int by2) {
int area1 = (ax2 - ax1) * (ay2 - ay1), area2 = (bx2 - bx1) * (by2 - by1);
int overlapWidth = Math.min(ax2, bx2) - Math.max(ax1, bx1), overlapHeight = Math.min(ay2, by2) - Math.max(ay1, by1);
int overlapArea = Math.max(overlapWidth, 0) * Math.max(overlapHeight, 0);
return area1 + area2 - overlapArea;
}
}
233. 数字 1 的个数
题目解析:枚举每一数位上 11 的个数。
代码如下:
/**
* 数学
*/
class Solution {
public int countDigitOne(int n) {
// mulk 表示 10^k
// 在下面的代码中,可以发现 k 并没有被直接使用到(都是使用 10^k)
// 但为了让代码看起来更加直观,这里保留了 k
long mulk = 1;
int ans = 0;
for (int k = 0; n >= mulk; ++k) {
ans += (n / (mulk * 10)) * mulk + Math.min(Math.max(n % (mulk * 10) - mulk + 1, 0), mulk);
mulk *= 10;
}
return ans;
}
}
241. 为运算表达式设计优先级
题目解析:首先对 expression 做一个预处理,把全部的操作数(包括数字和算符)都放到 ops 数组中,分别用 −1,−2,−3 来表示算符 +,−,*。因为对于表达式中的某一个算符op,我们将其左部可能的计算结果用 left 集合来表示,其右部可能的计算结果用 right 集合来表示。那么以该算符为该表达式的最后一步操作的情况的全部可能结果就是对应集合 left 和集合 right 中元素对应该算符操作的组合数。那么我们枚举表达式中的全部算符来作为 left 和 right 的分隔符来求得对应的集合,那么该表达式最终的可能结果就是这些集合的并集。
代码如下:
/**
* 记忆化搜索
*/
class Solution {
static final int ADDITION = -1;
static final int SUBTRACTION = -2;
static final int MULTIPLICATION = -3;
public List<Integer> diffWaysToCompute(String expression) {
List<Integer> ops = new ArrayList<Integer>();
for (int i = 0; i < expression.length();) {
if (!Character.isDigit(expression.charAt(i))) {
if (expression.charAt(i) == '+') {
ops.add(ADDITION);
} else if (expression.charAt(i) == '-') {
ops.add(SUBTRACTION);
} else {
ops.add(MULTIPLICATION);
}
i++;
} else {
int t = 0;
while (i < expression.length() && Character.isDigit(expression.charAt(i))) {
t = t * 10 + expression.charAt(i) - '0';
i++;
}
ops.add(t);
}
}
List<Integer>[][] dp = new List[ops.size()][ops.size()];
for (int i = 0; i < ops.size(); i++) {
for (int j = 0; j < ops.size(); j++) {
dp[i][j] = new ArrayList<Integer>();
}
}
return dfs(dp, 0, ops.size() - 1, ops);
}
public List<Integer> dfs(List<Integer>[][] dp, int l, int r, List<Integer> ops) {
if (dp[l][r].isEmpty()) {
if (l == r) {
dp[l][r].add(ops.get(l));
} else {
for (int i = l; i < r; i += 2) {
List<Integer> left = dfs(dp, l, i, ops);
List<Integer> right = dfs(dp, i + 2, r, ops);
for (int lv : left) {
for (int rv : right) {
if (ops.get(i + 1) == ADDITION) {
dp[l][r].add(lv + rv);
} else if (ops.get(i + 1) == SUBTRACTION) {
dp[l][r].add(lv - rv);
} else {
dp[l][r].add(lv * rv);
}
}
}
}
}
}
return dp[l][r];
}
}
258. 各位相加
题目解析:找规律的题,假如一个三位数’abc’,其值大小为s1 = 100 * a + 10 * b + 1 * c,经过一次各位相加后,变为s2 = a + b + c,减小的差值为(s1 -s2) = 99 * a + 9 * b,以后每次循环减少都为9的倍数,故当最后一次为一位数时,总共减少了 9*n ,由此可知,我们直接对9取余,如果最后num值为一位不为零时,直接返回num,为零则直接返回9。
代码如下:
/**
* 数学
*/
class Solution {
public int addDigits(int num) {
// 为零直接返回
if (num == 0) {
return 0;
}
// 对9取余,相当于减去 9*n
num %= 9;
if (num == 0) {
return 9;
}
return num;
}
}
263. 丑数
题目解析:分别循环除以2、3、5,当余数为1,即为丑数。
代码如下:
/**
* 数学
*/
class Solution {
public boolean isUgly(int n) {
if (n < 1) {
return false;
}
while (n % 2 == 0) {
n /= 2;
}
while (n % 3 == 0) {
n /= 3;
}
while (n % 5 == 0) {
n /= 5;
}
return n == 1;
}
}
273. 整数转换英文表示
题目解析:由于非负整数 num 的最大值已定,因此最多有 10 位数。将整数转换成英文表示中,将数字按照 3 位一组划分,将每一组的英文表示拼接之后即可得到整数 num 的英文表示。
代码如下:
/**
* 递归
*/
class Solution {
String[] singles = {"", "One", "Two", "Three", "Four", "Five", "Six", "Seven", "Eight", "Nine"};
String[] teens = {"Ten", "Eleven", "Twelve", "Thirteen", "Fourteen", "Fifteen", "Sixteen", "Seventeen", "Eighteen", "Nineteen"};
String[] tens = {"", "Ten", "Twenty", "Thirty", "Forty", "Fifty", "Sixty", "Seventy", "Eighty", "Ninety"};
String[] thousands = {"", "Thousand", "Million", "Billion"};
public String numberToWords(int num) {
if (num == 0) {
return "Zero";
}
StringBuffer sb = new StringBuffer();
for (int i = 3, unit = 1000000000; i >= 0; i--, unit /= 1000) {
int curNum = num / unit;
if (curNum != 0) {
num -= curNum * unit;
StringBuffer curr = new StringBuffer();
recursion(curr, curNum);
curr.append(thousands[i]).append(" ");
sb.append(curr);
}
}
return sb.toString().trim();
}
public void recursion(StringBuffer curr, int num) {
if (num == 0) {
return;
} else if (num < 10) {
curr.append(singles[num]).append(" ");
} else if (num < 20) {
curr.append(teens[num - 10]).append(" ");
} else if (num < 100) {
curr.append(tens[num / 10]).append(" ");
recursion(curr, num % 10);
} else {
curr.append(singles[num / 100]).append(" Hundred ");
recursion(curr, num % 100);
}
}
}
278. 第一个错误的版本
题目解析:二分法查找,注意数值太大时,不能用 (left + right)/2, 应该用left + (right -left)/2,防止溢出。
代码如下:
/**
* 二分法查找
*/
public class Solution extends VersionControl {
public int firstBadVersion(int n) {
int left = 1;
int right = n;
while(left <= right){
int mid = left + (right -left)/2;
if (isBadVersion(mid)){
right = mid - 1;
}else{
left = mid + 1;
}
}
return left;
}
}
292. Nim 游戏
题目解析:巴什博奕,n % (m+1) != 0时,先手总是会赢的。都会玩的情况下,先手4的整数一定会输,只要a先手拿n,b拿4-n即可保证,b一定会赢,而先手不是4的整数情况,拿掉余数就可赢。故:自己先手,只需要判断,该数是否是4的倍数就可以判断是否会赢
代码如下:
/**
* 巴什博奕
*/
class Solution {
public boolean canWinNim(int n) {
if(n%4 == 0) return false;
return true;
}
}