题目练习之综合运用
♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥
♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥
♥♥♥我们一起努力成为更好的自己~♥♥♥
♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥
♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥
✨✨✨✨✨✨ 个人主页✨✨✨✨✨✨
这一篇博客我们将进行算法题目练习,对我们的算法进行综合运用~准备好了吗~我们发车去探索奥秘啦~🚗🚗🚗🚗🚗🚗
目录
判断是不是平衡二叉树
最大子矩阵
小葱的01串
判断是不是平衡二叉树
判断是不是平衡二叉树
平衡二叉树给出的性质是它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。这里我们需要特别注意的是左右两个子树都是一棵平衡二叉树,所以我们还需要对左右子树进行判断~
涉及到二叉树就离不开我们的递归了,判断是不是平衡二叉树,我们就需要依次判断左右子树的高度差是不是不大于1,同时还需要返回判断结果,那么这不就有两个返回值嘛,一个是返回高度进行判断,一个是返回bool类型是不是平衡二叉树,这里有两种解决方式:
1、使用结构体,定义int类型和bool类型来返回结果
2、左右子树的高度肯定是大于等于0的,不可能是负数,那么我们就可以返回-1代表不是平衡二叉树~
我们这里使用第二种解决方式,更多的细节在代码里面给出:
//判断是不是平衡二叉树
/*** struct TreeNode {* int val;* struct TreeNode *left;* struct TreeNode *right;* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}* };*/
class Solution {
public:/*** 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可*** @param pRoot TreeNode类* @return bool布尔型*/int treeDepth(TreeNode* root){if (root == nullptr)return 0;//高度为0int ldepth = treeDepth(root->left);if (ldepth == -1)return -1;//左子树不是平衡二叉树int rdepth = treeDepth(root->right);if (rdepth == -1)return -1;//右子树不是平衡二叉树//根据左右子树高度差判断当前子树是否为平衡二叉树//是就返回高度//不是就返回-1return abs(ldepth - rdepth) <= 1 ? max(ldepth, rdepth) + 1 : -1;}bool IsBalanced_Solution(TreeNode* pRoot){// write code herereturn treeDepth(pRoot) != -1;//返回值不是-1就说明是平衡二叉树}
};
顺利通过~
最大子矩阵
最大子矩阵
这一个题目是要我们求最大子矩阵的和,小编目前想到的解法是暴力解法,也就是枚举所有的子矩阵,然后找到和最大的,如果大家有更好的解法,欢迎留言或者私信交流~
解法:枚举出所有的子矩阵求和,更新最大值~
这里有两个比较重要的地方:
1、如何枚举出所有的子矩阵呢?
我们需要依次找到两个下标(x1,y1),(x2,y2),求它们这个范围里面的数据和就可以了,但是需要注意x1,y1,x2,y2的范围问题,为了避免重复,我们让x1<=x2,y1<=y2,画图如下~
2、那么如何求(x1,y1),(x2,y2)这个范围里面的数据和呢?
有的人可能会说这还不简单,直接继续暴力统计和更新就好了,也可以,但是那样时间复杂度就很大了,所以我们这里给出一种新的思路,利用二维前缀和来处理~
首先,理解什么是二维前缀和?这里事实上就利用了我们动态规划的思想~我们来一步步分析:
1、状态表示
结合这里的题目要求+经验:我们这里的状态表示——dp[i][j]为以当前位置为结尾,前面所有数据的和
2、状态转移方程
我们以离【i】【j】位置最近的状态分析状态转移方程
现在我们需要的是dp【i】【j】,那么我们就可以利用前面得到的数据~
画图分析:
dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+arr[i][j]
3、初始化
我们可以看到,状态转移方程里面有i-1,j-1当i=0或者j=0的时候显然会出现越界的情况,所以我们需要进行初始化
结合前面如果不想初始化太麻烦,我们可以多申请一些空间,那么我们这里也就可以多申请一行一列~那么怎么初始化这一行一列呢?
相信大家已经知道结果了,根据状态转移方程 dp[i][j]=dp[i-1][j]+dp[i][j-1]-dp[i-1][j-1]+arr[i][j],我们直接把这一行一列初始化为0就不会影响到我们的结果
初始化方式:全部初始化为0
当然,这种初始化方式还需要注意的是下标映射关系~,当然,题目没有给定数组,我们也可以都多创建一行一列,减少映射关系的处理~
4、填表顺序
我们这里的逻辑是从上面依次推出下面的,从前面依次推出后面的,所以填表顺序是从上向下,从前往后
这就是我们利用动态规划的思想得到了一个二维前缀和数组~
那么如何利用这个二维前缀和数组求子矩阵数据和呢?
求【x1,y1】到【x2,y2】的数据和不就可以转换成
sum = dp[x2][y2] - dp[x2][y1-1] - dp[x1-1][y2] + dp[x1-1][y1-1]
枚举出所有子矩阵更新最大结果就可以了,这里还有一个小细节题目给出矩阵中整数的范围都在[-127, 127],那么矩阵和最小值就是-127*n*n(最坏的情况——所有的值都是-127)
代码实现:
//最大子矩阵
#include <iostream>
using namespace std;
#include<vector>
int main()
{int n = 0;cin >> n;//都多创建一行一列,减少映射关系处理vector<vector<int>> arr(n + 1, vector<int>(n + 1));vector<vector<int>> dp(n + 1, vector<int>(n + 1));for (int i = 1; i <= n; i++){for (int j = 1; j <= n; j++){cin >> arr[i][j];dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + arr[i][j];}}//枚举所有子矩阵更新结果int ret = -127 * n * n;for (int x1 = 1; x1 <= n; x1++){for (int y1 = 1; y1 <= n; y1++){for (int x2 = x1; x2 <= n; x2++){for (int y2 = y1; y2 <= n; y2++){int cur = dp[x2][y2] - dp[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1];ret = max(ret, cur);}}}}cout << ret << endl;return 0;
}
// 64 位输出请用 printf("%lld")
顺利通过~
事实上,这个题目的代码还可以进一步优化,我们可以发现arr数据只有在计算二维前缀和才有作用,那么也就可以直接创建一个变量进行输入,填dp表就可以了,另外,题目给出了n的范围,我们直接创建比它大几个空间的二维数组就可以了,空间复杂度就会变为O(1)
代码优化:
#include <iostream>
using namespace std;
const int N = 105;
int dp[N][N];
int main()
{int n = 0;cin >> n;int x = 0;for (int i = 1; i <= n; i++){for (int j = 1; j <= n; j++){cin >> x;dp[i][j] = dp[i - 1][j] + dp[i][j - 1] - dp[i - 1][j - 1] + x;}}//枚举所有子矩阵更新结果int ret = -127 * n * n;for (int x1 = 1; x1 <= n; x1++){for (int y1 = 1; y1 <= n; y1++){for (int x2 = x1; x2 <= n; x2++){for (int y2 = y1; y2 <= n; y2++){int cur = dp[x2][y2] - dp[x2][y1 - 1] - dp[x1 - 1][y2] + dp[x1 - 1][y1 - 1];ret = max(ret, cur);}}}}cout << ret << endl;return 0;
}
// 64 位输出请用 printf("%lld")
顺利通过~
小葱的01串
小葱的01串
它需要的是将一段连续的区间染成红色,使得红色的字符'0'数量等于白色的字符'0'数量,红色的字符'1'数量等于白色的字符'1'数量,涉及到连续的区间,我们很容易想到滑动窗口~
那么我们应该怎么使用这个滑动窗口呢?
分析:
红色1:x1 红色0:x2
白色1:y1 白色0:y2
题目要求:x1=y1,x2=y2——》x1+x2 == y1+y2
那么说明 连续区间==字符串长度的一半那么我们就可以当连续区间==字符串长度的时候,统计连续区间(窗口内)0,1的个数是不是字符串0,1个数的一半,如果是那么ret+=2,为什么+=2?因为题目给出的是环形字符串,但是我们考虑的是线性的,除了对当前的连续区间染成红色满足条件以外,环形的条件下剩下的区间也是满足的,所以ret+=2;除此之外,还需要注意的是right的范围应该是0 ~ n-2,以下面的例子为例
right到n-1的时候,事实上第一种已经ret+=2统计结果了,所以我们就不需要重复计算了~
总结:
1、统计结果时,ret+=2
2、right范围为【0】——【n-2】
代码实现:
//小葱的01串
#include<iostream>
using namespace std;
#include<string>
int main()
{int n = 0;cin >> n;//注意!题目给出了字符串长度string s;cin >> s;//int n=s.size();//err——不需要自己求长度int half = n / 2;//count , sum统计数组里面的值需要设置为0//否则在当前编译器下为随机值!!!int count[2] = { 0 };//统计字符串0,1个数for (auto e : s){count[e - '0']++;}/*for(int i=0;i<n;i++){count[s[i]-'0']++;}*///滑动窗口int ret = 0;int left = 0, right = 0;int sum[2] = { 0 };//统计窗口里面0,1个数while (right < n - 1)//right范围为【0】——【n-2】{//进窗口sum[s[right] - '0']++;while (right - left + 1 > half)//出窗口——窗口里面数据大于一半{sum[s[left++] - '0']--;}//更新结果if (right - left + 1 == half && sum[0] * 2 == count[0] && sum[1] * 2 == count[1])ret += 2;right++;}//输出结果cout << ret << endl;return 0;
}
顺利通过~
♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥
♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥
✨✨✨✨✨✨个人主页✨✨✨✨✨✨