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

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

文章目录

  • 前言
  • 笔试经典编程题目(三)
    • 3.1 参数解析
    • 3.2 跳石板
    • 3.3 计算日期到天数转换
    • 3.4 幸运的袋子
    • 3.5 查找输入整数二进制中1的个数
    • 3.6 手套
    • 3.7 完全数计算
    • 3.8 扑克牌大小
    • 3.9 杨辉三角的变形
    • 3.10 二叉树的镜像
    • 3.11 统计每个月兔子的总数
    • 3.12 字符串通配符
  • 后记

前言


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

这次白晨总结了一下大厂笔试时所出的经典题目,题型包括动态规划,数据结构,条件控制,数学归纳,回溯算法等,难度比上周有很大的提高,有些题目非常巧妙,都是很有代表性的经典题目,适合大家复习和积累经验。

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


笔试经典编程题目(三)


3.1 参数解析


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

原题链接:参数解析

这道题没有什么思想上的难度,核心就是分割字符串,主要是如何分割,这道题主要考验的是对于边界的控制能力,这里给出2种方法。

法一

算法思想

直接利用输入时的空格来将字符串分割,我们天然的得到了一串串分割的字符串。不过,我们必须单独处理 "" 的字符串,以保证符合题意。

代码实现

#include 
#include 
#include 
using namespace std;

int main()
{
    vector<string> v;
    string tmp;
    while (cin >> tmp)
    {
        v.push_back(tmp);
    }
    int size = v.size();

    for (int i = 0; i < v.size(); ++i)
    {
        // 找到"开头的字符串
        if (v[i][0] == '"')
        {
            int j = i;
            // 找到"结尾的字符串
            for (; j < v.size(); ++j)
            {
                if (v[j][v[j].size() - 1] == '"')
                {
                    break;
                }
            }
			// 现在要合并""内的字符串
            for (int k = i + 1; k <= j; ++k)
            {
                size--;
                // 修补细节,在字符串间加空格,将其合并到v[i]这个串上,并且清空v[k]
                v[i] += " ";
                v[i] += v[k];
                v[k] = "";
            }
            // 删除""
            v[i].erase(v[i].begin());
            v[i].erase(--v[i].end());
        }
    }

    cout << size << endl;
    for (auto str : v)
    {
        if (str != "")
            cout << str << endl;
    }
    return 0;
}

法二

算法思想

先读取一行,再分割字符串。

代码实现

#include 
#include 
#include 
using namespace std;

int main()
{
    string s;
    getline(cin, s);
    // 记录是否在""的指令中
    int flag = 0;
    // 记录当前的指令字符串
    string tmp;
    vector<string> v;

    for (int i = 0; i < s.size(); ++i)
    {
        // 当是后"
        if (flag)
        {
            // 如果遇到后",
            if (s[i] == '"')
            {
                v.push_back(tmp);
                flag = 0;
                tmp.clear();
            }
            else
                tmp += s[i];
        }
        else if (s[i] == ' ')
        {
            // 将当前指令插入后面
            if (!tmp.empty())
                v.push_back(tmp);
            tmp.clear();
        }
        else if (s[i] == '"')
        {
            flag = 1;
        }
        else
        {
            tmp += s[i];
        }
    }
	// 当前指令不为空,继续插入
    if (!tmp.empty())
    {
        v.push_back(tmp);
    }

    cout << v.size() << endl;
    for (auto& str : v)
    {
        cout << str << endl;
    }
    return 0;
}

3.2 跳石板


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

原题链接:跳石板

动态规划经典题目,但是很多人可能第一眼会将其当成贪心算法的题目,贪心算法+回溯是可以得出答案的,但是可能不是最优解,这个大家可以自行验证。

算法思想

  • 状态:v[i] —— 到达i最少的跳跃次数。
  • 初始化:v[i]=INT_MAX,INT_MAX为不可到达
  • 状态转移方程: v [ n u m + i ] = m i n ( v [ n u m + i ] , v [ n u m ] + 1 ) v[num + i] = min(v[num + i], v[num] + 1) v[num+i]=min(v[num+i],v[num]+1)inum的因数。
  • 返回值:v[m]

代码实现

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

