【每日刷题3.5/3.6】20道算法+10道面试 - 阿V

本文记录了作者在美团笔试中遇到的算法题目,包括合并有序数组、寻找链表环的入口节点、有效括号序列、删除链表节点、大数加法、按之字形顺序打印二叉树、最长公共子串、两个链表的第一个公共节点、链表相加、找二叉树最近公共祖先、反转字符串、螺旋矩阵等。还涉及了函数重载与重写的理解。通过实例解析,展示了如何解决这些问题。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

(3月5日)今天早上10点有美团的笔试,搞笑的事我看了公司不在广州,就没投递简历,就填了个内推码,好像也没提交,就通知我笔试了,怎么这么神奇。


(3月6日)昨天笔试去了,美团笔试五道写出了三道,有进步,今天努力20道算法(补回来)哈哈哈,加油!!!

算法题(牛客网)

1.合并两个有序的数组

仔细看题目,要求将数组B合并到数组A,如果创建了新数组就错了,第一反应是先从小的来,结果发现数组A不好处理,那就反过来,先从大处理,发现A后面的空位正好利用上,nice!!!


代码详情:

class Solution {
public:
    void merge(int A[], int m, int B[], int n) {
        int l = m-1,r = n-1; //创建双指针分别指A末尾和B末尾
        int i = m+n-1;
        while(i >= 0 && l >= 0 && r >= 0){
            if (A[l] >= B[r]){
                A[i--] = A[l--]; //将大的放末尾
            }
            else{
                A[i--] = B[r--]; //同理
            }
        }
        while(l >= 0){ //判断是否存在剩下的数,全部放入A中就行
            A[i--] = A[l--];
        }
        while(r >= 0){
            A[i--] = B[r--];
        }
    }
};

 2.链表中环的入口结点

 昨天把那道检查是否有环路,想成了这道了,所以这道直接就会了!!!


 代码详情:

/*
struct ListNode {
    int val;
    struct ListNode *next;
    ListNode(int x) :
        val(x), next(NULL) {
    }
};
*/
class Solution {
public:
    ListNode* EntryNodeOfLoop(ListNode* pHead) {
        ListNode* fast = pHead,* slow = pHead; //创建快慢指针
        
        do{
            if (!fast || !fast->next) return nullptr; //检查是否有环路
            fast = fast->next->next;
            slow = slow->next;
        }while(fast != slow);
        
        fast = pHead; //将快指针移动到头节点
        
        while(fast != slow){ //快慢指针都只走一步
            fast = fast->next;
            slow = slow->next;
        }
        
        return fast;
    }
};

3. 有效括号序列

 记得第一次写这题的时候,想了超久,现在觉得很简单,用栈保存遍历过的符号,当遇到后部分符号,再读栈配对是否符合就行。


代码详情:

class Solution {
public:
    /**
     * 
     * @param s string字符串 
     * @return bool布尔型
     */
    bool isValid(string s) {
        // write code here
        if (s.size() % 2 == 1) return false;
        
        unordered_map<char, char> map ={ //创建哈希表,为了比较符号是否搭配
            {')','('},
            {']','['},
            {'}','{'}
        };
        stack<char> stack;
        
        for(char c:s){
            if (c != ']' && c != '}' && c != ')'){ //前半部分符号直接压入栈
                stack.push(c);
            }
            else{
                if (!stack.empty() && map[c] == stack.top()){ //判断是否匹配
                    stack.pop(); //匹配成功,删除符号
                }
                else{
                    return false; //不成功,直接false
                }
            }
        }
        
        return stack.empty()?true:false; //如果栈还有符号,表明没匹配完
    }
};

4. 删除链表的倒数第n个节点

 链表不能倒过来读,那怎么办呢?使用快慢指针,让快指针先走n步,慢指针保持和快指针n步的距离往前走,快指针到末尾时,慢指针是不是就在倒数n步的位置啦。


 代码详情:

/**
 * struct ListNode {
 *	int val;
 *	struct ListNode *next;
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head ListNode类 
     * @param n int整型 
     * @return ListNode类
     */
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        // write code here
        if (head == nullptr){
            return nullptr;
        }
        
        ListNode* fast = head;
        ListNode* slow = new ListNode(-1); //创建空节点
        slow->next = head;
        head = slow;
        for(int i = 0;i<n;++i){ //让快指针先走n步
            fast = fast->next;
        }
        
        while (fast != nullptr){ //快慢指针同时走
            fast = fast->next;
            slow = slow->next;
        }
        slow->next = slow->next->next; //删除慢指针下一节点
        
        return head->next;
    }
};

