字节跳动byteDance

一、第一次笔试

1.完美字符串

题目描述:某天你得到了一个长度为n(1<=n<=500000)的字符串,并且这个字符串只包含小写字母。现在允许你修改m(1<=m<=n)个位置的字母,修改完毕你要选取这个字符串的一个连续子串,如果这个子串只包含一种字母,那么这个连续子串是一个完美字符串。你希望得到的完美字符串长度尽可能长,请计算出你所能得到的最长长度是多少。
样例输入:8 1
      aabaabaa
样例输出:5
做题思路:首先遍历,是26个字母中哪个字母组成的完美字符。典型的双指针问题,左指针指向起始的位置,右指针指向结束的位置。用一个变量记录,修改了几次,如果没有超过的话,右指针继续向右移动,如果超过了允许修改的次数,将左指针右移,直到第一次修改的地方,修改次数减一,然后取所有符合条件的字符串的最大长度。注意:两个指针之间字符串的长度,判断left指针移动的位置,当前字母不等,left应该移到下一位开始算新的完美字符的长度。

#include <bits/stdc++.h>

using namespace std;

int main() {
    int n, m; cin >> n >> m;
    string s; cin >> s;
    int ans = 0;
    for (int i = 0; i < 26; ++i) { // 尝试看每一个字母
        int left = 0, used = 0; // left表示左指针,used表示当前修改了几次
        for (int right = 0; right < n; ++right) { // 右指针,
            if (s[right] != 'a' + i) { // s[right]不是我们要的字母,用一次修改机会
                used++;
            }
            while (used > m) { // 检查目前的修改次数是否超出了预算
                if (s[left++] != 'a' + i) { // 持续挪动左指针,直到修改次数不超过m
                    used--;
                }
            }
            ans = max(ans, right - left + 1); // left到right之间的字符串是满足要求的
        }
    }
    cout << ans << endl;
    return 0;
}

2.最优连续子序列

题目描述:给定一个长度为n的整数序列,找出其中的一段连续子序列,使得该段连续子序列的交替和最大。序列的交替和为
在这里插入图片描述
输入描述:输入的第一行为一个正整数n(n<=1e5),表示序列的长度,输入的第二行为n个整数。
输出描述:输出一个整数,表示最大的交替和
样例:输入5
      1 2 3 4 5
   输出 5
做题思路:另外一个动态规划的经典题目,最大子序和。
力扣官方题解

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, maxAns = nums[0];
        for (const auto &x: nums) {
            pre = max(pre + x, x);
            maxAns = max(maxAns, pre);
        }
        return maxAns;
    }
};

对于这道题,求得是最大交替和,可以用两个数组,对于同一个元素,可以是正也可以是负数,可以套用这个动态规划代码。对于动态规划问题,首先是应该将大问题转化为多个小问题,写出状态转移函数和边界条件。本题目的解法,直接给输入乘上-1的幂次方,即可完成交替和的需求,相对比较简单。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int n,m;
    cin>>n;
    vector<int > num(n);
    vector<int > renum(n);

    for(int i=0;i<n;i++){
        cin>>m;
        num[i]=pow(-1,i)*m;
        renum[i]=-num[i];
    }
    int pre=0,repre=0,maxVal=num[0],remaxVal=renum[0];
    for(const int x:num)
    {
        pre=max(pre+x,x);
        maxVal=max(maxVal,pre);
    }
    for(const int y:renum)
    {
        repre=max(repre+y,y);
        remaxVal=max(remaxVal,repre);
    }

    cout<<maxVal<<endl;
    cout<<remaxVal<<endl;
    cout<<maxVal+remaxVal<<endl;
    return 0;
}

3.多米诺骨牌

题目描述:小高最近迷上了玩多米诺骨牌:沿直线将一长串排摆放起来,推倒一个引起连锁反应,非常有趣。现在有n块牌,每张牌都有各自的高度和宽度(分别记为h和w)。小高的摆放规则是,后面的牌的高度和宽度必须都大于前面的牌,请问小高用这n张牌最多能选出多少张组成一个最长牌阵呢?
输入描述:第一行一个整数n,接下来n行,每行两个整数h和w。(所有牌中没有长度相同或者宽度相同的两张牌)
输出描述:一个数x,表示最多能选出x张组成最长牌阵
样例:输入5
     5 5,3 1,2 6,4 2,1 4;
   输出3
做题思路:两个牌能放到一起当且仅当后面的牌的高度和宽度必须都大于前面的牌。如果我们将所有的牌按照宽度排序的话,那我们只需要找高度的最长上升子序列就可以了。最长上升子序列是一个经典的问题,一般的解法有两种,一种是动态规划解法,复杂度是O(n^2)的;另外一种是基于二分查找的,复杂度是O(nlogn)。
最长上升子序列的动态规划写法:维护一个等长的数组dp,初始化均为1,因为最短的最长上升子序列就是自己一个,在索引小于当前值中遍历,如果小于,则当前dp[i]加1,最终取dp数组中的最大值,即是最长上升子序列。但是时间复杂度为n^2,因为每次都要遍历之前的数值是否大于当前数值。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int num,ans;
    vector<int>  nums;
//    while(cin>>num)
//    {
//        nums.push_back(num);
//    }

    while (1) {
        cin >> num;
        nums.push_back(num);//每输入一个数字就把它添加到数组的最后
        if (cin.get() == '\n')//如果是回车符则跳出循环
        break;
    }
    int len=nums.size();
    if(len==0) return 0;
    vector<int> dp(len,0);
    for(int i=0;i<len;++i)
    {
        dp[i]=1;
        for(int j=0;j<i;j++)
        {
            if(nums[j]<nums[i])
                dp[i]=max(dp[i],dp[j]+1);
        }
    }
    ans=*max_element(dp.begin(),dp.end());
    cout<<ans<<endl;
    return 0;
}

