Leetcode 1349. 参加考试的最大学生数(Java + 按行状压暴力 + DP)

2023-12-26 06:45

本文主要是介绍Leetcode 1349. 参加考试的最大学生数(Java + 按行状压暴力 + DP),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 题目
  • 思路
    • Java + 按行状压暴力 + DP:
    • 第 1 步:
    • 第 2 步:
    • 第 3 步:
    • 第 4 步:
  • 复杂度
  • Code

题目

  • Problem: 1349. 参加考试的最大学生数
  • 给你一个 m * n 的矩阵 seats 表示教室中的座位分布。如果座位是坏的(不可用),就用 ‘#’ 表示;否则,用 ‘.’ 表示。
  • 学生可以看到左侧、右侧、左上、右上这四个方向上紧邻他的学生的答卷,但是看不到直接坐在他前面或者后面的学生的答卷。请你计算并返回该考场可以容纳的同时参加考试且无法作弊的 最大 学生人数。
  • 学生必须坐在状况良好的座位上。
  • seats 只包含字符 ‘.’ 和’#’
  • m == seats.length
  • n == seats[i].length
  • 1 <= m <= 8
  • 1 <= n <= 8

思路

Java + 按行状压暴力 + DP:

第 1 步:

  • 首先思考每个好座位选或不选的 DFS 暴力求解,会超时
  • 其次分析题意可知,仅有相邻两行之间有限制,
  • 因此可以想到将行拆开,仅每一行去暴力所有可能,使用 DP 判断相邻两行的限制即可

第 2 步:

  • 每行暴力:
  • 每行遍历从 0 到 (2 ^ n) - 1 的数字 seat,seat 转化为二进制、1 代表有人,
  • isRowUsableSeat 行内满足要求:遍历每个 1 相邻左侧没有 1 且 每个 1 均是好座位,
  • 此 seat 代表该行内部满足条件,

第 3 步:

  • DP 判断两行的限制:
  • 定义状态:dp[i][seat] 代表前 i 行中,第 i 行座位为 seat 时的最大学生数
  • 初始化:dp[0][isRowUsableSeat(seat)] = countOne(seat)(seat 中 1 个个数),代表第一行没有限制
  • 状态转移方程:
    • dp[i][isRowUsableSeat(seat)] = countOne(seat) + max(isCrossUsableSeat(0, seat)?dp[i-1][0]):0 , … , isCrossUsableSeat((2 ^ n) - 1, seat)?dp[i-1][(2 ^ n) - 1]:0)
  • 其中 isCrossUsableSeat(seat1, seat2) 代表两行(seat1-上一行、seat2-下一行)是否满足要求,即 seat2 每个 1 的下标 col、在 seat1 中 col-1 与 col+1 都不存在 1

第 4 步:

  • 预处理所有 isCrossUsableSeat,
  • 由于 i 仅与 i-1 相关,因此使用滚动数组即可

复杂度

时间复杂度:

时间复杂度: O ( ( m + n ) ∗ 2 2 n ) O((m + n) * 2 ^ {2n}) O((m+n)22n)

空间复杂度:

空间复杂度: O ( n ∗ 2 2 n ) O(n * 2 ^ {2n}) O(n22n)

Code

