本文涉及知识点
LeetCode 1349. 参加考试的最大学生数
给你一个 m * n 的矩阵 seats 表示教室中的座位分布。如果座位是坏的(不可用),就用 ‘#’ 表示;否则,用 ‘.’ 表示。
学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的同时参加考试且无法作弊的 最大 学生人数。
学生必须坐在状况良好的座位上。
示例 1:
输入:seats = [[“#”,“.”,“#”,“#”,“.”,“#”],
[“.”,“#”,“#”,“#”,“#”,“.”],
[“#”,“.”,“#”,“#”,“.”,“#”]]
输出:4
解释:教师可以让 4 个学生坐在可用的座位上,这样他们就无法在考试中作弊。
示例 2:
输入:seats = [[“.”,“#”],
[“#”,“#”],
[“#”,“.”],
[“#”,“#”],
[“.”,“#”]]
输出:3
解释:让所有学生坐在可用的座位上。
示例 3:
输入:seats = [[“#”,“.”,“.”,“.”,“#”],
[“.”,“#”,“.”,“#”,“.”],
[“.”,“.”,“#”,“.”,“.”],
[“.”,“#”,“.”,“#”,“.”],
[“#”,“.”,“.”,“.”,“#”]]
输出:10
解释:让学生坐在第 1、3 和 5 列的可用座位上。
提示:
seats 只包含字符 ‘.’ 和’#’
m == seats.length
n == seats[i].length
1 <= m <= 8
1 <= n <= 8
动态规划
动态规划的状态表示
dp’[i][mask] 表示已经处理了i行,最后一行考生的状态为mask。 (1<<x)&mask 表示第x列有考生。
pre = dp’[i] dp = dp’[i+1]
使用滚动向量空间复杂度:O(2n)
动态规划的转移方程
枚举当前mask,mask1表示坏椅子的状态。如果mask&mask1 说明怀椅子有人坐了,非法。
预处理所有本行没有挨着一起的状态:mask&(mask<<1)为0
mask2 = (mask << 1 ) | ( mask >>1) 前一行preMask & mask2必须为0,即这几位必须为0,其它位任意。
mask3 = (~mask2)&(1 << n ) 枚举mask3所有的子集sub(包括0),取pre[sub]的最大值。
一行的转移方程复杂度为:O(3n)
总时间复杂度为:O(m3n)
动态规划的初始值
pre全部为0。
动态规划的填表顺序
i = 0 to m-1
动态规划的返回值
pre的最大值
代码
核心代码
class CBitCounts
{
public:
CBitCounts(int iMaskCount)
{
for (int i = 0; i < iMaskCount; i++)
{
m_vCnt.emplace_back(bitcount(i));
}
}
template<class T>
static int bitcount(T x) {
int countx = 0;
while (x) {
countx++;
x &= (x - 1);
}
return countx;
}
vector<int> m_vCnt;
};
class Solution {
public:
int maxStudents(vector<vector<char>>& seats) {
vector<int> vilid;
const int R = seats.size();
const int C = seats.front().size();
const int iMaskCnt = 1 << C;
for (int i = 0; i < iMaskCnt; i++) {
if (i & (i << 1)) { continue; }
vilid.emplace_back(i);
}
CBitCounts bc(iMaskCnt);
vector<int> pre(iMaskCnt);
for (int r = 0; r < R; r++) {
vector<int> dp(iMaskCnt);
int iSeatMask = 0;
for (int c = 0; c < C; c++) {
if ('.' == seats[r][c]) { continue; }
iSeatMask |= (1 << c);
}
for (const auto& mask : vilid) {
if (mask & iSeatMask) { continue; }
auto mask2 = (mask << 1) | (mask >> 1);
auto mask3 = (~mask2) & (iMaskCnt - 1);
for (int sub = mask3; ; sub = (sub - 1) & mask3) {
dp[mask] = max(dp[mask], pre[sub] + bc.m_vCnt[mask]);
if (0 == sub) { break; }
}
}
pre.swap(dp);
}
return *std::max_element(pre.begin(), pre.end());
}
};
单元测试
template<class T1, class T2>
void AssertEx(const T1& t1, const T2& t2)
{
Assert::AreEqual(t1, t2);
}
void AssertEx( double t1, double t2)
{
auto str = std::to_wstring(t1) + std::wstring(1,32) + std::to_wstring(t2);
Assert::IsTrue(abs(t1 - t2) < 1e-5,str.c_str() );
}
template<class T>
void AssertEx(const vector<T>& v1, const vector<T>& v2)
{
Assert::AreEqual(v1.size(), v2.size());
for (int i = 0; i < v1.size(); i++)
{
Assert::AreEqual(v1[i], v2[i]);
}
}
template<class T>
void AssertV2(vector<vector<T>> vv1, vector<vector<T>> vv2)
{
sort(vv1.begin(), vv1.end());
sort(vv2.begin(), vv2.end());
Assert::AreEqual(vv1.size(), vv2.size());
for (int i = 0; i < vv1.size(); i++)
{
AssertEx(vv1[i], vv2[i]);
}
}
namespace UnitTest
{
vector<vector<char>> seats;
TEST_CLASS(UnitTest)
{
public:
TEST_METHOD(TestMethod00)
{
seats = { {'#','.','#','#','.','#'},
{'.','#','#','#','#','.'},
{'#','.','#','#','.','#'} };
auto res = Solution().maxStudents(seats);
AssertEx(4, res);
}
TEST_METHOD(TestMethod01)
{
seats = { {'.','#'},
{'#','#'},
{'#','.'},
{'#','#'},
{'.','#'} };
auto res = Solution().maxStudents(seats);
AssertEx(3, res);
}
TEST_METHOD(TestMethod02)
{
seats = { {'#','.','.','.','#'},
{'.','#','.','#','.'},
{'.','.','#','.','.'},
{'.','#','.','#','.'},
{'#','.','.','.','#'} };
auto res = Solution().maxStudents(seats);
AssertEx(10, res);
}
};
}
扩展阅读
视频课程
先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关推荐
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
按类别查阅鄙人的算法文章,请点击《算法与数据汇总》。 |
有效学习:明确的目标 及时的反馈 拉伸区(难度合适) 专注 |
闻缺陷则喜(喜缺)是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。