系列汇总:《刷题系列汇总》
文章目录
——————《剑指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~n
这n
个整数的十进制表示中1
出现的次数。例如,1~13
中包含1
的数字有1、10、11、12、13
因此共出现6
次 - 我的思路(82% - 72%):遍历
1~n
,求出每个数的所有位,若出现1
则count++
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
类型的浮点数base
和int
类型的整数exponent。求base
的exponent
次方。保证base
和exponent
不同时为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
报数…这样下去…直到剩下最后一个小朋友,可以不用表演,并且拿到牛客名贵的礼物。请你试着想下,哪个小朋友会得到这份礼品呢?(注:小朋友的编号是从0
到n-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、3
和5
的数称作丑数(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_num
和st_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. 字符串相乘
-
题目描述:给定两个以字符串形式表示的非负整数
num1
和num2
,返回num1
和num2
的乘积,它们的乘积也表示为字符串形式。
-
优秀思路:用一个数组
arr
来存储相乘后的每一位上的数字,从字符串num1
和num2
尾部向前扫描,将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
:说明res
和Integer.MAX_VALUE
位数相同时,尾数大于它
- 1.
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-1
和end+1
是为了保证能取到start
和end
这两个值
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;
}
}
- 写法2:
while
循环
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、3
或5
的正整数。 - 优秀思路:【三指针】核心就是把三个指针分别指向
*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;
}
}