牛客面试必考算法题刷题

文章目录

tips

注意重写比较函数,一般出现在堆和sort函数时,堆的重写是

struct cmp {
   
    bool operator() (const pair<string, int> &a, const pair<string, int> &b) {
   
        if(a.second == b.second) return a.first < b.first;
        return a.second > b.second;
    }
};

sort函数的重写为

static bool cmp(const Interval &int1, const Interval &int2) {
   
        return int1.start < int2.start;
    }

设计LRU缓存结构

设计LRU缓存结构,该结构在构造时确定大小,假设大小为K,并有如下两个功能
set(key, value):将记录(key, value)插入该结构
get(key):返回key对应的value值
[要求]
set和get方法的时间复杂度为O(1)
某个key的set或get操作一旦发生,认为这个key的记录成了最常使用的。
当缓存的大小超过K时,移除最不经常使用的记录,即set或get最久远的。
若opt=1,接下来两个整数x, y,表示set(x, y)
若opt=2,接下来一个整数x,表示get(x),若x未出现过或已被移除,则返回-1
对于每个操作2,输出一个答案

设置两个vector,分别按常用顺序存储key和value,set方法就是当vector大小小于k时,直接存储,如果大于k,则把容器头部的元素删除。get方法就是查找key,如果没找到返回-1,找到就把原有位置上的key删掉,然后插入新的key,就相当于get方法是在更新操作

class Solution {
   
public:
    /**
     * lru design
     * @param operators int整型vector<vector<>> the ops
     * @param k int整型 the k
     * @return int整型vector
     */
    vector<int> LRU(vector<vector<int> >& operators, int k) {
   
        // write code here
        vector<int> res;
        vector<int> keys;
        vector<int> values;
        if(operators.size() == 0) return{
   };
        for(int i=0; i<operators.size(); i++) {
   
            if(operators[i][0] == 1) {
   
                int key = operators[i][1];
                int value = operators[i][2];
                set(key, value, keys, values, k);
            }
            else if(operators[i][0] == 2) {
   
                int key = operators[i][1];
                res.push_back(get(keys, values, key));
            }
        }
        return res;
    }
    void set(int key, int value, vector<int>& keys, vector<int>& values, int k) {
   
        if(keys.size() < k) {
   
            keys.push_back(key);
            values.push_back(value);
        }
        else {
   
            keys.erase(keys.begin());
            values.erase(values.begin());
            keys.push_back(key);
            values.push_back(value);
        }
    }
    int get(vector<int>& keys, vector<int>& values, int key) {
   
        int pos = -1;
        for(int i=0; i<keys.size(); i++) {
   
            if(keys[i] == key) {
   
                pos = i;
                break;
            }
        }
        if(pos == -1) return -1;
        int temp = values[pos];
        keys.erase(keys.begin()+pos);
        values.erase(values.begin()+pos);
        keys.push_back(key);
        values.push_back(temp);    //此时values[pos]已经变了
        return temp;
    }
};

判断链表中是否有环

判断给定的链表中是否有环

class Solution {
   
public:
    bool hasCycle(ListNode *head) {
   
        if(head == nullptr) return false;
        ListNode *fast = head, *slow = head;
        while(fast && fast->next && slow) {
   
            slow = slow->next;
            fast = fast->next->next;
            if(slow == fast) return true;
        }
        return false;
    }
};

二分查找

请实现有重复数字的有序数组的二分查找。
输出在数组中第一个大于等于查找值的位置,如果数组中不存在这样的数,则输出数组长度加一。

要注意的是有重复数字,所以要找到mid时要判断一下左边的数是不是也等于mid,还有就是找不到时返回的是大于它的值,所以返回left

class Solution {
   
public:
    /**
     * 二分查找
     * @param n int整型 数组长度
     * @param v int整型 查找值
     * @param a int整型vector 有序数组
     * @return int整型
     */
    int upper_bound_(int n, int v, vector<int>& a) {
   
        if(a.size() == 0) return 1;
        // write code here
        int left = 0, right = n-1, mid;
        while(left <= right) {
   
            mid = (left + right)/2;
            if(a[mid] == v) {
   
                if(mid > 0 && a[mid - 1] == v) right = mid - 1;
                else return mid + 1;
            }
            else if(a[mid] > v) {
   
                right = mid - 1;
            }
            else {
   
                left = mid + 1;
            }
        }
        if(left > right) {
   
            return left + 1;
        }
    }
};

