【刷题日记】笔试经典编程题目(二)

大家好,我是白晨,一个不是很能熬夜,但是也想日更的人✈。如果喜欢这篇文章,点个赞关注一下白晨吧!你的支持就是我最大的动力!

文章目录

  • 前言
  • 笔试编程题(二)
    • ⚽2.1 Fibonacci数列
    • ⚾2.2 合法括号序列判断
    • 2.3 两种排序方法
    • 2.4 求最小公倍数
    • 2.5 另类加法
    • 2.6 求路径
    • 2.7 井字棋
    • 2.8 密码强度等级
    • 2.9 求最大连续bit数
    • 2.10 最近公共祖先
    • ⛳2.11 二进制插入
    • ⛸2.12 查找组成一个偶数最接近的两个素数
  • 后记


前言


虽然还有很多课,但是也不能忘了写编程题呀。

这次白晨总结了一下大厂笔试时所出的经典题目,题型包括贪心算法,动态规划,数据结构等,难度并不是很难,但是都是很有代表性的经典题目,适合大家复习和积累经验。

这里是第二周,大家可以自己先试着自己挑战一下,再来看解析哟!


笔试编程题(二)


⚽2.1 Fibonacci数列


【刷题日记】笔试经典编程题目(二)_第1张图片

原题链接:Fibonacci数列

具体思路

  • 从0,1开始计算斐波那契数列,记录本次结果 cur 和上一次的结果 prev
  • 当本次结果大于等于题目所给数 n 时,停止计算。
  • 此时 p r e v < n < = c u r prevprev<n<=cur ,输出 m i n ( n − p r e v , c u r − n ) min(n - prev,cur-n) min(nprev,curn) 即可。
#include 
#include 
using namespace std;

int main()
{
    int prev = 0, cur = 1, tmp;
    int n;
    cin >> n;

    while (cur < n)
    {
        tmp = cur;
        cur += prev;
        prev = tmp;
    }   	
    cout << min(n - prev, cur - n) << endl;
    return 0;
}

⚾2.2 合法括号序列判断


【刷题日记】笔试经典编程题目(二)_第2张图片

原题链接:合法括号序列判断

如果对于栈结构有了解的同学一定见过这个经典题目,所以这个题目的考点非常明显——栈。

没有了解过栈结构的同学可以先看一下这个文章——【数据结构】栈结构全解析

