给定n的二进制数字计数和m的最大连续出现次数,找到不同的可能二进制数的数量。此外,最左边和最右边的位必须为1.
例如n = 5,m = 3。
计数是7: 10001 10011 10101 10111 11001 11011 11101
注意我们排除了11111,因为其中存在太多连续的1。
这是我最近的一个采访问题,一直困扰着我。我不想暴力检查每个数字的合法性,因为n可以是> 32。
答案 0 :(得分:3)
如果二进制序列以“1”开头并且最多m
个连续的“1”数字,那么我们调用二进制序列几乎有效。
对于i = 1, ..., n
和j = 0, ..., m
,让a(i, j)
为长度为i
且以j
个连续“1”数字结尾的几乎有效序列的数量。
然后
a(1, 1) = 1
和a(1, j) = 0 for j != 1
,因为“1”是长度为1的唯一有效序列。n >= 2
和j = 0
,我们有a(i, 0) = a(i-1, 0) + a(i-1, 1) + ... + a(i-1, m)
,因为将“0”附加到任何几乎有效的长度为i-1
的序列,会得到一个几乎有效的长度序列{{1以“0”结尾。i
和n >= 2
,我们有j > 0
,因为将“1”附加到几乎有效的序列,a(i, j) = a(i-1, j-1)
尾随的序列会给出一个几乎有效的长度序列{{1与i-1
尾随的。{/ li>
最后,想要的数字是长度为j
并且尾随“1”的几乎有效序列的数量,所以这是
i
写成C函数:
n
甚至可以改进存储要求,因为只需要矩阵f(n, m) = a(n, 1) + a(n, 2) + ... + a(n, m)
的最后一列。运行时复杂度为int a[NMAX+1][MMAX+1];
int f(int n, int m)
{
int i, j, s;
// compute a(1, j):
for (j = 0; j <= m; j++)
a[1][j] = (j == 1);
for (i = 2; i <= n; i++) {
// compute a(i, 0):
s = 0;
for (j = 0; j <= m; j++)
s += a[i-1][j];
a[i][0] = s;
// compute a(i, j):
for (j = 1; j <= m; j++)
a[i][j] = a[i-1][j-1];
}
// final result:
s = 0;
for (j = 1; j <= m; j++)
s += a[n][j];
return s;
}
。
答案 1 :(得分:2)
如果没有太多的组合洞察力,你可以用DP解决这个问题。让我们调用 left # n,m right 长度为n的二进制字符串的数量,没有连续1的子字符串长于m,从头开始字符串左边,以字符串右边结束。显然,我们希望找到 1 # n-2,m 1 。
关键观察是 left # n,m right = left +'1'#< sub> n-1,m right + left +'0'# n-1,m right
js中的一个简单实现(不确定它是否适用于小m,并且通常未经测试):
function hash(n,m) {
return _('1',n-2);
function _(left,n){
if (m+1 <= left.length && left.lastIndexOf('0') <= left.length-m-2)
return 0;
if (n==0)
return (m <= left.length &&
left.lastIndexOf('0') <= left.length-m-1 ? 0:1);
return _(left+'1',n-1) + _(left+'0',n-1);
}
}
hash(5,3); // 7
当然这比蛮力更有效,但是运行时复杂度仍然是指数级的,因此对于大的n值来说是不切实际的。