【2021】阿里巴巴编程题(2星)
数组各数减去第一个数,归并相同特征的数组,使用map记录各数组的出现次数,对每一个map kv,查看map中是否存在相反数组,需对全0数组(相反数组为自身)进行特殊处理,为了避免重复计算,将相反数组出现次数置为-1
#include
using namespace std;
vector<int> Another(const vector<int>& v) {
vector<int> another(v.size());
for (int i = 0; i < v.size(); i++) {
another[i] = -v[i];
}
return another;
}
int main() {
int n, k;
while (cin >> n >> k) {
vector<vector<int>> arr(n, vector<int>(k));
for (int i = 0; i < n; i++) {
for (int j = 0; j < k; j++) {
cin >> arr[i][j];
}
}
map<vector<int>, int> ma;
for (int i = 0; i < n; i++) {
for (int j = k - 1; j >= 0; j--) {
arr[i][j] -= arr[i][0];
}
ma[arr[i]]++;
}
int ans = 0;
for (auto iter = ma.begin(); iter != ma.end(); ++iter) {
if (iter->second == -1) { // 标记为删除,跳过
continue;
}
auto another_iter = ma.find(Another(iter->first)); // 寻找是否存在相反数组
if (another_iter == iter) { // 全零数组,相反即本身
ans += iter->second * (iter->second - 1) / 2;
} else if (another_iter != ma.end()) { // 后面存在相反数组
ans += iter->second * another_iter->second;
another_iter->second = -1; // 标记为删除
}
}
cout << ans << endl;
}
}
全排列变形问题,长度为m时输出结果,数字交换时强制要求序列递增,去重处理
#include
using namespace std;
void dfs(vector<int>& vec, int start, int m) {
if (start == m) { // 长度达到要求,输出结果
for (int i = 0; i < m; i++) {
cout << vec[i] << " ";
}
cout << endl;
} else {
for (int i = start; i < vec.size(); i++) {
if (start == 0 || vec[i] > vec[start - 1]) { // 限制序列必须递增,去重
swap(vec[i], vec[start]);
dfs(vec, start + 1, m);
swap(vec[i], vec[start]);
}
}
}
}
int main() {
int n, m;
while (cin >> n >> m) {
vector<int> arr(n);
for (int i = 0; i < n; i++) { // 初始化数组内容
arr[i] = i + 1;
}
dfs(arr, 0, m);
}
}
动态规划问题,假设已解决n-1与n-2规模时最短时间,可能的方法是:n-1 (最轻的人单独过河把最重的人接过来 fn-1+v0+vn) n-2(最轻的人过去 最重的和第二重的人过去 第二轻的人过来 第一第二轻的人过去 fn-2+v1+vn+v2+v2)
#include
#include
using namespace std;
int main() {
int T, n;
cin >> T;
for (int i = 0; i < T; i++) {
cin >> n;
vector<int> vec(n);
for (int j = 0; j < n; j++) {
cin >> vec[j];
}
sort(vec.begin(), vec.end());
if (n == 1) {
cout << vec[0] << endl;
continue;
}
if (n == 2) {
cout << vec[1] << endl;
continue;
}
// 使用三个变量维持前两个 前一个 当前元素
int first = vec[0];
int second = vec[1];
int three;
for (int i = 2; i < n; i++) {
// 状态转移方程
three = min(second + vec[0] + vec[i], first + vec[i] + vec[0] + vec[1] * 2);
// 更新序列
first = second;
second = three;
}
cout << three << endl;
}
}
#include
using namespace std;
int main() {
long long A, B, a, b; // 使用long long存储数字,防止溢出
while (cin >> A >> B >> a >> b) {
if (A > B * a / b) { // 缩小查找范围
A = B * a / b;
}
bool find = false;
for (long long i = A; i >= 1; i--) { // 逆序遍历可能数字
if ((i * b) % a == 0) {
cout << i << " " << i * b / a << endl;
find = true;
break;
}
}
if (!find) { // 不存在可能数字组合
cout << 0 << " " << 0 << endl;
}
}
}
最开始发现最小距离点一定在端点上(若有多个点,一定存在端点),故从前往后变量,计算端点的距离和,left为与左边点的长度和,right为与右边点的长度和
#include
using namespace std;
int main() {
int n;
while (cin >> n) {
int x, y;
vector<long long> vec(n);
for (int i = 0; i < n; i++) {
cin >> x >> y;
vec[i] = x;
}
sort(vec.begin(), vec.end());
long long left = 0; // 与左部端点的距离和
long long right = accumulate(vec.begin(), vec.end(), -n * vec[0]); // 与右部端点的距离和
long long min_len = left + right;
for (int i = 1; i < n; i++) {
left += i * (vec[i] - vec[i - 1]);
right -= (n - i) * (vec[i] - vec[i - 1]);
min_len = min(min_len, left + right);
}
cout << min_len << endl;
}
}
后面发现最小距离和是中位数的性质,直接求中位数即可
#include
using namespace std;
int main() {
int n;
while (cin >> n) {
int x, y;
vector<long long> vec(n);
for (int i = 0; i < n; i++) {
cin >> x >> y;
vec[i] = x;
}
sort(vec.begin(), vec.end());
long long x = (vec[n / 2] + vec[(n - 1) / 2]) / 2; // 中位数
int sum_len = 0;
for (int xi : vec) {
sum_len += abs(x - xi);
}
cout << sum_len << endl;
}
}
最近小强主办了一场国际交流会,大家在会上以一个圆桌围坐在一起。由于大会的目的就是让不同国家的人感受一下不同的异域气息,为了更好地达到这个目的,小强希望最大化邻座两人之间的差异程度和。为此,他找到了你,希望你能给他安排一下座位,达到邻座之间的差异之和最大。
输入总共两行。
第一行一个正整数,代表参加国际交流会的人数(即圆桌上所坐的总人数,不单独对牛牛进行区分)
第二行包含个正整数,第个正整数a_i代表第个人的特征值。
其中
注意:
邻座的定义为: 第人的邻座为,第人的邻座是,第人的邻座是。
邻座的差异值计算方法为。
每对邻座差异值只计算一次。
输出总共两行。
第一行输出最大的差异值。
第二行输出用空格隔开的个数,为重新排列过的特征值。
(注意:不输出编号)
如果最大差异值情况下有多组解,输出任意一组即可。
思路
依次选取较大值与较小值即可
#include
using namespace std;
int main() {
int n;
while (cin >> n) {
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
sort(nums.begin(), nums.end()); // 数组排序
vector<int> ans(n);
for (int i = 0; i < n / 2; i++) { // 依次选取较大值与较小值
ans[2 * i] = nums[i];
ans[2 * i + 1] = nums[n - i - 1];
}
ans[n - 1] = nums[n / 2]; // 奇数时最后一位未赋值
long long diff = 0; // 差异和
for (int i = 0; i < n; i++) {
diff += abs(ans[i] - ans[(i + 1) % n]);
}
cout << diff << endl;
for (int num : ans) {
cout << num << " ";
}
cout << endl;
}
}
#include
using namespace std;
int main() {
int n;
while (cin >> n) {
vector<int> nums(n);
for (int i = 0; i < n; i++) {
cin >> nums[i];
}
sort(nums.begin(), nums.end()); // 数组排序
vector<int> ans(n);
// 双指针方式
int low = 0;
int high = n - 1;
for (int i = 0; i < n; i++) {
if (i % 2 == 0) {
ans[i] = nums[low];
low++;
} else {
ans[i] = nums[high];
high--;
}
}
long long diff = 0; // 差异和
for (int i = 0; i < n; i++) {
diff += abs(ans[i] - ans[(i + 1) % n]);
}
cout << diff << endl;
for (int num : ans) {
cout << num << " ";
}
cout << endl;
}
}
2021】阿里巴巴编程题(4星)
#include
using namespace std;
int main() {
int T;
cin >> T;
for (int k = 0; k < T; k++) {
int n, x, y;
cin >> n;
vector<pair<int, int>> item(n);
for (int i = 0; i < n; i++) {
cin >> item[i].first;
}
for (int i = 0; i < n; i++) {
cin >> item[i].second;
}
auto cmp = [](const pair<int, int>& p1, const pair<int, int>& p2) -> bool { return p1.first < p2.first || (p1.first == p2.first && p1.second > p2.second); };
sort(item.begin(), item.end(), cmp); // 使用自定义排序函数
vector<int> dp(n, 1); // 以item[i].second为末尾的最长增长子序列长度
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (item[i].second > item[j].second) { // 进行状态转移
dp[i] = max(dp[i], dp[j] + 1);
}
}
}
int ans = *max_element(dp.begin(), dp.end());
cout << ans << endl;
}
}
#include
using namespace std;
int main() {
int T;
cin >> T;
for (int k = 0; k < T; k++) {
int n, x, y;
cin >> n;
vector<pair<int, int>> item(n);
for (int i = 0; i < n; i++) {
cin >> item[i].first;
}
for (int i = 0; i < n; i++) {
cin >> item[i].second;
}
auto cmp = [](const pair<int, int>& p1, const pair<int, int>& p2) -> bool { return p1.first < p2.first || (p1.first == p2.first && p1.second > p2.second); };
sort(item.begin(), item.end(), cmp); // 使用自定义排序函数
int piles = 0; // 牌堆数
for (int i = 0; i < n; i++) {
int left = 0;
int right = piles;
int poker = item[i].second; // 待放置的扑克牌
while (left < right) {
int mid = (left + right) / 2;
if (item[mid].second >= poker) {
right = mid;
} else {
left = mid + 1;
}
}
if (left == piles) { // 新建牌堆
piles++;
}
item[left].second = poker; // 放置牌堆顶
}
cout << piles << endl;
}
}
记忆深刻的问题,之前写过一遍,当时也是写了很久没写出来,看了一下午愣是没看出为啥答案错了
题目的难点在于想出这个递推公式,然后就是mod运算的性质,不过我出错的点在于使用int存储A,B,并用A,B计算了一次dp[2],运算过程会溢出,即使dp[2]为long long,但表达式的组成元素类型全部为int,故仍然会发生溢出。
int A,B;
long long x = A * A - 2 * B;
#include
using namespace std;
const int kMod = 1e9 + 7;
using ll = long long;
int main() {
int T;
cin >> T;
for (int k = 0; k < T; k++) {
ll A, B, n; // 设置为long long,防止溢出
cin >> A >> B >> n;
vector<ll> dp(n + 3);
dp[0] = 2;
dp[1] = A;
for (int i = 2; i <= n; i++) { // fn = A * fn-1 - B * fn-2
dp[i] = (dp[i - 1] * A % kMod - dp[i - 2] * B % kMod + kMod) % kMod;
}
cout << dp[n] << endl;
}
}
ali字符串,刚开始忽略了对开头字符是否为Aa的判断,只有3种状态
#include
using namespace std;
int main() {
string str;
int n;
cin >> n;
for (int i = 0; i < n; i++) {
cin >> str;
int status = 0; // 状态机转换 0:初始状态 1:遇到Aa 2:遇到lL 3: 遇到iI
bool error = false; // 是否发生错误
for (char c : str) {
if (status == 0) {
if (c == 'a' || c == 'A') { // 状态转换
status = 1;
} else { // 预期之外的值,发生错误
error = true;
break;
}
} else if (status == 1) {
if (c == 'l' || c == 'L') { // 状态转换
status = 2;
} else if (c != 'a' && c != 'A') { // 预期之外的值,发生错误
error = true;
break;
}
} else if (status == 2) {
if (c == 'i' || c == 'I') { // 状态转换
status = 3;
} else if (c != 'l' && c != 'L') { // 预期之外的值,发生错误
error = true;
break;
}
} else if (status == 3) {
if (c != 'i' && c != 'I') { // 预期之外的值,发生错误
error = true;
break;
}
}
}
if (error || status != 3) { // 发生错误或未遇到i
cout << "No" << endl;
} else {
cout << "Yes" << endl;
}
}
}
使用dfs遍历各个翻转可能,加上一点剪枝策略,骗分
正确思路
可以使用备忘录的方式减少各个点最大获得价值的重复计算
#include
using namespace std;
const int kMax = 10001;
const int kInf = -1 * (1 << 30);
int n;
vector<int> food(kMax);
vector<vector<int>> path(kMax);
vector<int> max_get(kMax, kInf);
// void dfs(int cur_id, int cur_value, int& max_value) {
// cur_value += food[cur_id];
// max_value = max(max_value, cur_value);
// for (int dst_id : path[cur_id]) {
// dfs(dst_id, cur_value, max_value);
// }
// }
int dfs(int cur_id, int cur_value) {
cur_value += food[cur_id];
int max_value = cur_value;
for (int dst_id : path[cur_id]) {
if (max_get[dst_id] == kInf) {
max_get[dst_id] = dfs(dst_id, cur_value);
}
max_value = max(max_value, max_get[dst_id]);
}
return max_value;
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
int id, parent_id, value;
cin >> id >> parent_id >> value;
food[id] = value;
if (parent_id != -1) {
path[parent_id].emplace_back(id);
}
}
int ans = 0;
for (int id = 0; id < n; id++) {
int tmp = dfs(id, 0);
ans = max(ans, tmp);
}
cout << ans << endl;
}
/*
7
0 1 8
1 -1 -2
2 1 9
4 0 -2
5 4 3
3 0 -3
6 2 -3
*/
题目:美团2024届暑期实习第一轮后端笔试详解
通过了前三道,花了一堆时间在第四道上,一分没得,第五题都没时间看。题目的设置有点无语,一到四题安排在一起,第五题安排在一起,当时不知道提交后可以继续修改,进了一到四题的项就不敢提交,以为第五题单独放一起肯定很难,最后没时间了看了一下第五题,比较简单。搞不明白为啥要把第五题单独列一个项,要不然怎样都能写出四道来
小美有一个由数字字符组成的字符串。现在她想对这个字符串进行一些修改。 具体地,她可以将文个字符串中任意位置字符修改为任意的数字字符。她想知道,至少进行多少次修改,可以使得“修改后的字符串不包含两个连续相同的字符?
例如,对于字符串”111222333", 她可以进行3次修改将其变为” 121212313"。输入描述
一行,一个字符串s,保证s只包含数字字符。1<=|s|<= 100000输出描述
一行,一个整数,表示修改的最少次数。
思路
简单题,遇见相同的字符直接改成数字字符之外的‘a’即可
#include
using namespace std;
int main() {
string s;
while (cin >> s) {
int cnt = 0;
for (int i = 1; i < s.length(); i++) {
if (s[i] == s[i - 1]) {
s[i] = 'a';
cnt++;
}
}
cout << cnt << endl;
}
}
小团在一个n*m的网格地图上探索。 网格地图上第i行第j列的格子用坐标(i,j)简记。初始时,小团的位置在地图的左上角,即坐标(1,1)。 地图上的每个格子 上都有一定的金币, 特别地,小团位于的初始位置(1,1)上的金币为0。小团在进行探索移动时,可以选择向右移动-格(即从(x,y)到达(x,y+1))或向下移动一格(即从(x,y)到达(x+1,y)) 。地图上的每个格子都有一个颜色,红,色或蓝色。如果小团次移动前后的两个格子颜色不同,那么他需要支付k个金币才能够完成这-次移动;如果移动前后的两个格子颜色相同,则不需要支付金币。小团可以在任意格子选择结束探索。现在给你网格地图上每个格子的颜色与金币数量,假设小团初始时的金币数量为0,请你帮助小团计算出最优规划,使他能获得最多的金币,输出能获得的最多 金币数量即可。注意:要求保证小团任意时刻金币数量不小于零。
输入描述
第一行是三个用空格隔开的整数n、m和k,表示网格地图的行数为n,列数为m,在不同颜色的两个格子间移动需要支付k个金币。
接下来n行,每行是一个长度为m的字符串, 字符串仅包含字符R’或’ B’。第i行字符串的第j个字符表示地图上第i行第j列的格子颜色,如果字符为’ R’ 则表示格子颜色为红色,为’B’ 表示格子颜色为蓝色。
接下来是个n行m列的非负整数矩阵,第i行第j列的数字表示地图上第行第j列的格子上的金币数量。保证所有数据中数字大小都是介于[0, 10]的整数。
1<=n,m<=200, 1<=k<=5。
思路
刚开始直接暴力回溯,运行超时
#include
using namespace std;
// 定义全局变量
string color[201];
int coin_num[201][201];
int n, m, k;
int max_coin_num;
void dfs(int x, int y, int cur_coin_num) {
max_coin_num = max(max_coin_num, cur_coin_num);
if (x + 1 < n) { // 向下移动
int offer = color[x][y] == color[x + 1][y] ? 0 : -k;
if (cur_coin_num + offer >= 0) {
dfs(x + 1, y, cur_coin_num + offer + coin_num[x + 1][y]);
}
}
if (y + 1 < m) { // 向右移动
int offer = color[x][y] == color[x][y + 1] ? 0 : -k;
if (cur_coin_num + offer >= 0) {
dfs(x, y + 1, cur_coin_num + offer + coin_num[x][y + 1]);
}
}
}
int main() {
while (cin >> n >> m >> k) {
for (int i = 0; i < n; i++) {
cin >> color[i];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> coin_num[i][j];
}
}
max_coin_num = 0;
dfs(0, 0, 0);
cout << max_coin_num << endl;
}
}
后面用动态规划写了一个版本,通过
#include
using namespace std;
int main() {
string color[201];
int coin_num[201][201];
int n, m, k;
while (cin >> n >> m >> k) {
for (int i = 0; i < n; i++) {
cin >> color[i];
}
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> coin_num[i][j];
}
}
int ans = 0;
vector<vector<int>> max_coin_num(n, vector<int>(m, -1)); // 全部设成-1,不可达
max_coin_num[0][0] = 0;
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
int value = -1;
if (i - 1 >= 0) { // 从上方转移而来
int offer = color[i][j] == color[i - 1][j] ? 0 : -k;
value = max(value, max_coin_num[i - 1][j] + offer);
}
if (j - 1 >= 0) { // 从左边转移而来
int offer = color[i][j] == color[i][j - 1] ? 0 : -k;
value = max(value, max_coin_num[i][j - 1] + offer);
}
if (value >= 0) { // 进行状态转移
max_coin_num[i][j] = value + coin_num[i][j];
}
}
}
// 计算最大金币数目
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
ans = max(ans, max_coin_num[i][j]);
}
}
cout << ans << endl;
}
}
小美是位天文爱好者, 她收集了接下来段时间中所有 会划过她所在的观测地上空的流星信息。具体地,她收集了n个流星在她所在观测地上空的出现时刻和消失时刻。对于一个流星,若’其的出现时刻为s,消失时刻为t,那么小美在时间段[s, t]都能够观测到它。对于一个时刻,观测地上空出现的流星数量越多,则小美认为该时刻越好。小美希望能够选择一个最佳的时刻进行观测和摄影,使她能观测到最多数量的流星。现在小美想知道 ,在这个最佳时刻,她最多能观测到多少个流星以及一共有多少个最佳时刻可供她选择。
输入描述
第一行是一个正整数n,表示流星的数量。
第二行是n个用空格隔开的正整数,第i个数si表示第i个流星的出现时间。
第三行是n个用空格隔开的正整数,第i个数ti表示第i个流星的消失时间。
1<=n<=100000, 1<=si<=ti<=10^9
输出描述
输出一行用空格隔开的两个数x和y,其中x表示小美能观测到的最多流星数,y表示可供她选择的最佳时刻数量。
思路
排序,从左往右计算可观测的流星数量,不过后面看别人说标准解法是差分数组
#include
using namespace std;
struct Node {
int value; // 时间
bool is_start; // 是否为起始观测时间
int number; // 观测流星数目
};
int main() {
int n, s, t;
while (cin >> n) {
vector<Node> star(2 * n);
for (int i = 0; i < n; i++) {
cin >> s;
star[i].value = s;
star[i].is_start = true;
}
for (int i = n; i < 2 * n; i++) {
cin >> t;
star[i].value = t;
star[i].is_start = false;
}
auto cmp = [&](const Node& n1, const Node& n2) -> bool { return n1.value < n2.value || (n1.value == n2.value && n1.is_start == true); }; // 尽可能让起始观测时间在终止观测时间之前
sort(star.begin(), star.end(), cmp);
int observer = 0;
int max_observer = 0;
for (int i = 0; i < 2 * n; i++) {
if (star[i].is_start) { // 遇到起始时间,观测数加一
observer++;
star[i].number = observer;
} else { // 遇到终止时间,当前观测数不变,之后观测数减一
star[i].number = observer;
observer--;
}
max_observer = max(max_observer, star[i].number);
}
int time_num = 0;
for (int i = 0; i < 2 * n; i++) {
if (star[i].number == max_observer) { // 一定为start-end组合,跳过end节点
time_num += star[i + 1].value - star[i].value + 1;
i++;
}
}
cout << max_observer << " " << time_num << endl;
}
}
小D和小W最近在玩坦克大战,双方操控自己的坦克在16*1 6的方格图上战斗,小D的坦克初始位置在地图的左上角,朝向为右,其坐标(0,0), 小W的坦克初始位置在地图右下角,朝向为左,坐标为(15,15)。坦克不能移动到地图外,坦克会占领自己所在的格子,己方的坦克不可以进入对方占领过的格子。每一个回合双方必须对自己的坦克下达以下5种指令中的一种:
.移动指令U:回合结束后,使己方坦克朝向为上,若上方的格子未被对方占领,则向当前朝向移动一个单位(横坐标-1),否则保持不动;
.移动指令D:回合结束后,使己方坦克朝向为下,若下方的格子未被对方占领,则向当前朝向移动一个单位(横坐标+1),否则保持不动,
.移动指令L:回合结束后,使己方坦克朝向为左,若左侧的格子未被对方占领,则向当前朝向移动一个单位(纵坐标-1) ,否则保持不动;
.移动指令R:回合结束后,使己方坦克朝向为右,若右侧的格子未被对方占领,则向当前朝向移动一个单位(纵坐标+1),否则保持不动;
. 开火指令F:己方坦克在当前回合立即向当前朝向开火;
己方坦克开火后,当前回合己方坦克的正前方若有对方的坦克,对方的坦克将被摧毁,游戏结束,己方获得胜利;若双方的坦克在同一-回合被摧毁,游戏结束,判定为平局;若双方的坦克在同一回合内进入到同一个未被占领的格子,则双方的坦克发生碰撞,游戏结束,判定为平局;当游戏进行到第256个回合后,游戏结束,若双方坦克均未被摧毁,则占领格子数多的一方获得胜利,若双方占领的格子数一样多,判定为平局。*注意, 若-方开火, 另-方移动,则认为是先开火,后移动。
现在小D和小W各自给出一串长度为256的指令字符串, 请你帮助他们计算出游戏将在多少个回合后结束,以及游戏的结果。
输入描述
输入共两行,每行为一串长度为256的指令宁符串,字符串中只包含“U”,“D",“L" “R”,“F"这五个字符。第一行表示小D的指令,第工行表示小W的指令。
输出描述
输出一共两行,第一行一个整数k,表示游戏将在k个回合后结束。第二行为游戏的结 果,若小D获胜则输出“D",若小W获胜则输出“W”若平局则输出“P”
思路
大模拟,40分钟一分没得,懒得贴代码了
给一棵有n个点的有根树,点的编号为1到n,根为1。每个点的颜色是红色或者蓝色。对于树上的一个点,如果其子树中(不包括该点本身)红色点和蓝色点的数量相同,那么我们称该点是平衡的。
请你计算给定的树中有多少个点是平衡点。
输入描述
第一行是一个正整数n,表示有n个点。
接下来行一个长度为n的字符串,仅包含字符R’和’B’, 第i个字符表示编号为的节点的颜色,字符为’R’ 表示红色,’ B’ 表示蓝色。
接下来一行n-1个用空格隔开的整数,第1个整数表示编号为i+ 1的点的父亲节点编号。1<=n<=10000
输出描述
一行一个整数,表示树上平衡点的个数。
思路
没写,无语的题目安排
题目链接: 2023暑期实习-笔试-拼多多-算法实习生
晚上的笔试,过了三道半
还原压缩后的字符串
输入 10a1b1c
输出 aaaaaaaaaabc
思路
简单题
#include
using namespace std;
int main() {
string s;
while (cin >> s) {
string ans = "";
int number = 0;
for (char c : s) {
if (c >= '0' && c <= '9') { // 遇见数字
number = number * 10 + c - '0';
} else { // 遇见字母
ans += string(number, c); // 添加number个字符c
number = 0;
}
}
cout << ans << endl;
}
}
多多最近下载了一款飞机大战的游戏,多多可以通过游戏上的不同发射按键来控制飞机发射子弹:
按下A键,飞机会发射出2枚子弹,每个子弹会对命中的敌人造成1点固定伤害,但不能作用于同个敌人。
按下B键,飞机会发射出1枚子弹,子弹会对命中的敌人造成巨额伤害并瞬间将其秒杀。
多多是个游戏高手,总是能操控子弹命中想要命中的敌人。这个游戏—共有 T 个关卡,消灭当前关卡全部敌人后,发射出去多余的子弹会消失,游戏会自动进入下一个关卡。
假设每个关卡都会在屏幕中同时出现N个敌人,这N个敌人所能承受的伤害也已经知道。多多想知道,每个关卡自己最少按几次发射按键就可以将敌人全部消灭。
输入描述
第一行输入一个固定数字T(1<=T=1000)表示关卡的总数量,N(1<=N<=200)表示每个关卡出现的敌人数量。
接下来T行,每行有N个数字D1,D2,…,Dw(1<= Di <= 200)分别表示这N个敌人所能承受的伤害。
输出描述
结果共有N行,每行一个数字,分别表示对于这个关卡,最少按几次发射按键就可以将敌人全部消灭。
思路
承受伤害为1按A键,大于1则直接按B键
#include
using namespace std;
int main() {
int t, n;
while (cin >> t >> n) {
for (int i = 0; i < t; i++) {
int di;
unordered_map<int, int> cnt; // 承受伤害敌人与相应数目
for (int j = 0; j < n; j++) {
cin >> di;
cnt[di]++;
}
int ans = 0;
for (auto& [damage, num] : cnt) {
if (damage == 1) { // 全部选择A键
ans += (num + 1) / 2; // 向上取整
} else { // 全部选择B键
ans += num;
}
}
cout << ans << endl;
}
}
}
多多君准备了三个活动(分别编号A、B和C),每个活动分别有人数上限以及每个人参加的费用。
参加团建的有N个人(分别编号1~N),每个人先投票选择若干个意向的活动,最终每个人只能参加其中一个。
多多君收集完投票结果后,发现如何安排成为了大难题:如何在满足所有人的意向的情况下,使得活动的总费用最少。
于是多多君找到了擅长编程的你,希望你能帮助找到个合理的团建计划。
输入描述
第一行,一个整数N(1<=N<=100),代表准备参加活动的人数。
接下来N行,每行一个由 “ABC” 组成的字符串,其中第i行表示第i个人投票了哪几个活动。输入保证字符串非空,且由大写的 “ABC” 字符组成。
最后三行,每行两个整数,分别表示三个活动的人数上限以及每个人参加的费用。
输出描述
输出共2行
如果能满足所有人的要求,第一行输出 “YES”,第二行输出最少的总费用。
如果不能满足所有人的要求,第一行输出 “NO”,第二行输出最多能满足多少人。
思路
本来想用贪心,但觉得贪心策略不一定对,就直接用dfs暴力枚举所有可能了,好像得了40-60分,太久了,忘了
但使用贪心虽然得不到满分,也有80-90分了,不能一昧地暴力回溯
#include
using namespace std;
// 全局变量
const int kINf = 1 << 30;
int n;
vector<string> vote(101); // 每个人投票的活动
vector<int> limit(3); // 活动限制
vector<int> money(3); // 活动费用
int min_total; // 最小总费用
int max_require; // 最多满足多少人
bool can_all_require; // 是否能满足
int get_money(const vector<int>& origin_limit, const vector<int>& cur_limit) { // 通过活动名额差值计算总费用
int total_money = 0;
for (int i = 0; i < 3; i++) {
total_money += (origin_limit[i] - cur_limit[i]) * money[i];
}
return total_money;
}
bool is_all_zero(const vector<int>& cur_limit) { return cur_limit[0] == 0 && cur_limit[1] == 0 && cur_limit[2] == 0; } // 活动是否全部参加完
void dfs(int cur_index, vector<int> cur_limit, int cur_require) {
if (cur_index >= n || is_all_zero(cur_limit)) { // 当前下标越界或活动全部参加完
if (cur_require == n) { // 已经满足所有人
min_total = min(min_total, get_money(limit, cur_limit)); // 计算最小总费用
can_all_require = true; // 可以满足所有人
} else {
max_require = max(max_require, cur_require); // 计算最多满足人数
}
return;
}
if (vote[cur_index].length() == 1) { // 如果只有一个人(好像和下面的代码冗余了,不知道我当时怎么想的)
char c = vote[cur_index][0];
if (cur_limit[c - 'A'] > 0) { // 当前活动尚有名额
cur_limit[c - 'A']--;
dfs(cur_index + 1, cur_limit, cur_require + 1);
cur_limit[c - 'A']++;
} else {
if (can_all_require) { // 已经存在满足所有人的选择,略过当前选择
return;
}
dfs(cur_index + 1, cur_limit, cur_require); // 不考虑该人
}
} else {
for (char c : vote[cur_index]) { // 依次选择各个活动
if (cur_limit[c - 'A'] > 0) {
cur_limit[c - 'A']--;
dfs(cur_index + 1, cur_limit, cur_require + 1);
cur_limit[c - 'A']++;
}
}
if (can_all_require) { // 已经存在满足所有人的选择,略过当前选择
return;
}
dfs(cur_index + 1, cur_limit, cur_require); // 不考虑该人
}
}
int main() {
while (cin >> n) {
for (int i = 0; i < n; i++) {
cin >> vote[i];
}
for (int i = 0; i < 3; i++) {
cin >> limit[i] >> money[i];
}
min_total = kINf;
max_require = 0;
can_all_require = false;
dfs(0, limit, 0);
if (min_total == kINf) { // 不能满足所有人
cout << "NO" << endl << max_require << endl;
} else { // 可以满足所有人
cout << "YES" << endl << min_total << endl;
}
}
}
多多君开了一家自助餐厅,为了更好地管理库存,多多君每天需要对之前的客流量数据进行分析,并根据客流量的平均数和中位数来制定合理的备货策略。
输入描述
第一行一个整数N,表示餐厅营业总天数(0
第二行共N个整数,分别表示第i天的客流量R:(0= R:1, 000, 000)。
输出描述
输出共两行:
第一行长度为N,其中第i个值表示前i天客流量的平均值;第二行长度为N,其中第i个值表示前i天客流量的中位数。
一共有N天,每天的客流量为 Ri,求第1天到第i天的平均数和中位数,结果四舍五入保留整数。
思路
没啥思路,就用插入排序过了,标准方法是295. 数据流的中位数
#include
using namespace std;
int GetInterge(double d) { // 四舍五入
int n = d;
double test = n + 0.5;
if (d >= test) {
return n + 1;
}
return n;
}
void InsertSort(vector<int>& vec, int val) { // 借助二分查找实现插入排序
auto iter = upper_bound(vec.begin(), vec.end(), val);
vec.emplace(iter, val);
}
int main() {
int n;
while (cin >> n) {
vector<int> vec;
vector<int> avg(n + 1);
vector<int> mid(n + 1);
double sum = 0;
for (int i = 1; i <= n; i++) {
int r;
cin >> r;
sum += r;
avg[i] = GetInterge(sum / i); // 求平均值
InsertSort(vec, r);
double tmp = vec[i / 2] + vec[(i - 1) / 2];
mid[i] = GetInterge(tmp / 2); // 求中位数
}
// 输出结果
for (int i = 1; i <= n; i++) {
cout << avg[i] << " ";
}
cout << endl;
for (int i = 1; i <= n; i++) {
cout << mid[i] << " ";
}
cout << endl;
}
}
题目链接: 大厂真题|3月15日阿里春招笔试三道题
晚上的笔试,七道单选,八道多选,三道编程题,编程题过了2道半,不能使用本地IDE,故没当时的代码(临结束的时候应该把所有的代码拷贝到剪切板的)
思路
满二叉树,首先根据数组构建二叉树,而后寻找根节点,最后递归求解满二叉树,仅当两个子树皆为满二叉树且节点数目相同时该树为满二叉树
按记忆打的,不一定对
#include
using namespace std;
struct TreeNode {
TreeNode* left;
TreeNode* right;
TreeNode() : left(nullptr), right(nullptr) {}
};
using Result = pair<int, bool>; // 节点数量-是否为满二叉树
Result GetNodeInfo(TreeNode* node, int& full_node_num) {
if (node == nullptr) { // 空节点
return {0, true};
}
Result left_info = GetNodeInfo(node->left, full_node_num);
Result right_info = GetNodeInfo(node->right, full_node_num);
bool is_full = (left_info.first == right_info.first) && left_info.second && right_info.second; // 两个子节点均为满二叉树且节点数目一致
int nodes = left_info.first + right_info.first + 1;
if (is_full) { // 满二叉树节点加一
full_node_num++;
}
return {nodes, is_full};
}
int main() {
int n;
while (cin >> n) {
vector<TreeNode> tree_node(n + 1); // 树节点
vector<bool> appear(n + 1, false); // 节点是否出现在输入中
// 构建二叉树
for (int i = 1; i <= n; i++) {
int x, y;
cin >> x >> y;
if (x != -1) {
tree_node[i].left = &tree_node[x];
appear[x] = true;
}
if (y != -1) {
tree_node[i].right = &tree_node[y];
appear[y] = true;
}
}
// 寻找根节点(未出现的节点)
int root = 0;
for (int i = 1; i <= n; i++) {
if (!appear[i]) {
root = i;
break;
}
}
// 计算满二叉树节点数目
int ans = 0;
GetNodeInfo(&tree_node[root], ans);
cout << ans << endl;
}
}
/*
5
2 3
4 5
-1 -1
-1 -1
-1 -1
*/
三个数最大值减最小值=1,使用map记录数组与出现次数,对每个数字处理number与number+1的次数问题,注意结果可能超过int表示范围,需用long long存储
按记忆打的,不一定对
#include
using namespace std;
using ll = long long;
int main() {
int n;
while (cin >> n) {
map<int, ll> ma; // 数字-出现次数
for (int i = 0; i < n; i++) {
int x;
cin >> x;
ma[x]++;
}
ll ans = 0;
for (auto& [number, times] : ma) {
if (ma.count(number + 1) > 0) { // 考虑number-number+1的组合
ll another_times = ma[number + 1];
if (times > 1) { // 选择两个number 一个number+1
ans += times * (times - 1) / 2 * another_times;
}
if (another_times > 1) { // 选择两个number+1 一个number
ans += another_times * (another_times - 1) / 2 * times;
}
}
}
cout << ans << endl;
}
}
思路
由于是第三题,所以我默认它很难,我就直接使用暴力回溯写了一遍
数组排序
dfs:
0: 达到终止条件,计算极差
1: 将数组首部数据乘2
2: dfs
3: 还原数组
4: 将数组尾部数据除2
5: dfs
6: 还原数组
细想一下可以知道,这根本不需要dfs,只需要for循环即可,不过我养成了思维定式,第三题一律暴力回溯
前面i个数据乘2,后面k-i个数据除2
for(int i=0;i<=k;i++){
for(int j=0;j<i;j++){
首部数据乘2
}
for(int j=0;j<k-i;j++){
尾部数据除2
}
}
另外我使用全排列写了一遍,不再使用以上的策略,直接所有情况枚举一遍
for(int i=start;i<vec.size();i++){
swap(vec[i],vec[start];
当前数字乘2
dfs
还原数字
当前数字除2
dfs
还原数字
swap(vec[i],vec[start];
}
最终我的解法都以超时结束,不知道用for循环实现可以得多少分
题目链接:「技术笔试」蚂蚁金服 2023-03-16
最终得分2.3分,第三题写了40分钟只得了30分,一直没找到bug
1e14 范围以内的一个正整数,将其每一数位上的奇数和偶数分别抽取出来组成两个新的数字,求这两差的绝对值。
思路
先将数字压入栈,然后计算奇数 偶数大小,上面的题解使用的是字符串存储数字,省去了压栈这一步
#include
using namespace std;
using ll = long long;
ll GetValue(stack<int>& s) { // 计算数字大小
ll ans = 0;
while (!s.empty()) {
ans = ans * 10 + s.top();
s.pop();
}
return ans;
}
int main() {
ll n;
while (cin >> n) {
stack<int> one, two;
while (n != 0) {
int num = n % 10;
if (num % 2 == 0) { // 偶数
two.emplace(num);
} else if (num % 2 == 1) { // 奇数
one.emplace(num);
}
n /= 10;
}
cout << abs(GetValue(one) - GetValue(two)) << endl;
}
}
n 组零件,每组零件有若干种,每一种有一个价格和性能。你需要从每组里面选出一种零件,使得总价格不超过 x,并且性能总和最大。
n <= 40, 所有零件的种类数不超过 40,其他数值 1e9。
思路
首先简单判断一下是否能完成组装,不能就直接返回,能的话就使用dfs穷举所有可能,找到最大性能
#include
using namespace std;
using ll = long long; // 全部使用long long存储,避免溢出
const ll kInf = 1 << 30;
void dfs(const vector<vector<pair<ll, ll>>>& vec, ll cur_index, ll cur_x, ll cur_v, ll& max_v) {
if (cur_index >= vec.size()) { // 选完所有零件,计算最大性能
max_v = max(max_v, cur_v);
return;
}
for (auto [a, v] : vec[cur_index]) {
if (cur_x >= a) { // 选择可选的零件
dfs(vec, cur_index + 1, cur_x - a, cur_v + v, max_v);
}
}
}
int main() {
ll n, x;
while (cin >> n >> x) {
vector<vector<pair<ll, ll>>> arr(n);
ll min_x = 0; // 最小零件价格总和
for (ll i = 0; i < n; i++) {
ll m, a, v;
ll min_a = kInf;
cin >> m;
arr[i] = vector<pair<ll, ll>>(m);
for (ll j = 0; j < m; j++) {
cin >> arr[i][j].first;
min_a = min(min_a, arr[i][j].first); // 记录该类型零件最小价格
}
for (ll j = 0; j < m; j++) {
cin >> arr[i][j].second;
}
min_x += min_a;
}
if (x < min_x) { // 无法完成组装
cout << -1 << endl;
continue;
}
ll ans = 0;
dfs(arr, 0, x, 0, ans); // 计算最大零件价值和
cout << ans << endl;
}
}
n 行 m 列的矩阵,每个位置上有一个元素。你可以上下左右行走,代价是前后两个位置元素值差的绝对值。另外,你最多可以使用一次传送阵(只能从一个数跳到另外一个相同的树),求从走上角走到右下角最少需要多少时间。
1 <= n, m <= 500, 1 <= aij <= 1e9。
思路
刚开始想了想,如果用dfs,应该能得30-60分,但当时时间也挺多的,就想完全通过。方法就是计算各点到起点的距离,计算各点到终点的距离,设使用传送阵的起始点为x,终点为y,最终的路径长度即为dist_form_start[x] + 0(传送阵代价) + dist_to_end[y],可惜代码有bug,只得了30分,我觉得我思路是没问题的
以下为有bug的版本,先计算两个最短距离,然后使用传送计算最终距离
额,好像优先队列没设置为小顶堆,有点尴尬,这样都能得30分,运气不错
设置方式为
// 自带的比较函数
priority_queue<TP, vector<TP>, greater<TP>> q;
// 自定义比较函数
auto cmp = [](const pair<int, int>& p1, const pair<int, int>& p2) -> bool { return p1.second > p2.second; };
priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(cmp)> qu(cmp);
#include
using namespace std;
using ll = long long;
const ll kInf = 0xfffffffffffff;
int main() {
ll n, m;
while (cin >> n >> m) {
vector<vector<ll>> arr(n, vector<ll>(m));
unordered_map<ll, vector<pair<ll, ll>>> ma;
for (ll i = 0; i < n; i++) {
for (ll j = 0; j < m; j++) {
cin >> arr[i][j];
}
}
for (ll i = 0; i < n; i++) {
for (ll j = 0; j < m; j++) {
if (ma.count(arr[i][j]) == 0) {
vector<pair<ll, ll>> tmp;
tmp.emplace_back(i, j);
ma[arr[i][j]] = tmp;
} else {
ma[arr[i][j]].emplace_back(i, j);
}
}
}
vector<vector<ll>> dist1(n, vector<ll>(m, kInf));
vector<vector<bool>> visit1(n, vector<bool>(m, false));
priority_queue<tuple<ll, ll, ll>> qu;
dist1[0][0] = 0;
qu.emplace(0, 0, 0);
while (!qu.empty()) {
auto [from_start, i, j] = qu.top();
qu.pop();
if (visit1[i][j]) {
continue;
}
visit1[i][j] = true;
if (i + 1 < n) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i + 1][j]);
if (dist1[i + 1][j] > cur_dist) {
dist1[i + 1][j] = cur_dist;
qu.emplace(cur_dist, i + 1, j);
}
}
if (i - 1 >= 0) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i - 1][j]);
if (dist1[i - 1][j] > cur_dist) {
dist1[i - 1][j] = cur_dist;
qu.emplace(cur_dist, i - 1, j);
}
}
if (j + 1 < m) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i][j + 1]);
if (dist1[i][j + 1] > cur_dist) {
dist1[i][j + 1] = cur_dist;
qu.emplace(cur_dist, i, j + 1);
}
}
if (j - 1 >= 0) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i][j - 1]);
if (dist1[i][j - 1] > cur_dist) {
dist1[i][j - 1] = cur_dist;
qu.emplace(cur_dist, i, j - 1);
}
}
}
vector<vector<ll>> dist2(n, vector<ll>(m, kInf));
vector<vector<bool>> visit2(n, vector<bool>(m, false));
dist2[n - 1][m - 1] = 0;
qu.emplace(0, n - 1, m - 1);
while (!qu.empty()) {
auto [from_start, i, j] = qu.top();
qu.pop();
if (visit2[i][j]) {
continue;
}
visit2[i][j] = true;
if (i + 1 < n) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i + 1][j]);
if (dist2[i + 1][j] > cur_dist) {
dist2[i + 1][j] = cur_dist;
qu.emplace(cur_dist, i + 1, j);
}
}
if (i - 1 >= 0) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i - 1][j]);
if (dist2[i - 1][j] > cur_dist) {
dist2[i - 1][j] = cur_dist;
qu.emplace(cur_dist, i - 1, j);
}
}
if (j + 1 < m) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i][j + 1]);
if (dist2[i][j + 1] > cur_dist) {
dist2[i][j + 1] = cur_dist;
qu.emplace(cur_dist, i, j + 1);
}
}
if (j - 1 >= 0) {
ll cur_dist = from_start + abs(arr[i][j] - arr[i][j - 1]);
if (dist2[i][j - 1] > cur_dist) {
dist2[i][j - 1] = cur_dist;
qu.emplace(cur_dist, i, j - 1);
}
}
}
vector<vector<ll>> dist(n, vector<ll>(m, kInf));
ll min_dist = kInf;
for (ll i = 0; i < n; i++) {
for (ll j = 0; j < m; j++) {
auto vec = ma[arr[i][j]];
for (auto [xi, xj] : vec) {
if (dist1[i][j] != kInf && dist2[xi][xj] != kInf) {
dist[i][j] = min(dist[i][j], dist1[i][j] + dist2[xi][xj]);
}
}
min_dist = min(min_dist, dist[i][j]);
}
}
cout << min_dist << endl;
}
}
相比之下,牛客网上的题解就简洁多了,多加一个维度k,对维度同样使用状态转移,更加通用的做法,并且在处理方向上,我还是用习惯的方法,对每个方向进行枚举,而题解中则使用dir数组进行统一处理,既方便又不容易出错,map赋值时我还对是否存在该值进行分类讨论,实际上是不需要的,如果不存在map会先设置默认值而后进行更新
for (int i = 0; i < 4; i++) {
int nx = x + dir[i][0], ny = y + dir[i][1];
if (nx < 0 || nx >= n || ny < 0 || ny >= m || v[nx][ny][z] == 1) continue;
if (d[nx][ny][z] > dist + abs(a[x][y] - a[nx][ny])) {
d[nx][ny][z] = dist + abs(a[x][y] - a[nx][ny]);
q.push({d[nx][ny][z], nx, ny, z});
}
}
题目链接:全网首发-真题分享|3月19日米哈游校招研发岗三道题
参考题解:
米哈游笔试题目思路(客户端卷)
米哈游后端笔试题解
米小游拿到了一个矩阵,矩阵上有一格有一个颜色,为红色( R )。绿色( G )和蓝色( B )这三种颜色的一种。然而米小游是蓝绿色盲,她无法分游蓝色和绿色,所以在米小游眼里看来,这个矩阵只有两种颜色,因为蓝色和绿色在她眼里是一种颜色。米小游会把相同颜色的部分看成是一个连通块。请注意,这里的连通划是上下左右四连通的。由于色盲的原因,米小游自己看到的连通块数量可能比真实的连通块数量少。你可以帮米小游计算连通块少了多少吗?
思路
看到连通分量,就自然而然想用并查集实现,实际上只需要连接两个方向的节点
#include
using namespace std;
class UnionFind { // 并查集类
public:
UnionFind(int size) { // 初始化
data_ = vector<int>(size);
for (int i = 0; i < size; i++) {
data_[i] = i;
}
}
int Find(int k) { // 查找
if (data_[k] == k) {
return k;
}
data_[k] = Find(data_[k]);
return data_[k];
}
void Union(int p, int q) { // 合并
int rootp = Find(p);
int rootq = Find(q);
data_[rootp] = rootq;
}
int Size() { // 连通分量数目
int size = 0;
for (int i = 0; i < data_.size(); i++) {
if (data_[i] == i) {
size++;
}
}
return size;
}
private:
vector<int> data_;
};
int main() {
int n, m;
int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
while (cin >> n >> m) {
vector<vector<char>> arr(n, vector<char>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> arr[i][j];
}
}
// 真实情况连通数量
UnionFind uf1(n * m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < 4; k++) {
int nx = i + dir[k][0];
int ny = j + dir[k][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && arr[i][j] == arr[nx][ny]) {
uf1.Union(i * m + j, nx * m + ny);
}
}
}
}
int size1 = uf1.Size();
// 视角连通分量数量
UnionFind uf2(n * m);
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
for (int k = 0; k < 4; k++) {
int nx = i + dir[k][0];
int ny = j + dir[k][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && ((arr[i][j] == 'R') == (arr[nx][ny] == 'R'))) {
uf2.Union(i * m + j, nx * m + ny);
}
}
}
}
int size2 = uf2.Size();
cout << size1 - size2 << endl;
}
}
dfs的解法如下,比并查集少一点代码,写起来快点
#include
using namespace std;
void dfs(vector<vector<char>>& vec, vector<vector<bool>>& visit, int x, int y, char target, int n, int m) {
visit[x][y] = true; // 设置成已访问
static int dir[4][2] = {{0, 1}, {0, -1}, {1, 0}, {-1, 0}};
for (int k = 0; k < 4; k++) { // 遍历四个方向
int nx = x + dir[k][0];
int ny = y + dir[k][1];
if (nx >= 0 && nx < n && ny >= 0 && ny < m && visit[nx][ny] == false && vec[nx][ny] == target) { // 坐标合法,未访问过,为目标值
dfs(vec, visit, nx, ny, target, n, m);
}
}
}
int main() {
int n, m;
while (cin >> n >> m) {
vector<vector<char>> arr(n, vector<char>(m));
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
cin >> arr[i][j];
}
}
vector<vector<bool>> visit1(n, vector<bool>(m, false));
int cnt1 = 0; // 连通分量数目
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (visit1[i][j] == false) {
cnt1++;
dfs(arr, visit1, i, j, arr[i][j], n, m);
}
}
}
// 将蓝色改成绿色
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (arr[i][j] == 'B') {
arr[i][j] = 'G';
}
}
}
vector<vector<bool>> visit2(n, vector<bool>(m, false));
int cnt2 = 0; // 连通分量数目
for (int i = 0; i < n; i++) {
for (int j = 0; j < m; j++) {
if (visit2[i][j] == false) {
cnt2++;
dfs(arr, visit2, i, j, arr[i][j], n, m);
}
}
}
cout << cnt1 << " " << cnt2 << endl;
cout << cnt1 - cnt2 << endl;
}
}
米小游拿到了一个字符串 s 。她可以进行任意次以下两种操作:
1 删除 s 的一个 “mhy” 子序列。
2 添加一个 “mhy” 子序列在 s 上。
例如,给定 s 为 “mhbdy” ,米小游进行一次操作后可以使 s 变成 “bd” ,或者变成 “mhmbhdyy” 。
米小游想知道,经过若干次操作后 s 是否可以变成 t ?
注:子序列在原串中的顺序也是从左到右,但可以不连续。
思路
觉得写不出来,就直接进行骗分策略,看了别人的题解才发现解法很简单,最重要的是想到mhy三个字母的序列不重要
#include
using namespace std;
bool Check(string& s, string& t) {
vector<int> num1(26, 0);
vector<int> num2(26, 0);
// 统计字符串各字母次数
for (char c : s) {
num1[c - 'a']++;
}
for (char c : t) {
num2[c - 'a']++;
}
int m, h, y; // 记录mhy字母次数差值
for (int i = 0; i < 26; i++) {
if (i == 'm' - 'a') {
m = num1[i] - num2[i];
} else if (i == 'h' - 'a') {
h = num1[i] - num2[i];
} else if (i == 'y' - 'a') {
y = num1[i] - num2[i];
} else if (num1[i] != num2[i]) { // 其他字母数量不相等,不能转换
return false;
}
}
return (m == h) && (h == y); // 差值相等则可以进行转换
}
int main() {
int q;
cin >> q;
for (int i = 0; i < q; i++) {
string s, t;
cin >> s >> t;
if (Check(s, t)) {
cout << "Yes" << endl;
} else {
cout << "No" << endl;
}
}
}
思路:
我的思路与题解一样,只不过我少了倍数预处理的一步,直接两层for循环找到因数更新dp数组
#include
using namespace std;
using ll = long long;
const int mod = 1e9 + 7;
const int kMax = 1e6 + 1;
vector<bool> visit(kMax, false); // visit[i]: 数字i是否出现
vector<vector<int>> factor(kMax); // factor[i]: i的各个因数
vector<ll> dp(kMax, 0); // dp[i]: 以i结尾的数字集合数目
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
visit[a[i]] = true;
}
sort(a.begin(), a.end()); // 数字排序
int max_n = a[n - 1]; // 最大数字
// 预处理倍数关系
for (int i = 1; i <= max_n; i++) {
if (visit[i] == false) {
continue;
}
for (int j = i + i; j <= max_n; j += i) {
if (visit[j] == true) {
factor[j].emplace_back(i); // i为j的因数
}
}
}
for (int i = 1; i < n; i++) {
for (int num : factor[a[i]]) {
dp[a[i]] += dp[num] + 1; // a[i]可以和num的数字集合全部组合一遍,最后加上{num,a[i]}
}
dp[a[i]] %= mod;
}
ll ans = 0;
for (int i = 1; i < n; i++) {
ans += dp[a[i]];
}
ans %= mod;
cout << ans << endl;
}
题目:3.23 腾讯音乐 暑期 实习 技术类 笔试
中途肚子痛,提前半小时交卷了,第一题写了一半,第二题dfs骗了几十分,第三题签到
笔试或面试前千万别吃坏肚子
这个问题可以分解成两个子问题:
1 通过层序遍历得到奇偶层节点个数
2 假设奇数层节点个数为k,解决是否能从[1,n]中选择k个数,总和为target问题
若1-n总和为偶数,则target为sum/2,若为奇数,则target为sum/2或sum/2+1
代码片段如下:(不一定对)
第一步:层序遍历
TreeNode* fun(TreeNode* root) {
queue<Info> qu;
int one = 0; // 奇数节点数
int two = 0; // 偶数节点数
stack<TreeNode*> one_stack; // 奇数节点栈
stack<TreeNode*> two_stack; // 偶数节点栈
qu.emplace(root, 1);
while (!qu.empty()) {
Info info = qu.front();
TreeNode* node = info.first;
int level = info.second;
qu.pop();
if (level % 2 == 1) {
one++;
one_stack.emplace(node);
} else {
two++;
two_stack.emplace(node);
}
if (node->left != nullptr) { // 将左子树节点压入队列
qu.emplace(node->left, level + 1);
}
if (node->right != nullptr) { // 将右子树节点压入队列
qu.emplace(node->right, level + 1);
}
}
}
第二步:选择k个数总和为target
双指针法求得可能结果
刚开始左区域最大,右区域为空,此时sum最小,而后一直将left right向左移动,增大sum,直至sum>target
#include
using namespace std;
vector<int> FindSuitNumber(int n, int k, int target) { // [1,n]中选择k个数,总和为target
// 等差数列求和公式:Sn = n * (a1 + an) / 2
int min_sum = k * (1 + k) / 2;
int max_sum = k * (n - k + 1 + n) / 2;
if (target < min_sum || target > max_sum) {
// printf("target %d: no suit number\n", target);
return {};
}
// 如果target属于[min_sum,max_sum],则target一定能取到
vector<int> seq(n);
for (int i = 0; i < n; i++) {
seq[i] = i + 1;
}
// 最后数据构成:0...left extra_num right+1...n-1
int left = k - 1;
int right = n - 1;
int sum = min_sum;
int extra_num = -1;
while (left >= 0) {
int sub = seq[right] - seq[left];
if (sum + sub < target) { // 小于目标值,将右侧元素加入集合
left--;
right--;
sum += sub;
} else {
extra_num = seq[left] + target - sum;
left--;
break;
}
}
vector<int> ans;
for (int i = 0; i <= left; i++) {
ans.emplace_back(seq[i]);
}
ans.emplace_back(extra_num);
for (int i = right + 1; i < n; i++) {
ans.emplace_back(seq[i]);
}
// 校验结果
int result = accumulate(ans.begin(), ans.end(), 0);
if (result != target) { // 总和不为target
printf("target: %d result: %d\n", target, result);
}
if (ans.size() != k) { // 数量错误
printf("wrong ans size: %d", ans.size());
}
set<int> se;
for (int num : ans) {
if (num < 1 || num > n) { // 数字超出范围
printf("wrong value: %d\n", num);
}
if (se.count(num) > 0) { // 重复数字
printf("repeat value: %d\n", num);
}
se.emplace(num);
}
return ans;
}
int main() {
for (int i = 0; i < 10000; i++) {
for (int j = 1; j <= 100; j++) {
FindSuitNumber(100, j, i);
}
}
}
算法实现中为避免函数参数过多,可以将公有的数据结构放在类成员中,且没必要遵循命名规范(类成员名后加_),类成员的设置应该在具体实现前想好。
第二题看别人说是最大值最小化问题,使用二分+贪心的方法解决,但没看到啥具体实现
(二分答案,贪心验证答案是否符合,符合就再缩小一点答案,不符合就扩大一点 )
#include
using namespace std;
class Solution {
public:
int getValue(int start, int end) { // 计算str[str,end)的metric,种类x长度
vector<int> num(26, 0); // 字符出现次数
for (int i = 0; i < 26; i++) {
num[i] = char_num_[end][i] - char_num_[start][i];
}
int diff = 0; // 不同字符数目
for (int i = 0; i < 26; i++) {
if (num[i] > 0) {
diff++;
}
}
return diff * (end - start);
}
int computeMaxValue() {
int max_value = 0; // 计算最大metric
// 分割位置集合[0,seq0,seq1,..seqk-1,length]
for (int i = 0; i < k_; i++) {
if (i == k_ - 1) {
max_value = max(max_value, getValue(seq_[i], length_)); // 最后一个切割位置,与数组末尾配对
} else {
max_value = max(max_value, getValue(seq_[i], seq_[i + 1]));
}
}
return max_value;
}
void dfs(int index, int& max_value) {
if (index == k_) {
max_value = min(max_value, computeMaxValue()); // 记录最小的最大metric
return;
}
for (int i = index; i < seq_.size(); i++) { // 全排列,但seq[i]>seq[i-1]
if (seq_[i] > seq_[index - 1]) {
swap(seq_[index], seq_[i]);
dfs(index + 1, max_value);
swap(seq_[index], seq_[i]);
}
}
}
int getMaxValue(string str, int k) {
k_ = k;
length_ = str.length();
char_num_ = vector<vector<int>>(str.length() + 1, vector<int>(26, 0)); // char_num_[i]表示s[i]前的各字符出现次数,用于快速计算区间字符种类
for (int i = 0; i < str.length(); i++) {
char_num_[i + 1] = char_num_[i];
char_num_[i + 1][str[i] - 'a']++;
}
if (k == 1) {
return getValue(0, str.length()); // 不需要分割,直接计算metric
}
seq_ = vector<int>(str.length());
for (int i = 0; i < str.length(); i++) {
seq_[i] = i;
}
int ans = 1 << 30;
dfs(1, ans); // 第一位一定为0,数组开始索引为1
return ans;
}
private:
vector<vector<int>> char_num_;
vector<int> seq_;
int k_;
int length_;
};
int main() {
Solution solution;
solution.getMaxValue("ababbbb", 2);
}
签到题
#include
using namespace std;
class Solution {
public:
int getCnt(string str) {
int ans = 0;
for (int i = 0; i < str.length() - 1; i++) {
int sub = abs(str[i] - str[i + 1]);
if (sub == 'a' - 'A' || sub == 0) {
ans++;
}
}
return ans;
}
};
题目:腾讯笔试记录0326(研发)
就写出了第一第二题,第三题题目有点问题,导致我写了40分钟没得啥分,一直没理解最后一个样例是怎么来的,第四第五题一看就是我不会的题目,随便写了写
链表中相邻节点两两一组,然后相邻两组之间进行交换
类似的题目:25. K 个一组翻转链表
直接将链表节点指针存入数组,便于理解
class Solution {
public:
ListNode* reorderList(ListNode* head) {
ListNode virt_head(-1); // 虚拟头节点
virt_head.next = head;
vector<ListNode*> arr;
ListNode* cur = head;
while (cur != nullptr) {
arr.emplace_back(cur);
cur = cur->next;
}
int num = arr.size();
ListNode* prev = &virt_head; // 待处理组的前一个节点
for (int i = 0; i + 3 < num; i += 4) {
prev->next = arr[i + 2]; // 指向第二组的头节点
arr[i + 1]->next = arr[i + 3]->next; // 指向第二组后一个节点
prev = arr[i + 1]; // 更新prev节点
arr[i + 3]->next = arr[i]; // 更新第二组的next为第一组头节点
}
if (num % 4 == 3) { // 剩下3个节点,特殊处理
prev->next = arr[num - 1];
arr[num - 2]->next = arr[num - 1]->next;
arr[num - 1]->next = arr[num - 3];
}
return virt_head.next;
}
};
给定N个字符串,每个字符串全部由小写字母组成,且每个字符串的长度最多为8,请你断有多少重组字符串,重组字符串有以下规则:1.从每个字符串里面都抽取1个字母组成。2.新字符串不能有2个相同的字母。请问总共能组成多少个重组字符串。
字符串长度比较小,用dfs暴力枚举即可
#include
using namespace std;
void dfs(vector<string>& strs, int index, string cur_str, set<string>& res) {
if (index == strs.size()) { // 加入结果集合
res.emplace(cur_str);
return;
}
for (char c : strs[index]) {
if (cur_str.find(c) == string::npos) { // 无重复字母
cur_str.push_back(c); // 加入字符串
dfs(strs, index + 1, cur_str, res); // 遍历下一个字符串
cur_str.pop_back(); // 还原
}
}
}
int main() {
int n;
cin >> n;
vector<string> strs(n);
for (int i = 0; i < n; i++) {
cin >> strs[i];
}
set<string> ans; // 使用set去重
dfs(strs, 0, "", ans);
cout << ans.size();
}
给定2个整数数组A,B,数组长度都为N,数组B为权值数组,权值数据范国为[0,2],请你构造一个数组C,满足以下条件:
1,长应为N
2.数组元素范国为[1,N],且元素值不能更复,即为N的一个排列
3.如果数组下标i
4数组C与数组A每个元素之差的和的绝对值最小.
按理来说样例的答案应该是98,但样例是104,一直想不出来为什么
#include
using namespace std;
using ll = long long;
const int kMax = 2 * 10e5 + 1;
vector<int> A(kMax);
vector<int> B(kMax);
int n;
ll ans = 0xffffffffffff;
void dfs(vector<ll>& seq, int index, int one_min, int two_min) { // one_min 前面1位置的最小值 two_min 前面2位置的最小值
if (index == n) {
ll array_abs_sum = 0; // 差值的绝对值之和
for (int i = 0; i < n; i++) {
array_abs_sum += abs(seq[i] - A[i]);
}
ans = min(ans, array_abs_sum);
// 打印样例序列
if (array_abs_sum == 98) {
for (int num : seq) {
cout << num << " ";
}
cout << endl;
}
return;
}
for (int i = index; i < n; i++) {
if (B[index] == 0) {
if (seq[i] < one_min && seq[i] < two_min) { // 需小于1最小值与2最小值
swap(seq[i], seq[index]);
dfs(seq, index + 1, one_min, two_min);
swap(seq[i], seq[index]);
}
} else if (B[index] == 1) {
if (seq[i] < two_min) { // 需小于2最小值
int new_one_min = min<ll>(seq[i], one_min);
swap(seq[i], seq[index]);
dfs(seq, index + 1, new_one_min, two_min); // 更新1最小值
swap(seq[i], seq[index]);
}
} else {
int new_two_min = min<ll>(seq[i], two_min);
swap(seq[i], seq[index]);
dfs(seq, index + 1, one_min, new_two_min); // 更新2最小值
swap(seq[i], seq[index]);
}
}
}
int main() {
cin >> n;
for (int i = 0; i < n; i++) {
cin >> A[i];
}
for (int i = 0; i < n; i++) {
cin >> B[i];
}
vector<ll> seq(n); // [1,n]序列
for (int i = 0; i < n; i++) {
seq[i] = i + 1;
}
dfs(seq, 0, kMax + 1, kMax + 1);
cout << ans << endl;
}
/*
6
100 2 3 1 5 6
0 1 2 0 2 1
样例答案:104
我的答案:98
6 2 4 1 5 3
98
*/
题目链接:3.28百度笔试复盘
图解SQL的inner join、left /right join、 outer join区别
编程题过了2.8道,第三题一直超时,只得了80分
#include
using namespace std;
const int kMod = 1e9 + 7;
using ll = long long;
int main() {
int n;
cin >> n;
vector<int> a(n);
for (int i = 0; i < n; i++) {
cin >> a[i];
}
string str;
cin >> str;
ll red_sum = 0; // 红色权值之和
ll blue_sum = 0; // 蓝色权值之和
for (int i = 0; i < n; i++) {
if (str[i] == 'R') {
red_sum = (red_sum + a[i]) % kMod;
} else {
blue_sum = (blue_sum + a[i]) % kMod;
}
}
ll ans = (red_sum * blue_sum) % kMod;
cout << ans << endl;
}
题目:
标准解法是单调栈,不过我当时没想到,就用数组存从当前位置往右看最大value的索引
#include
using namespace std;
int main() {
string str;
cin >> str;
int length = str.length();
string ans = "0.";
vector<int> max_see(length); // 从当前位置往右看最大value的索引
max_see[length - 1] = length - 1;
for (int i = length - 2; i >= 2; i--) {
char next_max_value = str[max_see[i + 1]];
if (str[i] >= next_max_value) { // 当前位置比右边最大值大
max_see[i] = i;
} else { // 当前位置比右边最大值小
max_see[i] = max_see[i + 1];
}
}
for (int i = 2; i < length; i++) {
ans.push_back(str[max_see[i]]);
i = max_see[i];
}
cout << ans << endl;
}
题目:
只得了80分,要么时间超时,要么内存使用过多,可以优化的点在于输入x y时将信息暂存到某一个地方,而后在树遍历的过程中一次性计算所有,而不是每次都遍历x的所有子节点,并与info相加,不过最后没时间了,就没实现
#include
using namespace std;
using NumberInfo = pair<int, int>; // 记录数字中2,5因数的个数
NumberInfo GetInfo(int x) { // 得到数字2,5因数的个数
int two = 0, five = 0;
while (x > 0 && x % 2 == 0) {
two++;
x = x / 2;
}
while (x > 0 && x % 5 == 0) {
five++;
x = x / 5;
}
return {two, five};
}
void AddInfo(NumberInfo& p1, NumberInfo p2) { // 因数个数相加
p1.first += p2.first;
p1.second += p2.second;
}
int main() {
int n, q;
cin >> n;
vector<bool> visited(n + 1, false); // 该节点是否出现
vector<int> tree(n + 1); // 使用数组维护树结构
vector<vector<int>> child(n + 1); // 节点的子节点(包括自身)
vector<NumberInfo> num(n + 1); // 各节点2,5因数个数
for (int i = 1; i <= n; i++) {
int a;
cin >> a;
num[i] = GetInfo(a);
child[i].emplace_back(i);
}
// 构建树结构,1为根节点
visited[1] = true;
tree[1] = 1;
for (int i = 0; i < n - 1; i++) {
int u, v;
cin >> u >> v;
// 确定哪个为父节点
if (visited[u]) {
tree[v] = u;
visited[v] = true;
} else {
tree[u] = v;
visited[u] = true;
}
}
visited.clear();
// 计算各节点子节点信息
for (int i = 2; i <= n; i++) {
int root = i;
do {
root = tree[root];
child[root].emplace_back(i);
} while (root != 1);
}
cin >> q;
for (int i = 0; i < q; i++) {
int x, y;
cin >> x >> y;
NumberInfo info = GetInfo(y);
// 所有子节点信息加上info因数个数
for (auto node : child[x]) {
AddInfo(num[node], info);
}
}
// 计算各节点的最终值(因数2与因数5的个数)
for (int i = 2; i <= n; i++) {
int root = i;
do {
root = tree[root];
AddInfo(num[root], num[i]);
} while (root != 1);
}
// 计算各节点后缀0的个数(因数2与因数5个数的较小值)
for (int i = 1; i <= n; i++) {
cout << min(num[i].first, num[i].second) << " ";
}
}
可以刷一刷CodeTop
给你一个链表的头节点 head ,判断链表中是否有环。
#include
#include
struct ListNode {
int val;
struct ListNode* next;
};
bool hasCycle(struct ListNode* head) {
struct ListNode* slow = head;
struct ListNode* fast = head;
do {
if (fast == NULL || fast->next == NULL) { // 链表末尾,非环
return false;
}
slow = slow->next; // 前进一步
fast = fast->next->next; // 前进两步
} while (slow != fast);
return true;
}
C实现
将字符串数组中重复的字符串去掉。
#include
#include
#include
#include
struct string_array {
char** data;
int length;
};
struct string_array delete_repeat_string(struct string_array arr) {
char** p = (char**)malloc(arr.length * sizeof(char*)); // 申请空间并置空
memset(p, 0, arr.length * sizeof(char*));
int index = 0;
bool find_same_string; // 是否遇到相同字符串
for (int i = 0; i < arr.length; i++) {
find_same_string = false;
for (int j = 0; j < i; j++) {
if (strcmp(arr.data[i], arr.data[j]) == 0) {
find_same_string = true;
break;
}
}
if (!find_same_string) {
p[index] = (char*)malloc(strlen(arr.data[i]) + 1); // 申请空间拷贝字符串
strcpy(p[index], arr.data[i]);
index++;
}
}
struct string_array ans = {p, index};
return ans;
}
int main() {
char* test[] = {"apple", "banna", "apple", "test", "test1", "test2", "banna", "test23", "test2", "test234"};
struct string_array src = {test, sizeof(test) / sizeof(char*)};
struct string_array res = delete_repeat_string(src);
for (int i = 0; i < res.length; i++) {
printf("%s\n", res.data[i]);
}
return 0;
}
C实现
实现第一个大于等于,最后一个小于等于操作,见博客重载记录 二分查找
实现一个简单的check函数,处理函数执行前调用check函数,若TPS超过MAX TPS则返回失败
bool check(){}
见博客重载记录 流控算法
165. 比较版本号
class Solution {
public:
// std::size_t std::__cxx11::string::find(char __c, std::size_t __pos) const noexcept
vector<int> getVersion(string& version) {
vector<int> ans;
version.push_back('.'); // 在末尾加上.,统一处理
int last_pos = 0;
int pos;
while ((pos = version.find('.', last_pos)) != string::npos) {
ans.emplace_back(stoi(version.substr(last_pos, pos - last_pos)));
last_pos = pos + 1;
pos = version.find('.', last_pos);
}
return ans;
}
int compareVersion(string version1, string version2) {
vector<int> v1 = getVersion(version1);
vector<int> v2 = getVersion(version2);
// 在尾部补齐0
if (v1.size() > v2.size()) {
int padding = v1.size() - v2.size();
for (int i = 0; i < padding; i++) {
v2.emplace_back(0);
}
} else {
int padding = v2.size() - v1.size();
for (int i = 0; i < padding; i++) {
v1.emplace_back(0);
}
}
// 比较版本号
for (int i = 0; i < v2.size(); i++) {
if (v1[i] > v2[i]) {
return 1;
} else if (v1[i] < v2[i]) {
return -1;
}
}
return 0;
}
};
写代码时卡壳了,主要的错误在于:
1:忘记了find的参数原型
std::size_t std::__cxx11::string::find(char __c, std::size_t __pos) const noexcept
__c和pos的位置写反了
2:v1 v2都传入了version1
vector<int> v1 = getVersion(version1);
vector<int> v2 = getVersion(version1);
3:没有补齐操作,若共用的部分相等则长度大的版本号更大,忽略了修订号有可能为0的情况,1.0.0与1.0
优化版本1:
#include
using namespace std;
class Solution {
public:
vector<int> getVersion(string& version) {
vector<int> ans;
version.push_back('.');
int last_pos = 0;
int pos;
while ((pos = version.find('.', last_pos)) != string::npos) {
ans.emplace_back(stoi(version.substr(last_pos, pos - last_pos)));
last_pos = pos + 1;
pos = version.find('.', last_pos);
}
return ans;
}
int compareVersion(string version1, string version2) {
vector<int> v1 = getVersion(version1);
vector<int> v2 = getVersion(version2);
int i = 0;
int j = 0;
while (i < v1.size() || j < v2.size()) {
int x = 0;
int y = 0;
if (i < v1.size()) {
x = v1[i];
i++;
}
if (j < v2.size()) {
y = v2[j];
j++;
}
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
}
return 0;
}
};
对于长度不一致的数组,采用或操作的方式进行循环遍历,减少代码量,思路有点像字符串相加中的代码
class Solution {
public:
string addStrings(string num1, string num2) {
int i = num1.length() - 1;
int j = num2.length() - 1;
int carry = 0;
string ans;
while ((i >= 0) || (j >= 0) || (carry > 0)) {
int x = (i >= 0) ? num1[i] - '0' : 0;
int y = (j >= 0) ? num2[j] - '0' : 0;
int sum = x + y + carry;
ans.push_back(sum % 10 + '0');
carry = sum / 10;
i--;
j--;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
优化版本2:
class Solution {
public:
int compareVersion(string version1, string version2) {
// 末尾加.,统一处理
version1.push_back('.');
version2.push_back('.');
int i = 0;
int j = 0;
while (i < version1.length() || j < version2.length()) {
// 计算x y大小
long long x = 0;
while (i < version1.length()) {
if (version1[i] == '.') {
break;
}
x = x * 10 + version1[i] - '0';
i++;
}
long long y = 0;
while (j < version2.length()) {
if (version2[j] == '.') {
break;
}
y = y * 10 + version2[j] - '0';
j++;
}
// 比较x y大小
if (x > y) {
return 1;
}
if (x < y) {
return -1;
}
// 跳过.
i++;
j++;
}
return 0;
}
};
遍历时计算数字大小并比较
给定一个字符串数组,求其中有效单词(只能包含字母)的最大长度。示例:[abc abc0 abc–d ab] answer:3
版本1:传统的切割字符串然后计算最大长度
#include
#include
using namespace std;
vector<string_view> GetVaildWord(string& s) {
vector<string_view> ans;
s.push_back(' '); // 末尾加上空格,统一处理
string_view str_view(s);
int last_pos = 0;
int pos;
while ((pos = str_view.find(' ', last_pos)) != string::npos) {
string_view word = str_view.substr(last_pos, pos - last_pos);
bool vaild = true;
for (char c : word) {
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) {
vaild = false;
break;
}
}
if (vaild) {
ans.emplace_back(word);
}
last_pos = pos + 1;
}
return ans;
}
int main() {
string str = "abc abc0 abc--d ab";
auto res = GetVaildWord(str); // 分割字符串,得到有效单词
size_t max_len = 0;
for (auto word : res) {
max_len = max(max_len, word.length()); // 求单词的最大长度
}
cout << max_len << endl;
}
版本2:遍历时计算单词长度并判断单词是否有效
#include
using namespace std;
int main() {
string str = "abc abc0 abc--d ab";
str.push_back(' ');
int max_len = 0;
int len = 0;
bool vaild = true;
for (char c : str) {
if (((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'))) { // 字母字符
len++;
} else if (c == ' ') { // 空格
if (vaild) {
max_len = max(max_len, len);
}
len = 0;
vaild = true;
} else { // 其他无效字符
vaild = false;
}
}
cout << max_len << endl;
}
146. LRU 缓存
使用STL list实现
#include
using namespace std;
class LRUCache {
public:
LRUCache(int capacity) : capacity_(capacity), size_(0) {}
using kv = pair<int, int>;
int get(int key) {
if (speed_map_.count(key) == 0) {
return -1;
}
list<kv>::iterator pos = speed_map_[key];
int value = pos->second;
if (pos != data_.begin()) { // 移至队首
data_.erase(pos); // pos迭代器失效
data_.emplace_front(key, value);
speed_map_[key] = data_.begin();
}
return value;
}
void put(int key, int value) {
if (speed_map_.count(key) == 0) {
if (size_ == capacity_) { // 淘汰队尾数据
speed_map_.erase(data_.back().first);
data_.pop_back();
size_--; // 当前大小减一
}
// 插入队首
data_.emplace_front(key, value);
speed_map_.insert({key, data_.begin()});
size_++; // 当前大小加一
} else { // 更新key-value对
list<kv>::iterator pos = speed_map_[key];
pos->second = value;
if (pos != data_.begin()) { // 移至队首
data_.erase(pos); // pos迭代器失效
data_.emplace_front(key, value);
speed_map_[key] = data_.begin();
}
}
}
private:
int capacity_; // 容量
int size_; // 当前大小
unordered_map<int, list<kv>::iterator> speed_map_; // key与list迭代器
list<kv> data_; // 存储key-value对
};
注意list删除后相应迭代器也失效了
简单示例:
#include
using namespace std;
int main() {
list<int> head;
head.emplace_back(12);
auto iter = head.begin();
cout << *iter << endl; // 12
head.erase(iter); //删除元素
cout << *iter << endl; // -17891602
}
使用自定义双向链表,更新LRU时不需要修改map
#include
using namespace std;
struct ListNode { // 双向链表实现
int key;
int value;
ListNode* prev;
ListNode* next;
ListNode() = default;
ListNode(int key1, int value1) : key(key1), value(value1) {}
static void InsertNode(ListNode* head, ListNode* node) { // 在头节点后插入节点
node->next = head->next;
node->prev = head;
head->next->prev = node;
head->next = node;
}
static void DeleteNode(ListNode* node) { // 删除节点
node->next->prev = node->prev;
node->prev->next = node->next;
}
};
class LRUCache {
public:
LRUCache(int capacity) : capacity_(capacity), size_(0) {
// 初始化头节点,循环双向链表
head_.next = &head_;
head_.prev = &head_;
}
int get(int key) {
if (speed_map_.count(key) == 0) { // key不存在
return -1;
}
ListNode* node = speed_map_[key];
int value = node->value;
if (head_.next != node) { // 移至队首
ListNode::DeleteNode(node);
ListNode::InsertNode(&head_, node);
}
return value;
}
void put(int key, int value) {
if (speed_map_.count(key) == 0) { // 插入操作
if (size_ >= capacity_) { // 淘汰操作
ListNode* node = head_.prev; // 队尾节点
ListNode::DeleteNode(node);
speed_map_.erase(node->key);
delete node;
size_++;
}
ListNode* node = new ListNode(key, value);
ListNode::InsertNode(&head_, node);
speed_map_.emplace(key, node);
size_++;
} else { // 更新操作
ListNode* node = speed_map_[key];
node->value = value;
if (head_.next != node) {
ListNode::DeleteNode(node);
ListNode::InsertNode(&head_, node);
}
}
}
~LRUCache() { // 释放动态资源
while (head_.next != &head_) {
ListNode* node = head_.next;
ListNode::DeleteNode(node);
delete node;
}
size_ = 0;
}
private:
int capacity_;
int size_;
ListNode head_;
unordered_map<int, ListNode*> speed_map_;
};
143. 重排链表
遇到的问题:
1:面试时根本没想到解法,面试官给出了解法后实现上又出现了一些问题
2:链表反转函数实现受反转链表 II影响使用了虚拟头节点,却没有真正理解那道题中使用虚拟头节点的意义
3:前半部和后半部的连接没有断开,导致无限循环
美团的面试官都很nice,以后要多刷链表相关的算法题了
class Solution {
public:
ListNode* reverseList(ListNode* head) { // 链表反转 三指针法
ListNode* prev = nullptr;
ListNode* curr = head;
ListNode* next;
while (curr != nullptr) {
next = curr->next;
curr->next = prev;
prev = curr;
curr = next;
}
return prev;
}
ListNode* mergeList(ListNode* node1, ListNode* node2) { // 合并链表
ListNode virt_head(-1);
ListNode* node = &virt_head;
while (node1 != nullptr || node2 != nullptr) { // 先选择node1的节点,后选择node2的节点
if (node1 != nullptr) {
node->next = node1;
node = node1;
node1 = node1->next;
}
if (node2 != nullptr) {
node->next = node2;
node = node2;
node2 = node2->next;
}
}
return virt_head.next;
}
void reorderList(ListNode* head) {
if (head == nullptr || head->next == nullptr) {
return;
}
ListNode virt_head(-1, head);
ListNode* prev = &virt_head; // 慢指针的前继节点
ListNode* slow = head; // 慢指针
ListNode* fast = head; // 快指针
while (fast != nullptr && fast->next != nullptr) {
prev = slow;
slow = slow->next;
fast = fast->next->next;
}
prev->next = nullptr; // 断掉前后链表的连接
ListNode* latter_half = reverseList(slow); // 将后半部链表反转
head = mergeList(head, latter_half); // 再次合并前后链表
}
};
先进行分类讨论,处理简单的情况,再单独左子节点情况进行处理
当节点为左子节点且右子节点不为空时,需要想明白后继节点一定是叶子节点,后序遍历的顺序是左右根,那么先持续左再尝试向右,直至叶子节点
struct node {
struct node *left;
struct node *right;
struct node *parent;
};
struct node *next_postorder(const struct node *node) {
if (node->parent == NULL) { // 根节点
return NULL;
}
struct node *node_parent = node->parent;
if (node_parent->right == node) { // 为父节点的右子节点
return node_parent;
}
if (node_parent->right == NULL) { // 为父节点的左子节点,但右子节点为空
return node_parent;
}
struct node *next = node_parent->right; // 从右子节点开始
while (next->left != NULL || next->right != NULL) { // 不为叶子节点
while (next->left != NULL) { // 持续向左
next = next->left;
}
if (next->right != NULL) { // 向右拓宽路径
next = next->right;
}
}
return next;
}
/*
写一个C/C++的函数供别人调用,实现以下功能
给定任意一个字符串,对其中的每个字符进行忽略字母大小写的排序
*/
#include
using namespace std;
char get_equal_char(char c) { // 全部转换成大写字母
if (c >= 'a' && c <= 'z') {
return c - ('a' - 'A');
}
return c;
}
int compare(char c1, char c2) { // 自定义比较函数
c1 = get_equal_char(c1);
c2 = get_equal_char(c2);
if (c1 > c2) {
return 1;
}
if (c1 < c2) {
return -1;
}
return 0;
}
int partition(char* str, int start, int end) { // 划分函数
char p = str[end];
int slow = start - 1;
for (int fast = start; fast < end; fast++) {
if (compare(str[fast], p) == -1) { // str[fast] < p
slow++;
char tmp = str[slow];
str[slow] = str[fast];
str[fast] = tmp;
}
}
char tmp = str[slow + 1];
str[slow + 1] = str[end];
str[end] = tmp;
return slow + 1;
}
void quick_sort(char* str, int start, int end) { // 快排
if (start >= end) {
return;
}
int p = partition(str, start, end);
quick_sort(str, start, p - 1);
quick_sort(str, p + 1, end);
}
void my_sort(char* str, int len) { quick_sort(str, 0, len - 1); }
int main() {
char test[] = "dcAefgkLG";
printf("%s\n", test);
my_sort(test, strlen(test));
printf("%s\n", test);
}
/*
dcAefgkLG
AcdefGgkL
*/