文章目录
彩蛋题
一个棋盘,10行9列,一个马棋子(走日),走到(x,y)走k步有多少种走法
递归写法
//马从(0,0)出发,还有k步,并且一定要走完,最终到达x,y位置的方法数有多少
public static int f(int x,int y,int k)
{
//k步用完时还没到(0,0)则返回0,k步用完正好到(0,0)代表走到
if(k==0)
{
return x==0&&y==0?1:0;
}
//base case 越界情况
if(x<0||x>9||y<0||y>8)
{
return 0;
}
return f(x-2, y-1, k-1)
+f(x-1, y-2, k-1)
+f(x+2, y+1, k)
+f(x+1,y+2,k-1)
+f(x+2, y-1, k-1)
+f(x+1, y-2, k-1)
+f(x-1, y+2, k-1)
+f(x-2, y+1, k-1);
}
//看递归算法,有三个参数,代表是用三维表
//动态规划本质是
//初始化
dp[0][0][0]=1
dp[…][…][0]=0
其他全部为0
//状态转移方程
dp[i][j][k]=dp[i-1][j-2][k-1]+dp[i-2][j-1][k-1]+dp[i+2][j+1][k-1]+dp[i+1][j+2][k-1]
+dp[i-1][j+2][k-1]+dp[i-2][j+1][k-1]+dp[i+1][j-2][k-1]+dp[i+2][j-1][k-1]
//每个dp都要边界检测,越界则为0
//更新上,就是一个正方体,由最底面,逐层向上递推,每一层只依赖下面的一层。凭借此,可以写出循环,并且明确应该初始化的值
动态规划写法
public static int ways2(int x,int y,int k)
{
int[][][] dp=new int[10][9][k+1];//0~k
for(int level=1;level<=k;level++)
{//x,y怎么变化都没问题,其只依赖下面的一层
for(int i=0;i<10;i++)
{
for(int j=0;j<9;j++)
{
dp[i][j][k]=getvalue(i-1, j-2, k-1, dp)
+getvalue(i-2, j-1, k-1, dp)
+getvalue(i+2, j+1, k-1, dp)
+getvalue(i+1, j+2, k-1, dp)
+getvalue(i-2, j+1, k-1, dp)
+getvalue(i+2, j-1, k-1, dp)
+getvalue(i+1, j-2, k-1, dp)
+getvalue(i-1, j+2, k-1, dp);
}
}
}
return dp[x][y][k];
}
public static int getvalue(int x,int y,int k,int[][][] dp)
{
//如果x,y越界,则我给你0,如果没越界,我给你表里正常值
if(x<0||x>9||y<0||y>8)
return 0;
else
return dp[x][y][k];
}
斐波那契数列
1,1,2,3,5,8…
一般就是O(n),一项项列。
但可以优化成O(LOGN)
F
(
N
)
=
F
(
N
−
1
)
+
F
(
N
−
2
)
F(N)=F(N-1)+F(N-2)
F(N)=F(N−1)+F(N−2)
利用线性代数的方法:矩阵分解
∣
F
(
2
)
,
F
(
1
)
∣
|F(2),F(1)|
∣F(2),F(1)∣
∣
F
(
3
)
,
F
(
2
)
∣
=
∣
F
(
2
)
+
F
(
1
)
,
F
(
1
)
∣
=
∣
F
(
2
)
,
F
(
1
)
∣
∗
∣
1
0
∣
∣
1
1
∣
|F(3),F(2)|=|F(2)+F(1),F(1)|\\ =|F(2),F(1)|*\begin{matrix} |1&0|\\ |1&1|\\ \end{matrix}
∣F(3),F(2)∣=∣F(2)+F(1),F(1)∣=∣F(2),F(1)∣∗∣1∣10∣1∣
∣ F ( 4 ) , F ( 3 ) ∣ = ∣ F ( 3 ) + F ( 2 ) , F ( 2 ) ∣ = ∣ F ( 3 ) , F ( 2 ) ∣ ∗ ∣ 1 0 ∣ ∣ 1 1 ∣ = ∣ F ( 2 ) , F ( 1 ) ∣ ∗ ∣ 1 0 ∣ ∣ 1 1 ∣ ∗ ∣ 1 0 ∣ ∣ 1 1 ∣ . . . |F(4),F(3)|=|F(3)+F(2),F(2)|\\ =|F(3),F(2)|*\begin{matrix} |1&0|\\ |1&1|\\ \end{matrix}\\ =|F(2),F(1)|*\begin{matrix} |1&0|\\ |1&1|\\ \end{matrix}*\begin{matrix} |1&0|\\ |1&1|\\ \end{matrix}\\ ... ∣F(4),F(3)∣=∣F(3)+F(2),F(2)∣=∣F(3),F(2)∣∗∣1∣10∣1∣=∣F(2),F(1)∣∗∣1∣10∣1∣∗∣1∣10∣1∣...
∣ F ( n ) , F ( n − 1 ) ∣ = ∣ F ( 2 ) , F ( 1 ) ∣ ∗ D 的 n − 2 次 方 |F(n),F(n-1)|=|F(2),F(1)|*D的n-2次方 ∣F(n),F(n−1)∣=∣F(2),F(1)∣∗D的n−2次方
o(log2n)
运算的复杂度由D的n-2次方来决定了
理解的小tips,如何快速算
1 0 75 = 1 0 100101 1 2 = 1 ∗ 1 0 1 ∗ 1 0 2 ∗ 1 0 8 ∗ 1 0 64 10^{75}=10^{1001011_{2}}\\ =1*10^1*10^2*10^8*10^{64}\\ 1075=1010010112=1∗101∗102∗108∗1064
//每轮更新,t乘以自己本身
long t=10
int n=1;
for(int i=0;i<7;i++)
{
if(num[i]==1)
{
n=n*t;
}
t=t*t;
}
//将复杂度简化到O(log2(75))
那么将n-2二进制化,也可以简化了
以下代码实现了O(LOG2N)得到斐波那契第n个
public int f3(int n)
{
if(n<1)
{
return 0;
}
if(n==1||n==2)
{
return 1;
}
int[][] base=new int[][]{{1,1},
{1,0}};
int[][] res=matrixpower(base,n-2);
return res[0][0]+res[1][0];
}
//算出t矩阵的n次方
public int[][] matrixpower(int[][] t,int n)
{
int[][] res=new int[t.length][t[0].length];
for(int i=0;i<res.length;i++)
{
for(int j=0;j<res[0].length;j++)
{
res[i][j]=1;
}
}
int[][] tmp=t;
//注意十进制快速转成二进制的写法
for(;n!=0;n>>=0)
{
if((n&1)!=0)
{
res=mulimatrix(res, tmp);
}
tmp=mulimatrix(tmp, tmp);
}
return res;
}
//两个矩阵乘完之后的结果返回
public int[][] mulimatrix(int[][] m1,int[][] m2)
{
//m1.length为行数
//m1[0][...] 为列数
int[][] res=new int[m1.length][m2[0].length];
for(int i=0;i<m1.length;i++)
{
for(int j=0;j<m2[0].length;j++)
{//前两层循环对结果赋值
for(int k=0;k<m2.length;k++)
{
res[i][j]=m1[i][k]*m2[k][j];
}
}
}
return res;
}
推广
这样的递推式都可以通过O(log2n)的时间复杂度解决
F
(
n
)
=
C
1
F
(
n
−
1
)
+
C
2
F
(
n
−
2
)
+
.
.
.
C
k
F
(
n
−
k
)
F(n)=C_1F(n-1)+C_2F(n-2)+...C_kF(n-k)
F(n)=C1F(n−1)+C2F(n−2)+...CkF(n−k)
解决下面这道题:
有一个农场第一年有一只奶牛,每一只奶牛每年会产一只小奶牛,所有牛不会死,而且牛长到第三年即可生牛(全是母牛,不需要公牛)
F
(
n
)
=
F
(
n
−
3
)
+
F
(
n
−
1
)
=
1
∗
F
(
n
−
3
)
+
0
∗
F
(
n
−
2
)
+
1
∗
F
(
n
−
1
)
F(n)=F(n-3)+F(n-1)\\ =1*F(n-3)+0*F(n-2)+1*F(n-1)\\
F(n)=F(n−3)+F(n−1)=1∗F(n−3)+0∗F(n−2)+1∗F(n−1)
也就是去年的牛+有生育能力的牛(3年前时间节点就存在的牛)
∣ F ( 4 ) , F ( 3 ) , F ( 2 ) ∣ = ∣ F ( 1 ) + F ( 3 ) , F ( 3 ) , F ( 2 ) ∣ = ∣ F ( 3 ) , F ( 2 ) , F ( 1 ) ∣ ∗ ∣ 1 1 0 ∣ ∣ 0 0 1 ∣ ∣ 1 0 0 ∣ |F(4),F(3),F(2)|=|F(1)+F(3),F(3),F(2)|=|F(3),F(2),F(1)|*\begin{matrix} |1&1&0|\\ |0&0&1|\\ |1&0&0|\\ \end{matrix} ∣F(4),F(3),F(2)∣=∣F(1)+F(3),F(3),F(2)∣=∣F(3),F(2),F(1)∣∗∣1∣0∣11000∣1∣0∣
∣ F ( n ) , F ( n − 1 ) , F ( n − 2 ) ∣ = = ∣ F ( 3 ) , F ( 2 ) , F ( 1 ) ∣ ∗ D 的 n − 3 次 方 |F(n),F(n-1),F(n-2)|==|F(3),F(2),F(1)|*D的n-3次方 ∣F(n),F(n−1),F(n−2)∣==∣F(3),F(2),F(1)∣∗D的n−3次方
优化的迈台阶方法
小人迈台阶,可以迈1步或者迈2步,问迈第n层多少种走法(斐波那契数列)
F
(
n
)
=
F
(
n
−
2
)
+
F
(
n
−
1
)
F
(
1
)
=
1
F
(
2
)
=
2
F(n)=F(n-2)+F(n-1)\\ F(1)=1\\ F(2)=2\\
F(n)=F(n−2)+F(n−1)F(1)=1F(2)=2
依然可以优化到O(log2n)
补充题目1
给定一个数,想象由0和1两种字符,组成的所有长度为N的字符串,如果某个字符串,任何0字符的左边都有1紧挨着,认为这个字符达标,返回有多少达标的字符串。
d p [ i ] [ 0 ] 表 明 以 0 为 结 尾 长 度 为 i 的 字 符 串 个 数 d p [ i ] [ 1 ] = d p [ i − 1 ] [ 0 ] + d p [ i − 1 ] [ 1 ] d p [ i ] [ 0 ] = d p [ i − 1 ] [ 1 ] dp[i][0]表明以0为结尾长度为i的字符串个数\\ dp[i][1]=dp[i-1][0]+dp[i-1][1]\\ dp[i][0]=dp[i-1][1] dp[i][0]表明以0为结尾长度为i的字符串个数dp[i][1]=dp[i−1][0]+dp[i−1][1]dp[i][0]=dp[i−1][1]
d
p
[
1
]
[
1
]
=
1
d
p
[
1
]
[
0
]
=
0
dp[1][1]=1\\ dp[1][0]=0
dp[1][1]=1dp[1][0]=0
再
仔
细
观
察
后
:
d
p
[
i
]
[
1
]
=
d
p
[
i
−
2
]
[
1
]
+
d
p
[
i
−
1
]
[
1
]
再仔细观察后:dp[i][1]=dp[i-2][1]+dp[i-1][1]\\
再仔细观察后:dp[i][1]=dp[i−2][1]+dp[i−1][1]
石
破
天
惊
:
f
(
n
)
=
f
(
n
−
1
)
+
f
(
n
−
2
)
又
可
以
优
化
成
o
(
L
O
G
2
n
)
石破天惊:f(n)=f(n-1)+f (n-2)又可以优化成o(LOG2n)
石破天惊:f(n)=f(n−1)+f(n−2)又可以优化成o(LOG2n)
补充题目2
我 的 思 考 : d p [ 2 ] [ 1 ] = 1 d p [ 1 ] [ 2 ] = 1 d p [ i ] [ j ] = d p [ i − 1 ] [ j − 2 ] + d p [ i − 2 ] [ j − 1 ] 我的思考: dp[2][1]=1\\ dp[1][2]=1\\ dp[i][j]=dp[i-1][j-2]+dp[i-2][j-1]\\ 我的思考:dp[2][1]=1dp[1][2]=1dp[i][j]=dp[i−1][j−2]+dp[i−2][j−1]
很 棒 的 递 归 想 法 : F ( n ) = F ( n − 1 ) + F ( n − 2 ) 很棒的递归想法:\\ F(n)=F(n-1)+F(n-2)\\ 很棒的递归想法:F(n)=F(n−1)+F(n−2)
没条件转移 就用斐波那契!!!超级优化
蓄水池问题
问题场景:
左边在不断掉有编号的球,但不知道总共有多少个。
我们有一个包,它的容量为10,不断拿小球,我们要求当停止掉下小球时,包里取了10个小球,要求每个球被取的概率为
10 n \frac{10}{n} n10
规则
- 前10个小球,直接入袋
- 后面的第i个小球,以 10 i \frac{10}{i} i10的概率入袋
- 从袋中以 1 10 \frac{1}{10} 101的概率随机扔出去一个小球
证明
对 于 前 10 个 小 球 , 当 第 11 个 球 来 临 时 , 被 保 留 下 的 概 率 是 多 少 ? = 1 − 10 11 ∗ 1 10 = 10 11 对 于 前 10 个 小 球 , 当 第 12 个 球 来 临 时 , 被 保 留 下 的 概 率 是 多 少 ? = 10 11 ∗ ( 1 − 10 12 ∗ 1 10 ) = 10 12 . . . 对 于 前 10 个 小 球 , 当 第 n 个 球 来 临 时 , 被 保 留 下 的 概 率 是 多 少 ? = 10 n 对于前10个小球,当第11个球来临时,被保留下的概率是多少?\\ =1-\frac{10}{11}*\frac{1}{10}=\frac{10}{11}\\ 对于前10个小球,当第12个球来临时,被保留下的概率是多少?\\ =\frac{10}{11}*(1-\frac{10}{12}*\frac{1}{10})=\frac{10}{12}\\ ...\\ 对于前10个小球,当第n个球来临时,被保留下的概率是多少?\\ =\frac{10}{n}\\ 对于前10个小球,当第11个球来临时,被保留下的概率是多少?=1−1110∗101=1110对于前10个小球,当第12个球来临时,被保留下的概率是多少?=1110∗(1−1210∗101)=1210...对于前10个小球,当第n个球来临时,被保留下的概率是多少?=n10
对 于 后 面 的 球 来 说 ( 比 如 第 11 个 球 ) : 刚 来 时 = 10 11 对 于 后 面 的 球 来 说 ( 比 如 第 11 个 球 ) : 第 12 球 来 时 = 10 11 ∗ ( 1 − 1 10 ∗ 10 12 ) = 10 12 . . . 对 于 后 面 的 球 来 说 ( 比 如 第 11 个 球 ) : 第 n 球 来 时 = 10 n 对于后面的球来说(比如第11个球): 刚来时 \\ =\frac{10}{11}\\ 对于后面的球来说(比如第11个球): 第12球来时 \\ =\frac{10}{11}*(1-\frac{1}{10}*\frac{10}{12})=\frac{10}{12}\\ ...\\ 对于后面的球来说(比如第11个球): 第n球来时 \\ =\frac{10}{n}\\ 对于后面的球来说(比如第11个球):刚来时=1110对于后面的球来说(比如第11个球):第12球来时=1110∗(1−101∗1210)=1210...对于后面的球来说(比如第11个球):第n球来时=n10
适用很多场景哦
1.本质:给定一个数据流,数据流长度N很大,且N直到处理完所有数据之前都不可知,请问如何在只遍历一遍数据(O(N))的情况下,能够随机选取出m个不重复的数据。
彩蛋题2哦
如何用等概率的1~ 7生成等概率的1~10?
二 进 制 形 式 转 化 : 123 代 表 0 456 代 表 1 7 扔 掉 这 样 就 得 到 了 等 概 率 生 成 0 , 1 的 随 机 源 然 后 1 − 10 可 以 转 化 为 二 进 制 四 位 二 进 制 可 以 等 概 率 表 示 0 − 15 扔 掉 不 属 于 1 − 10 的 数 即 可 二进制形式转化: 1 2 3代表0 \\ 4 5 6代表1 \\ 7扔掉\\ 这样就得到了等概率生成0,1的随机源 \\ 然后 1-10 可以转化为二进制 \\ 四位二进制可以等概率表示0-15 \\ 扔掉不属于1-10的数即可 二进制形式转化:123代表0456代表17扔掉这样就得到了等概率生成0,1的随机源然后1−10可以转化为二进制四位二进制可以等概率表示0−15扔掉不属于1−10的数即可