大家好,今天给大家带来一些算法题,无他,只是想让大家认识一些题型,当以后遇到后会有头绪,能够使用优秀的算法。
题目一
一颗二叉树有n个节点,求最多有多少种二叉树形状。
题目分析
如果二叉树有0个节点,那么只有一种情况—空树;如果只有1个节点,那么只有一种情况—只有根节点;如果有两个节点,根节点占一个,我们先认为左侧节点有0个,那么右侧节点有一个,一种情况,当左侧节点有1个节点时,右侧无节点,也只有一种情况,两种情况相加共两种情况。这样我们就可以分析出解题思路:如果二叉树有n个节点,我们分别讨论二叉树左侧有0个,1个......n-1个节点时二叉树的数量,最后相加得到最终答案,每一次均为f(i)*f(n-i-1)。
代码分析
1.递归
int nodecount(int n){if(n==0||n==1){return 1;}int count=0;for(int left=0;left<=n-1;left++){//减去一个根节点 count=count+nodecount(left)*nodecount(n-left-1); }return count;
}
2.动态规划
int nodecount2(int n){//n为有多少结点 int dp[n+1];for(int i=0;i<n+1;i++){dp[i]=0;}dp[0]=dp[1]=1;for(int i=2;i<=n;i++){//总共结点数目 for(int left=0;left<=i-1;left++){//左侧结点数目 dp[i]=dp[i]+dp[left]*dp[i-left-1];}}return dp[n];
}
题目二
给你一个字符串仅有‘(’和‘)’构成,判断括号是否匹配,如果不匹配需要补充多少括号。
题目分析
我想很多小伙伴拿到这道题目时,第一时间想到的是用栈解决。当然可以,不过有更简单的方法,只需要两个计数器count和ans即可。
我们从左到右遍历整个字符串,当遇见左括号时count++,遇到右括号时count--。如果字符串需要右括号才能达到匹配目的时,那么最后count为一个大于0的数,count的数量就是需要的右括号数量。当遍历时发现count==-1时,说明字符串肯定不匹配,需要补充左括号,此时我们令count再次等于0,然后ans++。最终ans记录的就是需要补充左括号的数量。ans+count的值就是我们需要的补充的括号数量。
代码分析
#include<iostream>
using namespace std;
void theminmatch(string str){int count=0,ans=0;for(int i=0;i<str.length();i++){if(str[i]=='('){count++;}else{count--;}if(count<0){ans++;count=0;}}//括号匹配if(!count&&!ans){cout<<"无需添加"<<endl;}if(count){cout<<"需要添加"<<count<<"个右括号"<<endl; }if(ans){cout<<"需要添加"<<ans<<"个左括号"<<endl;}
}
int main(){string str="(((())()()()())";theminmatch(str);
}
题目三
有两个集合,我们想进行操作:将一个集合的元素移动到另一个集合中,并且达到让这两个集合的平均值均增大。求最多操作次数。
题目分析
首先,这两个集合应该不为空,否则无平均值这一概念。其次,当我们移动元素X到某集合时,若集合中包含X,那么该集合平均值不会发生变化(集合中相同元素当作一个元素),这是由集合性质决定的。
集合A平均值为ave1,集合B平均值为ave2。那么该如何移动呢?我们分情况讨论。
情况1:ave1=ave2:
我们从A移动元素X到B,若X<ave1,那么移动后ave1变大,ave2变小,不符合情况。X>ave1,那么移动后ave1变小,ave2变大,不符合情况。如果X=ave1,那么移动后ave和ave2均不变,不满足情况。因此ave1=ave2时,移动次数为0.
情况2:ave1<ave2(小移大):
我们从A移动元素X到B,若X>ave1,移动后ave1减小,不符合情况。若X=ave1,移动后ave1不变,不符合情况。若X<ave1,移动后ave2变小,不符合情况。因此小集合向大集合移动时也不满足情况。
情况3:ave1>ave2(大移小):
我们从A移动元素X到B,若X>ave1,移动后ave1减小,不符合情况。若X<ave2,移动后ave2减小不符合情况。若X=ave1或X=ave2,移动后肯定存在某个集合平均值不变,不符合情况。当X界于ave2和ave1时,移动后两个元素集合平均值均增大,满足情况。
此时我们知道何时才能移动,那么如果集合中有多个满足情况的元素呢?我们该优先移动小元素还是大元素呢?分析题目:移动次数最多!!肯定是优先移动小元素,因为移动小元素后ave1增长最大,ave2增加最小,二者差值最大,可能移动的次数最多。那如何保证我们先拿到小元素呢?对平均值大集合进行排序即可。
代码分析
#include<iostream>
#include<algorithm>
using namespace std;
int Sum(int *arr,int n){int sum=0;for(int i=0;i<n;i++){sum=sum+arr[i];}return sum;
}
int *copy(int *arr,int n){int *copy=new int[n];for(int i=0;i<n;i++){copy[i]=arr[i];}return copy;
}
bool notcontains(int *arr,int n,int x){for(int i=0;i<n;i++){if(arr[i]==x){return 0;}}return 1;
}
int main(){int arr1[4]={1,5,2,7};int arr2[5]={3,4,1,6,8};int *min,*max,length,ans=0,l,cnt1,cnt2;int sum1=Sum(arr1,4);int sum2=Sum(arr2,5);double average1=sum1*1.0/4;double average2=sum2*1.0/5;double minave,maxave;if(average1==average2){cout<<"0"<<endl;return 0;}if(average1>average2){max=copy(arr1,4);min=copy(arr2,5);sort(max,max+4);//对平均值大的数组进行排序 cnt1=length=4;cnt2=l=5;minave=average2;maxave=average1;}else{min=copy(arr1,4);max=copy(arr2,5);sort(max,max+5);cnt1=length=5;cnt2=l=4;maxave=average2;minave=average1;}//只能从大集合拿元素放到小集合才能满足两个集合平均值均变大 average1<x<average2 // 满足两个集合均不为空 移动到i集合时i集合不能有该元素 (集合两个相同元素相当于一个,平均值不变) for(int i=0;i<length;i++){if(max[i]>minave&&max[i]<maxave&¬contains(min,l,max[i])&&cnt1!=1){//若有多个元素满足条件选小的//这样可以保证大数组平均值加的多 小数组平均值加的少 保证最大magic次数 //这也是为什么对大数组进行排序的原因 ans++;sum1=sum1-max[i];sum2=sum2+max[i];minave=(sum1*1.0)/(length-ans);//更新平均值 maxave=(sum2*1.0)/(l+ans);cnt1--;}}cout<<"最大挪动次数为"<<ans;
}
有些小细节需要大家注意,算平均值时注意用double数据,思路比较简单,就是细节较多,大家多多注意。
题目四
给定一个字符串,只有左括号和右括号,字符串可能匹配也可能不匹配,求匹配的最长字串。
题目分析
首先我们应该区分子串和子序列,子串是连续的,子序列可以不连续。
我们这样考虑:dp【i】代表以i位置元素结尾所匹配的最大长度。当arr【i】为左括号时,dp【i】肯定为0。当arr【i】为右括号时,i前方dp【i-1】长度肯定已经匹配,此时我们需要看cur=i-dp【i-1】-1位置的元素,如果为右括号,那么dp【i】肯定为0。为什么呢?因为以cur位置元素结尾的最大匹配长度为0,如果不是0的话,那么dp【i-1】的值会变大,跟实际不符发生矛盾,因此cur位置为右括号时dp【i】=0。如果cur位置为左括号时,那么dp【i】长度至少为dp【i-1】+。此时我们需要看cur-1位置的dp值,最后dp【i】=dp【i】+dp【cur-1】。
代码分析
#include<iostream>
using namespace std;
int getmax(string s,int n){int dp[n];//以i位置结尾的最大匹配串for(int i=0;i<n;i++){dp[i]=0;}dp[0]=0;int cur;for(int i=1;i<=n-1;i++){if(s[i]=='('){dp[i]=0;continue;//以左括号结尾肯定不匹配 }dp[i]=dp[i]+dp[i-1];//*****)此时状态 cur=i-dp[i-1]-1;//检查*前一位 if(cur<0){dp[i]=0;//如果没有肯定跟最后的右括号无法对应为0 continue;}if(s[cur]==')'){dp[i]=0;//如果*前一位为),那么肯定不会存在((####)*****)//因为如果cur有匹配的话 i-1位置匹配长度会更长矛盾 continue;}dp[i]=dp[i]+2;//如果cur为左括号(*****)在原来基础上加2(首尾) //最后加上cur-1位置的dp值只需一次,因为该处记录了此处为结尾的最大匹配值 if(cur-1<0){continue;}dp[i]=dp[i]+dp[cur-1];} int max=-1;for(int i=0;i<n;i++){cout<<dp[i]<<" ";if(max<dp[i]){max=dp[i];}}cout<<endl;return max;//返回最大值
}
int main(){string str="(())(())";int max=getmax(str,str.length());cout<<max;
}
大家注意是否有越界的可能,小细节不能忽略。
题目五
给定一个二维数组,保证其行列均按照从小到大的顺序,但整体无序。求二维数组中是否存在元素X。
题目分析
很重要的一个条件就是行列有序!我们如果从数组右上角开始的话,比较当前位置元素和X大小关系,如果相等则返回。如果该元素小于X,那么行下标加一,列下标不变。如果该元素大于X,那么行下标不变,列下标减一。如果最后越界,说明不存在。没有越界说明存在。
我们这样操作每次移动一行或一列,为O(m+n)的算法,而普通的遍历为O(m*n)。
代码分析
#include<iostream>
using namespace std;
int main(){int n,m;cin>>n>>m;int arr[n][m];//数组每一行和每一列均是从小到大有序,但整体不有序for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>arr[i][j];}} //查找数组中是否存在元素Xint x;cout<<"输入想查找的元素"<<endl;cin>>x;int curx=0,cury=m-1,flag=0;while(arr[curx][cury]!=x){if(arr[curx][cury]>x){cury=cury-1;}else if(arr[curx][cury]<x){curx=curx+1;}if(curx<0||curx>n-1||cury<0||cury>m-1){flag=1;break;}}if(!flag){cout<<"元素所在位置为:"<<curx+1<<" "<<cury+1<<endl;} else{cout<<"不存在该元素"<<endl;}
}
题目六
给定一个二维数组,元素只有0和1,且保证每一行:0在左侧,1在右侧(可以全0或全1)。求行中最多1的数量。
题目分析
我们还是从右上角开始,一直向左走记录1的数量,当遇见0后向下走,对每一行均进行这种操作,最后count的值就是1的最大数量。
代码分析
#include<iostream>
using namespace std;
//数组只有0,1,且每一行1均在右侧,0均在左侧(可以全1或全0)
//求行中最多1的数量
int main(){int n,m;cin>>n>>m;int arr[n][m];for(int i=0;i<n;i++){for(int j=0;j<m;j++){cin>>arr[i][j];}}int curx=0,cury=m-1,count=0;while(curx<=n-1){while(cury>=0&&arr[curx][cury]==1){cury--;count++;}curx++;}cout<<count;
}
要注意一直向左走时的越界问题。
本次算法练习到此结束,希望大家多多点赞支持。