实现二叉树先序、中序、后序遍历

分别按照二叉树先序,中序和后序打印所有的节点。

class Solution {
   
public:
    /**
     * 
     * @param root TreeNode类 the root of binary tree
     * @return int整型vector<vector<>>
     */
    vector<vector<int> > threeOrders(TreeNode* root) {
   
        // write code here
        vector<vector<int> > res;
        vector<int> v1, v2, v3;
        pre(root, v1);
        res.push_back(v1);
        mid(root, v2);
        res.push_back(v2);
        back(root, v3);
        res.push_back(v3);
        return res;
        
    }
    void pre(TreeNode* root, vector<int>& v) {
   
        if(root == nullptr) return;
        v.push_back(root->val);
        pre(root->left, v);
        pre(root->right, v);
    }
    void mid(TreeNode* root, vector<int>& v) {
   
        if(root == nullptr) return;
        mid(root->left, v);
        v.push_back(root->val);
        mid(root->right, v);
    }
    void back(TreeNode* root, vector<int>& v) {
   
        if(root == nullptr) return;
        back(root->left, v);
        back(root->right, v);
        v.push_back(root->val);
    }
};

寻找第K大

有一个整数数组,请你根据快速排序的思路,找出数组中第K大的数。
给定一个整数数组a,同时给定它的大小n和要找的K(K在1到n之间),请返回第K大的数,保证答案存在。

class Finder {
   
public:
    int findKth(vector<int> a, int n, int K) {
   
        // write code here
        if(a.size() == 0) return 0;
        if(a.size() == 1) return a[0];
        int index, left = 0, right = n - 1;
        index = Partition(a, left, right);
        while(K-1 != index) {
   
            if(index < K-1) {
   
                left = index + 1;
                index = Partition(a, left, right);
            }
            else {
   
                right = index - 1;
                index = Partition(a, left, right);
            }
        }
        return a[index];
    }
    int Partition(vector<int>&a, int i, int j) {
   
        int temp = a[i];
        while(i < j) {
   
            while(temp >= a[j] && i < j) j--;
            a[i] = a[j];
            while(temp <= a[i] && i < j) i++;
            a[j] = a[i];
        }
        a[j] = temp;
        return j;
    }
};

合并有序链表

将两个有序的链表合并为一个新链表,要求新的链表是通过拼接两个链表的节点来生成的。

这道题给的listnode结构里没有构造方法,因此不设置哨兵节点

class Solution {
   
public:
    /**
     * 
     * @param l1 ListNode类 
     * @param l2 ListNode类 
     * @return ListNode类
     */
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
   
        // write code here
        ListNode *newhead, *p;
        if(l1 == nullptr && l2 == nullptr) return nullptr;
        if(l1 == nullptr) return l2;
        if(l2 == nullptr) return l1;
        if(l1->val < l2->val) {
   newhead = l1; l1 = l1->next;}
        else {
   newhead = l2; l2 = l2->next;}
        p = newhead;
        while(l1 && l2) {
   
            if(l1->val < l2->val) {
   
                p->next = l1;
                p = p->next;
                l1 = l1->next;
            } else {
   
                p->next = l2;
                p = p->next;
                l2 = l2->next;
            }
        }
        if(l1) p->next = l1;
        if(l2) p->next = l2;
        return newhead;
    }
};

求二叉树的层次遍历

要按照一层一层返回

class Solution {
   
public:
    /**
     * 
     * @param root TreeNode类 
     * @return int整型vector<vector<>>
     */
    vector<vector<int> > levelOrder(TreeNode* root) {
   
        // write code here
        if(root == nullptr) return {
   };
        vector<vector<int> > res;
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()) {
   
            int size = que.size();
            vector<int> temp;
            while(size--) {
   
                TreeNode *p = que.front();
                temp.push_back(p->val);
                que.pop();
                if(p->left) que.push(p->left);
                if(p->right) que.push(p->right);
            }
            res.push_back(temp);
        }
        return res;
    }
};

括号序列

