左神算法中级班第一课[C++代码]
声明:博客根据左神在毕站的讲课视频整理,代码是根据左神的java代码版本改写为c++版本
代码为自己动手改写,未经应用不能转载
第一题:绳子覆盖节点问题
技巧:让每个节点作为绳子的右端或者左端,字串子序列问题这种技巧很好用
代码和代码链接
github代码链接:根据左神java版本改写的c++版本
https://github.com/hjfenghj/zuoshen_middlecalss/blob/main/class01_problem01.cpp
三种方法求解:
-
方法一
让每个节点作为绳子的左节点,假设绳子左端刚好把某节点发改完毕,比如说数组[2,4,…],假设绳子左端刚好压在第1个节点上,和第三个节点上;根据右节点的位置更新结果 -
方法二:
假设绳子右端刚好覆盖完某节点,求绳子左端所在位置;设定绳子右端节点位置,根据左端位置位置更新结果
方法三: -
方法一的递归写法
-
code
#include<iostream>
#include<vector>
using namespace std;
class PROBLEM01_1
{
/*
* 假设绳子左端刚好把某节点发改完毕,比如说数组[2,4,....],假设绳子左端刚好压在第1个节点上,和第三个节点上
* 根据绳子右端到达的位置更新结果
*/
public:
int process_1(vector<int> Arr, int L)
{
int start = 0;
int end = 0;
int res = 0;//绳子覆盖的节点数量
for (int i = 0; i < Arr.size(); i++)
{
if (L < Arr[i])
break;
end = i;//绳子末尾覆盖到的节点
}
res = end - start + 1;//绳子从首节点出发覆盖的节点数
int LL = L - Arr[end];//绳子覆盖最后一个节点后还剩余多长
for (int start = 1; start < Arr.size(); start++)
{
//左端右移,增加绳子剩余的长度,注意数组的格式为累加值
if (start == 1)
LL = LL + Arr[start - 1];
else
LL = LL + (Arr[start - 1] - Arr[start - 2]);
int temp = res - 1;
while (LL >= (Arr[end + 1] - Arr[end]))
{
temp++;
end++;
if (end + 1 == Arr.size())
{
return res;
}
LL = LL - (Arr[end + 1] - Arr[end]);
}
res = max(temp, res);
}
return res;
}
void mian_1(vector<int> Arr,int len)
{
int res = process_1(Arr, len);
cout << res << endl;
return;
}
};
class PROBLEM01_2
{
/*
* 假设绳子右端刚好覆盖完某节点,求绳子左端所在位置
* 设定绳子右端节点位置,根据左端位置位置更新结果
*/
public:
//返回列表中索引R以前部分第一个大于等于某值的索引
//val表示寻找的目标值
int process_2(vector<int> Arr, int val, int R)
{
int L = 0;
int index = R;
while (L < R)
{
int mid = L + ((R - L) >> 1);
if (Arr[mid] >= val)
{
index = mid;
R = mid - 1;
}
else
L = mid + 1;
}
return index;
}
void main_2(vector<int> Arr, int len)
{
int max_L = 0;
for (int i = 0; i < Arr.size(); i++)
{
max_L = max(max_L, i - process_2(Arr, Arr[i] - len, i) + 1);
}
cout << max_L << endl;
return;
}
};
class PROBLEM01_3
{
public:
//Arr 原始节点数组的变换,新的数组表示每个元素代表i-1位置到i位置的节点数
//L_ptr 绳子左侧完全压到的节点
//R_ptr 绳子右侧完全压倒的节点
//len 在除去start到end的范围后,绳子还剩余的长度
//res 表示覆盖的节点数
//代表绳子从Arr索引L_ptr出发,到R_ptr结束还剩下Len的富余,前闭后开[L_ptr,R_ptr)
int process_3(vector<int> Arr, int L_ptr, int R_ptr, int Len, int res)
{
if (R_ptr >= Arr.size())
return res;
int r = 0;
if (Len < Arr[R_ptr])
r = process_3(Arr, L_ptr + 1, R_ptr, Len + Arr[L_ptr], max(res, R_ptr - L_ptr + 1));
else if (Len >= Arr[R_ptr])
r = process_3(Arr, L_ptr, R_ptr + 1, Len - Arr[R_ptr], max(res, R_ptr - L_ptr));
return r;
}
void main_3(vector<int> Arr, int len)
{
vector<int> new_arr(Arr.size(), 0);
new_arr[0] = Arr[0];
for (int i = 1; i < Arr.size(); i++)
new_arr[i] = Arr[i] - Arr[i - 1];
int ans = process_3(new_arr,0,0,len,0);
cout << ans << endl;
return;
}
};
int main()
{
vector<int> Arr = { 0, 13, 24, 35, 46, 57, 60, 72, 87 };
int len = 8;
PROBLEM01_1 P1;
P1.mian_1(Arr, len);
PROBLEM01_2 P2;
P2.main_2(Arr, len);
PROBLEM01_3 P3;
P3.main_3(Arr, len);
return 0;
}
第二题:几种袋子装苹果问题
技巧:单输入单输出打表找规律
- 思路
首先假设苹果全部用大袋子来装,剩余的用小袋子;如果不成功,大袋子依次减一,判断剩余的苹果能否用小袋子装完;当大袋子装完后剩余的苹果数量大于大小袋子可以装苹果的最小公倍数是退出;
代码和代码链接
-
code
github代码链接:https://github.com/hjfenghj/zuoshen_middlecalss/blob/main/class01_problem02.cpp -
code
#include<iostream>
#include<vector>
using namespace std;
//打表法的应用
class PROBLEM02{
public:
//k2>k1
int process(int k1,int k2,int S){
int res = 0;
int num1 = 0;
int num2 = S / k2;
int t = 0;//最小公倍数初始化
//求最小公倍数
for (int i = 1; i <= k2; i++){ //从1到m倍尝试
t = k1 * i;
if ((k1 * i) % k2 == 0)
break;
}
int remain = S - num2 * k2;//全部使用大袋子的话,剩余几个苹果
while (remain % k1 != 0){
k2--;
remain += k2; //更新剩余的苹果数
if (remain / t > 0){
cout << -1 << endl;
return -1;
}
}
num1 = remain / k1; //使用num1个小袋子
res = num1 + num2;
cout << res << endl;
S++;
return 0;
}
void main_1(int k1, int k2, int SS){
int S = 1;
while (S < SS){
int ans = process(k1, k2, S);
cout << ans << endl;
S++;
}
return;
}
};
int main()
{
int k1 = 6;
int k2 = 8;
k1 = min(k1, k2);
k2 = max(k1, k2);
int S = 102;
PROBLEM02 P;
P.main_1(k1, k2, S);
return 0;
}
第二题加题:先手后手问题
题目:两只牛先手后手吃草,每次只能吃4的倍数的草,假定两只牛都绝顶聪明;如果草为0,那么先手牛直接获胜,那只牛吃完草以后剩余的草为0谁获胜;
这类题目可以使用递归求解,
技巧:AB两只牛吃草,A牛吃完以后剩余的草N作为一个整体,让B牛开始吃,如果是B牛在N份草中作为先手赢了,那么在原始的草中,就是A牛作为先手赢
但是如果给的草N为一个亿,那么递归的时间就太长了,所以对于单输入单输出的题目,可以通过一部分数据来进行打表,找到规律
打表法
代码以及代码链接
github代码链接
https://github.com/hjfenghj/zuoshen_middlecalss/blob/main/class01_problem02-add.cpp
- code
#include<iostream>
#include<vector>
using namespace std;
class PROBLEM02_cow_eat_grown
{
public:
//使用一些列数据打表处理,找到规律,如果给定的N为1个亿,使用迭代递归就会非常耗时间
//使用打表法占到规律以后,就可以直接得到结果
int get_res(int N)
{
if (N < 5)
{
return (N == 0 || N == 2) ? 0 : 1;//0代表后手赢,1代表先手赢
}
//N > 5的时候
int t = 1;//表示从吃一份开始
while (t <= N)
{
//A牛吃完以后,B牛从剩余的草中开始吃,如果从剩余的草中开始,B牛赢了,就表示原始数据中A牛赢了
if (get_res(N - t) == 0)
return 1;
//防止t*4越界
if (t > N / 4)
break;
t *= 4;
}
return 0;
}
//使用打表法得到的规律
int get_res2(int N)
{
if (N % 5 == 0 || N % 5 == 2)
return 0;//代表后手赢
return 1;
}
};
int main()
{
int N = 50;
PROBLEM02_cow_eat_grown P;
for (int i = 0; i < 50; i++)
{
cout << i << " " << P.get_res(i) << endl;
}
int res = P.get_res2(100000);
cout << res<< endl;
return 0;
}
第三题:涂色问题
技巧:预处理技巧的应用
- 思路
题目要求,通过涂色,让R全在左边,G全在右边;这样就可以让R的左侧没有G
预处理,从左往右遍历,记录G的个数,存在辅助数组1中
从右往左遍历,记录R的个数,存在辅助数组2中;得到每个位置左侧(包括自身)应该涂的G个数和右侧应该被图的R个数
无code
第四题:矩阵中,只有0和1,返回边框为1的最大正方形边长
技巧1:预处理的作用
技巧2:矩阵中一个正方形由一个左上角点和边长确定,也可以由左上角和右上角两个点确定
规模为N的正方形矩阵中,按点+边的方式可以确定N3 规模的正子矩阵;按照点+点的方式可以确定N4 规模的子矩阵,因为每一点的可能性都为N2;
- 思路
预处理记录每个点右侧连续的1的个数和下侧连续的1的个数;
遍历矩阵元素,遇到元素0,那么以整个点位左上角的正方向矩阵边长为0;如果为1,那么就判断不同边长下的三个点的右侧和下侧连续的1个数是否达标;复杂度为O(N3);
代码和代码链接
github代码链接
https://github.com/hjfenghj/zuoshen_middlecalss/blob/main/class01_problem04.cpp
- code
#include<iostream>
#include<vector>
using namespace std;
//预处理技巧
void pre_process(vector<vector<int>> Arr,vector<vector<int>>& temp1, vector<vector<int>>& temp2)
{
int L1 = Arr.size();
int L2 = Arr[0].size();
//从左下角开始,按每行自右往左遍历
for (int i = L1 - 1; i >= 0; i--)
{
int s = 0;
for (int j = L2 - 1;j >= 0; j--)
{
if (Arr[i][j] == 1)
s++;
else
s = 0;
temp1[i][j] = s;
}
}
for (int i = L2 - 1; i >= 0; i--)
{
int s = 0;
for (int j = L1 - 1; j >= 0; j--)
{
if (Arr[i][j] == 1)
s++;
else
s = 0;
temp2[i][j] = s;
}
}
}
int main()
{
vector<vector<int>> Arr;//输入数组
vector<vector<int>> temp1;//辅助数组1,储存元素右侧连续1的个数
vector<vector<int>> temp2;//辅助数组2,储存元素下侧连续1的个数
pre_process(Arr, temp1, temp2);
int L1 = Arr.size();//行
int L2 = Arr[0].size();//列
int res = 0;
for (int i = 0; i < L1; i++)
{
for (int j = 0; j < L2; j++)
{
if (Arr[i][j] == 0)
continue;
//遍历不同的边长
for (int B = 1; B < L1 - i && B < L2 - j; B++)//设置边长的阈值
{
//判断左上角坐标为(i,j),边长为B的正方向的四条边是否全为1
bool b1 = (temp1[i][j] >= B && temp2[i][j] >= B) ? 1 : 0;
bool b2 = temp2[i][j + B - 1] >= B ? 1 : 0;
bool b3 = temp1[i + B - 1][j] >= B ? 1 : 0;
bool b = b1 & b2 & b3;
if (b == 1)
res = max(res, B);
}
}
}
cout << res << endl;
return 0;
}
第五题:等概率返回问题
技巧:二进制比特位的应用,使用第一个等概率返回器完成第二个等概率返回器
- 思路
对于第一个问题需要的概率返回1-7,使用三位bit位,三位二进制的范围是0-7,可以使用其一部分的区间0~6,然后加1得到需要返回得数据;
已知函数f可以等概率返回1~5,那么f返回 1和2作为第一大类,返回4和5作为第二大类;这样两个类得可能都为40%,满足等概率;然后g函数调用三次f函数,得到第一类作为1,第二类作为0,组成一个3位得二进制数据;如果组成得数据在 [0,6]范围内,返回;
根据g函数要求返回数据的范围确定使用的二进制位数量
== 注意需要修剪不需要得数据==
注意do while用法:满足条件一直循环
代码和代码链接
github代码链接
https://github.com/hjfenghj/zuoshen_middlecalss/blob/main/class01_problem05.cpp
- code
#include<iostream>
#include<vector>
using namespace std;
//等概率返回1-5
int f(int a,int b)
{
return (rand() % (b - a + 1)) + a;
}
//把f函数改为0,1产生器
int get_r01()
{
int v = 0;
do
{
v = f(1, 5);
} while (v == 3);
return v < 3 ? 0 : 1;
}
//等概率返回1-7;
int g()
{
int temp;
do
{
temp = (get_r01() << 2) + (get_r01() << 1) + get_r01();
} while (temp == 7);
return temp + 1;
}
第六题
== 递归和动态规划==
代码和代码链接
- code
#include<iostream>
#include<vector>
using namespace std;
int process1(int N)
{
int res = 0;
if (N < 2)
return 1;
else if (N == 2)
return 2;
for (int i = 0; i <= N - 1; i++)
{
int leftnum = process1(i);
int rightnum = process1(N - i - 1);
res += (leftnum * rightnum);
}
return res;
}
int process2(int N)
{
int res = 0;
if (N < 2)
return 1;
else if (N == 2)
return 2;
vector<int> dp(N, 1);
for (int i = 1; i < N; i++)
{
for (int j = 1; j < i; j++)
dp[i] += (dp[j] * dp[i-j-1]);
}
return dp[N - 1];
}
int main()
{
int N = 10;
int res1 = process1(10);
int res2 = process2(10);
cout << res1 << " " <<res2<<endl;
return 0;
}
第七题
- 思路
括号问题的扩展
判断一个括号字符串为完整的:
1) 使用栈
2)遍历字符串,变量temp;遇到左括号加1,遇到右括号减1;temp不能为负,且最终temp为0
这道题就延续上边的思路
- code
int process(string str)
{
int left = 0;
int need = 0;
for (int i = 0; i < arr.size(); i++)
{
if (str[i] == '(')
left++;
else
{
if (left == 0)
need++;
else
left--;
}
}
return need + left;
}