【刷题系列】数学

系列汇总:《刷题系列汇总》



——————《剑指offeer》———————

1. 不用加减乘除做加法(位运算)

  • 题目描述:写一个函数,求两个整数之和,要求在函数体内不得使用+、-、*、/四则运算符号
  • 优秀思路:用异或代替不进位的加法,用&后左移一位代替进位,前者结果异或后者则是最终结果。
public class Solution {
    public int Add(int num1,int num2) {
        int result;
        int carry; //进位
        do{
            result = num1 ^ num2; // 异或相当于不进位的加法
            carry = (num1 & num2) << 1; // 进位
            num1 = result;
            num2 = carry;
        }while(carry != 0);
        return result;
    }
}

2. 求1+2+3+…+n(逻辑与的短路特性)

  • 题目描述:求1+2+3+...+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)
  • 优秀思路:利用逻辑与的短路特性实现递归终止,即 && 表达式若输出false时自动停止,此时返回0
public class Solution {
    // 1.需利用逻辑与的短路特性实现递归终止。
    // 2.当n==0时,(n>0)&&((sum+=Sum_Solution(n-1))>0)只执行前面的判断,为false,然后直接返回0;
    // 3.当n>0时,执行sum+=Sum_Solution(n-1),实现递归计算Sum_Solution(n)。
    public int Sum_Solution(int n) {
        int sum = n;
        boolean ans = (n>0)&&((sum+=Sum_Solution(n-1))>0);
        return sum;
    }
}

3. 二进制中1的个数(位运算)

  • 题目描述:输入一个整数,输出该数32位二进制表示中1的个数。其中负数用补码表示。
  • 常规解法:从右到左分别与00001、00010、00100、01000、10000相与,若结果不为零,则说明该位为1
public class Solution {
    public int NumberOf1(int n) {
        int ans = 0;
        int mark = 0x01; // 检测因子
        while (mark != 0) {
            if((mark & n) != 0) ++ans;
            mark <<= 1; // 检测因子的1左移一位
        }
        return ans;
    }
}
  • 优秀思路:【技巧】如果一个整数不为0,那么这个整数至少有一位是1。如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。举个例子:一个二进制数1100,从右边数起第三位是处于最右边的一个1。减去1后,第三位变成0,它后面的两位0变成了1,而前面的1保持不变,因此得到的结果是1011.我们发现减1的结果是把最右边的一个1开始的所有位都取反了。这个时候如果我们再把原来的整数和减去1之后的结果做与运算,从原来整数最右边一个1那一位开始所有位都会变成0。如1100&1011=1000.也就是说,把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0.那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。
public class Solution {
    public int NumberOf1(int n) {
        int count=0;
        while(n!=0){
            count++;
            n=n&(n-1);
        }
        return count;
    }
}

4. 整数中1出现的次数(没看懂)

  • 题目描述:输入一个整数 n ,求1~nn个整数的十进制表示中1出现的次数。例如,1~13中包含1的数字有1、10、11、12、13因此共出现6
  • 我的思路(82% - 72%):遍历1~n,求出每个数的所有位,若出现1count++
public class Solution {
   public int NumberOf1Between1AndN_Solution(int n) {
       int count = 0;
       for(int i = 1;i <= n;i++){
           int temp = i;
           while(temp != 0){
               count = temp % 10 == 1? count + 1 : count;
               temp /= 10;
           }
       }
       return count;
   }
}
  • 优秀思路:分各十百…位分别统计
class Solution {
    public int countDigitOne(int n) {
        int count = 0;
        for(int i = 1;i <= n;i++){
            int temp = i;
            while(temp != 0){
                count = temp % 10 == 1? count + 1 : count;
                temp /= 10;
            }
        }
        return count;
    }
}

5. 数值的整数次方(位运算)

  • 题目描述:给定一个double类型的浮点数baseint类型的整数exponent。求baseexponent次方。保证baseexponent不同时为0。不得使用库函数,同时不需要考虑大数问题,也不用考虑小数点后面0的位数。
  • 我的思路(99% - 6%):【暴力】简单相乘就行,分正负输出结果。
