HW机试0811题目解析和答案

第一题 积木垒塔

给出一些标记了长和宽的方形积木,长度和宽度都是整数:就是给出一个二维数组,第一维表示不同的积木,第二维只取0和1,表示长和宽。当一个积木的长宽均不大于另一个积木的时候,这个积木可以垒在另一个积木上面。计算最多能垒多少积木。
例如:
输入:[[3,4],[5,7],[6,3],[6,7],[5,8],[6,6]]
输出:4
提示:[3,4], [6,3], [6,6], [6,7]即4个积木。
相关题目:
俄罗斯套娃:https://leetcode-cn.com/problems/russian-doll-envelopes/
最长递增子串:https://leetcode-cn.com/problems/longest-increasing-subsequence
本题和俄罗斯套娃信封的区别在于两点:大于等于关系,积木可以旋转
思路:
将二维(长、宽)的比较问题,先固定一个维度(长),然后只比较另一个维度(宽)即可,在长度的从小到大顺序确定以后,已经可以保证以该顺序所提出的任意子串都满足长度的要求,只需要考虑宽度要求即可(详细解释参考俄罗斯套娃的题解)
代码:

#include <algorithm>
#include <vector>
using namespace std;

class Solution {
public:
    void standardizeBlocks(vector<vector<int>> &blocks)
    {
        for (auto &block : blocks) {
            if (block[0] < block[1]) {
                std::swap(block[0], block[1]);
            }
        }
    }

    int maxBlocks(vector<vector<int>> blocks)
    {
        if (blocks.empty()) {
            return 0;
        }
        
        standardizeBlocks(blocks);  // 因为可以旋转,因此将所有的积木标准化为长度一定大于宽度,第0维度为长度

        sort(blocks.begin(), blocks.end(), [](const auto& b1, const auto& b2) {
            if (b1[0] == b2[0]) {
                return b1[1] < b2[1]; // 长度相等情况下,宽度排序与俄罗斯套娃也有区别
            }
            return b1[0] < b2[0];
        });

        int dpMax;
        // 此处的一维DP问题,可以参考最长递增子序列。
        vector<int> dp(blocks.size(), 1);
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (blocks[j][1] <= blocks[i][1]) //这里的 <= 是和俄罗斯套娃的一个区别
                {
                    dp[i] = max(dp[i], dp[j] + 1);
                    dpMax = max(dpMax, dp[i]);
                }
            }
        }
    }
};

第二题:字符串压缩算法

这道题目在leetcode上找不到同级别的对应题,比https://leetcode-cn.com/problems/compress-string-lcci/难很多很多(不是一个量级的)。要求提供两种形式的压缩:
1) 单个字符重复:aaaaaaaa -> a8
2) 子序列重复:abcabcabc -> ABC3
输入:全部小写字母的字符串(a-z)
输出:压缩后的字符串
例:
bbb应该压缩为b3, bb压缩后没有收益,因此不压缩(连续两个重复字母不压缩)
abcabcabc压缩为ABC3
重复字母和重复子串同时出现,优先进行重复子串的压缩,如aaaabcabc应该压缩成a3ABC2
有多个重复子串时,优先压缩最长的重复子串:
bbbbacbacbbbbacbac --- > BBBBACBAC2
思路:
这道题目用动态规划比较麻烦,我这边考虑在朴素穷举的基础上使用一个前项的链表来优化穷举效率。
代码:

// 返回子串的重复数量
int getRepTimes(const std::string &s, int i, int l) {
    int rep = 1;
    while (i + l * rep + l <= s.size()) {
        if (s.substr(i, l) != s.substr(i + rep * l, l)) {
            break;
        }
        ++rep;
    }
    return rep;
}

// 转为大写
std::string toUpperStr(const std::string &str) {
    std::string result;
    for (auto c : str) {
        if (c >= 'a' && c <= 'z') {
            c += 'A' - 'a';
        }
        result.push_back(c);
    }
    return result;
}