最长上升子序列的二分+贪心写法:要使得数组最长,需要增长的慢。维护一个一维动态数组dp,存储的是nums[i],即值,不是索引。若当前数大于前一个数值的时候,加到末尾,同时长度加一,但如果小于,就要去找最适合插入的位置,插入在第一个比当前值小的位置之后,若遍历没找到,就说明所有值都比当前值大,这时候就直接插到首位。由于dp是单调增的数组,所以可以采用二分法去寻找应该插入的位置,时间复杂度降低为nlogn。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int num,ans;
    vector<int>  nums;
    while (1) {
        cin >> num;
        nums.push_back(num);
        if (cin.get() == '\n')
        break;
    }
    int index=1;
    int len=nums.size();
    if(len==0) return 0;
    vector<int> dp(len+1,0);  //不是从0位开始存储的,最终要返回该长度
    dp[index]=nums[0];
    for(int i=1;i<len;i++)
    {
        if(nums[i]>dp[index])
            dp[++index]=nums[i];
        else{
            int left=1,right=index,pos=0;//考虑所有数都比当前数大
            while(left<=right)
            {
                int mid=left+(right-left)/2;
                if(dp[mid]<nums[i])
                {
                    pos=mid;
                    left=mid+1;
                }
                else{
                    right=mid-1}
            }
            dp[pos+1]=nums[i];
        }
    }
    cout<<index<<endl;
    return 0;

}

现在开始来解这道题,首先让宽度按升序排列,然后求高度的最大升序子序列。

#include <bits/stdc++.h>

using namespace std;

struct Domi{
    int h;
    int w;

}domi[10000];

bool order(Domi domi1,Domi domi2)
{
    return domi1.w<domi2.w;
}


int main()
{
    int n;
    cin>>n;

    for(int i=0;i<n;i++)
    {
        cin>>domi[i].h>>domi[i].w;
    }

    sort(domi,domi+n,order);

    int dp[n];
    int ans=0;
    for(int i=0;i<n;++i)
    {
        dp[i]=1;
        for(int j=0;j<i;++j)
        {
            if(domi[j].w<domi[i].w && domi[j].h<domi[i].h)
            dp[i]=max(dp[i],dp[j]+1);

        }
        ans=max(ans,dp[i]);

    }

    cout<<ans<<endl;
    return 0;

}

4.生产调度

题目描述:工厂中有N台机器,每台机器由于产出的时间不同,其生产能力也不一样。有一天,工厂接到了任务要生产N个产品。请你计算出一共有多少种生产方案。(每台机器生产一个产品,问有多少种匹配方式)。
输入:输入的第一行为一个正整数N(N<=1e5),给出了机器的数量(即所需生产的产品数),输入的第二行为N个正整数,第i个整数Ai代表着机器i的生产能力(只能生产型号<=Ai)的产品,Ai<=1e9,输入的第三行为N个正整数,第i个整数Bi代表着产品i的型号,输入的第四行有一个整数P(P<=1e9)。
输出:输出一个整数,为生产方案数 % P的结果。
样例:3;3 3 3;1 1 1;100
6
做题思路:因为最后要生产N种产品,而每个机器生产能力不同。因此我们优先考虑生产能力低的机器,快速找出当前机器能生产产品的总数。将机器能力和产品型号从小到大排序,枚举每个机器的同时二分找出能生产型号的数量,对于每个机器要减去之前机器生产过的数量,最后每个机器能生产的数量连乘。时间复杂的O(nlogn)。

这道题不想写。。。。

二、第二次笔试

1.二叉树的叶子结点数

题目描述:给定一颗二叉树,二叉树每个节点都有唯一的正整数值代表节点,在遍历时,我们使用节点的数值作为标记。给定二叉树的前序和中序遍历结果,求二叉树的叶子节点个数。
输入:第一行,输入二叉树节点个数N,其中0 < N < 30000,第二行与第三行,分别输入二叉树的前序和中序遍历结果,每个节点对应唯一整数值。
输出:二叉树叶子节点个数。
样例:3;1 3 4;3 1 4------->2

这道题不会!

2.编码协议

题目描述:小明在学习一种编码协议,二进制数据可以用一串16进制的字符来表示(0-9, A-F),其中的“0010”是这个协议的特殊字符串,不允许出现。现在给出一串16进制的字符串s,问:最少去掉几个字符,使得剩下的字符串中不存在“0010”。
输入:第一行t(1<= t <= 1000),表示有t组测试数据,第一行t(1<= t <= 1000),表示有t组测试数据。
输出:每个样例一行,输出最少去掉字符数
样例:输入2
     0123456789ABCDEF
     0010123456789ABCDEF00
   输出0
     1
做题思路:如果在字符串中出现了0010字符串,就将其修改一位,使其不出现就好了,但是也要考虑到001010这种情况,就是去掉第一个1,后面的依然是0010,这种情况是,如果0010后面一位数是1,那么就应该修改0使其变成1就行。即代码功能是统计出现几次字符串0010,下一次搜索从上一次搜索的末尾开始。如果搜索长度大于当前字符串长度,就break跳出去。

#include <bits/stdc++.h>

using namespace std;

int main()
{
    int n;
    cin>>n;
    int con;
    string strs;
    string target="0010";
    while(n--)
    {
        cin>>strs;
        size_t found=strs.find(target,0);
        while(found!=string::npos)
        {
            con++;
            found+=3;
            if(found>strs.size())
                break;
            else
                found=strs.find(target,found);

        }

        cout<<con<<endl;
    }
    return 0;

}

