下一个数
题目描述:
下一个数。给定一个正整数,找出与其二进制表达式中1的个数相同且大小最接近的那两个数(一个略大,一个略小)。
示例1:
- 输入:num = 2(或者0b10)
- 输出:[4, 1] 或者([0b100, 0b1])
示例2:
-
输入:num = 1
-
输出:[2, -1]
提示:num的范围在[1, 2147483647]之间;
如果找不到前一个或者后一个满足条件的正数,那么输出 -1。
思路
- 首先判断 输入是否符合 [1, 2147483647] 之间
- 不符合 输出 res={-1,-1}
- 符合
- 寻找上一个数
- 寻找下一个数
关键 如何寻找上下一个相邻的数,并且二进制中1的个数相同
寻找上一个数
n = 13948 举例
首先我们想要把这个数变大,比如十进制下加一,同时在二进制中1的个数又要保持不变。
给定一个数n和两个位的下标i和j,假设将位i从1翻转为0,位j从0翻转为1。若i>j,n就会减小;若i<j,则n就会变大。
继而得到以下几点,
1)若将某个0翻转为1,就必须将某个1翻转为0。
2)进行位翻转时,如果0变1的位处于1变0的左边,这个数字就会变大。
3)所以必须翻转最右边的0,而且它的右边必须还有个1。
所以我们要翻转的 0 的位置 需要符合 0 前有 一个或者若干连续的 1 ,且在这些 1 前面绝无 1,只能有 0 或者无 0 。
这里我们令c = n 以不改变n的大小。
step 1: 确定 需要翻转0的位置P
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fbSiEtYJ-1645690803959)(D:\Markdown\imge\image-20220224091405924.png)]
- 首先确定前面 0 的个数 c 0 c_0 c0,同时右移
- 确定P前面1的个数 c 1 c_1 c1
- P前面共有 c 1 + c 0 c_1+c_0 c1+c0个数
- 这里需要注意 11111000000 这种形式没有比它大的数并且1的个数相同 所以 如果 c 1 + c 0 c_1+c_0 c1+c0==32 结束即可
step 2: 翻转 P
n = n | (1<<( c 1 + c 0 c_1+c_0 c1+c0))
step 3:P后面置0
n = n & (~((1<<(c0+c1))-1));
step 4: n取最后 c 1 − 1 c_1-1 c1−1位为1
n = n | ((1<<(c1-1))-1);
寻找下一个数
与选择上一个数相似这是寻找 非拖尾1 的位置P
实现方法与取得较大数非常相似。
1)计算 c 0 c_0 c0和 c 1 c_1 c1。注意 c 1 c_1 c1是拖尾1的个数,而c0为紧邻拖尾1的左方一连串0的个数。
2)将最右边、非拖尾1变为0,其位置为p = c1+c0。
3)将位p右边的所有位清零。
4)在紧邻位置p的右方,插入c1+1个1。
Step 1: 确定P位置前 0的个数 c 0 c_0 c0和 1的个数 c 1 c_1 c1
Step 2:P位置翻转
step 3: P位置后置1
step 4:取 c 0 − 1 c_0-1 c0−1置为0
package 力扣;
/**
* @author yyq
* @create 2022-02-24 10:16
*/
public class day01 {
public static void main(String[] args) {
findClosedNumbers(2147483647);
}
public static int[] findClosedNumbers(int num) {
int[] res={-1,-1};
if(num<=0||num>2147483647){
return res;
}else {
res[0] = preNum(num);
res[1] = nextNum(num);
System.out.println("较大的数为"+res[0]+",较小的数为"+res[1]);
}
return res;
}
// 取相邻的较小的数
private static int nextNum(int n) {
// 10011 1(P) 00111
int c0=0;
int c1=0;
int c=n;
//step 1 获取P前 1和0的个数
while((c&1)==1){
c1++;
c=c>>1;
}
while((c&1)==0 && c0+c1!=32){
c0++;
c=c>>1;
}
// 000111111 没有比它小并且有相同个数的1
int x = c0+c1;
System.out.println("c0+c1="+x);
if(c0+c1==32) return -1;
//step 2 将P置0
n = n & (~(1<<(c0+c1)));
//step 3 将P后都置为 1
n = n | ((1<<(c0+c1))-1);
//step 4 将最后c0-1 个数置为0
n = n & (~((1<<(c0-1))-1));
return n;
}
// 取相邻的较大的数
public static int preNum(int n){
// 11100 0(P) 1111000
// step 1 确定P前 0的个数c0和1的个数c1
int c=n;
int c1=0;
int c0=0;
while((c&1)==0){
c0++;
c=c>>1;
}
while((c&1)==1 ){
c1++;
c=c>>1;
}
System.out.println("c0:"+c0);
System.out.println("c1:"+c1);
// step 2 这种形式 11111111100000000 没有比它大的并且1的个数相同的数
if((c0+c1)==31) return -1;
// step 3 置P为1
n = n | (1<<(c0+c1));
// System.out.println(n);
// step 4 置P后为0
n = n & (~((1<<(c0+c1))-1));
// System.out.println(n);
// step 5 取P后c1-1位为1
n = n | ((1<<(c1-1))-1);
return n;
}
}