public class Solution {
    public double Power(double base, int exponent) {
        if(base == 0) return 0;
        if(exponent == 0) return 1;
        double ans = base;
        int temp = Math.abs(exponent);
        while(--temp > 0){
            ans *= base;
        }
        return exponent > 0? ans : (double)1/ans;
    }
}
  • 优秀思路1:按exponent 的奇偶情况分别计算

  • 优秀思路2(自己编写):【位运算 / 快速幂】按数字的二进制计算,若二进制位为1,则乘入。例如在这里插入图片描述

public class Solution {
    public double Power(double base, int exponent) {
        if(exponent == 0) return 1;
        if(exponent == 1) return base;
        double res = 1;
        double curValue = base; // 以当前二进制位为幂的值
        int exponentValue = Math.abs(exponent);
        while(exponentValue != 0){
            if((exponentValue & 1) == 1){ // 此时的exponent二进制最右位为1
                res *= curValue;
            }
            curValue *= curValue;
            exponentValue >>= 1;
        }
        return exponent < 0 ? 1/res : res;
    }
}

6. 剪绳子(数学推导)

  • 题目描述:给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],...,k[m]。请问k[1]x...xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18
  • 优秀思路:参考题解《剪绳子后面的数学原理
public class Solution {
    public int cutRope(int target) {
        if(target<=0) return 0;
        if(target==1 || target == 2) return 1;
        if(target==3) return 2;
        int m = target % 3;
        switch(m){
            case 0 :
                return (int) Math.pow(3, target / 3);
            case 1 :
                return (int) Math.pow(3, target / 3 - 1) * 4;
            case 2 :
                return (int) Math.pow(3, target / 3) * 2;
        }
        return 0;
    }
}

7. 圆圈中最后剩下的数(模拟法)

  • 题目描述:有个游戏是这样的:首先,让小朋友们围成一个大圈。然后,他随机指定一个数m,让编号为0的小朋友开始报数。每次喊到m-1的那个小朋友要出列唱首歌,然后可以在礼品箱中任意的挑选礼物,并且不再回到圈中,从他的下一个小朋友开始,继续0...m-1报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的礼物。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0n-1)。如果没有小朋友,请返回-1
  • 我的思路:【模拟法】完全按照游戏描述变成
import java.util.*;
public class Solution {
    public int LastRemaining_Solution(int n, int m) {
        if(n <= 0) return -1;
        if(n == 1) return 0;
        if(m == 1) return n-1;
        ArrayList<Integer> curChild = new ArrayList<>(); 
        for(int i = 0;i < n;i++) curChild.add(i);
        del(curChild,0,n,m);
        return curChild.get(0);
    }
    private void del(ArrayList<Integer> curChild,int start,int n,int m){
        if(curChild.size()==1) return; // 只剩一个小孩
        // 下一个删除小孩的位置
        int del = (m - n + start) % n;
        if(del < 0) del = n + del - 1;
        else if(del == 0) del = n-1;
        else del--;
        curChild.remove(curChild.get(del));
        del(curChild,del,n-1,m);
    }
}
  • 优秀思路:推公式,懒得搞了

8. 丑数(三指针)

  • 题目描述:把只包含质因子2、35的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
  • 我的思路(超时):建立一个hashset存储已有的丑数,遍历后面的所有偶数,若其的1/2为丑数,则它一定为丑数