int main()
{
    int n, m;
    cin >> n >> m;
    vector<int> v(m + 1, INT_MAX);
    v[n] = 0;
    int num = n;

    while (num < m)
    {
        // 若此位置不可到达,直接跳过
        if (v[num] == INT_MAX)
        {
            num++;
            continue;
        }
		// 求num的因数
        for (int i = 2; i <= sqrt(num); ++i)
        {
            // 当i为因数并且在num位置走i步不会超过最终目的地
            if (num % i == 0 && num + i <= m)
            {
                v[num + i] = min(v[num + i], v[num] + 1);
            }
			// i为因数,说明num/i也为因数
            int repair = num / i;
            if (num % repair == 0 && num + repair <= m)
            {
                v[num + repair] = min(v[num + repair], v[num] + 1);
            }
        }
        num++;
    }
    if (v[m] != INT_MAX)
        cout << v[m] << endl;
    else
        cout << -1 << endl;
    return 0;
}

3.3 计算日期到天数转换


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

原题链接:计算日期到天数转换

算法思想

主要考实现和边界控制,在二月上控制一下就可以。

代码实现

#include 
using namespace std;

static int Month[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

int main()
{
    int _y, _m, _d;
    cin >> _y >> _m >> _d;
    int month = 1, day = 0;

    while (month < _m)
    {
        if (month == 2 && (_y % 400 == 0 || _y % 100 != 0 && _y % 4 == 0))
        {
            day++;
        }
        day += Month[month++]; 
    }

    day += _d;

    cout << day << endl;
    return 0;
}

3.4 幸运的袋子


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

原题链接:幸运的袋子

算法思想

  • 当两个正整数的和大于此两数的积时,至少一个数是1。
  • 当正整数组的积已经大于正整数组的和,此时向正整数组中加入不为1的数,依然积大于和,eg. 1* 2 * 3 * 4 > 1 + 2 + 3 + 4 ,此时加入一个2,1* 2 * 2 * 3 * 4 > 1 + 2 + 3 + 4 + 2 ,无法改变比较结果
  • 我们给定的数组进行排序,这样的目的是:
    • 将1统一处理。
    • 去重,保证得到的都是不同的袋子。
  • 我们使用深度优先搜索算法,具体实现细节见下面代码。

代码实现

#include 
#include 
#include 
using namespace std;

int getLuckyPocket(vector<int>& v, int pos, int add, int mul)
{
    int cnt = 0;// 不同袋子数量
    // 从pos位置开始遍历
    for (int i = pos; i < v.size(); ++i)
    {
        add += v[i];
        mul *= v[i];
		
        // 当和大于积时
        if (add > mul)
        {
            // 此时这种组合是可行的,+=1,再加上以这种组合前缀的所有组合满足题意的组合数
            cnt += 1 + getLuckyPocket(v, i + 1, add, mul);
        }
        else if (v[i] == 1)
        {
            // 当1为第一个元素时,1*1==0+1,此时虽然不满足题意,但是接下来无论是什么数都满足题意
            // 所以,cnt+= 以1为前缀满足题意的所有组合
            cnt += getLuckyPocket(v, i + 1, add, mul);
        }
        else
            // 如果add
            // 直接退出即可
            break;
		// 状态回溯到开始状态
        add -= v[i];
        mul /= v[i];
        // 跳过后面的相同元素
        while (i < v.size() - 1 && v[i] == v[i + 1])
            ++i;
    }

    return cnt;
}

int main()
{
    int n;
    while (cin >> n)
    {
        vector<int> v(n);
        for (int i = 0; i < n; ++i)
            cin >> v[i];
        sort(v.begin(), v.end());
        cout << getLuckyPocket(v, 0, 0, 1) << endl;
    }
    return 0;
}

3.5 查找输入整数二进制中1的个数


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

原题链接:查找输入整数二进制中1的个数

这是一道非常经典的题目了,这里我们直接上最优解法。

算法思想

  • 题目给定一个数 n
  • n &= (n - 1) ,这一步是整个算法的核心,这一步的意义是:每次必定消掉n最右边的1
  • 循环上面的步骤,直到 n == 0 ,循环的次数就是二进制中1的个数

代码实现

int main()
{
    int n;

    while (cin >> n)
    {
        int cnt = 0;
        while (n)
        {
            n &= n - 1;
            cnt++;
        }
        cout << cnt << endl;
    }
    return 0;
}

3.6 手套


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

原题链接:手套

这道题如果数学不好的话,真的会做的毫无头绪,并且牛客网的这个例子本来就很特殊,这更增加了这道题的难度。

算法思想

  • 假如有 n 个颜色的小球,每种颜色的小球数量为 $ $ (a != 0) ,要保证每种颜色的小球都有拿到,至少要拿 a 1 + a 2 + a 3 + . . . + a n − m i n ( < a 1 , a 2 , a 3 , . . . , a n > ) + 1 a_1+a_2+a_3+...+a_n -min( ) + 1 a1+a2+a3+...+anmin(<a1,a2,a3,...,an>)+1 个小球,这里我们称这个数为 最小覆盖数,这也被称为 鸽巢原理
  • 所以,我们要统计左右两个手套的 最小覆盖数 ,然后选出两个最小覆盖数中更小的最小覆盖数;
  • 此时,我们只需要在对应的手套中随便拿一个手套就可以匹配。
  • 那么如何求手套的 最小覆盖数 呢?
  • m i n ( l e f t S u m − l e f t M i n + 1 , r i g h t S u m − r i g h t M i n + 1 ) min(leftSum - leftMin + 1, rightSum - rightMin + 1) min(leftSumleftMin+1,rightSumrightMin+1)
  • 最终答案:
  • m i n ( l e f t S u m − l e f t M i n + 1 + 1 , r i g h t S u m − r i g h t M i n + 1 + 1 ) min(leftSum - leftMin + 1 + 1, rightSum - rightMin + 1 + 1) min(leftSumleftMin+1+1,rightSumrightMin+1+1)
  • 这里有个特殊情况,就是某个颜色左(右)手套数量为0,也就是说这个颜色的手套不可能匹配,此时我们必须要拿完对应的右(左)手套以保证其余可以匹配的手套不受影响。

代码实现

class Gloves {
public:
    int findMinimum(int n, vector<int> left, vector<int> right) {
        int left_min = 27, right_min = 27;
        int lsum = 0, rsum = 0;
        int sum = 0;

        for (int i = 0; i < n; ++i)
        {
            if (left[i] != 0 && right[i] != 0)
            {
                // 当手套可以匹配时,选出左右手套的最小值
                left_min = min(left[i], left_min);
                right_min = min(right[i], right_min);
                lsum += left[i];
                rsum += right[i];
            }
            else
            {
                // 当有手套数量为0时,无论左右哪个覆盖数小,都必须将这个颜色的手套拿完
                sum += left[i] + right[i];
            }

        }
		// min(leftSum - leftMin + 1, rightSum - rightMin + 1)
        lsum -= left_min;
        rsum -= right_min;
        // min(lsum, rsum) + 1 -- 最小覆盖数
        // sum + 1 -- 这里必须要将数量为0的对应手套全拿光 同时再随机拿一个手套即可保证匹配
        return min(lsum, rsum) + 1 + sum + 1;
    }
};

3.7 完全数计算


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

原题链接:完全数计算

算法思想

没有什么特别难的,新手入门题目。

代码实现

#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    int cnt = 0;
    for (int i = 2; i <= n; ++i)
    {
        int sum = 1;
        for (int j = 2; j <= sqrt(i); ++j)
        {
            if (i % j == 0)
            {
                sum += j + i / j;
            }
        }
        if (sum == i)
            cnt++;
    }

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

3.8 扑克牌大小


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

原题链接:扑克牌大小

这道题同样是实现大于思想的题目,主要考察字符串分割以及条件控制,属于是对于控制考察的比较难的了。

算法思想

  • 利用哈希表,给每个单牌一个权重,保证单牌之间可以直接比较。
  • 这道题主要是实现以及控制,具体见代码。

代码实现

#include 
#include 
#include 
using namespace std;
static const string Hash[] = { "3", "4", "5", "6", "7", "8", "9", "10", "J", "Q", "K", "A", "2", "joker", "JOKER" };

int getPower(string& s)
{
    for (int i = 0; i < 15; ++i)
    {
        if (Hash[i] == s)
            return i;
    }
    return -1;
}

int main()
{
    // v1为第一副牌,v2为第二副牌
    vector<string> v1, v2;
    string s;
    getline(cin, s);
    int begin = 0;
    int i = 0;
    // 在给定的字符串中,分割出第一手牌以及第二手牌
    // 这里比较难的是分割边界控制
    for (; i < s.size(); ++i)
    {
        if (s[i] == ' ')
        {
            v1.push_back(s.substr(begin, i - begin));
            begin = i + 1;
        }
        if (s[i] == '-')
        {
            v1.push_back(s.substr(begin, i - begin));
            begin = i + 1;
            break;
        }
    }
    i++;
    for (; i < s.size(); ++i)
    {
        if (s[i] == ' ')
        {
            v2.push_back(s.substr(begin, i - begin));
            begin = i + 1;
        }
        if (i == s.size() - 1)
        {
            v2.push_back(s.substr(begin, i - begin + 1));
        }
    }
	
    // 当两副牌的数量相当时,可能的情况就是(两个人)单牌,对子,顺子,三个,炸弹,以及(一个人)大小王
    // 这种情况下直接比较第一张牌就可以
    if (v1.size() == v2.size())
    {
        if (getPower(v1[0]) > getPower(v2[0]))
        {
            for (auto& str : v1)
            {
                cout << str << " ";
            }
        }
        else
        {
            for (auto& str : v2)
            {
                cout << str << " ";
            }
        }
    }
    // 当第一副牌为大小王时,第一副牌赢
    else if (v1.size() == 2 && ((v1[0] == "joker" && v1[1] == "JOKER") || (v1[1] == "joker" && v1[0] == "JOKER")))
    {
        for (auto& str : v1)
        {
            cout << str << " ";
        }
    }
    // 第二副牌为大小王时,第二副牌赢
    else if (v2.size() == 2 && ((v2[0] == "joker" && v2[1] == "JOKER") || (v2[1] == "joker" && v2[0] == "JOKER")))
    {
        for (auto& str : v2)
        {
            cout << str << " ";
        }
    }
    // 当第一副牌为炸弹时
    else if (v1.size() == 4)
    {
        for (auto& str : v1)
        {
            cout << str << " ";
        }
    }
    // 当第二副牌为炸弹时
    else if (v2.size() == 4)
    {
        for (auto& str : v2)
        {
            cout << str << " ";
        }
    }
    // 无法比较
    else
    {
        cout << "ERROR" << endl;
    }
    return 0;
}

3.9 杨辉三角的变形


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

原题链接:杨辉三角的变形

算法思想

白晨的第一想法就是动态规划对杨辉三角变式进行模拟,直接莽夫,先保证过了就行。

  • 状态转移方程: v [ i ] [ j ] = v [ i − 1 ] [ j ] + v [ i − 1 ] [ j − 1 ] + v [ i − 1 ] [ j − 2 ] ( 2 < = j < = 2 ∗ i ) v[i][j] = v[i-1][j]+v[i-1][j-1]+v[i-1][j-2](2<=j<=2*i) v[i][j]=v[i1][j]+v[i1][j1]+v[i1][j2](2<=j<=2i)
  • v [ i ] [ j ] = v [ i − 1 ] [ j ] + v [ i − 1 ] [ j − 1 ] ( j < 2 ) v[i][j] = v[i-1][j]+v[i-1][j-1](j<2) v[i][j]=v[i1][j]+v[i1][j1](j<2)
#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> v(n);
    for (int i = 0; i < n; ++i)
    {
        v[i].resize(2 * i + 3, 0);
        v[i][0] = v[i][2 * i] = 1;
    }

    for (int i = 1; i < n; ++i)
    {
        for (int j = 1; j < 2 * i + 1; ++j)
        {
            if (j - 2 >= 0)
            {
                v[i][j] = v[i - 1][j - 2] + v[i - 1][j - 1] + v[i - 1][j];
            }
            else
            {
                v[i][j] = v[i - 1][j - 1] + v[i - 1][j];
            }
        }
    }

    for (int i = 1; i < 2 * n - 1; ++i)
    {
        if (v[n - 1][i] % 2 == 0)
        {
            cout << i + 1 << endl;
            return 0;
        }
    }
    cout << -1 << endl;
    return 0;
}

