笔试面试辅导题(一)

215 阅读4分钟

1451. 单链表快速排序

image.png

定义3个头节点,递归排左边、右边,再串联即可

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
 //不改val只改*next,与数组快排不同,区间没有那么麻烦
 //快排:小于x的放左边,大于x的放右边,只要左边右边都有序,整体就是有序的
class Solution {
public:
    ListNode* findtail(ListNode* head)
    {
        while(head->next) head = head->next;
        return head;
    }
    ListNode* quickSortList(ListNode* head) {
        if(!head || !head->next) return head;
        
        auto left = new ListNode(-1), mid = new ListNode(-1), right = new ListNode(-1);
        auto ltail = left, mtail = mid, rtail = right;
        int val = head->val;
        for(auto p = head; p != NULL; p = p->next)
        {
            if(p->val < val) ltail->next = p, ltail = ltail->next;// ltail =ltail->next = p更简洁
            else if(p->val == val) mtail->next = p, mtail = mtail->next;
            else rtail->next = p, rtail = rtail->next;
        }
        ltail->next = mtail->next = rtail->next = NULL;//补充尾节点后才是完整链表进入下次循环
        left->next = quickSortList(left->next);
        right->next = quickSortList(right->next);
        
        findtail(left)->next = mid->next;
        findtail(left)->next = right->next;//mid可能不存在,所以从left重新找
        auto p = left->next;
        delete left;//可能有一些不用的节点避免在后面的链表中出现
        delete mid;
        delete right;
        return left->next;
    }
};

1452. 寻找矩阵的极小值

image.png

首先从一个点往小于它的点移动一定可以到达这个最小值,最坏为n*n复杂度。

当v为一列的最小值,在v左右2个值left&right中找更小的值时只会走一边,不会过界,则缩小一半,复杂度(n+2)*logn,最后如果只剩一列时遍历一次,一共(n+2)*logn+n次复杂度。如果left或者right都大,则已经找到.

注意:行列同时二分不正确,因为不能保证右上角小的值是严格小于中间那一列的值,只有当小值在下面,如下下图,才能保证边界里面有正确解

image.png

image.png

// Forward declaration of queryAPI.
// int query(int x, int y);
// return int means matrix[x][y].

class Solution {
public:
    vector<int> getMinimumValue(int n) {
        typedef long long LL;
        const LL INF = 1e15;//定义一个比int类型大的数
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = (l + r) >> 1;
            LL val = INF;//记录最小值
            int k = -1;//记录最小值序号
            for(int i = 0;i < n; i ++)
            {
                int tmp = query(i, mid);
                if( tmp < val)
                {
                    k = i;
                    val = tmp;
                }
            }
            //mid可能为0或者n-1,则没有left或者right
            LL left = mid == 0?INF:query(k, mid-1);
            LL right = mid == n-1?INF:query(k, mid + 1);
            if(val <left && val < right) return {k, mid};
            if(left < val) r = mid - 1;
            else l = mid + 1;
        }
        //在最后一列找一遍
        LL val = INF;//记录最小值
        int k = -1;//记录最小值序号
        for(int i = 0;i < n; i ++)
        {
            int tmp = query(i, l);
            if( tmp < val)
            {
                k = i;
                val = tmp;
            }
        }
        return {k,l};
        
    }
};

1048. 鸡蛋的硬度

f[i][j]:测量长度i,有j个鸡蛋的测量方案;表示最坏情况下的最小值 状态转移:j没有用过;j用过,第几个位置用的:1,2,3...k...i,如果在第k个位置碎了,那么下面的k-1个位置只能用j-1个鸡蛋,如果没碎,k上面的i-k个位置依旧有j个鸡蛋用。因为碎和没碎不能控制,所以取max,加上测的这次。能控制的情况选min,不能控制的选max

image.png

#include <iostream>
using namespace std;
const int N = 110, M = 11;
int n, m;
int f[N][M];
int main()
{
    
    while(cin>>n>>m)
    {
        for(int i = 1; i <=n; i ++) f[i][1] = i;
        for(int i = 1; i <= m; i ++) f[1][i] = 1;
        for(int i = 2; i <= n; i ++)
            for(int j = 2; j <= m; j ++)
            {
                f[i][j] = f[i][j-1];
                for(int k = 1; k <= i; k ++)
                    f[i][j] = min(f[i][j], max(f[k-1][j - 1], f[i-k][j]) + 1);
            }
        cout<<f[n][m]<<endl;
    }
}

41. 包含min函数的栈

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> stk, stk_min;
    MinStack() {
        
    }
    
    void push(int x) {
        stk.push(x);
        if(stk_min.size()) x = min(x, stk_min.top());
        stk_min.push(x);
    }
    
    void pop() {
        stk.pop();
        stk_min.pop();
    }
    
    int top() {
       return stk.top();
    }
    
    int getMin() {
       return stk_min.top();
        
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

34. 链表中环的入口结点

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *entryNodeOfLoop(ListNode *head) {
        if(!head) return NULL;
        ListNode * fast = head;
        ListNode* slow = head;
        while(fast)
        {
            fast = fast->next;
            slow = slow->next;
            if(fast) fast = fast->next;
            if(!fast) return NULL;
            if(fast == slow)
            {
                fast = head;
                while(slow != fast)
                {
                    fast = fast->next;
                    slow = slow->next;
                }
                return slow;
            }
        }
        return nullptr;
    }
};

35. 反转链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(!head || !head->next) return head;//递归调用到5只剩一个节点截止,!head是避免链表本身为空
        ListNode* tail = reverseList(head->next);//函数返回翻转后的尾节点,例如dfs(2)返回翻转后的尾节点2
        head->next->next = head;
        head->next = NULL;
        return tail;
        
    }
};

1453. 移掉K位数字