// 计算连续字符的重复次数, v[i]代表从i往后包括i本身共有多少个重复字符
vector<int> calcRepTable(std::string s)
{
    vector<int> repTable(s.size(), 0);
    int rep = 1;
    char prev = ' ';
    for (int i = s.size() - 1; i >= 0; --i) {
        if (!(s[i] >= 'a' && s[i] <= 'z')) {
            rep = 1;
        } else if (s[i] != prev) {
            rep = 1;
        } else {
            ++rep;
        }
        repTable[i] = rep;
        prev = s[i];
    }
    return repTable;
}

std::string zipIt(const std::string &s) {
    auto repTbl = calcRepTable(s);
    std::string s1;
    for (int i = 0; i < s.size(); ++i) {
        bool anyZip = false;
        for (int l = (s.size() - i) / 2; l > repTbl[i]; --l) {
            int repTimes = getRepTimes(s, i, l);
            if (repTimes > 1) {
                s1 += toUpperStr(s.substr(i, l)) + std::to_string(repTimes);
                i += l*repTimes - 1;
                anyZip = true;
                break;
            }
        }
        if (!anyZip) {
            s1.push_back(s[i]);
        }
    };

    auto repTblS1 = calcRepTable(s1);
    //std::cout << "s1 is : " << s1 << std::endl;
    //从后往前进行单字符的压缩
    std::string result;
    for (int i = 0; i < s1.size(); ++i) {
        if (repTblS1[i] > 2) {
            result.push_back(s1[i]);
            result.append(std::to_string(repTblS1[i]));
            i += repTblS1[i] - 1;
        } else {
            result.push_back(s1[i]);
        }
    }
    std::cout << "result : " << result << std::endl;
    return result;
}

第三题:货仓问题

有一个货仓,是台阶状的结构(形状类似接雨水题目),每个台阶的宽度都是1,高度由输入决定,是一个可正可负的整数,正整数表示高于地面,0表示和地面一样高,负整数表示低于地面。
给出一批大小相同的货物(只考虑长宽两个维度,类似薄纸片),货物的高度都是1,长度都是N(由输入给出)而且货物只能平着放,不能斜着放。货物的上表面不能超出地面(即高度为0的地面表面)。计算整个货仓能放多少个货物。

  • 输入: 货物长度N和一个整数数组
  • 输出: 一个正整数表示能放几个货物(0表示一个也放不下)
  • 例:[1,1,0,-1,-2,-2,-1,0,-1,3,-3,1]这个数组所代表的货仓如下图所示(红色线代表地面):
    HW机试0811题目解析和答案
    如果要求往里放长度为N=2的货物,则可以放下3个:
    HW机试0811题目解析和答案
    注意,最左侧的1,1即使可以放下一个货物,但在地表以上,因此不允许放货物。另外,一个货物的下表面可以被不完全支撑。
  • 思路:
    这道题目比接雨水简单很多,货物平放就已经排除了很多可能性,其实是个取整问题,因为平放导致每一个平面都与其他平面相互之间不干扰(考虑一种例外情况:货物有没有可能找不到支撑,这种情况是不存在的,如果货物下方完全没有支撑,那么下方的空间完全足够放下一个相同的货物,因此排除)。每个平面考虑其自身即可。将整个货仓地形从左到右遍历一遍,用另一个数组记录下当前每个水平面的货物已经放了几个长度单位,据此可以知道什么时候放下一个完整的货物。
    代码:
#include <vector>
#include <algorithm>

class Solution {
    int solveIt(int N, std::vector<int> heights) {
        int result = 0;
        std::vector<int> curLen = {0}; // 占位
        for (unsigned int i = 0; i < heights.size(); ++i) {
            int height = heights[i];
            height = std::max(-height, 0);
            while (curLen.size() <= height) {
                curLen.push_back(0);
            }
            unsigned int j = 1;
            for (;j <= height; ++j) {
                curLen[j]++;
                if (curLen[j] == N) {
                    curLen[j] = 0;
                    result++;
                }
            }
            for (;j <= curLen.size(); ++j) {
                curLen[j] = 0;
            }
        }
        return result;
    }
}
上一篇:JavaScript小游戏实例:统一着色


下一篇:模板继承