题目描述
小明在学习二进制时发现了一类不含101的数,也就是:
将数字用二进制表示不包含101
现给定一个区间 [L,R] 求该区间内包含多少不含101的数
暴力算法
这个题我们可以直接遍历L至R的闭区间,将数字转换为二进制字符串来判断是否包含101 如果包含直接continue, 不包含 res++ 然后输出res即可
import java.util.Scanner;
public class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int left = sc.nextInt();
int right = sc.nextInt();
int numOfRange101 = getNumOfRange101(left, right);
System.out.println(numOfRange101);
}
public static int getNumOfRange101(int left, int right){
int res = 0;
for (int i = left; i <= right; i++) {
String s = Integer.toBinaryString(i);
if (s.contains("101")){
continue;
}
res++;
}
return res;
}
}
当然上述的算法在区间范围很大的时候会超时
优化
求left~right的区间的不含101个数的数字我们可以转化为求0到right和0到left-1两个区间的不含101个数的数字,然后用0到right区间的减去0到left区间的就可以得到答案
问题是如何求0~right区间的不含101的个数呢
举个栗子
10的二进制数为1010
现在我们来求解0~10范围内有多少个数字不含101
列出0~10的二进制代码
0 : 0
1 : 1
2 : 10
3 : 11
4 : 100
5 : 101
6 : 110
7 : 111
8 : 1000
9 : 1001
10 : 1010
我们可以把1010看成一个字符串数组来进行dfs遍历列举前一位和前一位的前一位 如果和当前位组成101则后续的二进制不管是多少都可以不看
如 1010 在101的时候就可以提前终止
我们可以写出如下dfs代码
public static int dfs(char[] arr, int cur, int pre, int prePre,boolean flag){
if (cur == arr.length){
// 遍历到末尾返回1
return 1;
}
// flag的含义是指当前字符是否受限,比如 1010 现在来到最后一位 0 如果当前位置受限(前两位为01)只能为0,如果为1 则 1011可能会超出范围
int curRange = flag? arr[cur] - '0' : 1;
int res = 0;
for (int i = 0; i <= curRange; i++) {
if (prePre == 1 && pre == 0 && i == 1){
// 出现101
continue;
}
// 如果前面两个位置都受限制则下一个位置必然受限
res += dfs(arr, cur+ 1,i,pre, flag&& i == curRange);
}
return res;
}
根据上述代码也就不难写出最后未优化版本的代码了
public static int getNumOfRange101(int left, int right){
int right101 = dfs(Integer.toBinaryString(right).toCharArray(),0,0,0,true);
// 题目中 1 <= left,right <= 10^9
int left101 = dfs(Integer.toBinaryString(left - 1).toCharArray(),0,0,0,true);
return right101 - left101;
}
public static int dfs(char[] arr, int cur, int pre, int prePre,boolean flag){
if (cur == arr.length){
// 遍历到末尾返回1
return 1;
}
// flag的含义是指当前字符是否受限,比如 1010 现在来到最后一位 0 如果当前位置受限(前两位为01)只能为0,如果为1 则 1011可能会超出范围
int curRange = flag? arr[cur] - '0' : 1;
int res = 0;
for (int i = 0; i <= curRange; i++) {
if (prePre == 1 && pre == 0 && i == 1){
// 出现101
continue;
}
// 如果前面两个位置都受限制则下一个位置必然受限
res += dfs(arr, cur+ 1,i,pre, flag&& i == curRange);
}
return res;
}
在上述代码中我们可以发现最后的结果只和index,pre,prepre有关且范围固定,
我们是否可以通过加一个缓存来加快计算过程呢
比如 101011
现在来到最后一个0出现的位置, 前三位分别是 010 但是之前的一个1是不固定的,这个属于重复计算,有利可图,什么时候可以使用缓存,当前位置不固定的时候.就是可以取0 也可以取1的时候为什么值固定时不能取呢,这需要看我们的dfs循环了,我们的res是遍历curRange 而curRange的取值范围为0~1所以当当前位置确定的时候我们应该去下一位去判断取当前位置会将值扩大
因此最终的代码如下:
public class Solution {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int left = sc.nextInt();
int right = sc.nextInt();
int numOfRange101 = getNumOfRange101(left, right);
System.out.println(numOfRange101);
}
public static int getNumOfRange101(int left, int right){
int[][][] leftDp = new int[left][2][2];
int[][][] rightDp = new int[right][2][2];
int right101 = dfs(Integer.toBinaryString(right).toCharArray(),0,0,0,true,rightDp);
int left101 = dfs(Integer.toBinaryString(left == 0 ? left : left - 1).toCharArray(),0,0,0,true,leftDp);
return right101 - left101;
}
public static int dfs(char[] arr, int cur, int pre, int prePre,boolean flag, int[][][] dp){
if (cur == arr.length){
// 遍历到末尾返回1
return 1;
}
if (!flag && dp[cur][pre][prePre] != 0){
return dp[cur][pre][prePre];
}
// flag的含义是指当前字符是否受限,比如 1010 现在来到最后一位 0 如果当前位置受限(前两位为01)只能为0,如果为1 则 1011可能会超出范围
int curRange = flag? arr[cur] - '0' : 1;
int res = 0;
for (int i = 0; i <= curRange; i++) {
if (prePre == 1 && pre == 0 && i == 1){
// 出现101
continue;
}
// 如果前面两个位置都受限制则下一个位置必然受限
res += dfs(arr, cur+ 1,i,pre, flag&& i == curRange,dp);
}
if(!flag){
dp[cur][pre][prePre] = res;
}
return res;
}
}
我们可以使用对数器来验证我们的代码是否正确
如下是对数器
public class Main {
public static void main(String[] args) {
int times = 1000;
for (int i = 0; i < times; i++) {
int left = (int) (Math.random() * 1000000);
int len = (int) (Math.random() * 1000000);
int right = left + len;
if (getNumOfRange101(left,right) != getNumOfRange101_1(left,right)){
System.out.println("ops,出错了");
break;
}
}
}
public static int getNumOfRange101(int left, int right){
int res = 0;
for (int i = left; i <= right; i++) {
String s = Integer.toBinaryString(i);
if (s.contains("101")){
continue;
}
res++;
}
return res;
}
public static int getNumOfRange101_1(int left, int right){
int[][][] leftDp = new int[left][2][2];
int[][][] rightDp = new int[right][2][2];
int right101 = dfs(Integer.toBinaryString(right).toCharArray(),0,0,0,true,rightDp);
int left101 = dfs(Integer.toBinaryString(left == 0 ? left : left - 1).toCharArray(),0,0,0,true,leftDp);
return right101 - left101;
}
public static int dfs(char[] arr, int cur, int pre, int prePre,boolean flag, int[][][] dp){
if (cur == arr.length){
// 遍历到末尾返回1
return 1;
}
if (!flag && dp[cur][pre][prePre] != 0){
return dp[cur][pre][prePre];
}
// flag的含义是指当前字符是否受限,比如 1010 现在来到最后一位 0 如果当前位置受限(前两位为01)只能为0,如果为1 则 1011可能会超出范围
int curRange = flag? arr[cur] - '0' : 1;
int res = 0;
for (int i = 0; i <= curRange; i++) {
if (prePre == 1 && pre == 0 && i == 1){
// 出现101
continue;
}
// 如果前面两个位置都受限制则下一个位置必然受限
res += dfs(arr, cur+ 1,i,pre, flag&& i == curRange,dp);
}
if (!flag){
dp[cur][pre][prePre] = res;
}
return res;
}
}