5.大数加法

 一开始我直接string转long long,然后加在一起转回string,没考虑到数据溢出哈哈哈,果然中等题目没那么简单。

 就以小学学的加法表达式来写了,从数组最后一位开始加,重点考虑最后要不要进一就行。


 代码详情:

class Solution {
public:
    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     * 计算两个数之和
     * @param s string字符串 表示第一个整数
     * @param t string字符串 表示第二个整数
     * @return string字符串
     */
    string solve(string s, string t) {
        // write code here
        if (s.size() < t.size()) swap(s,t); //让s保持最长,在最长数组进行数据保存
        int m = s.size()-1,n = t.size()-1; //m为s的长度,n为t的长度
        if (m < 0) return t; //其中一个为空,返回另一个
        if (n < 0) return s;
        if (n < 0 && m < 0) return 0; //两个都为空,返回0
        
        bool enter = false; //是否要向前进1
        while(m >= 0 || n >= 0){
            int a = 0,b = 0,tem;
            if (m >= 0) a = s[m] - '0'; //s的数字
            if (n >= 0) b = t[n] - '0'; //t的数字
            tem = a + b; //对齐相加
            if (enter){ //进一
                ++tem;
                enter = false;
            }
            if (tem > 9){ //判断是否要往前进一
                enter = true;
                tem = tem - 10;
            }
            
            s[m] = ('0' + tem);
            m--,n--;
        }
        if (enter){ //检查是否还有进数
            s = '1' + s;
        }
        
        return s;
    }
};

6.按之字形顺序打印二叉树

 跟层序遍历一样,多了个奇数从左到右遍历,偶数从右向左遍历而已,加个判断条件就可以。


/*
struct TreeNode {
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
    TreeNode(int x) :
            val(x), left(NULL), right(NULL) {
    }
};
*/
class Solution {
public:
    vector<vector<int> > Print(TreeNode* pRoot) {
        if (!pRoot) return {}; //边界条件
        
        queue<TreeNode*> que; //创建队列
        que.push(pRoot); //将根节点压入队列
        bool flag = true; //设置标志,true从左向右,false从右向左
        vector<vector<int>> res; //存储最终答案
        
        while(!que.empty()){
            int size = que.size();
            vector<int> ans; //存储一层路径
            while(size--){
                pRoot = que.front(); //获取节点
                que.pop();
                ans.push_back(pRoot->val);
                
                if (pRoot->left) que.push(pRoot->left); //压入左右节点
                if (pRoot->right) que.push(pRoot->right);
            }
            if (!flag) reverse(ans.begin(), ans.end()); //转换数组
            flag = !flag;
            res.push_back(ans);
        }
        
        return res;
    }
    
};

 7.最长公共子串

 看了答案提示怎么动态规划,动态规划的题就难在怎么设值,最后substr忘了怎么用了,原来第一个参数为起始下标,第二个参数是子串长度。


代码详情: 

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 m = str1.size(),n = str2.size();
        vector<vector<int>> dp(m+1,vector<int>(n+1,0)); //多出来的一是哨兵
        int Maxsize = 0; //记录最长长度
        int lMax = 0; //记录最长子串下标
        
        for(int i = 0; i < m; ++i){
            for(int j = 0; j < n; ++j){
                if (str1[i] == str2[j]){ //如果尾部相同,获取前一个尾部的相同子串数
                    dp[i+1][j+1] = dp[i][j] + 1;
                    if (dp[i+1][j+1] > Maxsize){ //判断当前子串是否更长
                        Maxsize = dp[i+1][j+1]; //更新最长子串长度
                        lMax = j-Maxsize+1; //起始下标
                    }
                }
            }
        }
        
        return str2.substr(lMax,Maxsize);
    }
};

 8.两个链表的第一个公共结点

 这题有个小技巧,只要将两个链表长度变成一致就很好遍历了。

(图为牛客网@牛客题霸)


 代码详情:

/*
struct ListNode {
	int val;
	struct ListNode *next;
	ListNode(int x) :
			val(x), next(NULL) {
	}
};*/
class Solution {
public:
    ListNode* FindFirstCommonNode( ListNode* pHead1, ListNode* pHead2) {
        ListNode* p1 = pHead1,* p2 = pHead2;
        
        while(p1 != p2){
            p1 = p1?p1->next:pHead2; //当p1遍历结束转p2
            p2 = p2?p2->next:pHead1; //同理
        }
        
        return p1;
    }
};

 你可能会说,那岂不是不停的循环嘛,由于一样长了,最后肯定会同时遍历到nullptr的。

9.链表相加(二)

 跟上面的字符串相加一样,从最后开始加(链表反转),然后注意进位,和最后一位的进位就可以。


代码详情:

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */

class Solution {
public:
    /**
     * 
     * @param head1 ListNode类 
     * @param head2 ListNode类 
     * @return ListNode类
     */
    ListNode* addInList(ListNode* head1, ListNode* head2) {
        // write code here
        //边界判断
        if (!head1) return head2;
        if (!head2) return head1;
        if (!head1 && !head2) return nullptr;
        
        int length = 0;
        ListNode* node = head1;
        while(node){  //计算哪条链表长
            node = node->next;
            ++length;
        }
        node = head2;
        while(node){
            node = node->next;
            --length;
        }
        if (length < 0){ //令head1为最长链表
            node = head2;
            head2 = head1;
            head1 = node;
        }
        
        head1 = reversal(head1); //反转链表
        head2 = reversal(head2); //同理
        ListNode* root = head1; //记录head1根节点
        
        int enter = 0; //是否进一
        ListNode* pre = nullptr; //方便最后一位进一用的
        while(head1 || head2){
            int a, b, tem;
            a = head1?head1->val:0;
            b = head2?head2->val:0;
            tem = a + b + enter;
            enter = 0; //进位结束归0
            
            if (tem > 9){ //处理进位
                enter = 1;
                tem = tem - 10;
            }
            head1->val = tem;
            pre = head1;
            head1 = head1->next;
            head2 = head2?head2->next:head2; //如果head2为空则不能下一条
        }
        
        if (enter == 1){ //最后一位进位
            node = new ListNode(1);
            pre->next = node;
        }
        
        return reversal(root);
    }
    
    //链表反转函数
    ListNode* reversal(ListNode* right){
        ListNode* left = nullptr;
        ListNode* tem_right = nullptr;
        while(right){
            tem_right = right->next;
            right->next = left;
            left = right;
            right = tem_right;
        }
        
        return left;
    }
};

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

 

 从底向上查询,但分别在左右子树时,该节点就是公共祖先节点。


代码详情:

/**
 * struct TreeNode {
 *	int val;
 *	struct TreeNode *left;
 *	struct TreeNode *right;
 * };
 */

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
        return helper(root,o1,o2)->val;
    }
    
    //辅助函数
    TreeNode* helper(TreeNode* root,int o1,int o2){
        if (!root || root->val == o1 || root->val == o2){ //如果为空,或是查找的节点就返回
            return root;
        }
        TreeNode* left = helper(root->left,o1,o2); //查找左子树是否有查找节点
        TreeNode* right = helper(root->right,o1,o2); //同理
        
        if (!left) return right; //左子树没有查找节点,说明在右子树上
        if (!right) return left; //同理
        
        //左右子树都存在,说明当前节点就是公共祖先
        return root;
    }
};

11.反转字符串

 使用双指针,直接使用swap函数,进行原地交换。


代码详情:

class Solution {
public:
    /**
     * 反转字符串
     * @param str string字符串 
     * @return string字符串
     */
    string solve(string str) {
        // write code here
        int left = 0,right = str.size()-1; //设置头尾双指针
        
        while(left < right){
            swap(str[left++],str[right--]); //原地交换
        }
        
        return str;
    }
};

 12. 螺旋矩阵

 没想出来,看答案提示后慢慢写出来的,重点在于遍历时条件判断,判断的不好容易漏或者重复。


代码详情:

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int> > &matrix) {
        
        int m = matrix.size(),n = m ? matrix[0].size() : 0; //矩阵长宽
        //定位上下左右边界
        int top = 0, botton = m - 1, left = 0, right = n - 1;
        vector<int> ans; //记录答案
        
        while(top <= m / 2 && left <= n / 2){ //螺旋遍历
            for(int i = left;top <= botton && i <= right; ++i){ //遍历顶部
                ans.push_back(matrix[top][i]);
            }
            
            for(int i = top + 1; left <= right && i <= botton; ++i){ //遍历右部
                ans.push_back(matrix[i][right]);
            }
            for(int i = right - 1;top < botton && i >= left; --i){ //遍历底部
                ans.push_back(matrix[botton][i]);
            }
            for(int i = botton - 1;left < right && i > top; --i){ //遍历左部
                ans.push_back(matrix[i][left]);
            }
            ++left,++top,--right,--botton; //遍历一圈后往里收一圈
        }
        
        return ans;
    }
};

面试题

1. 函数重载与重写

 重载(overriding):子类改写父类方法,函数名必须相同,参数表必须不同,返回值类型可以不同。

覆写(overloading):派生类重写基类的函数,同一个函数的不同版本,相同的函数名,相同的参数列表,相同的返回值类型。

23点半了,不卷了不卷了,那道面试题让我疑惑了好久,才知道重写和覆写是一个东西。。。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZZW游戏制造

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值