注:(1)size_t是一种“整型”类型,里面保存的是一个整数,就像int, long那样。这种整数用来记录一个大小(size)。size_t的全称应该是size type,就是说“一种用来记录大小的数据类型”。
(2)查找字符串a是否包含子串b,不是用strA.find(strB) > 0而是strA.find(strB) != string:npos,写法string::size_type pos = strA.find(strB);if(pos !=string::npos){}。string::find()函数:是一个字符或字符串查找函数,该函数有唯一的返回类型,即string::size_type,即一个无符号整形类型,可能是整数也可能是长整数。如果查找成功,返回按照查找规则找到的第一个字符或者子串的位置;如果查找失败,返回string::npos,即-1。

3.插入广告

题目描述:某视频网站有N个视频,每个视频时长为Li秒。产品经理找到你,希望你帮忙在其中插入M个广告。一个视频里的两个广告必须间隔一段时间,当然间隔时间可以为0。为了用户体验,他希望这个间隔时间尽可能长。为了方便实现,间隔时间是一个整数。请你帮忙计算一下,这个间隔时间最大可以设置为多少秒呢?如果不能插入广告,则输出0。
输入:第一行有两个整数N, M(1<=N<=100000, N<M<=5000000),第二行有N个整数,表示视频的时长。
输出:一个整数,表示最大的间隔时间
样例:输入3 9
     90 100 120
   输出45
做题思路:这种题目的内核是在一定条件限制下的最大值,力扣相关题型是410分割数组的最大值,875爱吃香蕉的珂珂。只要是有序的数组查找,即使是局部有序,就可以用二分法,缩短时间复杂度。
力扣410分割数组的最大值
思路:题目给出保证,数组是非负整数,要求连续的非空子数组的最大值,分的组数越多,子数组的最大值越小,所以是一个负相关的关系。假设子数组和的最大值为maxnum,那么maxnum的最大值就是所给数组全部元素相加,最小值就是当前数组元素的最大值。我们可以给出一个target,要求子数组和不能超过这个值,一旦超过,就从当前开始,重新分出一个子数组,并把子数组的统计量加1,最后看子数组数量有没有超出限制。用mid去逼近最小的子数组和的最大值。

#include <bits/stdc++.h>

using namespace std;

bool isOverflow(vector<int> nums,int mid,int m)
{
    int con=1;   //最少也有一个数组
    long sum=0;
    for(int i=0;i<nums.size();++i)
    {
        if(sum+nums[i]>mid)
        {
            con++;
            sum=nums[i];    //理解循环次序,应该等于i还是i+1
        }
        else
             sum+=nums[i];
    }
    return con<=m;   //只要不超过就行

}

int main()
{
    vector<int> nums;
    int m,num;
    while (1)   //输入不定长度的数组
    {
        cin >> num;
        nums.push_back(num);
        if (cin.get() == '\n')
        break;
    }
    cin>>m;
    int len=nums.size();
    int left=0,right=0;
    for(int i=0;i<len;++i)
    {
        right+=nums[i];
        if(left<nums[i])
            left=nums[i];
    }

    while(left<right)
    {
        int mid=left+(right-left)/2;
        bool flag=isOverflow(nums,mid,m);
        if(flag)
            right=mid;    //为什么右指针这样写,是因为只要不超过m就行,取的是小于等于
        else
            left=mid+1;
    }

    cout<<left<<endl;


    return 0;
}

力扣875爱吃香蕉的珂珂
思路:珂珂吃香蕉的速度越快,使用的时间越短,速度越慢,时间越长,这是一个负相关的单调关系。要求的速度是一个范围内的最小整数,故可以使用二分法。所需时间最长的是,香蕉最多的那一堆,遍历的下界可以设为1。当不能整除速度的时候,剩下的一点需要向上取整。若时间恰好等于规定时间,还应该去找是否有更慢的速度。

#include <bits/stdc++.h>

using namespace std;

int timeCon( vector<int> piles,int mid)
{
    int sum=0;
    for(int pile:piles)
    {
        sum+=(pile+mid-1)/mid;
    }
    return sum;
}

int main()
{
    vector<int> piles;
    int H,pile;
    while(1)
    {
        cin>>pile;
        piles.push_back(pile);
        if(cin.get()=='\n')
            break;
    }
    cin>>H;
    int maxVal=0,left=1,right=0;
    //for(int i=0;i<piles.size();++i)
    for(int pile:piles)
    {
        maxVal=max(maxVal,pile);
    }
    right=maxVal;

    while(left<right)
    {
        int mid=left+(right-left)/2;
        int time=timeCon(piles,mid);
        if(time>H)
            left=mid+1;
        else
            right=mid;
    }

    cout<<left<<endl;
    return 0;

}

现在开始做这道题,就是要找最大的间隔时间。找到可以插入次数的最小值刚好等于题目所需的次数,就可以使间隔时间是最大值。注意二分的写法,是在有可能存在结果的区间段取值。

#include <bits/stdc++.h>
using namespace std;
int main()
{
    int N,M,pile;
    vector<int> piles;
    cin>>N>>M;
    for(int i=0;i<N;i++)
    {
        cin>>pile;
        piles.push_back(pile);
    }
    int left=0,right=0,timeGap=0;
    for(int i=0;i<N;++i)
    {
        right=max(right,piles[i]);
    }
    while(left<=right)
    {
        int mid=left+(right-left)/2;
        int timeSum=0;
        for(int i=0;i<N;++i)
        {
//            if(piles[i]%mid==0)
//                timeSum+=(piles[i]/mid+1);
//            else
//                timeSum+=piles[i]/mid;
           // timeSum+=(piles[i]+mid-1)/mid;
           timeSum+=(piles[i]/mid+1);     //怎么算总共可以插入的广告次数
        }
        if(timeSum>=M)
            //cout<<mid<<endl;
        {
            timeGap=mid;      //区间范围是大于等于当前次数都可以,但是要找到次数一个最小值,以使得间隔时间达到最大值
            left=mid+1;
        }
//        else if(timeSum>M)
//            left=mid+1;
        else
            right=mid-1;
    }
    cout<<timeGap<<endl;
    return 0;

}