具体思路

  • 遍历字符串,遇到 ( 就入栈;
  • 遇到 ) 要出栈,但是这里必须要保证栈不为空才能出栈,不然可能会出现 )( 这样的违法情况;
  • 不满足上面两种情况直接返回假。
  • 遍历完字符串,判断栈是否为空,为空返回真,反之为假。
class Parenthesis {
public:
    bool chkParenthesis(string A, int n) {
        stack<int> st;

        for (int i = 0; i < n; i++)
        {
            if (A[i] == '(')
                st.push(A[i]);
            else if (A[i] == ')' && !st.empty())
                st.pop();
            else
                return false;
        }

        return st.empty();
    }
};

2.3 两种排序方法


【刷题日记】笔试经典编程题目(二)_第3张图片

原题链接:两种排序方法

这道题思路没有什么难度,主要是代码实现。

代码实现

#include 
#include 
#include 
#include 
using namespace std;

// 判断是否按长度排序
bool is_len(vector<string>& v)
{
    for (int i = 1; i < v.size(); ++i)
    {
        if (v[i - 1].size() > v[i].size())
            return false;
    }
    return true;
}
// 判断是否按字符顺序排序
bool is_lex(vector<string>& v)
{
    for (int i = 1; i < v.size(); ++i)
    {
        if (strcmp(v[i - 1].c_str(), v[i].c_str()) > 0)
            return false;
    }
    return true;
}

int main()
{
    vector<string> v;
    int n;
    string tmp;
    cin >> n;
    for (int i = 0; i < n; ++i)
    {
        cin >> tmp;
        v.push_back(tmp);
    }
    bool b1 = is_lex(v);
    bool b2 = is_len(v);
    if (b1 && b2)
        cout << "both" << endl;
    else if (b1 && !b2)
        cout << "lexicographically" << endl;
    else if (!b1 && b2)
        cout << "lengths" << endl;
    else
        cout << "none" << endl;
    return 0;
}


2.4 求最小公倍数


【刷题日记】笔试经典编程题目(二)_第4张图片

原题链接:求最小公倍数

算法思想

  • 使用辗转相除法求出最大公约数 c

辗转相除法

【刷题日记】笔试经典编程题目(二)_第5张图片

具体实例:

【刷题日记】笔试经典编程题目(二)_第6张图片

  • ans = a * b / c

最小公倍数流程图

【刷题日记】笔试经典编程题目(二)_第7张图片

代码实现

#include 
using namespace std;

int main()
{
    int a, b, c;
    cin >> a >> b;
    int tmp1 = a, tmp2 = b;
    if (tmp1 < tmp2)
        swap(tmp1, tmp2);

	// 辗转相除法求出最大公约数,这里要注意的是:最后最大公约数存放在tmp2中
    while (c = tmp1 % tmp2)
    {
        tmp1 = tmp2;
        tmp2 = c;
    }
	// ans = a * b / tmp2
    int ans = a * b / tmp2;
    cout << ans << endl;
    return 0;
}

2.5 另类加法


【刷题日记】笔试经典编程题目(二)_第8张图片

原题链接:另类加法

一般来说,这种题目越短,给的条件越少的的题越让人摸不着头脑,这道题也不例外。

【刷题日记】笔试经典编程题目(二)_第9张图片

具体思路

  • 异或操作:如果a、b两个值不相同,则异或结果为1。如果a、b两个值相同,异或结果为0。
  • 异或操作本身可视为将两个数加在一起,但是没有进位,比如: 3 ^ 5 --> 011 ^ 101 = 110 ,这里没有算上第一位的进位。
  • 那么进位如何处理呢?
  • 二进制同位置上如果出现两个1就相当于进位了,所以进位我们可以用 & 来获得,但是获取之后为了使进位进到上一位,必须将结果左移一位,比如:3 & 5 --> 011 & 101 --> 001 ,左移一位,010。
  • 接下来,相当于要获取 110 + 001 的和,怎么获取呢?
  • 很简单,重复前两步,直到进位为0时,此时异或以后的值就是最后的和。

具体实例

  1. 我们以 5 + 13 举例,先将其化为2进制 5-->0101 , 13-->1101
  2. 5 ^ 13 = 1000 , (5 & 13) << 1 = 1010
  3. 1000 ^ 1010 = 0010 , (1000 & 1010) << 1 = 10000
  4. 0010 ^ 10000 = 10010 , (0010 & 10000) << 1 = 0
  5. 此时 10010-->18 就为最终答案。

代码实现

class UnusualAdd {
public:
    int addAB(int A, int B) {
        while (B != 0)
        {
            int tmp = A ^ B;
            B = (A & B) << 1;
            A = tmp;
        }
        return A;
    }
};

2.6 求路径


【刷题日记】笔试经典编程题目(二)_第10张图片

原题链接:求路径

法一:递归

class Solution {
public:

    int uniquePaths(int m, int n) {
        if (m == 1 || n == 1)
            return 1;

        return uniquePaths(m - 1, n) + uniquePaths(m, n - 1);
    }
};

法二:动态规划

【刷题日记】笔试经典编程题目(二)_第11张图片

class Solution {
public:

    int uniquePaths(int m, int n) {
        if (m == 1 || n == 1)
            return 1;
        // 初始化为1
        vector<vector<int> > a(m, vector<int>(n, 1));


        for (int i = 1; i < m; i++)
        {
            // 到达 [i][j] 的路径数 等于 到达[i - 1][j]和到达[i][j - 1]的路径数之和
            for (int j = 1; j < n; j++)
                a[i][j] = a[i][j - 1] + a[i - 1][j];
        }

        return a[m - 1][n - 1];
    }
};

法三:公式法

【刷题日记】笔试经典编程题目(二)_第12张图片

class Solution {
public:
    
    int uniquePaths(int m, int n) {
        long long ret = 1;

        for (int i = n, j = 1; j < m; i++, j++)
            ret = ret * i / j;

        return ret;
    }
};

2.7 井字棋


【刷题日记】笔试经典编程题目(二)_第13张图片

原题链接:井字棋

没什么思想上的难度,主要是考虑全部情况即可。

  • 井字棋的胜利条件是:有三个棋子在横线、直线或斜线连成一线。

代码实现

class Board {
public:
    bool checkWon(vector<vector<int> > board) {
        // 横三或者竖三胜利
        for (int i = 0; i < 3; ++i)
        {
            if (board[i][0] == board[i][1] && board[i][1] == board[i][2] && board[i][0] == 1)
                return true;
            if (board[0][i] == board[1][i] && board[1][i] == board[2][i] && board[0][i] == 1)
                return true;
        }
		// 斜三胜利
        if (board[1][1] == board[2][2] && board[1][1] == board[0][0] && board[1][1] == 1)
            return true;
        if (board[1][1] == board[2][0] && board[1][1] == board[0][2] && board[1][1] == 1)
            return true;

        return false;
    }
};

2.8 密码强度等级


【刷题日记】笔试经典编程题目(二)_第14张图片

原题链接:密码强度等级

同样是思路很简单,就是照着题目实现代码即可。

代码实现

#include 
#include 
#include 
using namespace std;

int main()
{
    string s;
    cin >> s;
    int grade = 0;

    // 密码长度
    if (s.size() <= 4)
        grade += 5;
    else if (s.size() > 4 && s.size() <= 7)
        grade += 10;
    else
        grade += 25;

    // 字母&数字&符号
    int NUM = 0, num = 0, cnt = 0, punc = 0;
    for (auto ch : s)
    {
        if (isalpha(ch))
        {
            if (islower(ch))
                num++;
            else
                NUM++;
        }
        else if (isdigit(ch))
        {
            cnt++;
        }
        else if (ispunct(ch))
        {
            punc++;
        }
    }

    if ((!num && NUM) || (!NUM && num))
        grade += 10;
    else if (num && NUM)
        grade += 20;

    if (cnt == 1)
        grade += 10;
    else if (cnt > 1)
        grade += 20;

    if (punc == 1)
        grade += 10;
    else if (punc > 1)
        grade += 25;

    // 奖励
    if (num && NUM && cnt && punc)
        grade += 5;
    else if (((!num && NUM) || (!NUM && num)) && cnt && punc)
        grade += 3;
    else if (((!num && NUM) || (!NUM && num)) && cnt)
        grade += 2;

    if (grade >= 90)
        cout << "VERY_SECURE" << endl;
    else if (grade >= 80)
        cout << "SECURE" << endl;
    else if (grade >= 70)
        cout << "VERY_STRONG" << endl;
    else if (grade >= 60)
        cout << "STRONG" << endl;
    else if (grade >= 50)
        cout << "AVERAGE" << endl;
    else if (grade >= 25)
        cout << "WEAK" << endl;
    else
        cout << "VERY_WEAK" << endl;
    return 0;
}

2.9 求最大连续bit数


【刷题日记】笔试经典编程题目(二)_第15张图片

原题链接 :求最大连续bit数

法一:二进制转换法

算法思想

  • 利用十进制向二进制转化的方式直接算出每一位,然后统计出连续出现的1的个数即可。

代码实现

#include 
using namespace std;

int main()
{
    // cnt--当前连续1的个数,Max--最多连续1的个数
    int cnt = 0, Max = 0;
    int num;
    cin >> num;

    while (num)
    {
        // 二进制位为1
        if (num % 2 == 1)
        {
            cnt++;
        }
        else
        {
            Max = max(Max, cnt);
            cnt = 0;
        }
        num /= 2;
    }
    Max = max(Max, cnt);

    cout << Max << endl;
    return 0;
}

法二:逻辑与

算法思想

  • 1110 这个数据有三个连续的1,所以我们可以将其左移1位与原数相与,此时结果应该还有两个连续的1, 1110 & (1110 << 1) = 1100 ,重复上面的操作,继续左移一位相与。
  • 1100 & (1100 << 1) == 10001000 & (1000 << 1) = 0
  • 经过三次左移相与结果为0,说明数中有3个连续的1。

代码实现

#include
using namespace std;

int main()
{
    int num;
    cin >> num;
    int cnt = 0;

    while (num)
    {
        num = num & (num << 1);
        ++cnt;
    }

    cout << cnt << endl;
    return 0;
}

2.10 最近公共祖先


【刷题日记】笔试经典编程题目(二)_第16张图片

原题链接:最近公共祖先

法一:暴力求解

算法思想

【刷题日记】笔试经典编程题目(二)_第17张图片

所以,我们可以直接算出a,b的全部祖先结点,最后找到序号最大的,二者共同出现的序号即可。

代码实现

#include 
#include 
using namespace std;

class LCA {
public:
    int getLCA(int a, int b) {
        // 保证a大于b
        if (a < b)
            swap(a, b);
        int tmp = b;
        // 哈希表映射
        vector<int> v1(a + 1, 0);
        // 计算a,b的祖先结点,每遇到一个祖先结点,在对应映射位置++
        while (a)
        {
            v1[a]++;
            a /= 2;
        }
        while (b)
        {
            v1[b]++;
            b /= 2;
        }
		
        // 反着找第一个两者共同出现的祖先
        for (int i = b; i > 0; --i)
        {
            if (v1[i] == 2)
                return i;
        }
        return 0;
    }
};

法二:交替求解

算法思想

  • 上一个方法太麻烦,效率不高而且空间复杂度比较高,这里我们可以进行一个优化。

代码实现

class LCA {
public:
    int getLCA(int a, int b) {
        while(a && b)
        {
            // a > b 时,a 寻求父节点
            if(a > b)
                a /= 2;
            // a < b 时,b 寻求父节点
            else if(a < b)
                b /= 2;
            // a = b 时,返回
            else
                return a;
        }
        return a;
    }
};

进阶题目:二叉树的最近公共祖先

【刷题日记】笔试经典编程题目(二)_第18张图片

法一:递归暴力求解

class Solution {
public:
    bool postorder(TreeNode* root, int target, vector<TreeNode*>& st)
    {
        if (root == nullptr)
            return false;

        st.push_back(root);
        if (root->val == target)
            return true;
        bool i = postorder(root->left, target, st);
        if (i == true)
            return true;
        bool j = postorder(root->right, target, st);
        if (j == true)
            return true;
        st.pop_back();
        return false;
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        vector<TreeNode*> st1, st2;
        postorder(root, p->val, st1);
        postorder(root, q->val, st2);

        while (!st1.empty())
        {
            TreeNode* tmp = st1.back();
            auto ans = find(st2.begin(), st2.end(), tmp);
            if (ans != st2.end())
                return tmp;
            st1.pop_back();
        }

        return nullptr;
    }
};

法二:递归优化

class Solution {
public:
    TreeNode* ans;
    bool dfs(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == nullptr) return false;
        bool lson = dfs(root->left, p, q);
        bool rson = dfs(root->right, p, q);
        if ((lson && rson) || ((root->val == p->val || root->val == q->val) && (lson || rson))) {
            ans = root;
        }
        return lson || rson || (root->val == p->val || root->val == q->val);
    }
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        dfs(root, p, q);
        return ans;
    }
};