上面就是第一次白晨动态模拟实现的杨辉三角,但是我忽略了n过大的情况,比如n=10000,就已经超出了可申请空间的范围,所以我又做了以下优化:

  • 由于状态转移方程只需要用到上一行的数据,所以我们可以创建两个数组换着保存数据即可(一个数组也可以,但是要从后向前遍历),这样就能将空间复杂度降到 O ( n ) O(n) O(n)
  • 并且由于变式三角是对称的,我们只用保存一半的数据即可。
#include 
#include 
using namespace std;

int main()
{
    int n;
    cin >> n;
    vector<vector<int>> v(2, vector<int> (n + 1));
    v[0][0] = v[1][0] = 1;
    for (int i = 1; i < n; ++i)
    {
        if (i % 2 == 1)
        {            
            for (int j = 1; j < i + 1; ++j)
            {
                if (j - 2 < 0)
                {
                    v[0][j] = v[1][j - 1] + v[1][j];
                }
                else if (j == i)
                {
                    v[0][j] = 2 * v[1][j - 2] + v[1][j - 1];
                }
                else
                {
                    v[0][j] = v[1][j - 2] + v[1][j - 1] + v[1][j];
                }
            }
        }
        else
        {
            for (int j = 1; j < i + 1; ++j)
            {
                if (j - 2 < 0)
                {
                    v[1][j] = v[0][j - 1] + v[0][j];
                }
                else if (j == i)
                {
                    v[1][j] = 2 * v[0][j - 2] + v[0][j - 1];
                }
                else
                {
                    v[1][j] = v[0][j - 2] + v[0][j - 1] + v[0][j];
                }
            }
        }
    }
    if (n % 2 == 0)
    {
        for (int i = 1; i < n; ++i)
        {

            if (v[0][i] % 2 == 0)
            {
                cout << i + 1 << endl;
                return 0;
            }
        }
    }
    else
    {
        for (int i = 1; i < n; ++i)
        {

            if (v[1][i] % 2 == 0)
            {
                cout << i + 1 << endl;
                return 0;
            }
        }
    }
    cout << -1 << endl;
    return 0;
}

