刷题笔记6.递归和分治模板
递归模板
public void recur(int level,int param){
//terminator1.终止条件
if(level > Max_Level){
// process result
return;
}
// process current logic 2.每一层的逻辑
process(level, param);
//drill down
recur(level:level+1, newParam);
//restore current status
}
70. 爬楼梯
方法一:动态规划
思路和算法
我们用 f(x)f(x) 表示爬到第 xx 级台阶的方案数,考虑最后一步可能跨了一级台阶,也可能跨了两级台阶,所以我们可以列出如下式子:
f(x) = f(x - 1) + f(x - 2)
f(x)=f(x−1)+f(x−2)
它意味着爬到第 xx 级台阶的方案数是爬到第 x - 1x−1 级台阶的方案数和爬到第 x - 2x−2 级台阶的方案数的和。很好理解,因为每次只能爬 11 级或 22 级,所以 f(x)f(x) 只能从 f(x - 1)f(x−1) 和 f(x - 2)f(x−2) 转移过来,而这里要统计方案总数,我们就需要对这两项的贡献求和。
以上是动态规划的转移方程,下面我们来讨论边界条件。我们是从第 00 级开始爬的,所以从第 00 级爬到第 00 级我们可以看作只有一种方案,即 f(0) = 1f(0)=1;从第 00 级到第 11 级也只有一种方案,即爬一级,f(1) = 1f(1)=1。这两个作为边界条件就可以继续向后推导出第 nn 级的正确结果。我们不妨写几项来验证一下,根据转移方程得到 f(2) = 2f(2)=2,f(3) = 3f(3)=3,f(4) = 5f(4)=5,……,我们把这些情况都枚举出来,发现计算的结果是正确的。
我们不难通过转移方程和边界条件给出一个时间复杂度和空间复杂度都是 O(n)O(n) 的实现,但是由于这里的 f(x)f(x) 只和 f(x - 1)f(x−1) 与 f(x - 2)f(x−2) 有关,所以我们可以用「滚动数组思想」把空间复杂度优化成 O(1)O(1)。下面的代码中给出的就是这种实现
class Solution {
public int climbStairs(int n) {
int p = 0, q = 0, r = 1;
for (int i = 1; i <= n; ++i) {
p = q;
q = r;
r = p + q;
}
return r;
}
}
class Solution {
public int climbStairs(int n) {
if (n ==1) return 1;
if (n ==2) return 2;
int[] climb =new int[n+1];
climb[1] = 1;
climb[2] = 2;
if(n<=2) return climb[n];
for(int i=3;i<=n;i++){
climb[i]=climb[i-1]+climb[i-2];
}
return climb[n];
}
}
22. 括号生成
class Solution {
public List<String> ans = new ArrayList();
public List<String> generateParenthesis(int n) {
generate(0,0,n, "");
return ans;
}
public void generate(int left,int right, int n , String s){
if(left==n&&right==n){
ans.add(s);
return;
}
if(left<n) generate(left+1,right,n,s+"(");
if(left>right) generate(left,right+1,n,s+")");
}
}
分治模板
public void recur(int level,int param){
//terminator
if(level > Max_Level){
// process result
return;
}
//prepara data
//conquer subproblems 分子问题
// process and generate the final result (merge)
}
public ListNode mergeKLists(ListNode[] lists) {
return merge(lists,0,lists.length-1);
}
public ListNode merge(ListNode [] lists,int left,int right){
if(left==right){
return lists[left];
}
if(right<left) {
return null;
}
int mid=left+(right-left)/2;
return mergeTwoLists(merge(lists,left,mid),merge(lists,mid+1,right));
}
drill down 和 revert state中间加一步把drill down得到的这些子结果要返回回去。
回溯
采用试错的思想,尝试分布的解决一个问题,一种特殊的递归。
50. Pow(x, n)
- 使用折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,这时候我们再往回乘,如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,如果是奇数,则还需要乘上个x的值。还有一点需要引起我们的注意的是n有可能为负数,对于n是负数的情况,计算出一个结果再取其倒数即可。我们让i初始化为n,然后看i是否是2的倍数,是的话x乘以自己,否则res乘以x,i每次循环缩小一半,直到为0停止循环。最后看n的正负,如果为负,返回其倒数
class Solution {
public double myPow(double x, int n) {
double result = 1.0;
if(n==0) return result;
for(int i=n;i!=0;i/=2){
if(i%2!=0) result*=x;
x*=x;;
}
return n < 0 ? 1 / result : result;
}
}
2. 快速幂加递归
class Solution {
public double myPow(double x, int n) {
long N=n;
return N>=0?fastpow(x,N):1.0/fastpow(x,-N);
}
double fastpow(double x , long n){
if(n==0) return 1.0;
double y=fastpow(x,n/2);
return n%=0?y*y*x:y*y;
}
}