import java.util.HashSet;
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        HashSet<Integer> allUglyNumber = new HashSet<>();
        allUglyNumber.add(1);
        allUglyNumber.add(2);
        allUglyNumber.add(3);
        allUglyNumber.add(4);
        allUglyNumber.add(5);
        int count = 5;
        for(int i = 6;i<Integer.MAX_VALUE;i += 2){
            if(allUglyNumber.contains(i/2)){
                count++;
                if(count == index) return i;
                allUglyNumber.add(i); // 更新丑数列表
            }
        }
        return 0;
    }
}
  • 优秀思路:【和我的思路有一丢丢类似,但是是相反的。且可以避免遍历所有偶数】。已有的丑数分别乘以2,3,5一定是丑数,例如1乘以 (2、3、5)=2、3、5;2乘以(2、3、5)=4、6、10;3乘以(2、3、5)=6,9,15;5乘以(2、3、5)=10、15、25;,但是其中有重复且无序,要维持三个指针来记录当前乘以2、乘以3、乘以5的最小值,然后当其被选为新的最小值后,要把相应的指针+1;因为这个指针会逐渐遍历整个数组,因此最终数组中的每一个值都会被乘以2、乘以3、乘以5,也就是实现了我们最开始的想法,只不过不是同时成乘以2、3、5,而是在需要的时候乘以2、3、5.
public class Solution {
    public int GetUglyNumber_Solution(int index) {
        if(index <= 6)return index;
        int p2 = 0,p3 = 0,p5 = 0;//初始化三个指向三个潜在成为最小丑数的位置
        int[] res = new int[index];
        res[0] = 1;
        for(int i = 1;i < index;i++){ // 循环的上限定为index即可
            // 把三个下标放入的操作很妙!!!
            res[i] = Math.min(Math.min(res[p2]*2,res[p3]*3),res[p5]*5); 
            if(res[i] == res[p2]*2) p2++;
            if(res[i] == res[p3]*3) p3++;
            if(res[i] == res[p5]*5) p5++;
        }
        return res[index -1];
    }
}

——————《LeectCode》———————

1. 森林中的兔子(需重写)

  • 题目描述:森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。返回森林中兔子的最少数量。

  • 优秀思路

    • 1.同一颜色的兔子回答的数值必然是一样的,但回答同样数值的,不一定就是同颜色兔子,可能是两种颜色的兔子具有相同的数量
    • 2.有兔子报了n,最少总数最少加n+1
    • 3.若报某count的兔子有n只,得分情况讨论
      • ① n ≤ count + 1,则认为是同一种颜色。例如有4只兔子报3,则认为这几只都是一种颜色
      • ② n > count + 1,则不可能是一种颜色。例如有5只兔子报3,则至少有两种颜色,应当将n分为若干种颜色,并尽可能让某一种颜色的兔子为 count +1 只,这样能够满足题意,同时不会导致「额外」兔子数量增加(颜色数量最少)
class Solution {
    public int numRabbits(int[] answers) {
    //m[i] > 0   先前已经记录到有回答数量 i 的兔子,这次遇到只需容量减 1
    //m[i] == 0  第一次遇到回答i的兔子 或者 上一次遇到回答 i 的兔子时创建颜色的容量已经用完
    //           此时创建新的颜色,容量为i,并将这一波兔子数量加到结果中
        int[] m = new int[1000]; // answers最大长度1000
        int result = 0;
        for (int i : answers) {
            if (m[i] > 0) {
                m[i]--; // 回答i的同一种颜色的兔子最多只能有i+1,这里有可以减 i 次
            } else {
                m[i] = i; // 这是第一次发现
                result += i + 1; // 发现报数i的兔子,则该颜色有 i+1 只
            }
        }
        return result;
    }
}

2. 回文数

  • 题目描述:给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数。例如,121 是回文,而 123 不是。
  • 我的思路1(8% - 15%):读取出每位上的数存到string里,再定义双指针判断;
class Solution {
    public boolean isPalindrome(int x) {
        //String s = "";
        //while(x != 0){
        //    s += String.valueOf(x % 10);
        //    x /= 10;
        //}
        // 上面部分可调用库函数实现:68%
        String s = String.valueOf(x);
        int i = 0,j = s.length()-1;
        while(i <= j){
            if(s.charAt(i) == s.charAt(j)){
                i++;
                j--;
            }else return false;
        }
        return true;
    }
}
  • 我的思路2(43% - 53%):思路同上,不过不用string实现,用arrayList
