牛客网刷题记录与机试面试记录

牛客网刷题记录与机试面试记录

  • 牛客网
    • 【2021】阿里巴巴编程题(2星)
      • 第一题 完美对
      • 第二题 选择物品
      • 第三题 小强去春游
      • 第四题 比例问题
      • 第五题 小强修水渠
      • 第六题 国际交流会
    • 2021】阿里巴巴编程题(4星)
      • 第一题 子集
      • 第二题 小强爱数学
    • 杂项
      • 判断字符串
      • 01翻转
      • dfs优化思路
  • 企业机试
    • 2023-03-11 美团机试
      • 第一题
      • 第二题
      • 第三题
      • 第四题
      • 第五题
    • 2023-03-12 拼多多机试
      • 第一题 多多的压缩编码II
      • 第二题 多多的飞机大战游戏
      • 第三题 多多的团建计划
      • 第四题 多多的餐厅客流量
    • 2023-3-15 阿里机试
      • 第一题
      • 第二题
      • 第三题
    • 2023-3-16 蚂蚁机试
      • 第一题 整数抽取
      • 第二题 组装电脑
      • 第三题 带传送阵的矩阵游离
    • 2023-03-19 米哈游机试
      • 第一题
      • 第二题
      • 第三题
    • 2023-03-23 腾讯音乐笔试
      • 第一题
      • 第二题
      • 第三题
    • 2023-03-26 腾讯笔试
      • 第一题 链表操作
      • 第二题 重组字符串
      • 第三题 最小权值排列数组
    • 2023-03-28 百度笔试
      • 第一题
      • 第二题
      • 第三题
  • 面试代码题
    • 链表是否存在环
    • 字符串数组去重
    • 二分查找
    • 流控算法
    • 比较版本号
    • 字符串最长单词
    • LRU算法
    • 重排链表
    • 寻找后序遍历的后继节点
    • 手写快排

牛客网

【2021】阿里巴巴编程题(2星)

【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;
  }
}

第四题 比例问题

在这里插入图片描述
枚举即可,时刻注意溢出问题,机试多使用long long

#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星)

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;
  }
}

超时,过不了,只能使用二分查找解法了
牛客网刷题记录与机试面试记录_第1张图片

#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;
  }
}

第二题 小强爱数学

在这里插入图片描述
记忆深刻的问题,之前写过一遍,当时也是写了很久没写出来,看了一下午愣是没看出为啥答案错了
牛客网刷题记录与机试面试记录_第2张图片
题目的难点在于想出这个递推公式,然后就是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;
    }
  }
}

01翻转

使用dfs遍历各个翻转可能,加上一点剪枝策略,骗分
正确思路
牛客网刷题记录与机试面试记录_第3张图片
牛客网刷题记录与机试面试记录_第4张图片

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
*/

企业机试

2023-03-11 美团机试

题目:美团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-03-12 拼多多机试

题目链接: 2023暑期实习-笔试-拼多多-算法实习生
晚上的笔试,过了三道半

第一题 多多的压缩编码II

还原压缩后的字符串
输入 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;
  }
}

2023-3-15 阿里机试

题目链接: 大厂真题|3月15日阿里春招笔试三道题
晚上的笔试,七道单选,八道多选,三道编程题,编程题过了2道半,不能使用本地IDE,故没当时的代码(临结束的时候应该把所有的代码拷贝到剪切板的)

第一题

牛客网刷题记录与机试面试记录_第5张图片
思路
满二叉树,首先根据数组构建二叉树,而后寻找根节点,最后递归求解满二叉树,仅当两个子树皆为满二叉树且节点数目相同时该树为满二叉树

按记忆打的,不一定对

#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
*/

第二题

牛客网刷题记录与机试面试记录_第6张图片
三个数最大值减最小值=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;
  }
}

第三题

牛客网刷题记录与机试面试记录_第7张图片
思路
由于是第三题,所以我默认它很难,我就直接使用暴力回溯写了一遍

数组排序
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-3-16 蚂蚁机试

题目链接:「技术笔试」蚂蚁金服 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});
    }
}

2023-03-19 米哈游机试

题目链接:全网首发-真题分享|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三个字母的序列不重要
牛客网刷题记录与机试面试记录_第8张图片

#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;
}

2023-03-23 腾讯音乐笔试

题目: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
双指针法求得可能结果
牛客网刷题记录与机试面试记录_第9张图片
刚开始左区域最大,右区域为空,此时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;
  }
};

2023-03-26 腾讯笔试

题目:腾讯笔试记录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.如果数组下标iB[j],那么一定要保证C[i]>C[j]
4数组C与数组A每个元素之差的和的绝对值最小.

按理来说样例的答案应该是98,但样例是104,一直想不出来为什么
牛客网刷题记录与机试面试记录_第10张图片

#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
*/

2023-03-28 百度笔试

题目链接:3.28百度笔试复盘

图解SQL的inner join、left /right join、 outer join区别
牛客网刷题记录与机试面试记录_第11张图片
编程题过了2.8道,第三题一直超时,只得了80分

第一题

题目:
牛客网刷题记录与机试面试记录_第12张图片
签到题

#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;
}

第二题

题目:
牛客网刷题记录与机试面试记录_第13张图片
标准解法是单调栈,不过我当时没想到,就用数组存从当前位置往右看最大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;
}

第三题

题目:
牛客网刷题记录与机试面试记录_第14张图片
只得了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;
}

LRU算法

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. 重排链表
牛客网刷题记录与机试面试记录_第15张图片
遇到的问题:
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
*/

你可能感兴趣的:(算法,牛客网)