【算法设计与分析】(三)动态规划_更新中:斐波那契、二项式系数、树的最大独立集、最长递增、公共子序列、编辑距离、Hischberg、最优二叉搜索树、交替拿硬币、石子合并、背包、乘电梯

写在前面

分治
动态规划本篇 还差一堆
贪心
网络流

首先,怕误人子弟必须声明一下本人很菜(越复习越觉得完蛋了 作为一个科班研究生算法学成这样非常惭愧(跪 ,可能写的都不是很懂,很多内容打算背过去了。因为我发现好像真的有人看所以多提醒一句。。(大家就只食用目录吧

其次,本文只也许够应付考试,个人使用。而且其实就是ppt内容只是我自己喜欢这样整理。虽然全力理解内容且认真书写但也可能存在错误,如有发现麻烦指正,谢谢🌹

最后,因为不知道考试怎么考,本人的复习方式是照着目录讲一遍自己的理解+写伪代码(如果来的及会再做一个综合纯享版),再做一下上课讲过的例题和作业(印象里只有主定理、FFT、网络流上课练习+分治和动规两次作业)。

文章持续更新中,或许考前能发完也可能发不完。

祝我们好运🌹

动态规划

动机: 在递归求解的过程中重复求解子问题
策略空间换时间——将子问题保存下来避免重复计算
- 空间:子问题的数量(由子问题的参数决定)
关键:找到正确且高效的递归关系
求解:自顶向下/自底向上备忘录递归,自底向上没有递归调用的时空开销。

当难以建立递归关系考虑以下两种方案:

  • 增加约束条件
  • 增加参数

思路:

  • 建立一个新的表示
  • 基于这个新表示转化原问题
  • 考虑基本情况
  • 考虑归纳步骤

最重要的还是写归纳步骤的递归关系,想不出来那万事休矣。

斐波那契

1.递归

直接递归求解存在很多重复计算故考虑将历史记录保存
在这里插入图片描述

2.自顶向下动规(被动备忘录)

在这里插入图片描述

3.自底向上动规(主动备忘录)

从小到大计算
在这里插入图片描述

4.进一步优化(空间优化)

上一步的自底向上递归保存了所有的f(n),但实际上对于斐波那契当前需要求解的n的值来说只有n-1和n-2是有用的故只保存当前的数和前两个数,用三个变量即可。
在这里插入图片描述

二项式系数

1.递归

二项式系数是可以用C(N,K)直接求解的,但是大数计算容易溢出。
考虑数学的方式拆分组合数做递归
在这里插入图片描述
仍然是存在重复运算

2.自顶向下动规(被动备忘录)

都是一个套路的
在这里插入图片描述

3.自底向上动规(主动备忘录)

也是从小到大直接算,这里可以考虑不同的填写顺序
在这里插入图片描述

4.进一步优化(空间优化)

仍然是不需要过多的变量存储
在这里插入图片描述

树的最大独立集

1.问题定义

在这里插入图片描述

所谓树的独立集:两两不相邻顶点构成的集合。
而最大独立集:不被其他独立集包含即为最大。
问题就是给定一棵树返回最大独立集。

2.递归关系①

如何考虑这个问题:顶点在不在独立集里。
所以问题的解只有两种情况:
在这里插入图片描述
自底向上递归直接用树存储伪码:
在这里插入图片描述
这里面孩子用的递归调用孙子直接x.MIS暂且理解成x.MIS可以直接从备忘录读取
下面的也是一样的

3.递归关系②

仍然是考虑父节点在不在独立集里,只是换一种定义:
在这里插入图片描述

最长递增子序列-(作业)

子序列:序列顺序不变,可以不连续。
求解s[1 … n]的最长子序列

1.难以建立递归关系的两个解决方案

考虑递归求解一般的思路:知道了n-1的情况求n
假设知道了前n-1的最长递增子序列的长度,但是不知道n能不能放进去。
如果同时知道了n-1的长度和最后一个是几呢?知道n能不能放进去了。但是如果n不能放进去,也有可能存在另一个n-1序列能把n放进去。
但是又不可能把所有的序列记录。

当难以建立递归关系考虑以下两种方案

  • 增加约束条件:比如定义L(k)是以 k结尾/以k开头 的最长子序列的长度
  • 增加参数:定义L(i,j)中的 s(j…n)所有数都大于s[i]/小于s[i] 的最长子序列长度 *注意i和j是什么

2.增加约束自底向上动规

定义L(k)是以 k结尾 的最长子序列的长度
重新定义原问题:求解max L(k) (1<=k<=n)
考虑基本情况:k=1: L(k)=1
考虑递归关系:L(k) = max{1, max{L(i)+1}(i∈(1,k-1),如果s[k]>s[i]可以加上s[k],选最大的)} 记得跟1比。
在这里插入图片描述
时间复杂度: O ( n 2 ) O(n^2) O(n2)
一个例子:
在这里插入图片描述
反过来定义是一样的
在这里插入图片描述
关于作业问过的返回序列:
(简单贴一下我写的
思路:使用一个额外的数组来记录以每个位置结尾的最长递增子序列。当找到全局的最长递增子序列长度时,可以根据这个信息来重建最长递增子序列。具体见代码实现。

int lis_dp1(const vector<int>& s, int n) {
 // insert code here...
	// let the L(k) means the LIS's  last number is s[k]. the question is to get max(L(k))
	vector<int> L1(n);
	int answer = 1;
	for(int k=0;k<=n;k++){
        if(k==0){
            L1[k]=1;
        }
        else{
            for(int i=0;i<=k-1;i++){
                if(s[k]>s[i]){
                    L1[k] = max(L1[k],L1[i]+1);
                }
                L1[k] = max(1,L1[k]);
            }
        }
        answer = max(answer,L1[k]);
//        cout<<L1[k]<<endl;
    }
	return answer;
}
vector<int> find_lis_dp1(const vector<int>& s, int n) {
 // insert code here...
 	n++; 
 	vector<int> L1(n, 1);
    vector<int> prev_index(n, -1);

    int max_length = 1;
    int end_index = 0;

    for (int k = 1; k < n; k++) {
        for (int i = 0; i < k; i++) {
            if (s[k] > s[i] && L1[k] < L1[i] + 1) {
                L1[k] = L1[i] + 1;
                prev_index[k] = i;
            }
        }

        if (L1[k] > max_length) {
            max_length = L1[k];
            end_index = k;
        }
    }

    vector<int> result;
    while (end_index != -1) {
        result.insert(result.begin(), s[end_index]);
        end_index = prev_index[end_index];
    }

    return result;
}

3.增加子问题参数自底向上动规

定义L(i,j)中的 s(j…n)所有数都大于s[i] 的最长子序列长度
原问题转化:求L(0,1),s[0] = -∞
基本情况:j>n L(i,j)=0(二维数组边界)
递归关系:
在这里插入图片描述
时间空间复杂度都是 O ( n 2 ) O(n^2) O(n2)
定义成都小于s[i]也是一样的。
在这里插入图片描述

int lis_dp2(const vector<int>& s, int n) {
 // insert code here...
 	n++; //number 
 	vector<int> ss(n+1); //0-9 10
    for(int i=0;i<=n;i++){
        if(i==0) ss[i]=INT_MIN;
        else ss[i]=s[i-1]; 
    }
    int matrics_length = n+2; 
    vector<vector<int>> L2(matrics_length,vector<int>(matrics_length));
    for(int j=matrics_length-2;j>=0;j--){
        for(int i=matrics_length-2;i>=0;i--){
            if(j>n) L2[i][j]=0;
            else if(ss[i]>=ss[j]){
                L2[i][j]=L2[i][j+1];
            }
            else{
                L2[i][j]=max(L2[i][j+1],1+L2[j][j+1]);
            }
        }
    }
    return L2[0][1];
}
vector<int> find_lis_dp2(const vector<int>& s, int n) {
 // insert code here...
 	n++;
    vector<int> ss(n + 1);
    for (int i = 0; i <= n; i++) {
        if (i == 0) ss[i] = INT_MIN;
        else ss[i] = s[i - 1];
    }

    int matrics_length = n + 2;
    vector<vector<int>> L2(matrics_length, vector<int>(matrics_length));

    for (int j = matrics_length - 2; j >= 0; j--) {
        for (int i = matrics_length - 2; i >= 0; i--) {
            if (j > n) L2[i][j] = 0;
            else if (ss[i] >= ss[j]) {
                L2[i][j] = L2[i][j + 1];
            }
            else {
                L2[i][j] = max(L2[i][j + 1], 1 + L2[j][j + 1]);
            }
        }
    }

    int length = L2[0][1];
    vector<int> result;
    int i = 0, j = 1;

    while (j < matrics_length - 1) {
        if (ss[i] < ss[j] && L2[i][j] == 1 + L2[j][j + 1]) {
            result.push_back(ss[j]);
            i = j;
        }
        j++;
    }

    return result;
}
  • 贴一下反过来的:
  • 在这里插入图片描述

4.对第一种思路进一步加约束优化

  • key:对于长度为k的最长递增子序列,只需要记住末尾元素最小的
  • 在这里插入图片描述
    时间复杂度:O(logn)
int lis_dp3(const vector<int>& s, int n) {
 // insert code here...
	vector<int> last(1);
    last[0]=INT_MIN;
    for(int k=0;k<=n;k++){
        if(k==0){
            last.insert(last.end(),s[k]);
        }
        else{
            int last_id = last.size()-1;
            if(s[k]>last[last_id]){
                last.insert(last.end(),s[k]);
            }
            else{
                for(int i=last_id;i>=0;i--){
                    if(s[k]>last[i]){
                        last[i+1]=s[k];
                        break;
                    }
                }
            }
            
        }
//         cout<<"k="<<k<<"  s[k]="<<s[k]<<"  last:"<<last<<endl;
    }		

    return last.size()-1;
	
}

在这里插入图片描述

编辑距离

1.问题定义

需要编辑的个数和为编辑距离,目标是让一个字符串转成另外一个让编辑距离最小。
提出序列比对:可视化修改字符串的过程。上行为原字符串,下行为需要更改的。第一行的空格表示插入第二行空格表示删除。上下不同的个数和就是编辑距离。

3. 递归关系

思路:考虑最后一列。
我觉得其实这个二位数组可以这样理解:填写顺序从左上到右下,每填写一个格子,这个格子所在的行和列就全都填写完。向右和向下是插入或者删除,而向右下就是在替换
在这里插入图片描述

2. 例子

在这里插入图片描述
上面图对应的三条路
在这里插入图片描述

Hischberg’s algorithm

上面的改进:用O(m+n)空间构造最优比对
直觉上是创建一个编辑图,求左上到右下的最短路径
我觉得不会考QAQ

最长公共子序列

最优二叉搜索树

交替拿硬币

石子合并

背包

每个物品存在价值v和重量w,给定背包最大承重W求如何放让价值最大。

递归关系

思路:考虑已知前n-1个物品的最优解,最后一个能不能放,能放的话放或不放取最优。
基本情况:n=0 那价值为零
在这里插入图片描述

乘坐电梯

1.问题描述

n个人,给出体重w[n]
电梯承重x
问最少几趟电梯能把人送下去

2.思路

考虑知道了n-1个人的最优解,最后一个人考虑能不能装进任何一趟,不能就新开一个。
问题在于这个最后一个人的选择,这是一个运送顺序的问题。
把假设扩大:

  • 假设对{1 … n}的任意子集c,可以计算他的最优运送B( c )
  • 这个B( c )需要记录两个值:B( c ).first运送趟数,

吃个饭先

3.例子

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值