给出一个仅包含字符’(’,’)’,’{’,’}’,’[‘和’]’,的字符串,判断给出的字符串是否是合法的括号序列
括号必须以正确的顺序关闭,"()“和”()[]{}“都是合法的括号序列,但”(]“和”([)]"不合法。

用栈实现,左括号进栈,遇到右括号就出栈

class Solution {
   
public:
    /**
     * 
     * @param s string字符串 
     * @return bool布尔型
     */
    bool isValid(string s) {
   
        // write code here
        stack<char> stk;
        for(int i=0; i<s.length(); i++) {
   
            if(s[i] == '[' || s[i] == '(' || s[i] == '{') {
   
                stk.push(s[i]);
            } else if(s[i] == ']' || s[i] == ')' || s[i] == '}') {
   
                if(stk.empty()) return false;
                char c = stk.top();
                if(match(c, s[i])) stk.pop();
                else return false;
            }
        }
        if(!stk.empty()) return false;
        return true;
    }
    bool match(char c1, char c2) {
   
        if(c1 == '(' && c2 == ')') return true;
        if(c1 == '[' && c2 == ']') return true;
        if(c1 == '{' && c2 == '}') return true;
        return false;
    }
};

删除列表的倒数第n个节点

给定一个链表,删除链表的倒数第n个节点并返回链表的头指针

双指针

class Solution {
   
public:
    /**
     * 
     * @param head ListNode类 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* removeNthFromEnd(ListNode* head, int n) {
   
        // write code here
        if(head == nullptr || n == 0) return nullptr;
         ListNode *p = head, *r, *q = head;
         for(int i=0; i<n-1; i++) {
   
             if(q == nullptr) return nullptr; 	//n大于链表长度
             q = q->next;
         }
         if(q == nullptr) return nullptr;	//n大于链表长度
         while(q->next) {
   
             r = p;
             p = p->next;
             q = q->next;
         }
        if(p == head) return head->next;	//删除的节点是头结点
        r->next = p->next;
        return head;
    }
};

链表中的节点每k个一组翻转

将给出的链表中的节点每k 个一组翻转,返回翻转后的链表
如果链表中的节点数不是k 的倍数,将最后剩下的节点保持原样
你不能更改节点中的值,只能更改节点本身。
空间复杂度O(1)

class Solution {
   
public:
    /**
     * 
     * @param head ListNode类 
     * @param k int整型 
     * @return ListNode类
     */
    ListNode* reverseKGroup(ListNode* head, int k) {
   
        // write code here
        if(head == nullptr || k == 1) return head;
        int sum = 0;
        ListNode *p = head, *r = nullptr, *hhead, *temp, *tail, *temp1;	//hhead就是新链表的头,temp1是每一组的第一个节点,记录下来为了下一组翻转时挂在该节点后面
        tail = new ListNode(0);	//tail是每前一组翻转链表的尾部
        while(p) {
   
            p = p->next;
            sum++;
        }
        p = head;
        int flag = 0;
        while(sum) {
   
            if(sum >= k) {
   
                for(int i=0; i<k; i++) {
   
                    if(i == 0) temp1 = p;
                    temp = p->next;
                    tail->next = p;
                    p->next = r;
                    r = p;
                    p = temp;
                }
                if(flag == 0) {
   hhead = tail; flag = 1;}	//给头结点赋值
                tail = temp1;
                sum -= k;
            }
            else {
   
                tail->next = p;
                if(flag == 0) {
   hhead = tail; flag = 1;}
                break;
            }
        }
        return hhead->next;
    }
};

在二叉树中找到两个节点的最近公共祖先节点

给定一棵二叉树以及这棵树上的两个节点 o1 和 o2,请找到 o1 和 o2 的最近公共祖先节点。

用递归实现,判断两个节点是否在同一棵树上,如果在同一棵树上就继续递归寻找,如果不在同一棵树上就返回根节点,如果一个节点是另一个节点的父节点,就返回那个节点

class Solution {
   
public:
    /**
     * 
     * @param root TreeNode类 
     * @param o1 int整型 
     * @param o2 int整型 
     * @return int整型
     */
    int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
   