class Solution {
    public boolean isPalindrome(int x) {
        if(x < 0) return false;
        ArrayList<Integer> num = new ArrayList<>();
        while(x != 0){
            num.add(x % 10);
            x /= 10;
        }
        int i = 0,j = num.size()-1;
        while(i <= j){
            if(num.get(i) == num.get(j)){
                i++;
                j--;
            }else return false;
        }
        return true;
    }
}
  • 优秀思路:【反转一半】反转后一半数字跟前一半数字进行比较即可,重点是怎么判断反转了一半,很简单,由于整个过程我们不断将原始数字除以 10,然后给反转后的数字乘上 10,所以,当原始数字小于或等于反转后的数字时,就意味着我们已经处理了一半位数的数字了。(特殊情况:负数/个位为0且不为0的数一定不是回文数)
class Solution {
    public boolean isPalindrome(int x) {
        if(x == 0) return true;
        if(x < 0 || x % 10 == 0) return false;
        int halfNumber = 0;
        while(x > halfNumber){
            halfNumber = halfNumber*10 + x % 10;
            x = x/10;
        }
        if(x == halfNumber || x == halfNumber/10) return true;
        return false;
    }
}

3. 基本计算器(需重写)

  • 题目描述:给你一个字符串表达式 s ,请你实现一个基本计算器来计算并返回它的值。
  • 优秀思路:本质上要认识到其实就是展开‘-’的问题。
    • 做法:建立两个栈(st_numst_signs),来存储遇到括号前的符号sign(初始值为1,代表正,-1代表负),和括号外面已经算过的结果ans(初始值值为0),从栈顶到栈底依次是从内层到外层的结果。扫描字符串,遇到空格直接跳过,遇到正负号则更新正负号,遇到左括号,将当前的结果和符号存入对应的栈中,遇到右括号,更新运算结果。
    • 为什么要这样做?假设没有括号的情况下,我们可以直接在当前得到的结果上累加。但是有括号就不同了,我们要先把之前算过的结果存起来,然后再单独算计算括号里面的结果,直到遇到反括号,将当前括号内的结果(当前括号内的结果是要包括括号前的符号的)和上一层的结果(上次的ans)加起来,上一层的结果即为st_num的栈顶元素。
class Solution {
     public int calculate(String s) {
        int ans = 0;
        char[] str = s.toCharArray();
        int len = str.length;
        Stack<Integer> st_num = new Stack<>();
        Stack<Integer> st_signs = new Stack<>();

        int sign=1; // 正负号,运算符号
        for(int i = 0;i < len;i++){
            if(str[i] == ' ') continue;
            if(str[i] == '+'|| str[i] == '-') sign = str[i] == '+'? 1:-1; // 当前符号
            else if(str[i] >= '0' && str[i] <= '9'){//数字
                int num = str[i]-'0';
                // 数字可能不止一位,不要忽略 
                while(i < len-1 && str[i+1] >= '0' && str[i+1] <= '9'){//将这个数字找完
                    num = num*10 + (str[++i] - '0');
                }
                ans += sign*num;
            }else if(str[i] == '('){//左括号,暂存结果
                st_num.push(ans);
                st_signs.push(sign);
                // 初始化ans、sign
                ans = 0;
                sign = 1;
            }
            else ans = st_num.pop() + ans*st_signs.pop();//右括号:更新结果
        }
        return ans;
    }
}

4. 字符串相乘

  • 题目描述:给定两个以字符串形式表示的非负整数 num1num2,返回 num1num2 的乘积,它们的乘积也表示为字符串形式。
    在这里插入图片描述

  • 优秀思路:用一个数组arr来存储相乘后的每一位上的数字,从字符串num1num2尾部向前扫描,将num1每一位上的数与num2每一位上的数相乘,将结果再加上结果arr[]中对应位上的数,得到一个数num。对应为上的数更新为num%10,然后进位上的数更新为num/10;最后结果要去掉前导0。

