常见数学问题
2. 两数相加(加法模板 + 链表操作)
/**
* Definition for singly-linked list.
* public class ListNode {
* int val;
* ListNode next;
* ListNode() {}
* ListNode(int val) { this.val = val; }
* ListNode(int val, ListNode next) { this.val = val; this.next = next; }
* }
*/
class Solution {
public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
/**
分析:
一般思路是将两个链表转换为数字,数字相加,得到答案,然后将答案逆序放回链表中.
经过思考后,发现实现十分麻烦,不如直接将链表的对应数组相加,然后判断有无进位。
这是一道加法模板题,只不过场景换成了链表
*/
// 定义一个结果链表(有点像伪头部法)
ListNode res = new ListNode();
// 定义一个工作节点,指向首节点
ListNode cur = res;
// 默认进位为 0
int carry = 0;
while(l1 != null || l2 != null){
// 定义对应数字
int x = l1 == null ? 0 : l1.val;
int y = l2 == null ? 0 : l2.val;
int sum = x + y + carry;
// 构造节点
cur.next = new ListNode(sum % 10);
// 更新进位
carry = sum / 10;
// 移动工作节点
cur = cur.next;
// 移动l1和l2
// 一般而言为了避免空指针异常,都要进行异常判断
if(l1 != null){
l1 = l1.next;
}
if(l2 != null){
l2 = l2.next;
}
}
// 处理最后一个进位信息
if(carry != 0){
// 尾巴增加一个节点
cur.next = new ListNode(carry);
}
// 返回结果
return res.next;
}
}
7. 整数反转
注:第一次自己写的代码(AC了,但是代码好丑啊),其实整体思路是没错的,转换成字符串,字符串比较好进行数字转换操作,更加进阶的操作是把字符串再转换成char数组,这样就可以进行原地修改啦!!!
class Solution {
public int reverse(int x) {
/**
分析:
将数字转换为字符串,然后再对字符串遍历,这里使用 long来进行存储,然后判断是否溢出。
但是自己写的代码总感觉有点丑~~~可以再优化的尤其是判断正负这一块
*/
if( x > 0){
String temp = String.valueOf(x);
long res = 0;
for(int i = temp.length() - 1; i >= 0; i--){
res = res * 10 + (temp.charAt(i) - '0');
}
if( res > Math.pow(2,31) - 1){
return 0;
}
return (int)res;
}else if(x < 0 ){
String temp = String.valueOf(Math.abs(x));
long res = 0;
for(int i = temp.length() - 1; i >= 0; i--){
res = res * 10 + (temp.charAt(i) - '0');
}
if( res > Math.pow(2,31) ){
return 0;
}
return (int)-res;
}
return 0;
}
}
注意:在第二种优化解法中,学到了符号的判断移动,数字型和字符串类型的调用转换,以及包装类中的MAX_VALUE和MIN_VALUE的用法!!!
class Solution {
public int reverse(int x) {
/**
分析:
将数字转换为字符串,然后再对字符串遍历,这里使用 long来进行存储,然后判断是否溢出。
但是自己写的代码总感觉有点丑~~~可以再优化的尤其是判断正负这一块===》使用char数组吧!~
*/
// 转换为字符串
String str = String.valueOf(x);
// 转换为char数组
char[] temp = str.toCharArray();
// 定义双指针
int left = 0, right = temp.length - 1;
if(temp[left] < '0' || temp[left] > '9'){
// 排除负号的影响
left++;
}
// 反转
while(left < right){
char t = temp[left];
temp[left] = temp[right];
temp[right] = t;
left++;
right--;
}
// 判断是否溢出
long res = Long.parseLong(new String(temp));
if(res > Integer.MAX_VALUE || res < Integer.MIN_VALUE){
return 0;
}
return (int)res;
}
}
9. 回文数(双指针)
class Solution {
public boolean isPalindrome(int x) {
/**
很简单,就是双指针的应用。整数==》字符串==》字符数组(这一步也不是很需要,因为不涉及到原地修改操作)
*/
String str = String.valueOf(x);
char[] temp = str.toCharArray();
int left = 0, right = temp.length - 1;
while(left < right){
if(temp[left] != temp[right]){
return false;
}
left++;
right--;
}
return true;
}
}
43. 字符串相乘(有点难理解…)
class Solution {
public String multiply(String num1, String num2) {
/**
分析:
题干中要求,不能使用大数类型,并且不能直接将输入转换为整数。那么就提示我们只能一个一个处理数字了!
首先得意识到这是一个数学问题,要去思考如何将数字的乘法进行分解。一个简单的思路是“加法”,num1乘以num2中的每个位置的数字,然后再累加,最后得到答案。算法执行过程中有很多细节需要注意。
- 使用StringBuilder数据结构,这样可以动态修改
- num2中除了最后一个数字,其它数字进行乘法时,后续要补0
- 在处理乘法的时候,时刻注意进位carry的存在
*/
// 特判
if (num1.equals("0") || num2.equals("0")) return "0";
String ans = "0";
int m = num1.length();
int n = num2.length();
// 处理被乘数的每一位
for (int i = n - 1; i >= 0; i--) {
StringBuilder currAns = new StringBuilder();
// 对当前位的后面补 0
for (int j = n - 1; j > i; j--) currAns.append("0");
int y = num2.charAt(i) - '0'; // 当前位的值
int carry = 0;
for (int j = m - 1; j >= 0; j--) {
// 将当前位的值和乘数的每一位进行相乘
int x = num1.charAt(j) - '0';
int product = x * y + carry;
currAns.append(product % 10);
carry = product / 10;
}
if (carry != 0) currAns.append(carry);
// 将被乘数每一位和乘数相乘的结果累加
ans = addStrings(ans, currAns.reverse().toString());
}
return ans;
}
public String addStrings(String num1, String num2) {
StringBuilder res = new StringBuilder();
int i1 = num1.length() - 1, i2 = num2.length() - 1;
int carry = 0;
while (i1 >= 0 || i2 >= 0) {
int x = i1 >= 0 ? num1.charAt(i1) - '0' : 0;
int y = i2 >= 0 ? num2.charAt(i2) - '0' : 0;
int sum = x + y + carry;
res.append(sum % 10);
carry = sum / 10;
i1--;
i2--;
}
if (carry != 0) res.append(carry);
return res.reverse().toString();
}
}
66. 加一(数学问题之特殊情况判别)
注意:本题的解法就是了解遇到10会进位,这个进位又和+1重复,所有可以使用遍历的方法
class Solution {
public int[] plusOne(int[] digits) {
/**
分析:
将这个数转换为数字,数字+1,然后数字再转换为数组.后来发现这个方法是可以的,但是代码十分臃肿,而且测试案例出错概率很大。
不如进行数学分析!!!反向遍历数组 + 1,观察是否会达到10,不会达到那么就是直接return,会的话重新申请一个新的数组(数组长度+1)
*/
for(int i = digits.length - 1; i >= 0 ; i--){
digits[i]++;
digits[i] %= 10;
if(digits[i] != 0){
// 说明没有达到10,那么就直接return结果
return digits;
}
}
// 遍历一圈结束后,没有return 则说明是99999类型的
int[] temp = new int[digits.length + 1];
temp[0] = 1;
return temp;
}
}
67. 二进制求和(加法模板)
注意:第一次没做出来(溢出了),但是学会了一个api,Integer.toBinaryString()
class Solution {
public String addBinary(String a, String b) {
/**
分析:
二进制求和就是将二进制转换为数字,然后数字相加,最后返回二进制.
最后测试案例不通过,不过倒是学会一个api,java中整数转换为二进制字符串Integer.toBinaryString()
*/
int sum = 0;
sum = binaryToInt(a) + binaryToInt(b);
return Integer.toBinaryString(sum);
}
public int binaryToInt(String a){
int count = 0, sum = 0;
for(int i = a.length() - 1; i >= 0; i--){
if(a.charAt(i) == '1'){
sum += Math.pow(2,count);
}
count++;
}
return sum;
}
// 本想自己写一个出来,发现这玩意太复杂,涉及底层源码
// public String intToBinary(int sum){
// }
}
注意:这种接法就是使用了加法模板!!!一定要有这种模板思维,不管多少进制,涉及到加法我都可以套用这个模板。
class Solution {
public String addBinary(String a, String b) {
/**
分析:
二进制求和就是将二进制转换为数字,然后数字相加,最后返回二进制.
最后测试案例不通过,不过倒是学会一个api,java中整数转换为二进制字符串Integer.toBinaryString()
本身二进制和十进制求和是一样的(只不过一个进制为2,一个进制为10),那么就可以套用加法模板
*/
// 使用sb动态修改
StringBuilder sb = new StringBuilder();
int len1 = a.length() - 1, len2 = b.length() - 1;
// 定义进位
int carry = 0;
while( len1 >= 0 || len2 >= 0){
// 取数字
int x = len1 < 0 ? 0 : a.charAt(len1) - '0';
int y = len2 < 0 ? 0 : b.charAt(len2) - '0';
int sum = x + y + carry;
sb.append(sum % 2);
carry = sum / 2;
len1--;
len2--;
}
// 最后判断进制
if(carry != 0){
sb.append(carry);
}
return sb.reverse().toString();
}
}
204. 计数质数(数学解法…)
注意:这里是暴力解法,算法超时了!!!
class Solution {
public int countPrimes(int n) {
// 使用暴力解法,结果超时了
int count = 0;
if(n == 0 || n== 1){
return 0;
}
for(int i = 2; i < n; i++){
if(isPrimes(i)){
count++;
}
}
return count;
}
public boolean isPrimes(int num){
for(int i = 2; i < num; i++){
if(num % i == 0){
return false;
}
}
return true;
}
}
注意:下面的解法可以通过测试案例,主要是运用一个标记数组,进行标记不可能是质数的数
class Solution {
public int countPrimes(int n) {
int ans = 0;
// 默认全是质数
boolean[] notPrimes = new boolean[n];
for (int x = 2; x < n; x++) {
// 不是质数就跳过循环
if (notPrimes[x]) continue;
// 是质数,就加1
ans++;
// 如果 x 是质数,那么 2x、3x、4x.... 肯定不是质数
for (int i = x; i < n; i += x) {
notPrimes[i] = true;
}
}
return ans;
}
}
415. 字符串相加(加法模板)
class Solution {
public String addStrings(String num1, String num2) {
/**
分析:
这个题干提示的很明显了,使用加法模板!
*/
int len1 = num1.length() - 1, len2 = num2.length() - 1;
// 使用sb进行动态修改
StringBuilder sb = new StringBuilder();
// 定义进位
int carry = 0;
while( len1 >= 0 || len2 >= 0){
// 定义分位
int x = len1 < 0 ? 0 : num1.charAt(len1) - '0';
int y = len2 < 0 ? 0 : num2.charAt(len2) - '0';
int sum = x + y + carry;
sb.append(sum % 10);
carry = sum / 10;
len1--;
len2--;
}
// 判断最后一个进位
if(carry != 0){
sb.append(carry);
}
return sb.reverse().toString();
}
}
989. 数组形式的整数加法(加法模板)
class Solution {
public List<Integer> addToArrayForm(int[] num, int k) {
/**
分析:
本题是我使用加法模板的启蒙题~~~
*/
int len = num.length - 1;
// 定义结果
LinkedList<Integer> res = new LinkedList<>();
// 定义进位
int carry = 0;
while( len >= 0 || k != 0){
// 定义分位
int x = len >= 0 ? num[len] : 0;
int y = k != 0 ? k % 10 : 0;
int sum = x + y + carry;
// 添加结果
res.addFirst(sum % 10);
carry = sum / 10;
len--;
k /= 10;
}
if(carry != 0){
res.addFirst(carry);
}
return res;
}
}
1232. 缀点成线(边界条件,分母的考虑)
class Solution {
public boolean checkStraightLine(int[][] coordinates) {
/**
分析:
依次判断相邻两点的斜率是否一致,这里如果直接使用斜率公式,有可能分母为0,所以需要变换为乘法!
这里规定第一个点为起点,后面的计算都以起点为依据
*/
// 定义起点
int x0 = coordinates[0][0], y0 = coordinates[0][1];
int deltaY = coordinates[1][1] - y0;
int deltaX = coordinates[1][0] - x0;
for(int i = 2; i < coordinates.length; i++){
int deltaYi = coordinates[i][1] - y0;
int deltaXi = coordinates[i][0] - x0;
if(deltaY * deltaXi != deltaYi * deltaX){
return false;
}
}
return true;
}
}