4.求余的最大值

题目描述:在n(1<=n<=35)个正整数中,任意挑选k个(不可重复挑选,0<=k<=n)数字的和记为sum,另有一个正整数m,请问sum%m的最大值是多少?
输入:第一行两个整数,分别为n, m,第一行两个整数,分别为n, m
输出:一个数x,x表示sum%m的最大值
样例:输入5 5
     1 2 3 4 5
   输出4

分析在这里插入图片描述

#include <bits/stdc++.h>

using namespace std;
using ll = long long;

vector<ll> a, x, y;
ll m;

void dfs(vector<ll> &vec, int index, int end, ll sum) {
    if (index == end) {
        return;
    }
    vec.emplace_back(sum);
    dfs(vec, index + 1, end, vec.back());
    vec.emplace_back((sum + a[index]) % m);
    dfs(vec, index + 1, end, vec.back());
}

int main() {
    int n;
    ll d, sum = 0;
    cin >> n >> m;
    for (int i = 0; i < n; i++) {
        cin >> d;
        d %= m;
        if (d != 0) {
            a.emplace_back(d);
            sum += d;
        }
    }
    if (sum < m) {
        cout << sum << endl;
        return 0;
    }
    n = a.size();
    dfs(x, 0, n >> 1, 0); // 得到前半部分的元素可以组成的和
    dfs(y, n >> 1, n, 0); // 得到后半部分的元素可以组成的和
    sort(x.begin(), x.end()); // 排序
    sort(y.begin(), y.end());
    x.erase(unique(x.begin(), x.end()), x.end()); // 去重
    y.erase(unique(y.begin(), y.end()), y.end());
    auto left = x.begin();
    auto right = y.rbegin();
    ll ans = max(x.back(), y.back()), pre = 0;
    while (ans < m - 1 && left != x.end() && right != y.rend()) {
        ll temp = (*left + *right) % m;
        ans = max(ans, temp);
        if (temp > pre) {
            ++left;
        } else {
            --right;
        }
    }
    cout << ans << endl;
    return 0;
}

三、第三次笔试

四、第四次笔试

五、0922面试15:00

想不起来了
1、define和const区别
区别:
(1)就起作用的阶段而言: #define是在编译的预处理阶段起作用,而const是在编译、运行的时候起作用
(2)就起作用的方式而言: #define只是简单的字符替换,没有类型检查,存在边界的错误;const对应数据类型,进行类型检查。
(3)就存储方式而言:#define只是进行展开,有多少地方使用,就替换多少次,它定义的宏常量在内存中有若干个备份,占用代码段空间;const定义的只读变量在程序运行过程中只有一份备份,占用数据段空间。
(4)从代码调试的方便程度而言: const常量可以进行调试的,define是不能进行调试的,因为在预编译阶段就已经替换掉了。
(5)从是否可以再定义的角度而言: const不足的地方,是与生俱来的,const不能重定义,而#define可以通过#undef取消某个符号的定义,再重新定义。
(6)从某些特殊功能而言: define可以用来防止头文件重复引用,而const不能;
(7)从用于类中来看:const用于类成员变量的定义,只要一定义,不可修改。define 不可用于类成员变量的定义,但是可以用于全局变量。
(8)const采用一个普通的常量名称,define可以采用表达式作为名称;
2、malloc和new
new和malloc的区别是C/C++一道经典的面试题
1、 属性
new/delete是C++关键字,需要编译器支持。malloc/free是库函数,需要头文件支持。
2、参数
使用new操作符申请内存分配时无须指定内存块的大小,编译器会根据类型信息自行计算。而malloc则需要显式地指出所需内存的尺寸。
3、返回类型
new操作符内存分配成功时,返回的是对象类型的指针,类型严格与对象匹配,无须进行类型转换,故new是符合类型安全性的操作符。而malloc内存分配成功则是返回void * ,需要通过强制类型转换将void*指针转换成我们需要的类型。
4、分配失败
new内存分配失败时,会抛出bac_alloc异常。malloc分配内存失败时返回NULL。
5、自定义类型
new会先调用operator new函数,申请足够的内存(通常底层使用malloc实现)。然后调用类型的构造函数,初始化成员变量,最后返回自定义类型指针。delete先调用析构函数,然后调用operator delete函数释放内存(通常底层使用free实现)。
malloc/free是库函数,只能动态的申请和释放内存,无法强制要求其做自定义类型对象构造和析构工作。
6、 重载
C++允许重载new/delete操作符,特别的,布局new的就不需要为对象分配内存,而是指定了一个地址作为内存起始区域,new在这段内存上为对象调用构造函数完成初始化工作,并返回此地址。而malloc不允许重载。
7、内存区域
new操作符从自由存储区(free store)上为对象动态分配内存空间,而malloc函数从堆上动态分配内存。自由存储区是C++基于new操作符的一个抽象概念,凡是通过new操作符进行内存申请,该内存即为自由存储区。而堆是操作系统中的术语,是操作系统所维护的一块特殊内存,用于程序的内存动态分配,C语言使用malloc从堆上分配内存,使用free释放已分配的对应内存。自由存储区不等于堆,如上所述,布局new就可以不位于堆中。
3、线程和进程
4、死锁
线程死锁是指由于两个或者多个线程互相持有对方所需要的资源,导致这些线程处于等待状态,无法前往执行。当线程进入对象的synchronized代码块时,便占有了资源,直到它退出该代码块或者调用wait方法,才释放资源,在此期间,其他线程将不能进入该代码块。当线程互相持有对方所需要的资源时,会互相等待对方释放资源,如果线程都不主动释放所占有的资源,将产生死锁。