但是还是哒咩,这一次是n=100000的时候超时了。所以,模拟的方法是彻底行不通了,我们现在要试着找规律了。

前十行的数据:

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

  • 前两行没有偶数,返回1。
  • 奇数行:
    • 第一个偶数都在第2个位置上。
  • 偶数行:
    • 可以被4整除时,第一个偶数在3个位置;
    • 不能被4整除时,第一个偶数在第4个位置。

代码实现

#include 
using namespace std;

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

    if (n <= 2)
        cout << -1 << endl;
    else if (n % 2 == 1)
        cout << 2 << endl;
    else
    {
        if (n % 4 == 0)
            cout << 3 << endl;
        else
            cout << 4 << endl;
    }
    return 0;
}

3.10 二叉树的镜像


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

原题链接:二叉树的镜像

这道题其实就是反转二叉树的套皮题目,只用理解如何反转一棵二叉树,就能理解这道题。

算法思想

这里我们使用使用分治的思想:

  • 先判断根结点是否为空,如果为空,返回 NULL ;不为空,执行以下操作。
  • 反转左子树,再拿到左子树的根结点。
  • 反转右子树,再拿到右子树的根结点。
  • 然后让反转后的右子树成为根结点的左孩子,再让反转后的左子树成为根结点的右孩子。

