HJ70 矩阵乘法计算量估算
描述
矩阵乘法的运算量与矩阵乘法的顺序强相关。
例如:
A是一个50×10的矩阵,B是10×20的矩阵,C是20×5的矩阵
计算A*B*C有两种顺序:((AB)C)或者(A(BC)),前者需要计算15000次乘法,后者只需要3500次。
编写程序计算不同的计算顺序需要进行的乘法次数。
数据范围:矩阵个数:1≤n≤15 ,行列数:1≤rowi,coli≤100 ,保证给出的字符串表示的计算顺序唯一。
进阶:时间复杂度:O(n) ,空间复杂度:O(n)
输入描述:
输入多行,先输入要计算乘法的矩阵个数n,每个矩阵的行数,列数,总共2n的数,最后输入要计算的法则
计算的法则为一个字符串,仅由左右括号和大写字母('A'~'Z')组成,保证括号是匹配的且输入合法!
输出描述:
输出需要进行的乘法次数
方法一:栈方法
#include <iostream>
#include <vector>
#include <stack>
using namespace std;
int main() {
int n;
while(cin >> n) {
string rule;
vector<pair<int, int>> matrix;
for (int i = 0; i < n; ++i) {//输入n个矩阵的行数和列数
pair<int, int> temp;
cin >> temp.first >> temp.second;
matrix.push_back(temp);
}
cin >> rule;//输入计算法则
stack<pair<int, int>> stk;
int ans = 0, k = 0;
for (int i = 0; i < rule.size(); ++i) {//遍历一遍计算法则
if (rule[i] == ')') {//当为右括号时从栈中取出两个矩阵计算
pair<int, int> y = stk.top();
stk.pop();
pair<int, int> x = stk.top();
stk.pop();
ans += x.first * x.second * y.second;//计算量
pair<int, int> temp(x.first, y.second);//结果矩阵的大小
stk.push(temp);
} else if(rule[i] != '('){
stk.push(matrix[k]);//当前为字母时,矩阵进栈
k++;
}
}
cout << ans <<endl;
}
return 0;
}
方法二:递归
#include<iostream>
#include<stack>
#include<string>
#include<vector>
#include<map>
using namespace std;
map<char, pair<int, int> > matrix;//矩阵和大小之间的映射
int count;//计算量
pair<int, int> compute(string str){
stack<pair<int, int> > stk; //记录尚未计算的矩阵
str = str.substr(1, str.size()-2);//去掉首位的两个括号
for(int i = 0; i < str.size();i++){
if(str[i] == '('){ //如果是左括号,需要递归计算
int layer = 0;
int j = i;
while(j <= str.size()){//找到和当前左括号匹配的右括号
if(str[j] == '('){
layer++;
}else if(str[j] == ')'){
layer--;
}
if(layer == 0){
break;
}
j++;
}
pair<int, int> res = compute(str.substr(i, j - i + 1));//递归计算括号中的部分
i = j ;//从括号后面的内容继续遍历
if(stk.empty()){ //如果stk为空,表示当前得到的矩阵是第一个矩阵,需要存下,等待下一个矩阵计算
stk.push(res);
}else{ //若stk不为空,需要计算
pair<int, int> temp = stk.top();
stk.pop();
count += temp.first * temp.second * res.second;
stk.push(make_pair(temp.first,res.second));//更新栈中的值
}
}else if(isupper(str[i])){ //如果是矩阵的话
if(stk.empty()){ //stk为空,进入到stk中
stk.push(matrix[str[i]]);
}else{ //如果栈不为空,需要计算
pair<int, int> temp = stk.top();
stk.pop();
count += temp.first * temp.second * matrix[str[i]].second;
stk.push(make_pair(temp.first,matrix[str[i]].second));//更新栈中的值
}
}
}
return stk.top(); //遍历一遍结束,返回当前计算的矩阵大小
}
int main(){
int n;
while(cin >> n){
count = 0;
string rule;
char ch = 'A';
for(int i = 0; i < n; i++){ //输入n个矩阵的行列数
cin >> matrix[ch].first >> matrix[ch].second;
ch++;
}
cin >> rule; //输入运算法则
stack<char> s; //记录代表矩阵的字符
compute(rule);
cout << count << endl;
}
return 0;
}
HJ71 字符串通配符
描述
问题描述:在计算机中,通配符一种特殊语法,广泛应用于文件搜索、数据库、正则表达式等领域。现要求各位实现字符串通配符的算法。
要求:
实现如下2个通配符:
*:匹配0个或以上的字符(注:能被*和?匹配的字符仅由英文字母和数字0到9组成,下同)
?:匹配1个字符
注意:匹配时不区分大小写。
输入:
通配符表达式;
一组字符串。
输出:
返回不区分大小写的匹配结果,匹配成功输出true,匹配失败输出false
数据范围:字符串长度:1≤s≤100
进阶:时间复杂度:O(n^2),空间复杂度:O(n)
输入描述:
先输入一个带有通配符的字符串,再输入一个需要匹配的字符串
输出描述:
返回不区分大小写的匹配结果,匹配成功输出true,匹配失败输出false
方法一:动态规划
#include<string>
#include<iostream>
#include<vector>
using namespace std;
int match_string(string str,string pattern){
int len1 = str.size();
int len2 = pattern.size();
vector<vector<int> > dp(len2+1,vector<int>(len1+1,0));
//多加一行一列作为初始初值所用
dp[0][0] = 1;//初始化
for(int i=1;i <=len2;i++){
char ch1 = pattern[i-1];
设置每次循环的初值,即当星号不出现在首位时,匹配字符串的初值都为false
dp[i][0] = dp[i-1][0]&&(ch1=='*');
for(int j=1;j<=len1;j++){
char ch2 = str[j-1];
if(ch1=='*'){
dp[i][j]=dp[i-1][j]||dp[i][j-1]; //当匹配字符为*号时,可以匹配0个或者多个
}else{
if(isalpha(ch2)){//ch2为字母时,尝试是否能匹配
dp[i][j]=dp[i-1][j-1]&&(ch1=='?'||(ch2==ch1||ch2==(ch1+('A'-'a'))||ch2==(ch1-('A'-'a'))));
}else if(isdigit(ch2)){//ch2为数字时,尝试是否能匹配
dp[i][j]=dp[i-1][j-1]&&(ch1=='?'||(ch1==ch2));
}else {//ch2既不为字母也不为数字时,只有ch1和ch2相同才能匹配
dp[i][j]=dp[i-1][j-1]&&(ch1==ch2);
}
}
}
}
return dp[len2][len1];
}
int main(){
string str1,str2;
while(cin >> str1 >> str2){
int flag = match_string(str2,str1);
if(flag){
cout << "true" << endl;
}else{
cout << "false" << endl;
}
}
}
方法二:递归
#include<bits/stdc++.h>
using namespace std;
bool match(const char* s,const char* p){
//两个字符串同时结束,返回true
if((*p=='\0')&&(*s=='\0')){
return true;
}
//两个字符串中有一个先结束,返回false
if((*p=='\0')||(*s=='\0')){
return false;
}
if(*p=='?'){//通配符为?时
if(!isdigit(*s)&&!isalpha(*s)){//只能匹配数字或字母
return false;
}
//匹配一个字符,从下一个位置开始继续匹配
return match(s+1,p+1);
}else if(*p=='*'){//通配符为!时
while(*p=='*'){//多个*和一个*效果相同
p++;
}
p--;
//遇到*号,匹配0个(str+1,str1不用动),匹配1个(str和str1都往前移动1位),匹配多个(str不用动,str+1)
return match(s,p+1) || match(s+1,p+1) || match(s+1,p);
}else if(tolower(*p)==tolower(*s)){//不区分大小写
//当前两字符相等,则进行下一个字符的匹配
return match(s+1,p+1);
}
return false;//不满足上述三种情况,不匹配
}
int main(){
string p,s;
while(cin>>p>>s){
bool res = match(s.c_str(),p.c_str());
if(res){
cout<<"true"<<endl;
}else{
cout<<"false"<<endl;
}
}
return 0;
}
HJ72 百钱买百鸡问题
描述
公元五世纪,我国古代数学家张丘建在《算经》一书中提出了“百鸡问题”:鸡翁一值钱五,鸡母一值钱三,鸡雏三值钱一。百钱买百鸡,问鸡翁、鸡母、鸡雏各几何?
现要求你打印出所有花一百元买一百只鸡的方式。
输入描述:
输入任何一个整数,即可运行程序。
输出描述:
输出有数行,每行三个整数,分别代表鸡翁,母鸡,鸡雏的数量
方法一:数学办法
#include<iostream>
using namespace std;
//鸡翁、鸡母、鸡雏分别为x, y, z 三个变量。
//x+y+z=100
//5x+3y+z/3=100
//确定x即可算出y和z,若y和z为非负整数,则为有效结果,输出。
int main(){
for(int x=0;x<=14;x++){//解方程,计算x的范围是[0,14],枚举x
if((100-7*x)%4==0){
int y=(100-7*x)/4;//求解y,z
int z=100-x-y;
printf("%d %d %d\n",x,y,z);
}
}
return 0;
}
方法二: 暴力法
#include<iostream>
using namespace std;
int main(){
for(int i = 0; i <= 20; i++) {
for(int j = 0; j <= 33; j++) {
for(int k = 0; k <= 100; k++){ //遍历所有可能的公鸡、母鸡、小鸡取值
if(i + j + k == 100 && 5 * i + 3 * j + double(k) / 3 == 100) {//鸡的总数等于100,且总共花了100元
cout << i << " " << j << " " << k << endl;
}
}
}
}
return 0;
}
HJ73 计算日期到天数转换
描述
根据输入的日期,计算是这一年的第几天。
保证年份为4位数且日期合法。
进阶:时间复杂度:O(n),空间复杂度:O(1)\
输入描述:
输入一行,每行空格分割,分别是年,月,日
输出描述:
输出是这一年的第几天
方法一:数组法
#include <iostream>
using namespace std;
int main(){
int year,month,day;
cin>>year>>month>>day;
int count=0;//统计天数
int monthday[13]={0,31,28,31,30,31,30,31,31,30,31,30,31};//monthday[i]表示第i月的天数
if(year%400==0||(year%4==0&&year%100!=0)){//当前月份大于两个月且为闰年时,二月有29天
monthday[2]=29;
}
for(int i=1;i<=month-1;i++){//统计到当前月份的天数
count=count+monthday[i];
}
count=count+day;//加上当前月的天数
cout<<count;
}
方法二:
#include<iostream>
#include<string>
using namespace std;
int main(){
int year,month,day;
int res;
int flag=0;
int num[12]={31,59,90, 120, 151, 181, 212, 243, 273, 304, 334, 365};//num[i]表示第i+1个月结束后的天数
while(cin>>year>>month>>day){
if(year%4==0 && year%100!=0){//如果是闰年
flag=1;
}
if(month<=2){
if(month == 1){
res = day;
}else{
res = num[0] + day;
}
}else{//超过2月就要考虑是否为闰年了
res=num[month-2]+day+flag;
}
flag=0;
cout<<res<<endl;
}
}
HJ74 参数解析
描述
在命令行输入如下命令:
xcopy /s c:\\ d:\\e,
各个参数如下:
参数1:命令字xcopy
参数2:字符串/s
参数3:字符串c:\\
参数4: 字符串d:\\e
请编写一个参数解析程序,实现将命令行各个参数解析出来。
解析规则:
1.参数分隔符为空格
2.对于用""包含起来的参数,如果中间有空格,不能解析为多个参数。比如在命令行输入xcopy /s "C:\\program files" "d:\"时,参数仍然是4个,第3个参数应该是字符串C:\\program files,而不是C:\\program,注意输出参数时,需要将""去掉,引号不存在嵌套情况。
3.参数不定长
4.输入由用例保证,不会出现不符合要求的输入
数据范围:字符串长度:1≤s≤1000
进阶:时间复杂度:O(n) ,空间复杂度:O(n)
输入描述:
输入一行字符串,可以有空格
输出描述:
输出参数个数,分解后的参数,每个参数都独占一行
方法一:字符连接
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main(){
string s;
while(getline(cin, s)){
vector<string> output;
string temp = "";
bool flag = false; //记录是否进入引号中
for(int i = 0; i < s.length(); i++){
if(flag){ //如果在引号中
if(s[i] != '\"') //遇到非引号都添加为字符串
temp += s[i];
else flag = false; //否则设置出引号
}else{ //如果不在引号中
if(s[i] == ' '){ //遇到空格隔断
output.push_back(temp);
temp = "";
}else if(s[i] == '\"') //遇到引号设置为进入引号
flag = true;
else //其余添加进字符串
temp += s[i];
}
}
output.push_back(temp); //最后一段
cout << output.size() << endl; //输出参数个数
for(int i = 0; i < output.size(); i++)
cout << output[i] << endl;
}
return 0;
}
方法二:字符串截取
#include<iostream>
#include<string>
#include<vector>
using namespace std;
int main(){
string s;
while(getline(cin, s)){
vector<string> output;
int p = 0;
bool flag = false; //记录是否进入引号中
for(int i = 0; i < s.length(); i++){
if(s[i] == '\"'){ //遇到引号
if(!flag) //第一个引号
flag = true;
else{ //第二个引号
flag = false;
output.push_back(s.substr(p, i - p)); //截取字符串加入
}
p = i + 1;
} else if(s[i] == ' ' && !flag){ //遇到引号外的空格
if(i != p) //非空字符串
output.push_back(s.substr(p, i - p)); //截取字符串加入
p = i + 1;
} else if(i == s.length() - 1) //最后一个参数字符串
output.push_back(s.substr(p, i - p + 1));
}
cout << output.size() << endl; //输出参数个数
for(int i = 0; i < output.size(); i++)
cout << output[i] << endl;
}
return 0;
}
HJ75 公共子串计算
描述
给定两个只包含小写字母的字符串,计算两个字符串的最大公共子串的长度。
注:子串的定义指一个字符串删掉其部分前缀和后缀(也可以不删)后形成的字符串。
数据范围:字符串长度:1≤s≤150
进阶:时间复杂度:O(n^3),空间复杂度:O(n)
输入描述:
输入两个只包含小写字母的字符串
输出描述:
输出一个整数,代表最大公共子串的长度
方法一:枚举法
#include<iostream>
#include<string>
#include<math.h>
using namespace std;
int main(){
string a,b;
while(cin>>a>>b){//输入两个字符串
int maxLen=0;
for(int i=0;i<a.size();i++){
for(int j=i;j<a.size();j++){
string temp=a.substr(i,j-i+1);//temp为a的子串
if(int(b.find(temp))<0){//若temp在b中没有出现,跳出当前循环,从下一个位置i开始找子串
break;
}else if(maxLen<temp.size()){//找到了更长的公共子串
maxLen=temp.size();
}
}
}
cout<<maxLen<<endl;
}
return 0;
}
方法二:动态规划
#include <iostream>
#include <vector>
using namespace std;
int main(){
string a,b;
while (cin >> a >> b)
{
int maxLen = 0;
vector<vector<int>> dp(b.size()+1,vector<int>(a.size()+1,0));//动态数组,dp[i][j]表示b以第i个字符结尾,a以第j个字符结尾的公共子串的长度
for (int i = 1; i <= b.size(); ++i){
for (int j = 1; j <= a.size(); ++j){
if (b[i - 1] == a[j - 1]) {//b中第i个字符和a中第j个字符相同
dp[i][j] = dp[i - 1][j - 1] + 1;//前一个长度加一
}else {
dp[i][j] = 0;//如果第i个字符和第j个字符不同,则以他们结尾的子串不可能相同
}
if (maxLen < dp[i][j]) {//更新最大值
maxLen = dp[i][j];
}
}
}
cout << maxLen << endl;
}
return 0;
}
HJ76 尼科彻斯定理
描述
验证尼科彻斯定理,即:任何一个整数m的立方都可以写成m个连续奇数之和。
例如:
1^3=1
2^3=3+5
3^3=7+9+11
4^3=13+15+17+19
输入一个正整数m(m≤100),将m的立方写成m个连续奇数之和的形式输出。
数据范围:1≤m≤100
进阶:时间复杂度:O(m) ,空间复杂度:O(1)
输入描述:
输入一个int整数
输出描述:
输出分解后的string
方法一:遍历查找
#include<iostream>
#include<string>
using namespace std;
int main(){
int m;
while(cin >> m){
int pow = m * m * m; //先获取三次方的值
for(int i = 1; i < pow; i += 2){ //从1开始找到pow
if(m * i + m * (m - 1) == pow){ //比较等差数列和与三次幂是否相等
cout << i; //相等开始输出连续m个数字
for(int j = 1; j < m; j++)
cout << '+' << i + 2 * j;
cout << endl;
break;
}
}
}
return 0;
}
方法二:数学规律
#include<iostream>
#include<string>
using namespace std;
int main(){
int m;
while(cin >> m){
int odd = m * m - (m - 1); //根据公式获取起点奇数
cout << odd;
for(int i = 1; i < m; i++) //遍历后续m-1个奇数
cout << '+' << odd + 2 * i; //输出
cout << endl;
}
return 0;
}
HJ77 火车进站
描述
给定一个正整数N代表火车数量,0<N<10,接下来输入火车入站的序列,一共N辆火车,每辆火车以数字1-9编号,火车站只有一个方向进出,同时停靠在火车站的列车中,只有后进站的出站了,先进站的才能出站。
要求输出所有火车出站的方案,以字典序排序输出。
数据范围:1≤n≤10
进阶:时间复杂度:O(n!) ,空间复杂度:O(n)
输入描述:
第一行输入一个正整数N(0 < N <= 10),第二行包括N个正整数,范围为1到10。
输出描述:
输出以字典序从小到大排序的火车出站序列号,每个编号以空格隔开,每个输出序列换行,具体见sample。
方法一:全排列+栈
#include<iostream>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std;
bool check(vector<int>& order, vector<int>& out){ //根据进来的顺序检查有无这种出去的顺序
stack<int> s;
int j = 0; //out数组的下标
for(int i = 0; i < order.size(); i++){
s.push(order[i]); //每次火车入栈
while(!s.empty() && s.top() == out[j]){ //如果刚好栈顶等于输出,就全部出栈
s.pop();
j++;
}
}
return s.empty();
}
int main(){
int n;
while(cin >> n){
vector<vector<int> > output;
vector<int> nums(n); //记录所有的数字
vector<int> order(n); //记录数字进来的顺序
for(int i = 0; i < n; i++){
cin >> nums[i];
order[i] = nums[i];
}
sort(nums.begin(), nums.end()); //对数字按照字典序排序
do{
output.push_back(nums);
}while(next_permutation(nums.begin(), nums.end())); //获取全排列
for(int i = 0; i < output.size(); i++){
if(check(order, output[i])){ //检查每一种排列输出的可能性
for(int j = 0; j < n; j++)
cout << output[i][j] << " ";
cout << endl;
}
}
}
return 0;
}
方法二:dfs+回溯
#include<iostream>
#include<set>
#include<stack>
#include<vector>
#include<algorithm>
using namespace std;
void dfs(vector<int>& nums, stack<int> s, vector<int> temp, set<vector<int>>& output, int index, int& n){
if(temp.size() == n){ //该情况结果已经完成
output.insert(temp);
return;
}
for(int i = 0; i < 2; i++){ //每次两个操作
if(i == 0 && !s.empty()){ //要么从栈出弹出一个输出
int num = s.top();
s.pop();
temp.push_back(num);
dfs(nums, s, temp, output, index, n); //继续递归
s.push(num); //回溯
temp.pop_back();
}else if(i == 1 && index < n){ //要么从数组中拿出一个加入栈中
int num = nums[index];
s.push(num);
index++;
dfs(nums, s, temp, output, index, n); //继续递归
index--; //回溯
s.pop();
}
}
}
int main(){
int n;
while(cin >> n){
vector<int> nums(n);
for(int i = 0; i < n; i++)
cin >> nums[i];
set<vector<int> > output;
stack<int> s;
vector<int> temp; //记录某一种情况的输出结果
s.push(nums[0]); // 默认第一辆车都要先进去
dfs(nums, s, temp, output, 1, n); //dfs找到全排列
for(auto iter = output.begin(); iter != output.end(); iter++){ //遍历集合
for(int i = 0; i < n; i++) //输出集合中每一个数组
cout << (*iter)[i] << " ";
cout << endl;
}
}
return 0;
}
HJ80 整型数组合并
描述
题目标题:
将两个整型数组按照升序合并,并且过滤掉重复数组元素。
输出时相邻两数之间没有空格。
输入描述:
输入说明,按下列顺序输入:
1 输入第一个数组的个数
2 输入第一个数组的数值
3 输入第二个数组的个数
4 输入第二个数组的数值
输出描述:
输出合并之后的数组
方法一:hash + 排序
#include <bits/stdc++.h>
using namespace std;
vector<int> arr;
int n, m;
int main() {
while (cin >> n) { //多组测试数据
arr.clear();
//输入与合并两个数组
for (int i = 1; i <= n; i ++ ) {
int x; cin >> x;
arr.push_back(x);
}
cin >> m;
for (int i = 1; i <= m; i ++ ) {
int x; cin >> x;
arr.push_back(x);
}
//对合并数组升序排序
sort(arr.begin(), arr.end());
map<int, int> st;
vector<int> ans;
//遍历并保存未标记元素
for (int i = 0; i < arr.size(); i ++ ) {
if (!st[arr[i]]) {
st[arr[i]] = 1; //更新标记
ans.push_back(arr[i]);
}
}
for (int i = 0; i < ans.size(); i ++ ) cout << ans[i];
cout << "\n";
}
return 0;
}
方法二:直接使用stl函数
#include <bits/stdc++.h>
using namespace std;
vector<int> arr;
int n, m;
int main() {
while (cin >> n) { //多组测试数据
arr.clear();
//输入与合并两个数组
for (int i = 1; i <= n; i ++ ) {
int x; cin >> x;
arr.push_back(x);
}
cin >> m;
for (int i = 1; i <= m; i ++ ) {
int x; cin >> x;
arr.push_back(x);
}
//对合并数组升序排序
sort(arr.begin(), arr.end());
//去重
arr.erase(unique(arr.begin(), arr.end()), arr.end());
for (auto i : arr) cout << i;
cout << "\n";
}
return 0;
}