当然死锁的产生是必须要满足一些特定条件的:
1.互斥条件:进程对于所分配到的资源具有排它性,即一个资源只能被一个进程占用,直到被该进程释放
2.请求和保持条件:一个进程因请求被占用资源而发生阻塞时,对已获得的资源保持不放。
3.不剥夺条件:任何一个资源在没被该进程释放之前,任何其他进程都无法对他剥夺占用
4.循环等待条件:当发生死锁时,所等待的进程必定会形成一个环路(类似于死循环),造成永久阻塞。

产生死锁的原因?
可归结为如下两点:
a. 竞争资源
系统中的资源可以分为两类:
可剥夺资源,是指某进程在获得这类资源后,该资源可以再被其他进程或系统剥夺,CPU和主存均属于可剥夺性资源;
另一类资源是不可剥夺资源,当系统把这类资源分配给某进程后,再不能强行收回,只能在进程用完后自行释放,如磁带机、打印机等。
产生死锁中的竞争资源之一指的是竞争不可剥夺资源(例如:系统中只有一台打印机,可供进程P1使用,假定P1已占用了打印机,若P2继续要求打印机打印将阻塞)
产生死锁中的竞争资源另外一种资源指的是竞争临时资源(临时资源包括硬件中断、信号、消息、缓冲区内的消息等),通常消息通信顺序进行不当,则会产生死锁
b. 进程间推进顺序非法
若P1保持了资源R1,P2保持了资源R2,系统处于不安全状态,因为这两个进程再向前推进,便可能发生死锁
例如,当P1运行到P1:Request(R2)时,将因R2已被P2占用而阻塞;当P2运行到P2:Request(R1)时,也将因R1已被P1占用而阻塞,于是发生进程死锁
死锁产生的4个必要条件?
产生死锁的必要条件:
互斥条件:进程要求对所分配的资源进行排它性控制,即在一段时间内某资源仅为一进程所占用。
请求和保持条件:当进程因请求资源而阻塞时,对已获得的资源保持不放。
不剥夺条件:进程已获得的资源在未使用完之前,不能剥夺,只能在使用完时由自己释放。
环路等待条件:在发生死锁时,必然存在一个进程–资源的环形链。

解决死锁的基本方法
预防死锁:
资源一次性分配:一次性分配所有资源,这样就不会再有请求了:(破坏请求条件)
只要有一个资源得不到分配,也不给这个进程分配其他的资源:(破坏请保持条件)
可剥夺资源:即当某进程获得了部分资源,但得不到其它资源,则释放已占有的资源(破坏不可剥夺条件)
资源有序分配法:系统给每类资源赋予一个编号,每一个进程按编号递增的顺序请求资源,释放则相反(破坏环路等待条件)

避免死锁:
预防死锁的几种策略,会严重地损害系统性能。因此在避免死锁时,要施加较弱的限制,从而获得 较满意的系统性能。由于在避免死锁的策略中,允许进程动态地申请资源。因而,系统在进行资源分配之前预先计算资源分配的安全性。若此次分配不会导致系统进入不安全的状态,则将资源分配给进程;否则,进程等待。其中最具有代表性的避免死锁算法是银行家算法。
银行家算法:首先需要定义状态和安全状态的概念。系统的状态是当前给进程分配的资源情况。因此,状态包含两个向量Resource(系统中每种资源的总量)和Available(未分配给进程的每种资源的总量)及两个矩阵Claim(表示进程对资源的需求)和Allocation(表示当前分配给进程的资源)。安全状态是指至少有一个资源分配序列不会导致死锁。当进程请求一组资源时,假设同意该请求,从而改变了系统的状态,然后确定其结果是否还处于安全状态。如果是,同意这个请求;如果不是,阻塞该进程知道同意该请求后系统状态仍然是安全的。
检测死锁
首先为每个进程和每个资源指定一个唯一的号码;
然后建立资源分配表和进程等待表。
解除死锁:
当发现有进程死锁后,便应立即把它从死锁状态中解脱出来,常采用的方法有:

剥夺资源:从其它进程剥夺足够数量的资源给死锁进程,以解除死锁状态;
撤消进程:可以直接撤消死锁进程或撤消代价最小的进程,直至有足够的资源可用,死锁状态.消除为止;所谓代价是指优先级、运行代价、进程的重要性和价值等。
死锁检测
5、数据库引擎、事务
MySql四种引擎
6、http请求响应的过程
完整的一次http请求(一)
完整的一次http请求(二)
7、一天的日志,用户量峰值和最长在线时间
8、我的项目

六、第六次笔试

2020.10.11 10:00-12:00

1、员工奖励

答题,输出前三名。

2、矩形最大面积

和力扣某道题一样。

3、分蛋糕

分出1*2的蛋糕几块。
一道类似的题目。输入长宽,和块数,输出最大块的面积

