文章目录
1. 组队竞赛
牛牛举办了一次编程比赛,参加比赛的有3*n个选手,每个选手都有一个水平值a_i.现在要将这些选手进行组队,一共组成n个队伍,即每个队伍3人.牛牛发现队伍的水平
值等于该队伍队员中第二高水平值。
例如:
一个队伍三个队员的水平值分别是3,3,3.那么队伍的水平值是3
一个队伍三个队员的水平值分别是3,2,3.那么队伍的水平值是3
一个队伍三个队员的水平值分别是1,5,2.那么队伍的水平值是2
为了让比赛更有看点,牛牛想安排队伍使所有队伍的水平值总和最大。
如样例所示:
如果牛牛把6个队员划分到两个队伍
如果方案为:
team1:{1,2,5}, team2:{5,5,8}, 这时候水平值总和为7.
而如果方案为:
team1:{2,5,8}, team2:{1,5,5}, 这时候水平值总和为10.
没有比总和为10更大的方案,所以输出10.
思路:
贪心算法:每次选值时都选当前能看到的局部最解忧,所以这里的贪心就是保证每组的第二个值取到能选择的最大值就可以,我们每次尽量取最大,即取每组中第二大的。
#include <iostream>
#include <algorithm>
#include <vector>
using namespace std;
int main() {
size_t n;
// 输入
cin >> n;
vector<int> v;
v.resize(3*n);
for (size_t i = 0; i < n * 3; i++)
{
cin>>v[i];
}
sort(v.begin(),v.end());
// 已经升序排序
// 有n队,从大的开始找第2大、第4大、第6大...
size_t sum = 0;
for(size_t i = n; i > 0; i--)
{
sum += v[n*3-i*2];
}
cout << sum;
return 0;
}
2. 删除公共字符
输入两个字符串,从第一字符串中删除第二个字符串中所有的字符。例如,输入”They are students.”和”aeiou”,则删除之后的第一个字符串变成”Thy r stdnts.”
思路:
本题如果使用传统的暴力查找方式,如判断第一个串的字符是否在第二个串中,在再挪动字符删除这个字符的方式,效率为 O(N^2),效率太低,很难让人满意。
- 将第二个字符串的字符都映射到一个 hashtable 数组中,用来判断一个字符在这个字符串。
- 判断一个字符在第二个字符串,不要使用删除,这样效率太低,因为每次删除都伴随数据挪动。这里可以考虑使用将不在字符添加到一个新字符串,最后返回新新字符串。
#include <iostream>
#include <unordered_map>
using namespace std;
int main() {
// 输入
// 注意这里不能使用cin接收,因为cin遇到空格就结束了。
// oj中IO输入字符串最好使用getline。
string s1;
string s2;
getline(cin,s1);
getline(cin,s2);
// 用哈希解决,在不在问题
// ps:直接建数组利用哈希映射更省空间
unordered_map<char,int> m;
for(auto e: s2)
{
m[e]++;
}
// 把s1中的每个值都拿去s2中找在不在,不在就保存到新string
string news;
for(auto e: s1)
{
if(m.count(e) == 0)
news += e;
}
cout << news;
return 0;
}
3. 排序子序列
题目描述:
- 牛牛定义排序子序列为一个数组中一段连续的子序列,这段子序列是非递增或者非递减排序的。
- 牛牛有一个长度为 的整数数组 ,他现在需要把数组 分为若干段排序子序列,牛牛想知道最少可以把这个数组分为几段排序子序列。
输入描述:
- 第一行输入一个正整数 。
- 第二行输入 个正整数 ,表示数组的每个数。
思路:
- 本题依次比较整个数组
- a[i+1]>a[i] ,则进入非递减序列判断,直到遍历到下一个值不大于等于为止count++,然后进行下一位置的判断
- a[i+1]<a[i],则进入非递增序列判断,直到遍历到下一个值不小于等于为止count++,然后进行下一位置的判断
- a[i+1] == a[i]不进行操作,++i进行下一位置遍历,因为相等既可以属于非递增序列,也可以属于非递减序列。
总之:出一个非递增/递减序列,count++,就能得到最后的子序列数
注意:
本题开始比较a[i+1]与a[i]进行比较,为了避免越界,数组定义为n+1个,同时给a[n] = 0;
a[n] = 0带来的影响,我们分为三种情况讨论:
- 若到a[n-1] 的最后一组是非递减序列,当i==n-1,a[i] >a[i+1],因为前面的数都是大于0的,这个输入
条件已经说明了(去看看题目输入条件描述),里面的循环结束,i++,count++,i==n,外面的循环结
束。 - 若到a[n-1] 的最后一组是非递增序列,当i==n-1,a[i] >a[i+1],因为前面的数都是大于0的,这个输入
条件已经说明了(去看看题目输入条件描述),循环再走一次,i++, i== n,里面的循环结束,i++,
count++,i==n+1,外面的循环结束。 - 第三种情况 1 2 1 2 1最后一个数是单独的情况,后面补个0,序列变成1 2 1 2 1 0,当走完全面的序列
i==n-1时,a[i] > a[i+1],进入判断出一个非递增序列,count++,i++,循环结束。 - 也就是说数组最后一个位置多增加一个0,不会影响第1、2情况的判断,主要是帮助第3情况的正确判断。
int main()
{
int n;
cin >> n;
vector<int> a;
// 注意这里多给了一个值,是处理越界的情况的比较,具体参考上面的解题思路
//这里有个坑,这个题越界了牛客测不出来,给n,并且不写a[n] = 0
a.resize(n + 1);
a[n] = 0;
//读入数组
int i = 0;
for (i = 0; i < n; ++i)
cin >> a[i];
i = 0;
int count = 0;
while (i < n)
{
if (a[i] < a[i + 1])
{// 非递减子序列
while (i < n && a[i] <= a[i + 1])
i++;
count++;
}
else if (a[i] > a[i + 1])
{// 非递增子序列
while (i < n && a[i] >= a[i + 1])
i++;
count++;
}
else
{}
i++;
}
cout << count << endl;
return 0;
}
(如果 vector 不多给一个值,需要分析复杂情况…容易错
#include <iostream>
#include <type_traits>
#include <vector>
using namespace std;
int main() {
size_t n;
cin >> n;
/*if (n == 1 || n == 2)
{
cout << 1;
return 0;
}可以省去开vector的空间*/
vector<int> a;
a.resize(n);
for (size_t i = 0; i < n; i++)
{
cin >> a[i];
}
// <
size_t count = 0;
size_t i = 0;
while (i < n - 1)
{
if (a[i] < a[i + 1])
{
while ((a[i] <= a[i + 1]) && (i < n - 1))
{
i++;
}
count++;
}
else if (a[i] > a[i + 1])
{
while ((a[i] >= a[i + 1]) && (i < n - 1))
{
i++;
}
count++;
}
else
{
// 特殊情况:最后一个子序列以相等结束
if (i == n - 2)
count++;
i++;
}
// 特殊情况:最后两个数据是一个子序列
if (i == n - 2)
count++;
i++;
}
cout << count;
return 0;
}
4. 倒置字符串
将一句话的单词进行倒置,标点不倒置。比如 I like beijing. 经过函数后变为:beijing. like I
输入描述:
每个测试输入包含1个测试用例: I like beijing. 输入用例长度不超过100
输出描述:
依次输出倒置之后的字符串,以空格分割
示例1:
输入
I like beijing.
输出
beijing. like I
思路一:
step1. 逆置整个字符串
step2.遍历字符串,对每个单词逆置。
因为使用的stl中的reverse,所以这里使用迭代器遍历。
#include <iostream>
#include <string>
#include <algorithm>
using namespace std;
int main()
{
string s;
// 注意这里要使用getline,cin>>s遇到空格就接收结束了
getline(cin, s);
// 翻转整个句子
reverse(s.begin(), s.end());
// 翻转单词
auto start = s.begin();
while (start != s.end())
{
// 让 end 从 start 开始,遇到 '' 停下来,此时他俩之间就是一个单词
auto end = start;
while (end != s.end() && *end != ' ')
end++;
reverse(start, end);
if (end != s.end()) // 不是最后一个单词,继续往后
start = end + 1;
else // 是最后一个单词,使start出循环,break 一样。
start = end;
}
cout << s << endl;
return 0;
}
思路二:
利用 cin 后空格是一次输入,把每个输入的单词,放在已经输入的词的前面。创建两个 string 可以做到。
#include <iostream>
#include <string>
using namespace std;
int main()
{
string a;
string b = "";
while(cin >> a)
{
b = a + " " + b;
}
cout << b;
return 0;
}
5. 字符中找出连续最长的数字串
字符串中找出连续最长的数字串 | 时间限制:1秒 | 内存限制:32768K
读入一个字符串str,输出字符串str中的连续最长的数字串
输入描述:
个测试输入包含1个测试用例,一个字符串str,长度不超过255。
输出描述:
在一行内输出str中里连续最长的数字串。
示例1:
输入
abcd12345ed125ss123456789
输出
123456789
思路:
遍历字符串,使用cur去记录连续的数字串,如果遇到不是数字字符,则表示一个连续的数字串结束了,则将数字串跟之前的数字串比较,如果更长,则更新更长的数字串更新到ret。
#include <iostream>
#include <string>
using namespace std;
int main() {
string s;
getline(cin, s);
string str;
string ret;
for (size_t i = 0; i < s.size()+1; i++)
{
if (s[i] >= '0' && s[i] <= '9')
{
str += s[i];
}
else
{
if (ret.size() < str.size())
{
ret = str;
}
else
{
str.clear();
}
}
}
cout << ret;
return 0;
}
6. 数组中出现次数超过一半的数字🔺
给一个长度为 n 的数组,数组中有一个数字出现的次数超过数组长度的一半,请找出这个数字。
例如输入一个长度为9的数组[1,2,3,2,2,2,5,4,2]。由于数字2在数组中出现了5次,超过数组长度的一半,因此输出2。
思路一:
中位数: 数组排序后,如果符合条件的数存在,则一定是数组中间那个数。这种方法虽然容易理解,但由于涉及到快排sort,其时间复杂度为O(NlogN)并非最优;
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers)
{
// 因为用到了sort,时间复杂度O(NlogN),并非最优
if(numbers.empty()) return 0;
sort(numbers.begin(),numbers.end()); // 排序,取数组中间那个数
int middle = numbers[numbers.size()/2];
int count=0; // 出现次数
for(int i=0;i<numbers.size();++i)
{
if(numbers[i]==middle) ++count;
}
return (count>numbers.size()/2) ? middle : 0;
}
};
思路二:
众数:就是出现次数超过数组长度一半的那个数字。
如果两个数不相等,就消去这两个数,最坏情况下,每次消去一个众数和一个非众数,那么如果存在众数,最后留下的数肯定是众数。
思路二写法一:遍历每个元素,并记录次数;若与前一个元素相同,则次数加1,否则次数减1。
result是对照位,跟result一样的time++不一样的–,直到time=0,说明result数在这段不是众数,接着走。
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int> numbers)
{
if(numbers.empty()) return 0;
int result = numbers[0];
int times = 1; // 次数
for(int i=1;i<numbers.size();++i)
{
if(times != 0)
{
if(numbers[i] == result)
{
++times;
}
else
{
--times;
}
}
else
{
result = numbers[i];
times = 1;
}
}
// 判断result是否符合条件,即出现次数大于数组长度的一半
times = 0;
for(int i=0;i<numbers.size();++i)
{
if(numbers[i] == result) ++times;
}
return (times > numbers.size()/2) ? result : 0;
}
};
思路二写法二(个人觉得更好理解):
用两个下标,两两依次遍历比较,如果不同,就标记位置。这里用了位图标记,其实观察所给数据范围都是正数,可以把当前位置改成 -1 作为标记!!
class Solution {
public:
int MoreThanHalfNum_Solution(vector<int>& numbers) {
if (numbers.size() == 1)
return numbers[0];
size_t cur1 = 0;
size_t cur2 = 1;
bitset<150000> bs;
while (cur2 < numbers.size())
{
// 一对一对排除不等的
if (numbers[cur1] != numbers[cur2])
{
bs.set(cur2);
cur1++;
cur2++;
while (bs[cur1] == 1) // 如果被标记过就继续往后走
{
cur1++;
}
}
else
{
cur2++;
}
}
return numbers[cur1];
}
};
day 04
9. 统计回文
“回文串”是一个正读和反读都一样的字符串,比如“level”或者“noon”等等就是回文串。花花非常喜欢这种拥有对称美的回文串,生日的时候她得到两个礼物分别是字符串A和字符串B。现在她非常好奇有没有办法将字符串B插入字符串A使产生的字符串是一个回文串。你接受花花的请求,帮助她寻找有多少种插入办法可以使新串是一个回文串。如果字符串B插入的位置不同就考虑为不一样的办法。
例如:
A = “aba”,B = “b”。这里有4种把B插入A的办法:
- 在A的第一个字母之前: “baba” 不是回文
- 在第一个字母‘a’之后: “abba” 是回文
- 在字母‘b’之后: “abba” 是回文
- 在第二个字母’a’之后 “abab” 不是回文
所以满足条件的答案为2
#include <iostream>
#include <stack>
using namespace std;
bool isit(const string& a)
{
size_t begin = 0;
size_t end = s.size()-1;
while(begin < end)
{
if(s[begin] != s[end])
return false;
++begin;
--end;
}
return true;
}
int main() {
string a, b;
cin >> a;
cin >> b;
int count = 0;
string tmp;
for (int i = 0; i < a.size()+1; i++)
{
tmp = a;
if (isit(tmp.insert(i, b)))
count++;
}
cout << count;
return 0;
}
10. 连续最大和 🔺🔺
一个数组有 N 个元素,求连续子数组的最大和。 例如:[-1,2,1],和最大的连续子数组为[2,1],其和为 3
思路: 动态规划思想
状态方程式:max( dp[ i ] ) = getMax( max( dp[ i -1 ] ) + arr[ i ] ,arr[ i ] )
dp[i]:当数组下标为 i 的数作为一个子序列 结尾 时,这个子序列的最大和为 dp[i]
结尾为 i 的每一个子序列的最大值计算时,有两种可能,此时 dp[i-1] 是 i-1 结尾的最大值:
情况一:dp[i] = dp[i-1] + arr[i],即加上 arr[i] 会变大
情况二:dp[i] = arr[i],即加上 dp[i-1] 后更小了,即自己就是最大的。
究竟是哪种情况,我们用max函数计算取大的。这样,遍历每个位置 i,得可以得到当前 i 位置为结束的子序列的最大值,记录这个值,遇到更大值后刷新。
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main()
{
int size;
cin >> size;
vector<int> nums(size);
for(size_t i = 0; i < size; ++i)
cin >> nums[i];
int Sum = nums[0]; //临时最大值
int MAX = nums[0]; //比较之后的最大值
for (int i = 1; i < size; i++)
{
Sum = max(Sum + nums[i], nums[i]); //状态方程
if (Sum >= MAX)
M AX = Sum;
}
cout << MAX << endl;
return 0;
}
11.不要 2(wy)
二货小易有一个W*H的网格盒子,网格的行编号为0H-1,网格的列编号为0W-1。每个格子至多可以放一块蛋糕,任意两块蛋糕的欧几里得距离不能等于2。
对于两个格子坐标(x1,y1),(x2,y2)的欧几里得距离为:
( (x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) ) 的算术平方根
小易想知道最多可以放多少块蛋糕在网格盒子里。
输入描述:
每组数组包含网格长宽W,H,用空格分割.(1 ≤ W、H ≤ 1000)
输出描述:
输出一个最多可以放的蛋糕数
思路:
vector<vector<char>>,建立二维数组,初始化为 1,遍历二维数组,将不欧几里得距离为 2 的设置为 0,遍历结束后,为 1 的位置就是能放蛋糕的位置。
欧几里得距离为 2?由题意,
(x1-x2) * (x1-x2) + (y1-y2) * (y1-y2) = 4
1 + 3 = 4 ×
2 + 2 = 4 ×
0 + 4 = 4
4 + 0 = 4
就是说 [x1][y1] 有蛋糕时,如下情况的 [x2][y2] 不能放蛋糕
x1 == x2;y1 - y2 = 2
x1 - x2 = 2;y1 == y2
#include <iostream>
#include <vector>
using namespace std;
int main() {
int w,h;
cin >> w;
cin >> h;
vector<vector<char>> vv;
vv.resize(w);
for(auto& e : vv) // 这里要改变 e,需要加引用才行!!
{
e.resize(h,1);
}
int count = 0;
for(int i = 0; i < w; i++)
{
for(int j = 0; j < h; j++)
{
if(vv[i][j] == 1)
{
count++;
if(i+2 < w)
vv[i+2][j] = 0;
if(j+2 < h)
vv[i][j+2] = 0;
}
}
}
cout << count;
return 0;
}
12. 把字符串转换成整数
描述
将一个字符串转换成一个整数,要求不能使用字符串转换整数的库函数。 数值为 0 或者字符串不是一个合法的数值则返回 0
注意:
①字符串中可能出现任意符号,出现除 +/- 以外符号时直接输出 0
②字符串中可能出现 +/- 且仅可能出现在字符串首位。
输入描述:
输入一个字符串,包括数字字母符号,可以为空
返回值描述:
如果是合法的数值表达则返回该数字,否则返回0
思路:
上次计算的结果10,相当于10进制进位,然后加当前位的值。
例如:“123”转换的结果是
sum=0
sum10+1->1
sum10+2->12
sum10+3->123
本题的关键是要处理几个关键边界条件:
- 空字符串
- 正负号处理
- 数字串中存在非法字符
class Solution {
public:
int StrToInt(string str) {
if(str.empty())
return 0;
int symbol = 1;
if(str[0] == '-')
{
symbol = -1;
str[0] = '0';
}
else if(str[0] == '+')
{
symbol = 1;
str[0] = '0';
}
int sum = 0;
for(int i = 0; i < str.size(); i++)
{
if(str[i]<'0' || str[i]>'9')
return 0;
sum = sum*10 + (str[i]-'0');
}
return symbol*sum;
}
};
13. Fibonacci数列
Fibonacci数列是这样定义的:
F[0] = 0
F[1] = 1
for each i ≥ 2: F[i] = F[i-1] + F[i-2]
因此,Fibonacci数列就形如:0, 1, 1, 2, 3, 5, 8, 13, …,在Fibonacci数列中的数我们称为Fibonacci数。给你一个N,你想让其变为一个Fibonacci数,每一步你可以把当前数字X变为X-1或者X+1,现在给你一个数N求最少需要多少步可以变为Fibonacci数。
思路:
本题可以通过先找到距离N最近的两个Fibonacci数,这两个数分别取自距离N的最近的左边一个数L和右边一个数R,然后通过 min(N - L, R - N) 找到最小步数
#include <iostream>
using namespace std;
int main() {
int a = 0, b = 1, c;
cin >> c;
int fi;
while(!(a<=c && c<=b)) // 找到这个数的两侧的斐波那契数后停下
{
fi = a + b;
a = b;
b = fi;
}
int ret = ((c-a)<(b-c))?(c-a):(b-c);
cout<< ret << endl;
return 0;
}
14. 合法括号序列判断
给定一个字符串A和其长度n,请返回一个bool值代表它是否为一个合法的括号串(只能由括号组成)。
思路:
用栈结构实现,栈中存放左括号,当遇到右括号之后,检查栈中是否有左括号,如果有则出栈,如果没有,则说明不匹配。
#include<string>
#include<stack>
class Parenthesis {
public:
bool chkParenthesis(string A, int n) {
if(n%2 == 1)
return false;
stack<char> s;
for(auto e : A)
{
if(e == '(')
s.push(e);
else if(e == ')')
{
if (s.empty())
return false;
else
s.pop();
}
else
{
return false;
}
}
return true;
}
};
17.另类加法
给定两个int A和B。编写一个函数返回A+B的值,但不得使用+或其他算数运算符。
思路:
本题可以通过位运算实现,具体实现如下:
两个数求和,其实就是 求和后当前位的数据+两个数求和的进位
例如:
1 + 2; 00000001 + 00000010
求和后当前位的数据: 00000011 ; 求和后的进位数据: 没有进位,则 00000000
两者相加,则得到: 00000011 就是3
2 + 2; 00000010 + 00000010
求和后当前位的数据: 00000000, 1和1进位后当前为变成0了
求和后进位的数据: 00000100, 两个1求和后进位了
相加后得到: 00000100 就是4
求和后当 前位 的数据:简便的计算方法就是两个数进行异或 00000001 ^ 00000010 -> 00000011
求和后 进位 的数据:简便的计算方法就是两个数相与后左移一位 (00000010 & 00000010) << 1
所以这道题使用递归更加容易理解
class UnusualAdd {
public:
int addAB(int A, int B) {
if (A == 0)
return B;
if (B == 0)
return A;
int a = A ^ B; //求和后当前位的数据
int b = (A & B) << 1; //求和后进位的数据
return addAB(a, b); //递归两个数进行相加,任意为0时截止
}
};
非递归写法:
class UnusualAdd {
public:
int addAB(int a, int b) {
while(b)
{
int symbol = (a & b) << 1; // 进位符号位
a = a ^ b;
b = symbol; // 一直算到没有进位(为0)为止
}
return a;
}
};
18. 走方格的方案数
请计算n*m的棋盘格子(n为横向的格子数,m为竖向的格子数)从棋盘左上角出发沿着边缘线从左上角走到右下角,总共有多少种走法,要求不能走回头路,即:只能往右和往下走,不能往左和往上走。
注:沿棋盘格之间的边缘线行走
思路:
本题为求取路径总数的题目,一般可以通过递归求解,对于复杂的问题,可以通过动态规划求解。此题比较简单,可以通过递归解答。
通过题目路径不能回头的特点,我们可以清楚的得知,一条路径但凡走到右侧或底侧的最边缘(即如下图 绿点情况),就只剩一条直路可以通向右下角。而无论哪条路径,最后一定会先走到某一侧的边缘(不存在不通过边缘能到达的路径)。
按照上图的坐标系。
我们就可以将函数 walkmethod(n,m) 做为到达坐标 (n,m) 的路径方法数。代码如下:
#include <iostream>
using namespace std;
int walkmethod(int n, int m)
{
if(n == 0)
return 1;
if(m == 0)
return 1;
return walkmethod(n-1,m) + walkmethod(n,m-1);
}
int main() {
int n, m;
cin >> n >> m;
cout << walkmethod(n, m) << endl;
return 0;
}