⛳2.11 二进制插入


【刷题日记】笔试经典编程题目(二)_第19张图片

原题链接:二进制插入

这道题其实非常简单,但是牛客网这个描述很搞人心态,把明明很简单的题目描述的很复杂,这里用一个例子来解释这道题:

【刷题日记】笔试经典编程题目(二)_第20张图片

代码实现

class BinInsert {
public:
    int binInsert(int n, int m, int j, int i) {
        return n | (m << j);
    }
};

⛸2.12 查找组成一个偶数最接近的两个素数


【刷题日记】笔试经典编程题目(二)_第21张图片

原题链接:查找组成一个偶数最接近的两个素数

算法思想

  • 设题目给的数为 n ,我们可以从 n / 21 找离 n / 2 最近的素数,用 n - 据n / 2最近的素数 可以得到另一个解。
  • 由于结果必须为两个素数,所以另一个解必须验证为素数,如果不为素数,继续查找距离 n / 2 次近的素数。
  • 重复上述过程,直到找到两个素数解。

代码实现

#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    int a1, a2;
	// 从 n / 2 开始查找素数
    for (int i = n / 2; i >= 1; --i)
    {
        // flag为1代表为素数,0代表不为素数
        int flag = 1;
        // 判断i是否为素数
        for (int j = 2; j <= sqrt(i); ++j)
        {
            if (i % j == 0)
            {
                flag = 0;
                break;
            }
        }
        // 如果i为素数
        if (flag)
        {
            a1 = i;
            a2 = n - a1;
            // 判断另一个解是否为素数
            for (int j = 2; j <= sqrt(a2); ++j)
            {
                if (a2 % j == 0)
                {
                    flag = 0;
                    break;
                }
            }
            // 如果另一个解为素数,跳出
            if (flag)
                break;
        }
    }

    cout << a1 << endl << a2 << endl;
    return 0;
}

后记


本次加入了数据结构的考察,题目难度有一个小小的提升,并且出现了许多类型的经典题目,希望大家可以从中学习。

这个是一个新的系列——《经典笔试编程题》,隶属于【刷题日记】系列,白晨开这个系列的目的是向大家分享经典的笔试编程题,以便于大家参考,查漏补缺以及提高代码能力。如果你喜欢这个系列的话,不如关注白晨,更快看到更新呀。

本文是经典笔试编程题的第二篇,如果喜欢这个系列的话,不如订阅【刷题日记】系列专栏,更快看到更新哟!✈。


如果解析有不对之处还请指正,我会尽快修改,多谢大家的包容。

如果大家喜欢这个系列,还请大家多多支持啦!

如果这篇文章有帮到你,还请给我一个大拇指小星星 ⭐️支持一下白晨吧!喜欢白晨【算法】系列的话,不如关注白晨,以便看到最新更新哟!!!

我是不太能熬夜的白晨,我们下篇文章见。

你可能感兴趣的:(刷题日记,c++,算法,leetcode,c语言,数据结构)