#include <bits/stdc++.h>
using namespace std;
//largets[i][j][k]表示将i*j的蛋糕分为k+1块(切k刀)最大的那块蛋糕的面积下限
#define INF 0x3f3f3f3f   //无穷
int main()
{
    int l,w,m;
    while(cin>>l>>w>>m && (l||w||m)){
        int largets[l+1][w+1][m+1];
        memset(largets,INF, sizeof(largets));
        for(int i=0;i<=l;i++)
            for(int j=0;j<=w;j++)
                for(int k=0;k<=m;k++){
                    if(k==0)
                        largets[i][j][k]=i*j;  //蛋糕不分时,本身最大
                    else if(i*j<k+1)   //切的太多,不够分
                        largets[i][j][k]=INF;
                    else{
                        for(int r=1;r<i;r++) //横着切
                            for (int kk=0;kk<k;kk++)     //剩下左边切几刀,右边切几刀,全部枚举
                                largets[i][j][k]=min(largets[i][j][k],max(largets[r][j][kk],largets[i-r][j][k-kk-1]));
                        for(int c=1;c<j;c++)//竖着切
                            for (int kk=0;kk<k;kk++)   //左右切出来最大的面积,与当前最小值面积比较
                                largets[i][j][k]=min(largets[i][j][k],max(largets[i][c][kk],largets[i][j-c][k-kk-1]));
                    }
                }
        cout<<largets[l][w][m-1]<<endl;
    }
}

4、阶乘使用魔法

数组,魔法次数,目标和。
可以使用魔法变成阶乘,不可以对一个数使用多次,不可以对未选的数使用。

七、1021面试

1 进程调度
五种进程调度

2 编程:三树之和

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        int n = nums.size();
        sort(nums.begin(), nums.end());
        vector<vector<int>> ans;
        // 枚举 a
        for (int first = 0; first < n; ++first) {
            // 需要和上一次枚举的数不相同
            if (first > 0 && nums[first] == nums[first - 1]) {
                continue;
            }
            // c 对应的指针初始指向数组的最右端
            int third = n - 1;
            int target = -nums[first];
            // 枚举 b
            for (int second = first + 1; second < n; ++second) {
                // 需要和上一次枚举的数不相同
                if (second > first + 1 && nums[second] == nums[second - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (second < third && nums[second] + nums[third] > target) {
                    --third;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (second == third) {
                    break;
                }
                if (nums[second] + nums[third] == target) {
                    ans.push_back({nums[first], nums[second], nums[third]});
                }
            }
        }
        return ans;
    }
};

3 计算机网络每一层
在这里插入图片描述
现在结合互联网的情况,自上而下的、非常简要的介绍各层的主要功能。
应用层(application layer)
应用层是体系结构中的最高层。应用层的任务是通过应用进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则。这里的进程就是主机中正在运行的程序。对于不听的网络应用需要不有不同的应用层协议。在互联网的应用层协议很多,如域名DNS,支持万维网应用的HTTP协议,支持电子邮件的SMTP协议,等等。我们把应用层的数据单元称为报文(messgae)。
运输层(transport layer)
运输层的任务就是负责向两台主机中进程之间的通信提供通用的数据传输服务。应用进程利用该服务穿上那个应用层报文。所谓通用的,是指并不针对某个特定网路应用,而是多种应用可以使用同一个运输层服务。由于一台主机可以同时运行多个进程,因此运输层有复用和分用的功能。复用就是多个应用层进程可同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付给上面应用层的相关进程。
运输层主要有下面两种协议:
传输控制协议TCP(Transmission Control Protocol)-提供面向连接的、可靠的数据传输服务,其数据传输的单位是报文段(segment)。
用户数据报协议 UDP(User Datagram)。 Protocol)-提供无连接的、尽最大努力(best-effort)的数据传输服务(不保证数据传输的可靠性),其数据传输的单位是用户数据报。
顺便指出,有人原意把运输层称为传输层,理由是这一层使用的TCP协议就叫做传输控制协议。从意思上看,传输和运输差别也不大,但OSI定义的第四层使用的是Transport,而不是Transmission。这两个字的含义还是有些差别。因此,使用运输层这个译名比较准确。
网络层(network layer)
网络层负责为分组交换网上的不同主机提供通信服务。
在发送数据时,网络层吧运输层产生的报文段或者用户数据报封装成分组或者包进行传送。在TCP/IP体系中,由于网络层使用IP协议,因此分组也叫作IP数据报,或简称数据报。
请注意:不要将运输层的"用户数据报UDP"和网络层的"IP数据报"弄混,此外,无论哪一层传输的数据单元,都可以笼统的用"分组"来表示。
网络层的另一个任务就是选择合适的路由,是源主机运输层所传下来的分组,能够通过网络中的路由器来找到目的主机。
互联网是有大量的异构(heterogeneous)网络来通过路由器(Router)相互连接起来的。互联网使用的网络层协议是无连接的网际协议IP(Internet Protocol)和许多路由选择协议, 因此互联网的网络层也叫作网际层或IP层。
数据链路层(data link layer)
数据链路层通常简称为链路层。我们知道,两台主机之间的数据传输,总是在一段一段的链路上传送的, 这就需要使用专门的链路层的协议。在两个相邻结点之间传送数据时,数据链路层量网络层交下来的IP数据报封装成帧(frameing),在两个相邻节点间的链路上传送帧, 每一帧包括数据和必要的控制信息(如同步信息、地址信息、差错信息等)。
在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始到哪个比特结束,这样数据链路层在收到一个帧后,就可从中提取数据部分,上交到网络层。
控制信息还能使接收端能够检测到所收到的帧中有无差错。如发现有差错,数据链路层就简单的丢弃了这个出了差错的帧,以免继续在网络传输下去白白的浪费资源。如果需要改正数据在数据链路层传输时出现的差错(这就是说,数据链路层不仅要检错,还要纠错), 那么就可以采用可靠数据传输协议来纠正出现的差错。这种方法会使数据链路层的协议复杂些。
物理层(physical layer)
在物理层上所传数据的单位是比特(bit)。发送方发送1或者0时,接收方应该接收相同的1或者0,因此物理层要考虑用多大的电压代表"1"或者"0", 以及接收方如何识别发送方所发出的比特。物理层还要确定连接电缆的插头应当有多少根引脚以及各引脚如何连接。当然解释比特代表的意思,就不是物理层的任务。请注意,传递信息所利用的一些物理媒体,如双绞线、同轴电缆、光缆、无线信道等,并不是物理层协议之内而是在物理层协议的下面。因此也有人把物理层当做第0层。