class Solution {
    public String multiply(String num1, String num2) {
        // 特殊情况:0
        if(num1.equals("0") || num2.equals("0")) return "0"; //字符串之间的比较用equals()

        int len1 = num1.length();
        int len2 = num2.length();
        int[] arr = new int[len1 + len2];
        for(int i = len1 - 1;i >= 0;i--){
            int value1 = num1.charAt(i) - '0'; //这句放这儿可以提升效率
            for(int j = len2 - 1;j >= 0;j--){
                int value2 = num2.charAt(j) - '0';
                int sum = arr[i+j+1] + value1 * value2;
                arr[i+j+1] = sum % 10;
                arr[i+j] += sum / 10; // 注意 arr[i+j] 原来存在进位,加的时候不能忽略
            }
        }
        StringBuffer sb = new StringBuffer();
        for(int i = 0;i < arr.length;i++){
            if(i == 0 && arr[i] == 0) continue;
            sb.append(arr[i]); // StringBuffer可以直接append整数
        }
        return sb.toString();
    }
}

5. 整数反转

  • 题目描述:给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231, 231 − 1] ,就返回 0。假设环境不允许存储 64 位整数(有符号或无符号)。
  • 我的思路:核心是怎么判断数据溢出,怎么理解文章中的核心代码?
    • 1.Integer.MAX_VALUE/10 < res:注意判断时tempX > 0,则res*10+tempX % 10一定会大于Integer.MAX_VALUE
    • 2.Integer.MAX_VALUE/10 == res && Integer.MAX_VALUE % 10 < digit:说明resInteger.MAX_VALUE位数相同时,尾数大于它
class Solution {
    public int reverse(int x) {
        if(x == 0) return 0;
        int tempX = x < 0 ? -x : x;
        int res = 0;
        while(tempX > 0){
            int digit = tempX % 10;
            // 核心:判断数据溢出
            if(Integer.MAX_VALUE/10 < res || (Integer.MAX_VALUE/10 == res && Integer.MAX_VALUE % 10 < digit)) return 0;
            res = res*10 + digit;
            tempX /= 10;
            
        }
        return x < 0 ? -res : res;
    }
}
  • 优秀思路:由于负数对正数取余是负数,所以不用区分正负判断,其判断数据溢出可以推出为下式
    在这里插入图片描述
class Solution {
    public int reverse(int x) {
        int res = 0;
        while(x != 0){
            if(res > Integer.MAX_VALUE/10 || res < Integer.MIN_VALUE/10) return 0;// 核心:判断数据溢出
            int digit = x % 10;
            res = res*10 + digit;
            x /= 10;
        }
        return res;
    }
}

6. x 的平方根

  • 题目描述:实现 int sqrt(int x) 函数。计算并返回 x 的平方根,其中 x非负整数。由于返回类型是整数,结果只保留整数的部分,小数部分将被舍去。
  • 我的思路(5%):暴力法
class Solution {
    public int mySqrt(int x) {
        for(int i = 1;i <= x;i++){
            if(x/i >= i && x/(i+1) < i+1) return i; //不能写作i*i <= x,需要考虑数据溢出问题
        }
        return 0;
    }
}
  • 我的思路改进(100%):【二分法】迭代时start-1end+1是为了保证能取到startend这两个值
class Solution {
    public int mySqrt(int x) {
        if(x == 0 || x == 1) return x;
        return findSqrt(0,x,x);
    }
    private int findSqrt(int start,int end,int x){
        int mid = (start + end) >> 1;
        int res = 0;
        if(x/mid == mid){
            res = mid;
        }else if(x/mid < mid){
            if(x/(mid-1) >= mid-1) res = mid-1;
            else res = findSqrt(start-1,mid,x); // mid太大了
        }else{
            res = findSqrt(mid,end+1,x);// mid太小了
        }
        return res;
    }
}
  • 写法2while循环