class Solution {/*** Java + 按行状压暴力 + DP:** 第 1 步:* 首先思考每个好座位选或不选的 DFS 暴力求解,会超时* 其次分析题意可知,仅有相邻两行之间有限制,* 因此可以想到将行拆开,仅每一行去暴力所有可能,使用 DP 判断相邻两行的限制即可** 第 2 步:* 每行暴力:* 每行遍历从 0 到 (2 ^ n) - 1 的数字 seat,seat 转化为二进制、1 代表有人,* isRowUsableSeat 行内满足要求:遍历每个 1 相邻左侧没有 1 且 每个 1 均是好座位,* 此 seat 代表该行内部满足条件,** 第 3 步:* DP 判断两行的限制:* 定义状态:dp[i][seat] 代表前 i 行中,第 i 行座位为 seat 时的最大学生数* 初始化:dp[0][isRowUsableSeat(seat)] = countOne(seat)(seat 中 1 个个数),代表第一行没有限制* 状态转移方程:dp[i][isRowUsableSeat(seat)] = countOne(seat) * + max(isCrossUsableSeat(0, seat)?dp[i-1][0]):0 , ... , isCrossUsableSeat((2 ^ n) - 1, seat)?dp[i-1][(2 ^ n) - 1]:0)* 其中 isCrossUsableSeat(seat1, seat2) 代表两行(seat1-上一行、seat2-下一行)是否满足要求,即 seat2 每个 1 的下标 col、在 seat1 中 col-1 与 col+1 都不存在 1** 第 4 步:* 预处理所有 isCrossUsableSeat,* 由于 i 仅与 i-1 相关,因此使用滚动数组即可* 时间复杂度:O((m + n) * 2 ^ 2n),空间复杂度:O(n * 2 ^ 2n)**/public int maxStudents(char[][] seats) {int m = seats.length;int n = seats[0].length;int seatTotal = 1 << n;// 预处理所有 isCrossUsableSeatboolean[][] crossUsableSeat = preCrossUsableSeat(seatTotal);int[][] dp = new int[2][seatTotal];// 初始化for (int j = 0; j < seatTotal; j++) {// 第 0 行满足 isRowUsableSeatif (isRowUsableSeat(seats[0], j)) {dp[0][j] = countOne(j);}}// 状态转移方程:dp[i][isRowUsableSeat(seat)] = countOne(seat) // + max(isCrossUsableSeat(0, seat)?dp[i-1][0]):0 , ... , isCrossUsableSeat((2 ^ n) - 1, seat)?dp[i-1][(2 ^ n) - 1]:0)for (int i = 1; i < m; i++) {for (int j = 0; j < seatTotal; j++) {// 第 i 行内满足条件if (isRowUsableSeat(seats[i], j)) {int countOneJ = countOne(j);for (int k = 0; k < seatTotal; k++) {// 第 i 行与 i-1 行满足条件if (crossUsableSeat[j][k]) {dp[i & 1][j] = Math.max(dp[i & 1][j], dp[(i - 1) & 1][k] + countOneJ);}}}}}int res = 0;for (int j = 0; j < seatTotal; j++) {res = Math.max(res, dp[(m - 1) & 1][j]);}return res;}/*** 遍历每个 1:相邻左侧没有 1 且 每个 1 均是好座位*/private boolean isRowUsableSeat(char[] seats, int seat) {for (int i = 0; (1 << i) <= seat; i++) {if (((1 << i) & seat) > 0) {if (seats[i] == '#' || ((1 << i + 1) & seat) > 0) {return false;}}}return true;}/*** 预处理所有 isCrossUsableSeat,*/private boolean[][] preCrossUsableSeat(int seatTotal) {boolean[][] crossUsableSeat = new boolean[seatTotal][seatTotal];// seat2 每个 1 的下标 col、在 seat1 中 col-1 与 col+1 都不存在 1for (int seat1 = 0; seat1 < seatTotal; seat1++) {for (int seat2 = 0; seat2 < seatTotal; seat2++) {if (isCrossUsableSeat(seat1, seat2)) {crossUsableSeat[seat1][seat2] = true;}}}return crossUsableSeat;}private boolean isCrossUsableSeat(int seat1, int seat2) {for (int bitNum = (seat2 & -seat2); bitNum > 0; bitNum = (seat2 & -seat2)) {if ((bitNum != 1 && (seat1 & (bitNum >> 1)) > 0) || ((seat1 & (bitNum << 1)) > 0)) {return false;}seat2 -= bitNum;}return true;}/*** 二进制 1 的个数*/private int countOne(int seat) {int res = 0;while (seat > 0) {seat &= seat - 1;res++;}return res;}
}

这篇关于Leetcode 1349. 参加考试的最大学生数(Java + 按行状压暴力 + DP)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/538444

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

哈希leetcode-1

目录 1前言 2.例题  2.1两数之和 2.2判断是否互为字符重排 2.3存在重复元素1 2.4存在重复元素2 2.5字母异位词分组 1前言 哈希表主要是适合于快速查找某个元素(O(1)) 当我们要频繁的查找某个元素,第一哈希表O(1),第二,二分O(log n) 一般可以分为语言自带的容器哈希和用数组模拟的简易哈希。 最简单的比如数组模拟字符存储,只要开26个c

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

hdu4826(三维DP)

这是一个百度之星的资格赛第四题 题目链接:http://acm.hdu.edu.cn/contests/contest_showproblem.php?pid=1004&cid=500 题意:从左上角的点到右上角的点,每个点只能走一遍,走的方向有三个:向上,向下,向右,求最大值。 咋一看像搜索题,先暴搜,TLE,然后剪枝,还是TLE.然后我就改方法,用DP来做,这题和普通dp相比,多个个向上