另一种详细写法
五层协议是综合OSI七层协议和TCP/IP四层协议的优点,采用一种只有五层协议的体系结构,从下往上依次为:物理层、数据链路层、网络层、运输层、应用层。下面就对计算机网络中的五层协议体系结构作一下简单介绍。
物理层:物理层考虑的是怎样才能在连接各种计算机的传输媒体上传输数据化比特流,而不是指具体的传输媒体。
物理层的作用是要尽可能地屏蔽掉不同传输媒体和通信手段的差异
物理层传输数据的单位是比特,任务是透明的传输比特流,功能是在物理媒介上为数据端设备透明的传输比特流。
物理层的主要任务
主要任务:确定与传输媒体的接口的一些特性。
①机械特性:指明接口所用接线器的形状和尺寸、引线数目和排列、固定和锁定装置等。
②电气特性:指明在接口电缆的各条线上出现的电压的范围。
③功能特性:指明某条线上出现的某一电平的电压表示何种意义。
④过程特性:指明对于不同功能的各种可能事件的出现顺序。

数据链路层:数据链路层的传输单位是帧,任务将网络层交下来的IP数据报封装成帧,在两个相邻结点间的链路上传送帧,每一帧包括数据和必要的控制信息。在接收数据时,控制信息使接收端能够知道一个帧从哪个比特开始到哪个比特结束,当数据链路层接收到一个帧后就可以从中提取出数据部分,然后提交到网络层。
比特在传输过程中可能0变1,1变0,将其称为比特差错,数据链路层广泛使用了循环冗余检验CRC检测到所收到的帧中有无差错,如发现差错,数据链路层就将该帧丢弃,以免浪费网络资源。如果需要改正数据链路层传输时出现的差错,就需要采用可靠传输协议纠正出现的差错。数据链路层的功能可以概括为:封装成帧、透明传输、差错检验。

网络层:网络层是负责点到点的传输(“点”指主机或路由器)。网络层负责为分组交换网上的不同主机提供通信服务,在发送数据时,网络层将运输层产生的报文段或者用户数据报封装成分组或包进行传送。网络层使用的是IP协议,所以分组也叫做IP数据报,或简称为数据报。IP数据报首部中的检验和字段,只检验首部是否出现差错而不检查数据部分,所以,网络层不提供服务质量的承诺。如果主机中的进程之间的通信需要是可靠的,那么就由网络主机中的运输层负责(包括差错处理、流量控制等)。
网络层的主要功能如下:
1.处理来自运输层的分组发送请求:收到请求后,将分组装入IP数据报,填充报头,选择去往信宿机的路径,然后将数据报发往适当的网络接口。
2.处理输入数据报:首先检查其合法性,然后进行寻径–假如该数据报已到达信宿机,则去掉报头,将剩下部分交给适当的传输协议;假如该数据报尚未到达信宿机,则转发该数据报。
3.处理路径、流控、拥塞等问题。(其中拥塞控制是通过ICMP传递的)网络层包括:IP协议、ICMP控制报文协议、ARP地址转换协议、RARP反向地址转换协议。IP是网络层的核心,通过路由选择将下一跳IP封装后交给下一层,IP数据报是无连接服务。ICMP协议可以回送报文,用来检测网络是否通畅。Ping命令就是发送ICMP的echo包,通过回送的echo relay进行网络测试。ARP是正向地址解析协议,通过已知的IP寻找对应主机的MAC地址。RARP是反向地址解析协议,通过MAC地址确定IP地址。

运输层:运输层的任务是负责向两台主机中进程间的通信提供通用的数据传输服务。应用进程利用该服务传送应用层报文。所谓的“通用的”并不是针对某个特定网络的应用,而是多种应用可以使用同一个运输层服务。由于一台主机可以同时运行多个进程,因此,运输层有复用和分用的功能。复用就是多个应用层进程可以同时使用下面运输层的服务,分用和复用相反,是运输层把收到的信息分别交付上面应用层中的相应进程。
运输层主要有两个协议:传输控制协议TCP和用户数据报协议UDP。

应用层:应用层是体系结构中的最高层。
特点:①每个应用层协议都是为了解决某一类应用问题,而问题的解决又往往是通过位于不同主机中的多个应用进程之间的通信和协调工作来完成的。应用层的具体内容就是规定应用进程在通信是所遵循的协议。②应用层的许多协议都是基于客户服务器方式。客户(client)和服务器(server)都是指通信中所涉及的两个应用进程。客户服务器方式所描述的是进程之间服务和被服务的关系。客户是服务请求方,服务器是服务提供方。
应用层的任务是通过进程间的交互来完成特定网络应用。应用层协议定义的是应用进程间通信和交互的规则,“进程”是指主机中正在运行的程序。对于不同的网络应用需要有不同的应用层协议。在互联网中的应用层协议很多,如FTP、TELNET、DNS、SMTP、POP3。FTP是文件传输协议,一般上传下载用FTP服务,数据端口是20H,控制端口是21H。Telnet服务是用户远程登录服务,使用23H端口。DNS是域名解析服务,提供域名到IP地址之间的转换。SMTP是简单邮件传输协议,用来控制信件的发送、中转。POP3是邮局协议第3版本,用于接收邮件。

4 tcp和udp,举例说明
1、TCP应用

(1)FTP:文件bai传输协议;

(2)SSH:安全登录du、文件传送(SCP)和端zhi口重定向;

(3)Telnet:不dao安全的文本传送;

(4)SMTP:简单邮件传输协议Simple Mail Transfer Protocol (E-mail);