//如果是递增,122345,要删除2位,肯定是最后两个,因为是递增的,不删45的话45往前移动后都会更大
//dp:左右两边不能判断哪边更好所以都看;贪心:可以排除其中一边
#include <iostream>
using namespace std;
int main()
{
    string num;
    int k;
    cin>>num>>k;
    
    string res = "0";
    for(int i = 0; i < num.size(); i ++)
    {
        while(k && num[i] < res.back())//维护递增数组,遇到小于res最后的值时
        {
            res.pop_back();
            k --;
        }
        res += num[i];
    }
    while(k--) res.pop_back();//当k有剩余,删除递增数组res最后几位
    int i = 0; 
    while(i < res.size() && res[i] == '0') i ++;
    
    if(i == res.size()) puts("0");//答案为空时
    else cout<<res.substr(i)<<endl;//从不为空的地方输出
    return 0;
}

1454. 异或和是质数的子集数

举例: image.png image.png

在1-8191中寻找组合数,属于背包问题

//背包问题:给定某一些选择,满足一些条件下求max等;前i个物品中异或和是j的方案数
//异或:不进位加法
//dp二维数组:1000万int数组5需要40Mb空间,5000w需要200mb,超出题目的64mb,所以用滚动数组,第i层只依赖于前一层的值,所以[0]表示偶数个,[1]表示奇数个,直接找规律用i&1可得滚动数组下标
//判断质数:试除法,判断到根号x即可
#include <iostream>
using namespace std;
int n;
const int N = 5010, M= 8192, MOD=1e9+7;//小于5000最大数:2^12=4096,异或的最大值最多13位为1:2^12+2^11+2^10+...1 = 2^13 - 1=8191,所以异或和不会超过8191
int a[N];
int f[2][M];
bool is_prime(int x)
{
    for(int i = 2; i * i <= x; i ++)
    {
        if(x % i == 0) return false;
    }
    return true;
}
int main()
{
    cin>>n;
    for(int i = 1; i <= n; i ++) cin>>a[i];
    f[0][0] = 1;
    for(int i = 1; i <= n; i ++)
        for(int j = 0; j < M; j ++)
        {
            f[i&1][j] = f[i-1&1][j];
            if((j ^ a[i]) < M) f[i&1][j] = (f[i&1][j] + f[i-1 & 1][j ^ a[i]]) %  MOD;//因为要判断j^a[i]的大小,所以不能只用一维数组递增或者递减;异或的自反性,x^a[i]=j,则x=j^a[i],2个a[i]抵消
        }
    int res = 0;
    for(int i = 2; i < M; i ++)//01不是质数
        if(is_prime(i)) 
            res = (res + f[n & 1][i]) % MOD;
    cout<<res<<endl;
    return 0;
}

1455. 招聘

image.png

image.png image.png

//约瑟夫环进阶问题,可模拟来做,可dp来做
#include <iostream>
using namespace std;

const int N = 1010;

int n,m;
int a[N];

int main()
{
    int T;
    cin>>T;
    while(T--)
    {
        cin>>n>>m;
        for(int i = 0; i < m; i ++) cin>>a[i];
        
        int res = 0;
        for(int i = 1, j = (n-1)%m; i <n;)
        {
            i ++;
            j = (j + m - 1) % m;
            res = (res + a[j]) % i;
        }
        cout<<res<<endl;
    }
    
    
    return 0;
}

797.差分

//差分:前缀和的逆运算,构造前缀和数组后,相邻数相减可以还原原数组,o(n)复杂度
//[l,r]之间的数加c,遍历的话o(n)复杂度,前缀和o(1)
#include <iostream>
using namespace std;

const int N = 100010;

int n, m;
int b[N];//都是在b数组上操作:利用原数组构造初始b数组、插入新元素到b数组、重新求b的前缀和
void insert(int l, int r, int c)//在[l,r]间加c的插入函数
{
    b[l] += c;//a[l],a[l+1]...之后的数都会加c
    b[r + 1] -= c;//a[r+1], a[r+2]...之后的数就不变
}
int main()
{
    cin>>n>>m;
    int c;
    
    for(int i = 1; i <= n; i ++) cin>>c, insert(i, i, c);
    
    while(m --)
    {
        int l, r, c;
        cin>>l>>r>>c;
        insert(l, r, c);
    }
    
    for(int i = 1; i <= n; i ++) b[i] += b[i-1];
    for(int i = 1; i <= n; i ++) cout<<b[i]<<" ";
    
    return 0;
}

173. 矩阵距离

image.png

#include <cstring>
#include <iostream>
#include <queue>
using namespace std;

typedef pair<int,int> PII;
const int N = 1010;

int n,m;
char g[N][N];
int d[N][N];
void bfs()
{
    memset(d, -1, sizeof(d));
    queue<PII> q;
    for(int i = 0; i < n; i ++)
        for(int j = 0; j < m; j ++)
        {
            if(g[i][j] == '1')
            {
                q.push({i,j});
                d[i][j] = 0;
            }
        }
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1};
    while(q.size())
    {
        auto t = q.front();
        q.pop();
        int x = t.first, y = t.second;
        for(int i = 0; i < 4; i ++)
        {
            int a = x + dx[i], b = y + dy[i];
            if(a >= 0 && a < n && b >= 0 && b < m && d[a][b] == -1)
            {
                d[a][b] = d[x][y] + 1;
                q.push({a,b});
            }
        }
    }
}
int main()
{
    scanf("%d%d",&n, &m);
    for(int i = 0;i < n; i ++) scanf("%s", g[i]);
    
    bfs();
    
    for(int i = 0; i < n; i ++)
    {
        for(int j = 0; j < m; j ++) printf("%d ", d[i][j]);
        puts("");
    }
    
    return 0;
}