上面的过程可以类似的视为前序遍历的过程。

代码实现

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == nullptr)
            return nullptr;
        
        TreeNode* left = mirrorTree(root->left);
        TreeNode* right = mirrorTree(root->right);
        
        root->left = right;
        root->right = left;
        return root;
    }
};

3.11 统计每个月兔子的总数


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

原题链接:统计每个月兔子的总数

算法思想

  • 这个题其实就是一道找规律+动态规划的题目,我们可以现写出前7个月兔子的变化情况。
时间/月份 1 2 3 4 5 6 7
兔子数量 1 1 2 3 5 8 13
  • 观察图表,我们可以得到这样一个动态转移方程: f ( x ) = f ( x − 1 ) + f ( x − 2 ) f(x) = f(x-1)+f(x-2) f(x)=f(x1)+f(x2)
  • 这就是斐波那契数列的变式题。

代码实现

#include 
using namespace std;

int main()
{
    int f1 = 1, f2 = 1;
    int n;
    cin >> n;
    
    for(int i = 3; i <= n; ++i)
    {
        int tmp = f1 + f2;
        f1 = f2;
        f2 = tmp;
    }
    
    cout << f2 << endl;
    return 0;
}

3.12 字符串通配符


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

原题链接:字符串通配符

法一:递归+记忆化搜索