        // write code here
        if(root == nullptr) return 0;
        if(root->val == o1 || root->val == o2) return root->val;
        if(search(root->left, o1) && search(root->right, o2)) return root->val;
        if(search(root->right, o1) && search(root->left, o2)) return root->val;
        if(search(root->left, o1) && search(root->left, o2)) return lowestCommonAncestor(root->left, o1, o2);
        if(search(root->right, o1) && search(root->right, o2)) return lowestCommonAncestor(root->right, o1, o2);
    }
    bool search(TreeNode* root, int o) {
   
        if(root == nullptr) return false;
        if(root->val == o) return true;
        if(search(root->left, o)) return true;
        if(search(root->right, o)) return true;
        return false;
    }
};

合并两个有序的数组

给出两个有序的整数数组A和B,请将数组A合并到数组B中,变成一个有序的数组
注意:
可以假设A数组有足够的空间存放B数组的元素,A和B中初始的元素数目分别为m和n

class Solution {
   
public:
    void merge(int A[], int m, int B[], int n) {
   
        int p1 = m-1, p2 = n-1, p = m+n-1;
        while(p1 >=0 && p2 >= 0) {
   
            if(A[p1] > B[p2]) {
   
                A[p--] = A[p1--];
            } else {
   
                A[p--] = B[p2--];
            }
        }
        while(p1 >= 0) {
   
            A[p--] = A[p1--];
        }
        while(p2 >= 0) {
   
            A[p--] = B[p2--];
        }
        return;
    }
};

最长公共子串

给定两个字符串str1和str2,输出两个字符串的最长公共子串,如果最长公共子串为空,输出-1。

动态规划,但是这里用的是二维数组,后续可以改成一维数组

class Solution {
   
public:
    /**
     * longest common substring
     * @param str1 string字符串 the string
     * @param str2 string字符串 the string
     * @return string字符串
     */
    string LCS(string str1, string str2) {
   
        // write code here
        int len1 = str1.length(), len2 = str2.length(), maxlength = 5005, maxlen = 0, start;
        if(len1 == 0 || len2 == 0) return "-1";
        int dp[maxlength][maxlength];
        memset(dp, 0, sizeof(dp));
        for(int i=1; i<=len1; i++) {
   
            for(int j=1; j<=len2; j++) {
   
                if(str1[i-1] == str2[j-1]) {
       // 因为i j都是从1开始 所以要-1
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                if(dp[i][j] > maxlen) {
   
                    maxlen = dp[i][j];
                    start = i;
                }
            }
        }
        if(maxlen == 0) return "-1";
        return str1.substr(start-maxlen, maxlen);
    }
};

两数之和

给出一个整数数组,请在数组中找出两个加起来等于目标值的数,
你给出的函数twoSum 需要返回这两个数字的下标(index1,index2),需要满足 index1 小于index2.。注意:下标是从1开始的
假设给出的数组中只存在唯一解

用哈希表

class Solution {
   
public:
    /**
     * 
     * @param numbers int整型vector 
     * @param target int整型 
     * @return int整型vector
     */
    vector<int> twoSum(vector<int>& numbers, int target) {
   
        // write code here
        map<int, int> m;
        vector<int> res;
        for(int i=1; i<=numbers.size(); i++) {
   
            m[numbers[i-1]] = i;
        }
        for(int i=0; i<numbers.size(); i++) {
   
            int temp = target - numbers[i];
            if(m.count(temp) != 0 && (i+1) != m[temp]) {
   
                res.push_back(i+1);
                res.push_back(m[temp]);
                break;
            }
        }
        return res;
    }
};

子数组的最大累加和问题

给定一个数组arr,返回子数组的最大累加和

class Solution {
   
public:
    /**
     * max sum of the subarray
     * @param arr int整型vector the array
     * @return int整型
     */
    int maxsumofSubarray(vector<int>& arr) {
   
        // write code here
        int sum = 0;
        for(int i=0; i<arr.size(); i++) {
   
            if(sum < 0) {
   
                int temp = max(sum + arr[i], arr[i]);
                sum = temp;
            }
            else {
   
                sum += arr[i];
            }
        }
        return sum;
    }
};

找到字符串的最长无重复子串

示例:
输入[2, 3, 4, 5] 输出4
输入[2, 2, 3, 4, 3] 输出3

用双指针和哈希表,双指针记录的是无重复子串的起始和结束位置,哈希表记录是否已经存在这个元素,如果发现起始和结束位置重复了,就更新起始位置

class Solution {
   
public:
    /**
     * 
     * @param arr int整型vector the array
     * @return int整型
     */
    int maxLength(vector<int>& arr) {
   
        // write code here
        if(arr.size() == 0) return 0;
        vector<int> v(100005);
        int i = 0, j = 0, res = 0;
        while(j < arr.size()) {
   
            if(v[arr[j]] == 0) {
   
                v[arr[j]] = 1;
                res = max(res, j-i+1);
                j++;
            } else {
   
                v[arr[i]] = 0;    //一定是j与i这两个位置上的元素重复了,因此把这个位置重置为0, 并且更新起始位置
                i++;
            }
        }
        return res;
    }
};

🌸最长递增子序列

给定数组arr,设长度为n,输出arr的最长递增子序列。(如果有多个答案,请输出其中字典序最小的)

解题思路
用动态规划是O(n^2)的时间复杂度,会运行超时,所以用贪心法加二分查找

class Solution {
   
public:
    /**
     * retrun the longest increasing subsequence
     * @param arr int整型vector the array
     * @return int整型vector
     */
    vector<int> LIS(vector<int>& arr) {
   
        // write code here
        vector<int> dp(arr.size(), 0);
        vector<int> end;
        dp[0] = 1;
        end.push_back(arr[0]);
        for(int i=1; i<arr.size(); i++) {
   
            if(arr[i] > end.back()) {
   
                end.push_back(arr[i]);
                dp[i] = end.size();
            }
            else {
   
                int index = search(0, end.size()-1, end, arr[i]);
                end[index] = arr[i];
                dp[i] = index + 1;
            }
        }
        vector<int> res(end.size(), INT_MAX);
        int len = end.size();
        for(int i=arr.size()-1; i>=0; i--) {
   
            if(dp[i] == len) {
   
                if(arr[i] < res[len-1]) {
   
                    res[len-1] = arr[i];
                }
                len--;
            }
        }
        return res;
    }
    
