文章目录
跳台阶扩展问题
第一次跳的是一阶,那么剩下的是n-1个台阶,跳法是f(n-1);第N个台阶肯定是要跳的,前面的台阶有2种可能(跳or不跳):有n阶时:f(n)=2^(n-1);f(n):总共的跳法、跳上n级台阶的方法个数;f(n - step) :step表示青蛙最后—跳跳了几个台阶.f(0):站在原地,一次跳到要的台阶数,
F(i): F(i - 1) + F(i - 2)+…F(0)
F(i - 1): F(i - 2) +F(i - 3) + … F(0)
F(i) : F(i - 1)+F(i - 1)= 2*F(i - 1)
class Solution{
public:
int jumpFloorII(int number){
if (number == 1)
{
return 1;一阶的时候 f(1) = 1
}
return 2*jumpFloorII(number - 1);
}
};
斐波那契数列变形:
一次只能跳1级或者2级
F(i): F(i- 1) + F(i - 2)
短形覆盖
添加链接描述
是从实际问题中提炼 出我们的解决问题的数学模型
f(n) : 总方法数 //状态递推:f(n) = f(n-1)【最后一个竖着放】 + f(n-2)【最后两个横着放】 f(0)我们这个可以不考虑,如果考虑值设为1,
int rectCover(int number) {number:最后1个的下标
if (number < 2){
return number; }
int *dp = new int[number+1];
dp[1] = 1; dp[2] = 2;初始化
for( int i = 3; i <= number; i++)
{ dp[i] = dp[i-1]+dp[i-2]; }
int num = dp[number];
delete dp;
return num;
}
跳石板
多种解,最优解,分解规模–〉前面答案要能被后面的问题所利用,被后面利用不了,则为分治。
#include<iostream>
#include<vector>
#include<limits.h>//INT_MAX
#include<math.h>
using:namespace std;
void get_div_num(int v, vector<int> &a)
{求约数
for (int i = 2; i <= sqrt(v); ++i){
if (v% i == 0)
{
a.push_back(i);
if (v / i != i)
a.push_back(v / i);
}
}
}
int Jump(int n, int m){//int_ max:不可到达,m个值都初始化为int_ max
vector<int> step(m + 1, INT_MAX);
step[n] = 0; //当前位置初始化
for (int i = n; i < m; ++i)
{
if (step[i] == INT_MAX);
continue;
vector<int> a;
get_div_num(i, a);//i位置的约数保存到数组a
for (int j = 0; j < a.size(); ++j){ //a.size():有多少个约数 = 有多少种跳法
if (a[j] + i <= m && step[a[j] + i] != INT_MAX)
{//需要挑选一个最小值
step[a[j] + i] = step[a[j] + i] < step[i] + 1 ? step[a[j] + i] : step[i] + 1;
}约数 + 当前位置i = a[j] + i
else if (a[j] + i <= m){
step[a[j] + i] = step[i] + 1;
}
}
}// step[m]:当前位置i到起始位置N需要跳的步数
return step[m] == INT_MAX ? -1 : step[m];
}
int main(){
int n, m, min_step;
while (cin >> n >> m){
min_step = Jump(n, m);
cout << min_step << endl;
}
return 0;
}
HJ52 字符串的编辑距离
添加链接描述
为了有初始值,5个字符,开6个空间,
F(i,j):word1的前i个字符转成word2的前j个字符的最小操作次数
F(i,j) = min { F(i-1,j)+1, F(i,j-1) +1, F(i-1,j-1) +(w1[i]==w2[j]?0:1) }
上式表示从删除,增加和替换操作中选择一个最小操作数;
初始化: 初始化一定要是确定的值,如果这里不加入空串,初始值无法确定
F(i,0) = i :word与空串的编辑距离:删除i个字符 ;F(0,i) = i :空串与word的编辑距离,增加j个字符
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int minDistance(const string &str1, const string &str2)
{ // word与空串之间的编辑距离为word的长度
if(str1.empty() || str2.empty())
return max(str1.size(), str2.size());
int len1 = str1.size();
int len2 = str2.size();
vector<vector<int>> f(len1+1, vector<int>(len2+1, 0)); //初始化距离
for(int i=0; i<=len1; ++i)
f[i][0] = i;
for(int j=0; j<=len2; ++j)
f[0][j] = j;
for(int i=1; i<=len1; ++i)
{
for(int j=1; j<=len2; ++j)
{
// 判断word1的第i个字符是否与word2的第j个字符相等
if(str2[j-1] == str1[i-1])
{删除,增加
f[i][j] = 1 + min(f[i-1][j], f[i][j-1]);
//字符相同,距离不发生变化 替换
f[i][j] = min(f[i][j], f[i-1][j-1]);
}
else
{删除,增加
f[i][j] = 1 + min(f[i-1][j], f[i][j-1]);
//由于字符不相同,所以距离+1 替换
f[i][j] = min(f[i][j], 1+f[i-1][j-1]);
}
}
}
return f[len1][len2];
}
int main()
{
string str1, str2;
while(cin >> str1 >> str2)
cout<<minDistance(str1, str2)<<endl;
return 0;
}
JD1 年终奖
class Bonus {
public:
int getMost(vector<vector<int> > board) {
int length = board.size();
int wideth = board[0].size();
vector<vector<int>> allPrice;
for (int i = 0; i < length; i++)
{
vector<int> tmp(wideth, 0);
allPrice.push_back(tmp);
}
allPrice[0][0] = board[0][0];
for (int i = 0; i < length; i++)
{
for (int j = 0; j < wideth; j++)
{
//如果是起点坐标,不做任何处理。
if (i == 0 && j == 0)
continue;
//如果走在行的临界边,也就是第一行,那么他只能向右走
//向右走的时候该点就要将后面的值加起来。
else if (i == 0) {
allPrice[i][j] = allPrice[i][j - 1] + board[i][j];
}
//如果走在列的临界边,也就是第一列,那么他只能向下走
//向下走的时候该点就要将上面的值加起来。
else if (j == 0) {
allPrice[i][j] = allPrice[i - 1][j] + board[i][j];
}
else {
//除去两个临界边,剩下的就是既能向右走,也能向下走,
//这时候就要考虑走到当前点的所有可能得情况,也就是走到当前点
//各自路径的和是不是这些所有到达该点路径当中最大的了。
allPrice[i][j] = max(allPrice[i][j - 1],
allPrice[i - 1][j]) + board[i][j];
}
}
}
// 返回最后一个坐标点的值,它就表示从左上角走到右下角的最大奖励
return allPrice[length - 1][wideth - 1];
}
};
表1:;礼物价值;表2:每一步的最优解,最右下角是要的答案。
CC12 拆分词句
添加链接描述
F(i): 前i个字符能否根据词典中的词被成功分词
F(i): true{j <i && F(j) && substr[j+1,i]能在词典中找到}
在j小于i中,只要能找到一个F(j)为true,并且从j+1到i之间的字符能在词典中找到,则F(i)为true
初始值: 对于初始值无法确定的,可以引入一个不代表实际意义的空状态,作为状态的起始 空状态的值需要保证状态递推可以正确且顺利的进行,到底取什么值可以通过简单 的例子进行代入 F(0) = true
bool wordBreak(string s, unordered_set<string> &dict){
if (s.empty()){ return false; }
if (dict.empty()){ return false; }
size是指向vector中数组长度后的那一个位置,即size处没有元素
vector<bool> can_break(s.size() + 1, false);
// 初始化F(0) = true
can_break[0] = true;
for (int i = 1; i <= s.size(); i++)
{
for (int j = i - 1; j >= 0; j--)
{
// 第j+1个字符的索引为j
if (can_break[j] && dict.find(s.substr(j, i - j)) != dict.end())
{ can_break[i] = true;
break; }
}
}
return can_break[s.size()];
}
CC31 三角形
F(i,j): 从(0,0)到(i,j)的最短路径和
状态递推: F(i,j) = min( F(i-1, j-1), F(i-1, j)) + triangle[i][j]
初始值: F(0,0) = triangle[0][0]
添加链接描述
int minimumTotal(vector<vector<int>> &triangle) {
if (triangle.empty()){ return 0; }
//初始化
vector<vector<int>> min_sum(triangle);
int line = triangle.size();
for (int i = 1; i < line; i++)
{
for (int j = 0; j <= i; j++)
{
// 处理左边界和右边界
if (j == 0)
{ min_sum[i][j] = min_sum[i - 1][j]; }
else if (j == i)
{ min_sum[i][j] = min_sum[i - 1][j - 1]; }
else
{min_sum[i][j] = min(min_sum[i - 1][j], min_sum[i - 1][j - 1]); }
// F(i,j) = min( F(i-1, j-1), F(i-1, j)) + triangle[i][j]
min_sum[i][j] = min_sum[i][j] + triangle[i][j];
}
}
int result = min_sum[line - 1][0];
// min(F(n-1, i))
for (int i = 1; i < line; i++)
{ result = min(min_sum[line - 1][i], result); }
return result; }
方法2:
F(i,j): 从(i,j)到最后一行的最短路径和
F(i,j) = min( F(i+1, j), F(i+1, j+1)) + triangle[i][j]
F(n-1,0) = triangle[n-1][0], F(n-1,1) = triangle[n-1][1],…, F(n-1,n-1) = triangle[n-1] [n-1]
逆向思维不需要考虑边界,也不需要最后寻找最小值,直接返回F(0,0)即可
int minimumTotal(vector<vector<int>> &triangle) {
if (triangle.empty()){ return 0; }
// F[n-1][n-1],...F[n-1][0]初始化
vector<vector<int>> min_sum(triangle);
int line = triangle.size();
// 从倒数第二行:line - 2开始
for (int i = line - 2; i >= 0; i--)
{ for (int j = 0; j <= i; j++){
// F(i,j) = min( F(i+1, j), F(i+1, j+1)) + triangle[i][j]
min_sum[i][j] = min(min_sum[i + 1][j], min_sum[i + 1][j + 1]) + triangle[i] [j];
}
}
return min_sum[0][0]; }
CC86 带权值的最小路径和
添加链接描述
子状态:从(0,0)到达(1,0),(1,1),(2,1),…(m-1,n-1)的最短路径
F(i,j): 从(0,0)到达F(i,j)的最短路径
状态递推: F(i,j) = min{F(i-1,j) , F(i,j-1)} + (i,j)
初始化: F(0,0) = (0,0)
特殊情况:第0行和第0列 F(0,i) = F(0,i-1) + (0,i)
F(i,0) = F(i-1,0) + (i,0)
int minPathSum(vector<vector<int> > &grid)
{ // 如果为空或者只有一行,返回0
if (grid.empty() || grid[0].empty())
{ return 0; }
// 获取行和列大小
const int M = grid.size();
const int N = grid[0].size();
// F(i,j)
vector<vector<int> > ret(M, vector<int>(N, 0));
// F(0,0), F(0,i), F(i,0)初始化
ret[0][0] = grid[0][0];
for (int i = 1; i != M; ++i)
{ ret[i][0] = grid[i][0] + ret[i - 1][0]; }
for (int i = 1; i != N; ++i)
{ ret[0][i] = grid[0][i] + ret[0][i - 1]; }
// F(i,j) = min{F(i-1,j) , F(i,j-1)} + (i,j)
for (int i = 1; i < M; ++i)
{ for (int j = 1; j < N; ++j)
{ ret[i][j] = grid[i][j] + min(ret[i - 1][j], ret[i][j - 1]);
}
}
return ret[M - 1][N - 1]; }
CC88 不同路径的数目(一)
F(i,j): 从(0,0)到达F(i,j)的路径数 状态递推: F(i,j) = F(i-1,j) + F(i,j-1)
添加链接描述
int uniquePaths(int m, int n) {
if (m < 1 || n < 1) { return 0; }// 第0行和第0列
vector<vector<int> > ret(m, vector<int>(n, 1));
for (int i = 1; i < m; ++i)
{
for (int j = 1; j < n; ++j)
{
ret[i][j] = ret[i - 1][j] + ret[i][j - 1];
}
}
return ret[m - 1][n - 1]; }
背包问题
F(i, j): 前i个物品放入大小为j的背包中所获得的最大价值
装不下:此时的价值与前i-1个的价值是一样的 F(i,j) = F(i-1,j)
装入:需要在两种选择中找最大的 F(i, j) = max{F(i-1,j), F(i-1, j - A[i]) + V[i]}
第0行和第0列都为0,没有装物品时的价值都为0
添加链接描述
int backPackII(int m, vector<int> A, vector<int> V) {
if (A.empty() || V.empty() || m < 1)
{ return 0; }
//多加一行一列,用于设置初始条件
const int N = A.size() + 1;
const int M = m + 1;
vector<vector<int> > result;
result.resize(N); //初始化所有位置为0
方法1:
for (int i = 0; i != N; ++i)
{ result[i].resize(M, 0); }
for (int i = 1; i < N; ++i)
{
for (int j = 1; j != M; ++j)
{
//第i个商品在A中对应的索引为i-1: i从1开始
//如果第i个商品大于j,说明放不下, 所以(i,j)的最大价值和(i-1,j)相同
if (A[i - 1] > j)
{ result[i][j] = result[i - 1][j]; }
//如果可以装下,分两种情况,装或者不装
//最后在装与不装中选出最大的价值
F(i-1,j): 不把第i个物品放入背包中, 价值就是前i-1个物品放入大小为j的背包的最大价值
F(i-1, j-A[i])+V[i]:第i个物品放入背包中,价值增加V[i],腾出j-A[i]的大小放第i 个商品
else
{int newValue = result[i-1][j - A[i - 1]] + V[i - 1];
result[i][j] = max(newValue, result[i - 1][j]);
}
}
}
//返回装入前N个商品,物品大小为m的最大价值
return result[N-1][m];
}
方法2:
for (int i = 0; i != N; ++i)
{
for (int j = M - 1; j > 0; --j)
{
if (A[i] > j)
{ result[j] = result[j]; }
else
{int newValue = result[j - A[i]] + V[i];
result[j] = max(newValue, result[j]); 从后往前更新
}
}
}
return result[m];
CC19 分割回文串-ii
添加链接描述
F(i): 到第i个字符需要的最小分割数
F(i) = min{F(i), 1 + F(j)}, where j<i && j+1到i是回文串
j+1到i判断为回文字符串,且已经知道从第1个字符 到第j个字符的最小切割数,再切一次,就可以保证 1–>j, j+1–>i都为回文串。如果整体为回文串[1,i]: F(i) = 0。
初始化: F(i) = i - 1: 到第i个字符需要的最大分割数 比如单个字符只需要切0次,因为单子符都为回文串, 2个字符最大需要1次,3个2次…
int minCut(string s) {
if (s.empty()) return 0;
int len = s.size();
vector<int> cut; // F(i)初始化
for (int i = 0; i < 1 + len; ++i)
{
cut.push_back(i - 1);
}
for (int i = 1; i < 1 + len; ++i)
{
for (int j = 0; j < i; ++j)
{
if (isPalindrome(s, j, i - 1)) 下标从0开始,第1个字符到第i个字符之间
{ cut[i] = min(cut[i], 1 + cut[j]);
}
}
}
return cut[len];
}//判断是否回文串 ,空串是回文串
bool isPalindrome(string s, int i, int j)
{ while (i<j)
{ if (s[i] != s[j])
{ return false; }
i++; j--;
}
return true;
}
斐波那契数列
添加链接描述
递归:时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下 当输入比较大时,可能导致栈溢出,有大量的重复计算,
int Fibonacci(int n){ 方法1
// 初始值
if (n <= 0){ return 0; }
if (n == 1 || n == 2)
{ return 1; }递归:大量的函数调用
return Fibonacci(n - 2) + Fibonacci(n - 1); }
动态规划:
int Fibonacci(int n){ // 初始值 方法2
if (n <= 0)
{ return 0; }
if (n == 1 || n == 2)
{ return 1; }// 申请一个数组,保存子问题的解,题目要求从第0项开始
int* record = new int[n + 1];
record[0] = 0; record[1] = 1;
for (int i = 2; i <= n; i++)
{
record[i] = record[i - 1] + record[i - 2];
}
return record[n];
delete[] record; }
上述解法的空间复杂度为O(n) 其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解 只需要保存两个子问题的解就可以 下面方法的空间复杂度将为O(1)
int Fibonacci(int n){ // 初始值 方法3
if (n <= 0)
{ return 0; }
if (n == 1 || n == 2)
{ return 1; }
int fn1 = 1; int fn2 = 1;
int result = 0;
for (int i = 3; i <= n; i++){
result = fn2 + fn1;
// 更新值
fn1 = fn2;
fn2 = result;
}
return result; }
方法4:map进行“剪枝”,因为枝太多,类似方法2
private: unordered_map<int, int> filter;
public: int Fibonacci(int n) {
if (n == 0 || n == 1)
{ return n; }
int pre = 0; 对应n-1
if (filter.find(n - 1) == filter.end())
{ pre = Fibonacci(n - 1); 如果不存在(没有找到),则计算
filter.insert({ n - 1, pre }); 插入容器
}
else
{ pre = filter[n - 1]; }找到了
int ppre = 0; 对应n-2
if (filter.find(n - 2) == filter.end())
{ ppre = Fibonacci(n - 2);
filter.insert({ n - 2, ppre }); }
else
{ ppre = filter[n - 2]; }
return pre + ppre;
}
不同子序列
不能把字符串写死,每次都是发生变化的
S[1:m]中的子串与T[1:n]相同的个数 由S的前m个字符组成的子串与T的前n个字符相同的个数 状态:子状态:由S的前1,2,…,m个字符组成的子串与T的前1,2,…,n个字符相同的个数 F(i,j): S[1:i]中的子串与T[1:j]相同的个数 状态递推: 在F(i,j)处需要考虑S[i] = T[j] 和 S[i] != T[j]两种情况 当S[i] = T[j]: 1>: 让S[i]匹配T[j],则 F(i,j) = F(i-1,j-1) 2>: 让S[i]不匹配T[j],则问题就变为S[1:i-1]中的子串与T[1:j]相同的个数,则 F(i,j) = F(i-1,j) 故,S[i] = T[j]时,F(i,j) = F(i-1,j-1) + F(i-1,j) 当S[i] != T[j]: 问题退化为S[1:i-1]中的子串与T[1:j]相同的个数 故,S[i] != T[j]时,F(i,j) = F(i-1,j) 初始化:引入空串进行初始化 F(i,0) = 1 —> S的子串与空串相同的个数,只有空串与空串相同
连续子数组的最大和
int FindGreatestSumOfSubArray(vector<int> array) {
if (array.empty())
return 0;
vector<int> maxSum(array);
int ret = maxSum[0];
for (int i = 1; i < array.size(); ++i){
maxSum[i] = max(maxSum[i-1] + array[i], array[i]);局部最大
ret = max(ret, maxSum[i]);全局最大
} return ret;
}
F(i):以第i个元素结尾的最大连续子序列和