算法思想

  • s1为带有通配符的串,s2为待匹配的串。

  • 具体思路见代码

代码实现

#include 
#include 
#include 
using namespace std;

bool is_match(const char* s1, const char* s2)
{
    // 当s2已经结束
    if (*s2 == '\0')
    {
        // 如果s1也已经结束,返回真
        if (*s1 == '\0')
        {
            return true;
        }
        else
        {
            int i = 0;
            // 如果s1后面还有*,则将*跳过再判断是不是结束
            while (s1[i] == '*')
                i++;
            if (s1[i] == '\0')
                return true;
            else
                return false;
        }
    }
    // 如果s1先结束,说明不匹配
    else if (*s1 == '\0')
    {
        return false;
    }

	// 当s1遇到?时,如果s2对应的字符为数字或者字母时,可以继续向前匹配
    if (*s1 == '?' && isalnum(*s2))
    {
        return is_match(s1 + 1, s2 + 1);
    }
    // 当s1遇到*时
    else if (*s1 == '*' && isalnum(*s2))
    {
        int i = 0;
        // 跳过连续的*
        while (s1[i] == '*')
            i++;
        // 当下面的匹配条件任意满足,就能说明为真,这一步是本代码的灵魂,但是很难想到
        return is_match(s1 + i - 1, s2 + 1) || is_match(s1 + i, s2) || is_match(s1 + i, s2 + 1);
    }
    // 当s1,s2对应都是字符时,进行比较
    else if (*s1 == *s2 || *s1 + 32 == *s2 || *s1 == *s2 + 32)
    {
        return is_match(s1 + 1, s2 + 1);
    }
    return false;
}

int main()
{
    string s1, s2;
    cin >> s1 >> s2;
    bool match = is_match(s1.c_str(), s2.c_str());
    if (match == true)
        cout << "true" << endl;
    else
        cout << "false" << endl;
    return 0;
}

法二:动态规划

算法思想

带通配符的字符串匹配:动态规划

  • 状态:v[i][j] —— s1的前i个字符是否匹配s2的前j个字符
  • 初始化:v[0][0]=true,对于s1一开始为*的情况,v[i][0]=0,其余都设为false
  • 状态转移方程:见代码实现
  • 返回值:v[s1.size()][s2.size()]

代码实现

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

int main()
{
	string s1, s2;
	cin >> s1 >> s2;
    // 多开辟一行一列,方便判断
	vector<vector<bool>> v(s1.size() + 1, vector<bool>(s2.size() + 1));
	v[0][0] = true;
	
    // 对于前几个字符就是*的要做特殊处理
	for (int i = 0; s1[i] == '*'; ++i)
	{
		v[i + 1][0] = true;
	}
	for (int i = 1; i < v.size(); ++i)
	{
		for (int j = 1; j < v[0].size(); ++j)
		{
            // s1[i-1] == '?'时,此时可以跳过一个数字或字母字符
			if (s1[i - 1] == '?' && isalnum(s2[j - 1]))
                // 此时的状态转移如下:
				v[i][j] = v[i - 1][j - 1];
            // 为*时,只要v[i - 1][j - 1] || v[i][j - 1] || v[i - 1][j]任意有一个为真即可为真
            // 与上一个方法有异曲同工之妙
			else if (s1[i - 1] == '*' && isalnum(s2[j - 1]))
				v[i][j] = v[i - 1][j - 1] || v[i][j - 1] || v[i - 1][j];
			else
                // s1[i-1]与s2[j-1]都为字符时,直接比较
				v[i][j] = v[i - 1][j - 1] && (s1[i - 1] == s2[j - 1] || s1[i - 1] + 32 == s2[j - 1] || s1[i - 1] == s2[j - 1] + 32);
		}
	}

	if (v[s1.size()][s2.size()])
		cout << "true" << endl;
	else
		cout << "false" << endl;
	return 0;
}

后记


本次题目难度有了很大提高,特别是动态规划以及数学归纳题目的难度,本次增加了不同动态规划的题目,比如:一维数组,二维矩阵,有助于大家提高应对动态规划题目的能力。

对于数学归纳的能力也有很强的考察,相信大家做完会有所收获。

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

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


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

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

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

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

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