自己在秋招过程中遇到的算法笔试题,包含中大厂,都附解析!
汽水瓶
如果汽水瓶数目为1或者0,那么一瓶都喝不到
如果汽水瓶数目为2或者3,那么只能喝到一瓶
如果为2,喝到一瓶后手里一个瓶子都没有了,没法继续循环下去
如果为3,喝到一瓶后手里只剩一个瓶子,没法继续循环下去
如果汽水瓶数目大于3,每次耗费三个瓶子喝到一瓶,然后多了一个瓶子,即每一轮耗费两个瓶子
# include <iostream>
using namespace std;
unsigned int sln(unsigned int N){
if (N == 2 || N == 3) {
return 1;
}
if (N <= 1){
return 0;
}
unsigned int Total = 0;
while (N > 3) {
N -= 2;
Total++;
if (N == 2)
{
Total++;
N = 0;
}
if (N == 3)
{
Total++;
N = 1;
}
}
return Total;
}
int main(){
unsigned int N;
while (cin >> N) {
if (N == 0) break;
cout << sln(N) << endl;
}
return 0;
}
明明的随机数
//方法一: 暴力版本
#include <iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main() {
int n;
cin>>n;
int m;
vector<int>v;
while(n--){
cin>>m;
v.push_back(m);
}
sort(v.begin(),v.end());
cout<<v[0]<<endl;
for(int i=1;i<v.size();i++){
if(v[i]!=v[i-1]){
cout<<v[i]<<endl;
}
}
}
//方法二:STL(set)版本
#include <bits/stdc++.h>
using namespace std;
int main() {
int n;
cin>>n;
int m;
set<int>us;//插入有序
while(n--){
cin>>m;
us.insert(m);
}
for(auto &i:us){
cout<<i<<endl;
}
}
**进制转换
**
#include <iostream>
using namespace std;
int main() {
int a;
while (cin >> hex>> a) { // 注意 while 处理多个 case
cout << a << endl;
}
}
Words
#include<bits/stdc++.h>
using namespace std;
int main(){
string str="";
while(getline(cin,str)){
int n=str.size();
int countspace=0;
double ans=0;
for(int i=0;i<str.size();i++){
if(str[i]==' '){
countspace++;
}
}
ans=1.0*(n-countspace)/(1.0*(countspace+1));
cout<<fixed<<setprecision(2)<<ans<<endl;
}
return 0;
}
Vowel
#include<set>
#include<string>
#include<iostream>
using namespace std;
int main(){
string str="";
string ans="";
set<char>se;
se.insert('a');
se.insert('e');
se.insert('i');
se.insert('o');
se.insert('u');
se.insert('A');
se.insert('E');
se.insert('I');
se.insert('O');
se.insert('U');
while(getline(cin,str)){
for(int i=0;i<str.size();i++){
if(se.find(str[i])==se.end()){
ans.push_back(tolower(str[i]));
}
else{
ans.push_back(toupper(str[i]));
}
}
cout<<ans;
}
return 0;
}
计算字符串重新排列数
栈数据合并
整体代码
#include <iostream>
#include<stack>
#include <sstream>
using namespace std;
//没有消除时,栈里面大小一直是1
int main() {
stack<int> s;
string input;
//输入:10 20 50 80 1 1
//输出:2 160
getline(cin, input);
istringstream iss(input);//输入的一行挨个读取
int num;
while (iss >> num) {
if (s.empty()) {
s.push(num);
continue;//关键
}
int sum = num;
while (!s.empty() && s.top() <= num) {
int top = s.top();
s.pop();
sum += top;
if (sum == top * 2) {
sum = top * 2;
continue;//关键
}
if (!s.empty() && sum == top * 2 + s.top()) {
sum = top * 2 + s.top();
s.pop();
}
break;
}
s.push(sum);
}
while (!s.empty()) {
cout << s.top() << " ";
s.pop();
}
return 0;
}
寻找密码串
C++字符串转数字: stoi C语言字符串转数字:atoi(s.c_str())
C++数字转字符串to_string
整体代码
#include <iostream>
#include <string>
#include<set>
#include <algorithm>
using namespace std;
bool is_valid(string s) {
return set<char>(s.begin(), s.end()).size() == 1;
}
int main() {
string a, c;
long long b;
cin >> a >> b >> c;
long long size = a.length();
long long ans = 0;
for (int i = 0; i < size; i++) {
for (int len = 0; len < 13; len++) {
if (i + len >= size) {
break;
}
long long now = stoll(a.substr(i, len + 1));
if (c == "+") {
if (is_valid(to_string(now + b))) {
ans = max(ans, now);
}
}
else if (c == "-") {
if (is_valid(to_string(now - b))) {
ans = max(ans, now);
}
}
else {
if (is_valid(to_string(now * b))) {
ans = max(ans, now);
}
}
}
}
cout << ans << endl;
return 0;
}
染色服务器,最短耗费时间走完所有的服务器-5.10
#include <iostream>
#include <queue>
#include <vector>
#include <unordered_map>
#include <climits>
using namespace std;
int main() {
int n;
cin >> n;//几个服务器
int m;
cin >> m;//几条路径
unordered_map<int, int> sysTime;
unordered_map<int, vector<pair<int, int>>> nxs;
//m条路径从哪到哪,耗时多久
for (int i = 0; i < m; i++) {
int s, d, t;
cin >> s >> d >> t;
if (s != d)
nxs[s].push_back({ d, t });
else
sysTime[s] = t;
}
//从哪个服务器开始
int start;
cin >> start;
priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> h;
h.push({ 0, start });
vector<int> dis(n + 1, INT_MAX);
dis[start] = 0;
while (!h.empty()) {
int time = h.top().first;
int node = h.top().second;
h.pop();
for (auto& nx : nxs[node]) {
int nxNode = nx.first;
int nxTime = nx.second;
if (dis[nxNode] > time + sysTime[nxNode] + nxTime) {
h.push({ time + sysTime[nxNode] + nxTime, nxNode });
dis[nxNode] = min(dis[nxNode], time + sysTime[nxNode] + nxTime);
}
}
}
int cnt = 0, res = 0;
for (int d : dis) {
if (d != INT_MAX) {
cnt++;
res = max(res, d);
}
}
cout << cnt << " " << res << endl;
return 0;
}
小美的送花路线
根据题设,考虑树状结构。求:
sum1:从花店到所有客户地址的距离之和;
sum2:骑手实际走的最短路程,且最后一趟不需再返回。
求sum1:
根据输入的u、v、w,对于任意节点都能写出dp[v]=dp[u]+w,从而得到sum1。
对于例子1来说,sum1+=dp[v]。如dp[5]=dp[2]+1=3+1=4。sum1=3+1+2+4=10。
求sum2:
要求最短路径,根据最后一趟不需返回,可知最后一趟一定是从花店到最远的客户地址处,这部分距离只需计算一次,其余的到其他客户地址的距离需要计算往返路程。因此sum2+=2×w,还要-maxlen,其中maxlen是dp[v]中的最大值。
对于例子1来说,离花店最远的客户地址是5,路程中还途径了客户1。因此,sum2=2×(3+1+2+1)-(3+1)=10。
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;
int main(){
int n;
cin>>n;
vector<int>dp(n,0);
int sum1 = 0,maxlen = 0,sum2 = 0;
int u,v,w;
for(int i=0;i<n-1;i++){
cin>>u>>v>>w;
dp[v] = dp[u]+w;
sum2 += w*2;
maxlen = max(dp[v],maxlen);
sum1 += dp[v];
}
cout << sum1 << " " << sum2-maxlen;
}
小美的评分计算器
需注意的点:
①输入的是1星到5星的评价的数量;
②两个整数相除还是整数,需要用强制转换成小数;
③需要考虑输出为0.0的情况。
④不四舍五入的小数去尾的实现:乘整十(一位小数)向下取整再除十,使用fixed和setprecision控制实数部分及小数部分精度
fixed 和 setprecision 是用来控制输出浮点数精度和格式的两个重要工具。
fixed 是一个 C++ 标准库中的输出流修饰符,它用于指示输出的浮点数使用定点表示法而非默认的科学计数法。使用 fixed 后,输出的浮点数会以固定的小数位数格式进行输出。
fixed 是一个全局性质的修饰符,一旦使用,它会一直生效,直到被另外的修饰符覆盖为止。
setprecision 是另一个输出流修饰符,用于指定输出浮点数的小数位数。
向上取整:ceil,向下取整:floor,四舍五入:round
#include<bits/stdc++.h>
using namespace std;
int main(){
int n1,n2,n3,n4,n5;
cin>>n1>>n2>>n3>>n4>>n5;
double rate = 0;
rate = (double)(1*n1+2*n2+3*n3+4*n4+5*n5)/(n1+n2+n3+n4+n5);
if(n1==0&&n2==0&&n3==0&&n4==0&&n5==0){
cout<<"0.0";
}
else{
cout<<fixed<<setprecision(1)<<floor(rate*10.0)/10.0;
}
}
小美的外卖省钱计划
#include <iostream>
using namespace std;
int main() {
int n;
cin>>n;
int x=0, y=0;
int sum1=0,sum2=0;
for(int i=0;i<n;i++){
cin>>x>>y;
if(x>y){
sum2+=(x-y);
sum1+=x;
}
else{
sum1+=y;
}
}
cout<<sum1<<" "<<sum2;
}
小美的代金券要过期啦
例子中是从后向前进行消消乐的,因此考虑使用栈结构,将序列元素入栈后,当栈顶元素和序列中元素相等时,将输入的x+1。
#include<bits/stdc++.h>
using namespace std;
int main(){
int n;
int x = 0;
cin>>n;
int* s = new int[n];
for(int i=0;i<n;i++)
{
cin>>s[i];
}
stack<int> nums;
nums.push(s[0]);
for(int i=1;i<n;i++){
while(!nums.empty()&&s[i]==nums.top()){
nums.pop();
s[i]++;
x++;
}
nums.push(s[i]);
}
cout<<x;
}
字符分割
如给你一串字符“path=/chat.openai.com, language=c++, path=baidu.com",
输入 2 path language,输出 baidu.com c++
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
using namespace std;
int main() {
string str;
getline(cin, str);
unordered_map<string, string> mmap;
//字符分割
for (int i = 0; i < str.size(); i++) {
int start = i;
while (start < str.size() && str[start] != '=') {
start++;
}
string str2 = str.substr(i, start - i);
int start2 = start + 1;
while (start2 < str.size() && str[start2] != ',') {
start2++;
}
string str3 = str.substr(start + 1 , start2 - start - 1);
i = start2;
//此处说相同的key取后者的value,应该直接替代是没有问题的
mmap[str2] = str3;
}
//输入验证
int n;
cin >> n;
string str4;
vector<string> vec;
while (n > 0 && cin >> str4) {
vec.push_back(str4);
n--;
}
for (string& strTmp : vec) {
if (mmap.count(strTmp) >= 1) {
cout << mmap[strTmp] << endl;
}
else {
cout << "EMPTY" << endl;
}
}
return 0;
}
美味值数组
给你一个每一天糖果的美味值数组,只能隔天吃,但也可以反悔,有K次反悔机会
- 思路:先用打家劫舍的方法,然后对没有选的取k个最大值
#include<iostream>
#include<vector>
#include<string>
#include<unordered_map>
#include<queue>
using namespace std;
int maxTaste(int k, vector<int>& vec) {
int len = vec.size();
vector<int> dp(len);
dp[0] = vec[0];
if (len == 1) {
return dp[0];
}
vector<bool> used(len, false);
if (len >= 2) {
dp[1] = max(dp[0], vec[1]);
if (len == 2) {
if (k > 0) {
return vec[0] + vec[1];
}
else {
return dp[1];
}
}
}
for (int i = 2; i < len; i++) {
dp[i] = max(dp[i - 2] + vec[i], dp[i - 1]);
}
//判断最后一个选了没
bool jus;
if (dp[len - 3] + vec[len - 1] > dp[len - 2]) {
jus = true;
}
else {
jus = false;
}
priority_queue<int, vector<int>, less<int>> pq;
if (!jus) {
for (int i = len - 1; i > 0; i = i - 2) {
pq.push(vec[i]);
}
}
else {
for (int i = len - 2; i > 0; i = i - 2) {
pq.push(vec[i]);
}
}
int res = dp[len - 1];
for (int i = 0; i < k; i++) {
res += pq.top();
pq.pop();
}
return res;
}
int main() {
//反悔次数
int k;
cin >> k;
//输入美味值
int n; //天数
cin >> n;
vector<int> vec;
int num;
while (n > 0 && cin >> num) {
vec.push_back(num);
n--;
}
cout << maxTaste(k, vec) << endl;
return 0;
}
0-1背包问题
n块巧克力板,边长为a[i], 重量为a[i]*a[i], m 个背包, 可以装重量为b[i], 求每个背包最多可以装多少?
- 思路: 可以对巧克力板按重量从小到大排序,前缀和即可
#include<iostream>
#include<algorithm>
#include<vector>
#include<stack>
using namespace std;
int large(vector<int>& a, int b) {
int sum = 0;
int len = a.size();
int i;
for (i = 0; i < len; i++) {
sum += a[i];
if (sum > b) {
break;
}
}
return i;
}
int main() {
int n, m;
cin >> n >> m;
vector<int> a, b;
int ins;
while (n > 0 && cin >> ins) {
a.push_back(ins * ins);
n--;
}
while (m > 0 && cin >> ins) {
b.push_back(ins);
m--;
}
sort(a.begin(), a.end());
for (int tmp : b) {
cout << large(a, tmp)<< " ";
}
cout << endl;
return 0;
}
用O(n)写一个数组的第K大数
快速选择算法的基本思想是通过每次选取一个基准元素,将数组分为两个部分,小于基准元素的部分和大于基准元素的部分。然后根据基准元素所在位置和K的关系,决定在哪个部分继续查找
#include <iostream>
#include <vector>
// 快速选择算法
int quickSelect(std::vector<int>& nums, int left, int right, int k) {
// 选取基准元素
int pivot = nums[left];
int i = left + 1;
int j = right;
// 将大于基准元素的数移到右侧,小于基准元素的数移到左侧
while (i <= j) {
if (nums[i] < pivot && nums[j] > pivot) {
std::swap(nums[i], nums[j]);
i++;
j--;
}
if (nums[i] >= pivot) {
i++;
}
if (nums[j] <= pivot) {
j--;
}
}
// 将基准元素放在正确的位置
std::swap(nums[left], nums[j]);
// 判断基准元素的位置与K的关系
int pos = j - left + 1;
if (pos == k) {
return nums[j];
}
else if (pos > k) {
return quickSelect(nums, left, j - 1, k);
}
else {
return quickSelect(nums, j + 1, right, k - pos);
}
}
// 找出数组的第K大数
int findKthLargest(std::vector<int>& nums, int k) {
int n = nums.size();
return quickSelect(nums, 0, n - 1, k);
}
int main() {
std::vector<int> nums = { 3, 1, 5, 2, 4 };
int k = 2;
int kthLargest = findKthLargest(nums, k);
std::cout << "第" << k << "大数是:" << kthLargest << std::endl;
return 0;
}
合并有序数组
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1=m-1;
int p2=n-1;
int tail=p1+p2+1;
int cur;
while(p1>=0||p2>=0){
if(p1==-1){
cur=nums2[p2--];
}
else if(p2==-1){
cur=nums1[p1--];
}
else if(nums1[p1]>nums2[p2]){
cur=nums1[p1--];
}
else{
cur=nums2[p2--];
}
nums1[tail--]=cur;
}
}
};
输入一堆数,每个数都是出现 4 次,只有一个数出现三次,找出这个数。时间复杂度 O(nlogn),空间复杂度 O(logn)
1)手撕快排再遍历
2)一直异或,最后异或的结果一定是那个数
#include<iostream>
#include<vector>
using namespace std;
//时间复杂度O(N)
int main() {
vector<int>nums{ 1,1,1,1,2,2,2,3,3,3,3,6,6,6,6,5,5,5,5};
int ans = nums[0];
for (int i = 1; i < nums.size(); i++) {
ans = ans ^ nums[i];
}
cout << ans << endl;
return 0;
}
手写生产者消费者模型
为应对系统的通信上消息收发的高耦合度,在接受和发送消息后设计一个缓存队列,并使用多线程生产者-消费者来进行收发解耦。避免通信上的拥堵情况。
class MyTest{
private:
std::deque<T> queue_; //缓存队列
size_t size_limit_; //缓存队列大小的限制
std::mutex lock_; //互斥锁
std::condition_variable empty_, full_; //并发条件变量类
std::atomic<int> producer_num_; //生产者数量
};
实现思路是:当缓存队列满的时候,生产者将被挂起,直到队列重新拥有空间。但缓存队列为空时,消费者线程将被挂起,直到有“商品”被放入队列之中。
void Productor(T& item) {
{
std::unique_lock<std::mutex> lk(lock_); //加锁防止冲突
while (queue_.size() >= size_limit_) {
full_.wait(lk);
}
queue_.emplace_back(item);
}
empty_.notify_one();
}
full_.wait(lk)在queue队列满时将会把Productor挂起,直到queue有空间放入新的item。notify_one方法任意从WAITTING状态的线程中挑选一个进行通知,使得调用wait()方法的线程从等待队列移入到同步队列中,等待有机会再一次获取到锁,从而使得调用wait()方法的线程能够从wait()方法处退出。
bool Consumer(T& item) {
{
std::unique_lock<std::mutex> lk(lock_);
while (queue_.empty() && (producer_num_ != 0)) {
empty_.wait(lk);
}
if (queue_.empty() && (producer_num_ == 0)) {
return false;
} else {
item = std::move(queue_.front());
queue_.pop_front();
full_.notify_one();
return true;
}
}
}
同理,empty_.wait(lk)会在生产者数量为0或者队列queue为空的时候,将Consumer挂起。直到有商品进入到队列中。这样就可以实现一个简单的生产者消费者。
二叉树层序遍历
vector<vector<int>> levelOrder(TreeNode*root) {
queue<TreeNode*>que;
if(root==NULL) return root;
que.push(root);
vector<vector<int>>ans;
while(!que.empty()){
int n=que.size();
vector<int>path;
for(int i=0;i<n;i++){
TreeNode*node=que.front();
que.pop_back();
path.push_back(node->val);
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
ans.push_back(path);
}
return ans;
}
输入高维张量,返回shape
import numpy as np
def get_shape(tensor):
shape = np.shape(tensor)
return shape
# 示例输入一个3维张量
tensor = np.array([[[1, 2], [3, 4]], [[5, 6], [7, 8]]])
shape = get_shape(tensor)
print(shape)
#include <iostream>
#include <vector>
using namespace std;
template<typename T>
vector<size_t> getShape(const vector<T>& tensor) {
vector<size_t> shape;
size_t size = tensor.size();
while (size > 0) {
shape.push_back(size);
size /= tensor[0].size();
}
return shape;
}
int main() {
vector<vector<vector<int>>> tensor{
{{1, 2}, { 3, 4 }},
{ {5, 6}, {7, 8} }
};
vector<size_t> shape = getShape(tensor);
cout << "Shape: ";
for (size_t dim : shape) {
std::cout << dim << " ";
}
cout << std::endl;
return 0;
}
GPU内存结构
- 全局内存(Global Memory):全局内存是GPU中最大的内存池,通常具有较大的容量。它用于存储核心数据结构、变量和计算结果等。全局内存对于所有线程可见,但相对于其他内存层级而言,它的访问速度较慢。
- 共享内存(Shared Memory):共享内存是位于GPU的多处理器(Multiprocessor)中的每个线程块(Thread Block)之间共享的内存区域。它的访问速度非常快,可以在同一线程块的线程之间共享数据。共享内存主要用于线程之间的通信和协作,以提高性能。
- 常量内存(Constant Memory):常量内存用于存储在执行期间不会发生改变的常量数据。它在访问速度上比全局内存快,但容量较小。常量内存对于所有线程可见,可以通过缓存机制提高访问效率。
- 纹理内存(Texture Memory):纹理内存是专门用于图形渲染的内存区域,具有高速缓存和过滤器等功能。它主要用于存储纹理数据,并提供一些特殊的访问方式,如双线性插值和边界处理等。
- 寄存器文件(Register File):寄存器文件是GPU中每个线程的私有内存空间。它用于存储线程的局部变量、中间计算结果和函数调用参数等。寄存器的访问速度非常快,但容量有限。对于某些计算密集型任务,寄存器的使用可能需要精心管理以避免资源耗尽。
CUDA三维结构好还是二维好
CUDA(Compute Unified Device Architecture)是NVIDIA提供的用于GPU编程的并行计算平台和编程模型。在CUDA中,既可以使用二维结构(二维线程块和二维网格)也可以使用三维结构(三维线程块和三维网格)。选择使用二维还是三维结构取决于问题的特性和并行计算的需求。
二维结构:
- 二维结构适用于处理具有二维或类似矩阵的数据结构。例如,图像处理中的像素数据可以组织为二维数组,因此使用二维结构可以方便地处理图像。
- 二维结构可以更好地利用数据的空间局部性。相邻的线程可以访问相邻的数据,这有助于减少全局内存访问和数据传输的次数,提高数据访问的效率。
三维结构: - 三维结构适用于处理具有三维特性的数据结构,例如体积数据或具有复杂三维结构的数据集。
- 使用三维结构可以更好地描述和组织三维数据,提高代码的可读性和可维护性。
- 在某些情况下,三维结构可以提供更好的并行计算的均匀性和负载平衡,特别是当问题具有三维的均匀性或对称性时。
总体而言,选择二维结构还是三维结构应根据问题的特性、数据的组织方式以及并行计算的需求来决定。如果问题可以自然地映射到二维结构,并且能够充分利用二维数据的空间局部性,那么二维结构可能更合适。如果问题具有三维特性或需要更好的均匀性和负载平衡,则可以考虑使用三维结构。在实际使用中,可以通过实验和性能分析来确定最适合问题的结构。
tensor core 了解吗
Tensor Core提供了高度优化的矩阵乘法和累积运算功能,能够在相对较短的时间内执行大规模矩阵运算,从而加速深度学习模型的训练和推理过程。
Tensor Core的主要特点包括:
- 数值精度:Tensor Core支持半精度浮点数(FP16)运算,它将浮点运算的计算精度降低一半,但同时大大增加了计算性能。在深度学习中,使用半精度计算通常可以满足训练和推理的准确性要求,能够显著提升计算速度。
- 矩阵乘法:Tensor Core使用特定的算法和硬件电路来加速矩阵乘法运算,尤其是小批量(mini-batch)矩阵乘法。它能够同时执行多个矩阵乘法操作,并且支持半精度累积运算,使得在一个时钟周期内可以完成更多的计算。
- 混合精度计算:Tensor Core可以与其他精度计算单元结合使用,如32位浮点数(FP32)和整数计算单元。这种混合精度计算的方法能够在保持准确性的同时提高计算性能,常用于深度学习的训练和推理过程。
Tensor Core广泛应用于深度学习框架和库,如TensorFlow和PyTorch等,以加速深度神经网络的计算。它可以显著提高模型训练的速度,并在一定程度上降低对计算资源的需求。然而,使用Tensor Core需要特定的软件支持和配置,以确保正确地利用它的性能优势。
ReduceSum怎么优化
- 数据布局优化:调整数据的布局方式,以便在内存访问和数据传输方面更加高效。例如,优化数据的连续性和缓存友好性,可以利用缓存和向量化指令集提高访问效率。
- 并行化:使用并行计算技术,如线程、向量化和GPU并行处理等,将ReduceSum操作分布到多个计算单元上并发执行,以提高计算效率。
3.** 数据复用**:尽可能地复用数据,减少不必要的读取和写入操作。可以通过共享内存、寄存器或缓存等机制来缓存中间计算结果,以减少数据的重复读取和写入。 - 剪枝和分解:对于大规模ReduceSum操作,可以考虑使用剪枝和分解等技术将其分解为更小的子任务,以降低每个子任务的计算负载,并充分利用并行性。
- 异步计算:使用异步计算技术,如CUDA流或异步任务队列,将ReduceSum操作与其他计算任务重叠,以充分利用计算和数据传输之间的隐藏延迟,提高整体性能。
- 内存访问优化:通过合理地利用局部性原则、数据对齐和访问模式等技术,优化内存访问方式,减少数据传输次数和延迟,提高数据吞吐量。
- 分级存储:对于大规模ReduceSum操作,可以使用分级存储策略,将数据分为多个层级,并使用不同的存储介质来适应不同的访问需求。例如,结合全局内存、共享内存和寄存器等多级存储来提高性能。
- 算法选择:根据实际需求,选择适合的ReduceSum算法。不同的算法可能有不同的计算复杂度和内存访问模式,选择合适的算法可以提高性能。
异构设备内存局部性怎么优化
- 内存访问模式优化:调整数据的访问模式,以提高内存局部性。例如,尽量保证线程对内存的访问是连续的、对齐的,避免随机的、不规则的内存访问。
- 数据布局优化:重组数据的存储方式,以提高内存局部性。例如,将相关的数据项存储在相邻的内存位置,或者使用数据结构和算法来组织数据,以充分利用局部性原则。
- 数据复用:尽量复用已经从内存加载到高速缓存的数据,减少对主存的访问。可以使用缓存、共享内存或寄存器等层级存储来存储中间计算结果,以避免重复的内存访问。
- 数据预取:利用硬件的数据预取机制,提前将需要使用的数据从主存加载到高速缓存,以减少访问延迟。可以使用编译器指令、预取指令或手动编码来控制数据的预取行为。
- 空间局部性优化:利用数据的空间局部性原则,以减少数据的移动和传输。例如,通过使用局部变量、循环展开、数据重用等技术来减少内存的读写操作。
- 访问模式分析和优化:通过对代码进行访问模式分析,了解内存访问的模式和特点,进而针对性地进行优化。可以使用工具、分析器或性能剖析器来帮助确定访问模式和性能瓶颈,并针对性地进行优化。
- 数据压缩和存储优化:在某些情况下,可以使用数据压缩技术来减少内存占用和传输带宽。通过压缩算法、数据压缩库或自定义的数据存储方案等方式,将数据压缩后存储在内存中,然后在需要使用时进行解压缩。
K 个一组翻转链表
class Solution {
public:
// 翻转一个子链表,并且返回新的头与尾
pair<ListNode*, ListNode*> myReverse(ListNode* head, ListNode* tail) {
ListNode* prev = tail->next;
ListNode* p = head;
while (prev != tail) {
ListNode* nex = p->next;
p->next = prev;
prev = p;
p = nex;
}
return {tail, head};
}
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* hair = new ListNode(0);
hair->next = head;
ListNode* pre = hair;
while (head) {
ListNode* tail = pre;
// 查看剩余部分长度是否大于等于 k
for (int i = 0; i < k; ++i) {
tail = tail->next;
if (!tail) {
return hair->next;
}
}
ListNode* nex = tail->next;
// 这里是 C++17 的写法,也可以写成
// pair<ListNode*, ListNode*> result = myReverse(head, tail);
// head = result.first;
// tail = result.second;
tie(head, tail) = myReverse(head, tail);
// 把子链表重新接回原链表
pre->next = head;
tail->next = nex;
pre = tail;
head = tail->next;
}
return hair->next;
}
};
最长回文子串:中心扩散法
class Solution {
public:
pair<int, int> expandAroundCenter(const string& s, int left, int right) {
while (left >= 0 && right < s.size() && s[left] == s[right]) {
--left;
++right;
}
return {left + 1, right - 1};
}
string longestPalindrome(string s) {
int start = 0, end = 0;
for (int i = 0; i < s.size(); ++i) {
auto [left1, right1] = expandAroundCenter(s, i, i);
auto [left2, right2] = expandAroundCenter(s, i, i + 1);
if (right1 - left1 > end - start) {
start = left1;
end = right1;
}
if (right2 - left2 > end - start) {
start = left2;
end = right2;
}
}
return s.substr(start, end - start + 1);
}
};
字符串首次出现3次的字符
哈希表就行,遍历字符串,存放在map中,并判断value==3就返回key
#include<iostream>
#include<unordered_map>
using namespace std;
string str;
cin>>str;
unordered_map<char,int>um;
for(auto &i:str){
um[i]++;
if(i.second==3){
cout<<i.first<<endl;
}
}
数组中只出现一次的数字
//时间复杂度O(n)
//空间复杂度O(n)
#include<iostream>
#include<unordered_map>
#include<vector>
using namespace std;
class Solution {
public:
int singleNumber(vector<int>& nums) {
unordered_map<int,int>mp;
int ans=0;
for(auto &i:nums){
mp[i]++;
}
for(auto &i:mp){
if(i.second==1){
ans=i.first;
}
}
//for(auto &[key,value]:mp){
// if(value==1){
// ans=key;
// break;
// }
//}
return ans;
}
};
合并2个有序数组
法一排序
将nums2放到nums1中,直接排序
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
for(int i=0;i<n;i++){
nums1[m++]=nums2[i];
}
sort(nums1.begin(),nums1.end());
}
};
法二,双指针
逆向双指针,尾部覆盖
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1=m-1,p2=n-1;
int tail=m+n-1;
while(p1>=0||p2>=0){
if(p1==-1){
nums1[tail--]=nums2[p2--];
}
else if(p2==-1){
nums1[tail--]=nums1[p1--];
}
else if(nums1[p1]<nums2[p2]){
nums1[tail--]=nums2[p2--];
}
else{
nums1[tail--]=nums1[p1--];
}
}
}
};
435.不重叠区间
先计算不重叠的区间数,然后用总的区间数—重叠的=要去掉的,记得自定义排序规则
class Solution {
public:
/// static bool cmp(vector<int>&a,vector<int>&b){
// return a[1]<b[1];
//}
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
//sort(intervals.begin(),intervals.end(),cmp);
sort(intervals.begin(),intervals.end(),[](const auto &a,const auto &b){
return a[1]<b[1];
});
int count=1;
int right=intervals[0][1];
for(int i=1;i<intervals.size();i++){
if(intervals[i][0]>=right){
count++;
right=intervals[i][1];
}
}
return intervals.size()-count;
}
};
73.矩阵置零
2个标记数组,如果有元素为0,元素所在的行列所对应的标记数组的位置置true,用标记数组更新原数组
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
int m=matrix.size();
int n=matrix[0].size();
vector<bool>row(m),col(n);
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(!matrix[i][j]){
row[i]=col[j]=true;
}
}
}
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(row[i]||col[j]){
matrix[i][j]=0;
}
}
}
}
};
分割字符串,知道字符数量
输入一个字符串,求[“xiao”,“mi”]的组和有多少种
比如 输入:“I love xiaomi, I think mi com is the best"
输出:2
计算输入字符串中指定子字符串组的数量
#include <iostream>
#include <vector>
#include <string>
//s输入字符串
int countSubstringGroups(const std::string& s, const std::vector<std::string>& substrings) {
int count = 0;
size_t startIndex = 0;
while (true) {
bool found = false;
//遍历目标串
for (const auto& substring : substrings) {
size_t index = s.find(substring, startIndex);
if (index != std::string::npos) {
found = true;
startIndex = index + substring.length();
break;
}
}
if (!found) {
break;
}
bool allNotFound = true;
for (const auto& substring : substrings) {
if (s.find(substring, startIndex) != std::string::npos) {
allNotFound = false;
break;
}
}
if (allNotFound) {
count++;
}
}
return count;
}
int main() {
// 输入字符串
std::string inputStr = "I love xiaomi, I think mi com is the best";
// 目标子字符串列表
std::vector<std::string> targetSubstrings = {"xiao", "mi"};
// 计算组数
int result = countSubstringGroups(inputStr, targetSubstrings);
// 输出结果
std::cout << result << std::endl;
return 0;
}
划分3*3矩阵
有一个m*n的字符矩阵,
要求:3*3的连续子矩阵里面x,a,o,m至少出现1次,i至少出现2次
求这样的3*3的矩阵有多少?
比如输入4*4的话
x i a q
o i m e
x a i c
c a d f
输出结果为2,有2个符合条件的连续子矩阵
x i a
o i m
x a i
o i m
x a i
c a d
#include <iostream>
#include <vector>
using namespace std;
bool isValidSubmatrix(const vector<vector<char>>& matrix, int startRow, int startCol) {
int count_x = 0, count_a = 0, count_o = 0, count_m = 0, count_i = 0;
for (int i = startRow; i < startRow + 3; ++i) {
for (int j = startCol; j < startCol + 3; ++j) {
char currentChar = matrix[i][j];
if (currentChar == 'x') count_x++;
else if (currentChar == 'a') count_a++;
else if (currentChar == 'o') count_o++;
else if (currentChar == 'm') count_m++;
else if (currentChar == 'i') count_i++;
else{
continue;
}
}
}
return (count_x >= 1 && count_a >= 1 && count_o >= 1 && count_m >= 1 && count_i >= 2);
}
int countSubmatrices(const vector<vector<char>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
int count = 0;
for (int i = 0; i < m - 2; ++i) {
for (int j = 0; j < n - 2; ++j) {
if (isValidSubmatrix(matrix, i, j)) {
count++;
}
}
}
return count;
}
int main() {
// 示例 4x4 的字符矩阵
vector<vector<char>> matrix = {
{'x', 'i', 'a', 'q'},
{'o', 'i', 'm', 'e'},
{'x', 'a', 'i', 'c'},
{'c', 'a', 'd', 'f'}
};
int result = countSubmatrices(matrix);
cout << "满足条件的3x3子矩阵数量为: " << result << endl;
return 0;
}
42.接雨水
动态规划
1)统计每个下标左边的最大值,正向遍历
2)统计每个下标右边的最大值,反向遍历
3)计算每个下标的储水量,累加
//时间复杂度O(n)
//空间复杂度O(n)
class Solution {
public:
int trap(vector<int>& height) {
if(height.size()<=2) return 0;
vector<int>leftHeight(height.size(),0);
vector<int>rightHeight(height.size(),0);
int size=rightHeight.size();
leftHeight[0]=height[0];
for(int i=1;i<size;i++){
leftHeight[i]=max(height[i],leftHeight[i-1]);
}
rightHeight[size-1]=height[size-1];
for(int i=size-2;i>=0;i--){
rightHeight[i]=max(height[i],rightHeight[i+1]);
}
int sum=0;
for(int i=0;i<size;i++){
int h=min(leftHeight[i],rightHeight[i])-height[i];
if(h>0) sum+=h;
}
return sum;
}
};
有序链表
法一,先放到数组
class Solution
{
public:
ListNode* sortList(ListNode* head)
{
if(head==nullptr) return head;
vector<ListNode*>v;
ListNode*cur=head;
while(cur)
{
v.push_back(cur);
cur=cur->next;
}
sort(v.begin(),v.end(),[](ListNode*a,ListNode*b){return a->val<b->val;});
cur=v[0];
for(int i=1;i<v.size();++i)
{
cur->next=v[i];
cur=v[i];
}
cur->next=nullptr;
return v[0];
}
};
法二:合并2个有序链表,中点划分链表
class Solution
{
public:
ListNode* sortList(ListNode* head)
{
return sortList(head,nullptr);
}
ListNode*sortList(ListNode*head,ListNode*tail)
{
if(head==nullptr)
{
return head;
}
if(head->next==tail)
{
head->next=nullptr;
return head;
}
//找中点,以中点位2分界拆分成2个子链表,快慢指针
ListNode*slow=head,*fast=head;
while(fast!=tail)
{
slow=slow->next;
fast=fast->next;
if(fast!=tail)
{
fast=fast->next;
}
}
ListNode*mid=slow;
return merge(sortList(head,mid),sortList(mid,tail));
}
//合并两个有序链表
ListNode*merge(ListNode*head1,ListNode*head2)
{
ListNode*dummyHead=new ListNode(0);
ListNode*temp=dummyHead,*temp1=head1,*temp2=head2;
while(temp1!=nullptr&&temp2!=nullptr)
{
if(temp1->val<=temp2->val)
{
temp->next=temp1;
temp1=temp1->next;
}
else
{
temp->next=temp2;
temp2=temp2->next;
}
temp=temp->next;
}
if(temp1!=nullptr)
{
temp->next=temp1;
}
if(temp2!=nullptr)
{
temp->next=temp2;
}
return dummyHead->next;
}
};
一个数恰好等于它的因子之和
比如6=1+2+3,称为完数,问1-10000有多少个完数
#include <iostream>
#include <vector>
//找因子
std::vector<int> findFactors(int num) {
std::vector<int> factors = {1}; // 1 是所有正整数的因子
for (int i = 2; i <= num / 2; ++i) {
if (num % i == 0) {
factors.push_back(i);
}
}
return factors;
}
//找出所有完数
std::vector<int> findPerfectNumbers(int limit) {
std::vector<int> perfectNumbers;
for (int num = 2; num <= limit; ++num) {
if (std::find(perfectNumbers.begin(), perfectNumbers.end(), num) != perfectNumbers.end()) {
continue; // 避免重复计算已经找到的完数
}
std::vector<int> factors = findFactors(num);
int sum = 0;
for (int factor : factors) {
sum += factor;
}
if (sum == num) {
perfectNumbers.push_back(num);
}
}
return perfectNumbers;
}
int main() {
int limit = 10000;
std::vector<int> perfectNumbersList = findPerfectNumbers(limit);
std::cout << "在1到" << limit << "之间有" << perfectNumbersList.size() << "个完数,它们分别是:" << std::endl;
for (int num : perfectNumbersList) {
std::cout << num << " ";
}
std::cout << std::endl;
return 0;
}
大数相减
// 函数用于模拟字符串相加,包括进位处理
string addStrings(string num1, string num2) {
int carry = 0;
string result;
// 让两个字符串等长,短的字符串前面补0
int len = max(num1.length(), num2.length());
num1 = string(len - num1.length(), '0') + num1;
num2 = string(len - num2.length(), '0') + num2;
for (int i = len - 1; i >= 0; i--) {
int digit1 = num1[i] - '0';
int digit2 = num2[i] - '0';
int sum = digit1 + digit2 + carry;
carry = sum / 10;
result += to_string(sum % 10);
}
if (carry > 0) {
result += to_string(carry);
}
reverse(result.begin(), result.end()); // 反转结果字符串
return result;
}
浮点数相加
//字符串转浮点stod
//自定义函数用于将浮点数字符串转换为 double
double stringToDouble(const std::string& str) {
double result = 0.0;
bool isNegative = false;
bool isFraction = false;
double fractionMultiplier = 0.1;
for (char ch : str) {
if (ch == '-') {
isNegative = true;
} else if (ch == '.') {
isFraction = true;
} else {
if (isFraction) {
result += (ch - '0') * fractionMultiplier;
fractionMultiplier *= 0.1;
} else {
result = result * 10 + (ch - '0');
}
}
}
return isNegative ? -result : result;
}
统计元音字母个数
对偶性质
class Solution {
public:
long long countVowels(string word) {
int n = word.size();
unordered_set<char> vowels = {'a', 'e', 'i', 'o', 'u'};
long long ans = 0;
for (int i = 0; i < n; ++i) {
if (vowels.count(word[i])) {
ans += (long long)(i + 1) * (n - i);
}
}
return ans;
}
};
双链表模拟
假设双链表把一个 q 所指新结点作为非空双向链表中的 p 所指结点的前驱结点插入到该双链表中,算法步骤。
q->Link=p->prev;
p->Link=q;
q->Rink=p;
q->Link->Rlink=q;
输入的句子使用find函数找出NiuNiu,没有则输出-1;限定Python语言
input_sentence = input("请输入句子:") # 获取用户输入的句子
# 使用find方法查找子字符串
index = input_sentence.find("NiuNiu")
if index != -1:
print("NiuNiu 在句子中的索引是:", index)
else:
print("NiuNiu 未在句子中找到")
字符串排序
2.给定几个字符串,请你分类,对于同一类字符串,可以通过N次交换其中的元素变为一样的,如ABCD和CDBA,不限定交换次数;
输入
4
ABCD
BCDA
ACBD
CDAB
输出
1
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <set>
int classifyStrings(std::vector<std::string> strings) {
std::set<std::string> sortedStrings;
for (const std::string& str : strings) {
std::string sortedStr = str;
std::sort(sortedStr.begin(), sortedStr.end());
sortedStrings.insert(sortedStr);
}
return sortedStrings.size();
}
int main() {
int n;
std::cin >> n;
std::vector<std::string> strings(n);
for (int i = 0; i < n; i++) {
std::cin >> strings[i];
}
int result = classifyStrings(strings);
std::cout << result << std::endl;
return 0;
}
105.岛屿的最大面积
class Solution {
int dfs(vector<vector<int>>& grid, int cur_i, int cur_j) {
if (cur_i < 0 || cur_j < 0 || cur_i == grid.size() || cur_j == grid[0].size() || grid[cur_i][cur_j] != 1) {
return 0;
}
grid[cur_i][cur_j] = 0;
int di[4] = {0, 0, 1, -1};
int dj[4] = {1, -1, 0, 0};
int ans = 1;
for (int index = 0; index != 4; ++index) {
int next_i = cur_i + di[index], next_j = cur_j + dj[index];
ans += dfs(grid, next_i, next_j);
}
return ans;
}
public:
int maxAreaOfIsland(vector<vector<int>>& grid) {
int ans = 0;
for (int i = 0; i != grid.size(); ++i) {
for (int j = 0; j != grid[0].size(); ++j) {
ans = max(ans, dfs(grid, i, j));
}
}
return ans;
}
};
641.双端队列模拟
class MyCircularDeque {
private:
vector<int> elements;
int rear, front;
int capacity;
public:
MyCircularDeque(int k) {
capacity = k + 1;
rear = front = 0;
elements = vector<int>(k + 1);
}
bool insertFront(int value) {
if (isFull()) {
return false;
}
front = (front - 1 + capacity) % capacity;
elements[front] = value;
return true;
}
bool insertLast(int value) {
if (isFull()) {
return false;
}
elements[rear] = value;
rear = (rear + 1) % capacity;
return true;
}
bool deleteFront() {
if (isEmpty()) {
return false;
}
front = (front + 1) % capacity;
return true;
}
bool deleteLast() {
if (isEmpty()) {
return false;
}
rear = (rear - 1 + capacity) % capacity;
return true;
}
int getFront() {
if (isEmpty()) {
return -1;
}
return elements[front];
}
int getRear() {
if (isEmpty()) {
return -1;
}
return elements[(rear - 1 + capacity) % capacity];
}
bool isEmpty() {
return rear == front;
}
bool isFull() {
return (rear + 1) % capacity == front;
}
};
141.环形链表
判断是否有环
class Solution {
public:
bool hasCycle(ListNode *head) {
ListNode*slow=head,*fast=head;
while(fast&&fast->next){
slow=slow->next;
fast=fast->next->next;
if(slow==fast){
return true;
}
}
return false;
}
};
142.环形链表 II
判断环的位置
相遇之后,slow指针回到出发点,一起走就行
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode*fast=head,*slow=head;
while(fast!=NULL&&fast->next!=NULL){
fast=fast->next->next;
slow=slow->next;
if(slow==fast){
ListNode*cur=slow;
ListNode*temp=head;
while(cur!=temp){
temp=temp->next;
cur=cur->next;
}
return temp;
}
}
return NULL;
}
};
怎么判断顺子5个
1)法一,先sort,然后遍历从第2个开始,看当前的是不是等于前一个+1
2)法二:放到set里面,判断最大值-最小值<5,且set的size=5
83.删除链表中的重复元素
1-2-2删除后为1-2
法一, 借助数组
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
vector<int>ans;
ListNode*cur=head;
while(cur){
ans.push_back(cur->val);
cur=cur->next;
}
ans.erase(unique(ans.begin(),ans.end()),ans.end());
ListNode*dummyNode=new ListNode(0);
ListNode*now=dummyNode;
for(int i=0;i<ans.size();i++){
now->next=new ListNode(ans[i]);
now=now->next;
}
return dummyNode->next;
}
};
法二,一次遍历
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==NULL||head->next==NULL){
return head;
}
ListNode*cur=head;
while(cur->next!=NULL){
if(cur->val==cur->next->val){
cur->next=cur->next->next;
}
else{
cur=cur->next;
}
}
return head;
}
};
82.删除排序链表中的重复元素 II
删除链表中所有重复出现过的数,即1-2-2删除后为1
//一次遍历
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* dummy = new ListNode(0, head);
ListNode* cur = dummy;
while (cur->next && cur->next->next) {
if (cur->next->val == cur->next->next->val) {
int x = cur->next->val;
while (cur->next && cur->next->val == x) {
cur->next = cur->next->next;
}
}
else {
cur = cur->next;
}
}
return dummy->next;
}
};
//哈希集合
class Solution
{
public:
ListNode* deleteDuplicates(ListNode* head)
{
unordered_set<int>us;
ListNode*s=head;
//将链表中的重复元素的值放入哈希集合
while(s!=NULL&&s->next!=NULL)
{
if(s->val==s->next->val)
us.insert(s->val);
s=s->next;
}
//遍历链表,与哈希集合比较,重复则删除
ListNode* prehead=new ListNode(-1);
prehead->next=head;
ListNode*pre=prehead;
s=head;
while(s!=NULL)
{
if(us.find(s->val)==us.end())//没有重复结点
{
s=s->next;
pre=pre->next;
}
else
{
pre->next=s->next;
s=s->next;
}
}
return prehead->next;
}
};
已经排好序的数组,找两个和为num的数字
//二分查找
//数组中的元素最多遍历一次,时间复杂度为 O(n*log(n))。
//只使用了两个额外变量,空间复杂度为 O(1)。
class Solution
{
public:
vector<int> twoSum(vector<int>& numbers, int target)
{
for (int i=0;i<numbers.size();++i)
{
int low=i+1,high=numbers.size()-1; //细节i+1
while(low<=high)
{
int mid=low+(high-low)/2;
if(numbers[mid]==target-numbers[i])
{
return {i+1,mid+1};
}
else if(numbers[mid]>target-numbers[i])
{
high=mid-1;
}
else
{
low=mid+1;
}
}
}
return {-1,-1};
}
};
补充知识
set_union并集
求两个有序序列的并集,合并结果包含所有的不重复元素,并拷贝到一个新的序列,前提是这两个序列的排序规则一样。
set_intersection交集
求两个有序序列的交集,合并结果是两个集合重合的元素,并拷贝到一个新的序列,前提是这两个序列的排序规则一样。
set_difference差集
求两个有序序列的差集,合并结果是在第一个集合中存在而在第二个集合中不存在的元素,并拷贝到一个新的序列,前提是这两个序列的排序规则一样。
set_symmetric_difference异或集
求两个有序序列的异或集,合并结果是两个序列合并后去除重合的元素,并拷贝到一个新的序列,前提是这两个序列的排序规则一样。