class Solution {
    public int mySqrt(int x) {
        if(x == 0 || x == 1) return x;
        int left = 0,right = x,res = -1;
        while(left <= right){
            int mid = (left + right) >> 1;
            if(x/mid == mid){
                return mid;
            }else if(x/mid < mid){
                if(x/(mid-1) >= mid-1){
                    return mid - 1;
                }else{
                    left--;
                    right = mid;
                }
            }else{
                left = mid;
                right++;
            }
        }
        return res;
    }
}
  • 官方写法
class Solution {
    public int mySqrt(int x) {
        int l = 0, r = x, ans = -1;
        while (l <= r) {
            int mid = l + (r - l) / 2; // 巧妙的解决了取不到l的问题
            if ((long) mid * mid <= x) { // long防溢出
                ans = mid;
                l = mid + 1; 
            } else {
                r = mid - 1;
            }
        }
        return ans;
    }
}
  • 优秀思路1:【骚气数学公式法】在这里插入图片描述

7. 字符串转整数(myAtoi)

  • 题目描述:请你来实现一个 myAtoi(string s) 函数,使其能将字符串转换成一个 32 位有符号整数(类似 C/C++ 中的 atoi 函数)。
  • 我的思路(优秀,99%-83%):面向测试对象编程
class Solution {
    public int myAtoi(String s) {
        // 数字之前只能有
        char[] arr = s.toCharArray();
        int countOther = 0; // 数字前面正负号的数量
        int num_flag = 0; // 数字出现标志
        int res = 0;
        int sign = 1; //正负
        for(int i = 0;i < arr.length;i++){
            if(arr[i] == ' '){
                if(num_flag == 1 || countOther > 0) break; // 空格前出现了符号或数字
                else continue;
            }else if(arr[i] <= '9' && arr[i] >= '0'){
                if(countOther >= 2) return 0;
                int digit = arr[i] - '0';
                if(sign*res < Integer.MIN_VALUE/10 || (sign*res == Integer.MIN_VALUE/10 && digit > Math.abs(Integer.MIN_VALUE % 10))) return Integer.MIN_VALUE;
                if(sign*res > Integer.MAX_VALUE/10 || (sign*res == Integer.MAX_VALUE/10 && digit > Integer.MAX_VALUE % 10)) return Integer.MAX_VALUE;
                num_flag = 1;
                res = res*10 + digit;
            }else if(arr[i] == '-' && num_flag == 0){
                countOther++;
                sign = -1;
            }else if(arr[i] == '+' && num_flag == 0){
                countOther++;
            }else{ // 没有出现数字先出现了 ‘ ’ ‘+’ ‘-’ ‘.’以外的字符
                break;
            }
        }
        return sign*res;
    }
}