    int search(int left, int right, vector<int>& vec, int target) {
   
        while(left < right) {
   
            int mid = (left + right) / 2;
            if(vec[mid] < target) left = mid + 1;
            else right = mid; 
        }
        return left;
    }
};

反转字符串

写出一个程序,接受一个字符串,然后输出该字符串反转后的字符串。(字符串长度不超过1000

用双指针,从前从后遍历字符串然后交换

class Solution {
   
public:
    /**
     * 反转字符串
     * @param str string字符串 
     * @return string字符串
     */
    string solve(string str) {
   
        // write code here
        int i = 0, j = str.length() - 1;
        while(i < j) {
   
            char temp = str[i];
            str[i] = str[j];
            str[j] = temp;
            i++;
            j--;
        }
        return str;
    }
};

螺旋矩阵

给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。

class Solution {
   
public:
    vector<int> spiralOrder(vector<vector<int> > &matrix) {
   
        if(matrix.size() == 0) return {
   };
        int rows = matrix.size(), cols = matrix[0].size();
        int left = 0, right = cols - 1, up = 0, down = rows - 1;
        vector<int> res;
        while(left <= right && up <= down) {
   
            for(int j=left; j<=right; j++) {
   
                res.push_back(matrix[up][j]);
            }
            up++;
            if(up > down) break;
            for(int i=up; i<=down; i++) {
   
                res.push_back(matrix[i][right]);
            }
            right--;
            if(left > right) break;
            for(int j=right; j>=left; j--) {
   
                res.push_back(matrix[down][j]);
            }
            down--;
            if(up > down) break;
            for(int i=down; i>=up; i--) {
   
                res.push_back(matrix[i][left]);
            }
            left++;
            if(left > right) break;
        }
        return res;
    }
};

两个链表生成相加链表

假设链表中每一个节点的值都在 0 - 9 之间,那么链表整体就可以代表一个整数。
给定两个这种链表,请生成代表两个整数相加值的结果链表。

将两个链表先进行反转,从末位开始相加,并且计算进位。注意,反转链表的时候不需要用头插法,这道题如果用头插法会内存不够,所以要free

class Solution {
   
public:
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    ListNode* Reverse(
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值