(5)HTTP:超文本传送协议 (WWW);
2、UDP应用

(1)流媒体

采用TCP,一旦发生丢包,TCP会将后续包缓存起来,等前面的包重传并接收到后再继续发送,延迟会越来越大。基于UDP的协议如WebRTC是极佳的选择。

(2)实时游戏

对实时要求较为严格的情况下,采用自定义的可靠UDP协议,比如Enet、RakNet(用户有sony online game、minecraft)等,自定义重传策略,能够把丢包产生的延迟降到最低,尽量减少网络问题对游戏性造成的影响。

采用UDP的经典游戏如FPS游戏Quake、CS,著名的游戏引擎Unity3D采用的也是RakNet。

(3)物联网

2014年google旗下的Nest建立Thread Group,推出了物联网通信协议Thread,完善物联网通信。

全球将近50%的人都在使用互联网,人们不断的追求更快、更好的服务,一切都在变化,在越来越多的领域,UDP将会抢占TCP的主导地位。

(4)QQ 文件传输、QQ语音、QQ视频

对于网络通讯质量要求不高的情况下,要求网络通讯速度能尽量快捷方便,就可以使用UDP技术。

tcp和udp
5 malloc的底层实现

6 哈希表的底层实现
7 自己学习了什么技术

八、1025 第七次笔试

1 RGB转24位图,需要对齐,输出对其后的十进制integer
输入字符串,提取RGB数字,对齐后输出十进制数字

#include <bits/stdc++.h>
using namespace std;

//int GetNum(const char* str,int* num);
//void part(string _str, vector<int> &_num, vector<char> &_op);

int main()
{
    string strs;
    cin>>strs;
    vector<int>  nums;
    int len;
    long long res;
    len=strs.size();
    //len=GetNum(&strs,&nums);
    vector<char> op(0);
    vector<int>  ans;
    int result[24]={0};
    int r[8]={0};
    int g[8]={0};
    int b[8]={0};

    int i=0,j=0;
    while(i<len)
    {
        if(strs[i]>='0'&&strs[i]<='9')
        {
            j=i;
            int ll=0;
            while(strs[i]>='0' && strs[i]<='9')
            {
                i++;
                ll++;
            }
            string s0=strs.substr(j,ll);
            int num=0;
            stringstream s1(s0);
            s1>>num;
            ans.push_back(num);
        }
        else{
            i++;
        }
    }
    //转二进制
    int con=1;
    for(int index=0;index<ans.size();index++)
    {
        int k,z=0;
        int temp[8]={0};
        k=ans[index];
        while(k)
        {
           temp[z]=k%2;
           k/=2;
           z++;
        }
        if(con==1)
        {
            for(int i=0;i<8;++i)
            {
                r[i]=temp[i];
            }
        }
        else if(con==2)
        {
            for(int i=0;i<8;++i)
            {
                g[i]=temp[i];
            }
        }
        else{
            for(int i=0;i<8;++i)
            {
                b[i]=temp[i];
            }
        }
        con++;
    }

    for(int ii=0;ii<8;++ii)
    {
        result[ii]=b[ii];
    }

    for(int ii=0;ii<8;++ii)
    {
        result[ii+8]=g[ii];
    }

    for(int ii=0;ii<8;++ii)
    {
        result[ii+16]=r[ii];
    }

    //十进制
    for(int ii=0;ii<24;++ii)
    {
        res=res+(result[ii]* pow(2,ii));
    }

    cout<<res<<endl;

    return 0;

}

2 调度器

3 时尚达人,同学围坐一圈,衣服颜色0和1,如果左右两侧不一样,明天就换衣服,输出第几天稳定,时尚达人有几个,没有就输出-1,-1。

4 两两数之和,没有就输出Impossible。

九、0314春招笔试

单选10多选10编程2
1、看电影,0代表没人,1代表有人,环形座位,需要远离人群
2、最近的共同祖先,初代病毒0,无毒,后代有的有毒,给出遗传关系,找出最近的共同祖先。

十、0318面试

问得比较多,记不清了
1、奇数降序,偶数升序,怎么排序
参考合并两个升序链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* preHead=new ListNode(-1);
        ListNode* dummy=preHead;

        while(l1!=nullptr && l2!=nullptr)
        {
            if(l1->val <= l2->val)
            {
                dummy->next=l1;
                l1=l1->next;
            }
            else
            {
                dummy->next=l2;
                l2=l2->next;
            }
            dummy=dummy->next;
        }
        dummy->next=l1==nullptr?l2:l1;
        return preHead->next;
    }
};

合并多个

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
//bool comp(ListNode* l1,ListNode* l2)
//{
//    return l1->val >l2->val;
//}
struct comp
{
    bool operator()(ListNode* l1,ListNode* l2){
        return l1->val >l2->val;
    }
};
    ListNode* mergeKLists(vector<ListNode*>& lists) {
        priority_queue<ListNode*,vector<ListNode*>,comp >  pp;  //小顶堆
        for(auto list:lists)
        {
            if(list)
            pp.push(list);
        }

        ListNode* head=new ListNode(-1);
        ListNode* dummy=head;

        while(!pp.empty())
        {
            ListNode* top=pp.top();
            pp.pop();
            dummy->next=top;
            dummy=top;
            if(top->next)
            pp.push(top->next);
        }

        return head->next;
    }
};

2、排序算法
3、vector有哪些运用
4、进程间通信
5、tcp和udp,握手挥手
6、内存管理
7、数组和链表

十一、0323面试

不会
1、虚函数和纯虚函数
2、虚拟地址是怎么映射到物理地址的
3、关于内存管理,分页和分段
4、URL过程,DNS找,缓存在哪儿,怎么找的
5、根服务器

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值