8. 两数相除

  • 题目描述:给定两个整数,被除数 dividend 和除数 divisor。将两数相除,要求不使用乘法、除法和 mod 运算符。返回被除数 dividend 除以除数 divisor 得到的商。整数除法的结果应当截去(truncate)其小数部分,例如:truncate(8.345) = 8 以及 truncate(-2.7335) = -2
  • 优秀思路:用long存一下,然后判断一下结果正负号,并把除数和被除数都转成正的,然后两个while循环 `位运算

例子(31 / 1):
第一轮
31 - 1 >= 0 所以进入第一个while,然后 temp = 1 << 1 = 2 ,result = 1 ;
31 - 2 >= 0 所以进入第二个while,然后 result = result << 1 = 2; temp = temp << 1 = 4;
31 - 4 >= 0 所以还在第二个while,然后 result = result << 1 = 4; temp = temp << 1 = 8;
31 - 8 >= 0 所以还在第二个while,然后 result = result << 1 = 8; temp = temp << 1 = 16;
31 - 16 >= 0 所以还在第二个while,然后 result = result << 1 = 16; temp = temp << 1 = 32;
31 - 32 < 0 所以跳出第二个while,然后更新被除数divid -= temp >> 1 = 31 - 16 = 15; 更新结果ans += result = 0 + 16 = 16;
新一轮
15 - 1 >= 0 所以进入第一个while,然后 temp = 1 << 1 = 2 ,result = 1 ;
15 - 2 >= 0 所以进入第二个while,然后 result = result << 1 = 2; temp = temp << 1 = 4;
……
15 - 8 >= 0 所以还在第二个while,然后 result = result << 1 = 8; temp = temp << 1 = 16;
15 - 16 < 0 所以跳出第二个while,然后更新被除数divid -= temp >> 1 = 15 - 8 = 7; 更新结果ans += result = 16 + 8 = 24;
新一轮
……

class Solution {
    public int divide(int dividend, int divisor) {
        if(dividend == Integer.MIN_VALUE && divisor == -1) return Integer.MAX_VALUE;

        // 用long存储,防止越界
        long divid = dividend;
        long divis = divisor;

        int flag = 1; // 结果的正负
        long times_divisor = 1; // 除数的倍数值
        long times_div = 0; // 可除的次数
        long ans = 0;

        // 除数、被除数全部转成正数操作
        if(divid < 0 && divis > 0){
            divid = - divid; 
            flag = -1;
        }else if(divid > 0 && divis < 0){
            divis = - divis; 
            flag = -1;
        }else if(divid < 0 && divis < 0){
            divid = - divid; 
            divis = - divis;
        }

        // 倍乘法计算商
        while(divid >= divis){
            times_divisor = divis << 1; 
            times_div = 1;
            while(divid >= times_divisor){
                times_divisor <<= 1;
                times_div <<= 1;
            }
            ans += times_div;
            divid -= times_divisor >> 1;
        }
        return (int)(flag*ans);
    }
}

9. 丑数 II

  • 题目描述:给你一个整数 n ,请你找出并返回第 n 个 丑数 。丑数 就是只包含质因数 2、35 的正整数。
  • 优秀思路:【三指针】核心就是把三个指针分别指向*2,*3,*5,并作为下标进行维护
class Solution {
    public int nthUglyNumber(int n) {
        int[] arr = new int[n];
        arr[0] = 1;
        int p2 = 0, p3 = 0, p5 = 0;
        for(int i = 1;i < n;i++){
            arr[i] = Math.min(Math.min(arr[p2]*2,arr[p3]*3),arr[p5]*5);
            if(arr[i] == arr[p2]*2) p2++;
            if(arr[i] == arr[p3]*3) p3++;
            if(arr[i] == arr[p5]*5) p5++;
        }
        return arr[n-1];
    }
}

10. 最大整除子集(待补充)

  • 题目描述:给你一个由 无重复 正整数组成的集合 nums ,请你找出并返回其中最大的整除子集 answer ,子集中每一元素对 (answer[i], answer[j]) 都应当满足:answer[i] % answer[j] == 0 ,或answer[j] % answer[i] == 0如果存在多个有效解子集,返回其中任何一个均可。
  • 优秀思路:【动态规划】
class Solution {
    public List<Integer> largestDivisibleSubset(int[] nums) {
        int len = nums.length;
        Arrays.sort(nums);

        // 第 1 步:动态规划找出最大子集的个数、最大子集中的最大整数
        int[] dp = new int[len];
        Arrays.fill(dp, 1);
        int maxSize = 1;
        int maxVal = dp[0];
        for (int i = 1; i < len; i++) {
            for (int j = 0; j < i; j++) {
                // 题目中说「没有重复元素」很重要
                if (nums[i] % nums[j] == 0) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }

            if (dp[i] > maxSize) {
                maxSize = dp[i];
                maxVal = nums[i];
            }
        }

        // 第 2 步:倒推获得最大子集
        List<Integer> res = new ArrayList<Integer>();
        if (maxSize == 1) {
            res.add(nums[0]);
            return res;
        }
        
        for (int i = len - 1; i >= 0 && maxSize > 0; i--) {
            if (dp[i] == maxSize && maxVal % nums[i] == 0) {
                res.add(nums[i]);
                maxVal = nums[i];
                maxSize--;
            }
        }
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值