首页 > 其他分享 >【面试高频题】难度 3/5,状态压缩 DP 及其优化

【面试高频题】难度 3/5,状态压缩 DP 及其优化

时间:2022-12-10 11:34:40浏览次数:41  
标签:int 复杂度 mask 数值 面试 state 高频 DP

题目描述

这是 LeetCode 上的 ​​526. 优美的排列​​ ,难度为 中等

Tag : 「位运算」、「状压 DP」、「动态规划」

假设有从 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_02 的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_02 个整数,如果从这 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_02 个数字中成功构造出一个数组,使得数组的第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 位 (【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_06) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。

条件:

  • 第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 位的数字能被 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05
  • 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 能被第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05

现在给定一个整数 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_02,请问可以构造多少个优美的排列?

示例1:

输入: 2

输出: 2

解释:
第 1 个优美的排列是 [1, 2]:
第 1 个位置(i=1)上的数字是1,1能被 i(i=1)整除
第 2 个位置(i=2)上的数字是2,2能被 i(i=2)整除

第 2 个优美的排列是 [2, 1]:
第 1 个位置(i=1)上的数字是2,2能被 i(i=1)整除
第 2 个位置(i=2)上的数字是1,i(i=2)能被 1 整除

说明:

  • 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_02 是一个正整数,并且不会超过 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_13

状态压缩 DP

利用数据范围不超过 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_13,我们可以使用「状态压缩 DP」进行求解。

使用一个二进制数表示当前哪些数已被选,哪些数未被选,目的是为了可以使用位运算进行加速。

我们可以通过一个具体的样例,来感受下「状态压缩」是什么意思:

例如 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_15 代表值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 和值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_17 的数字已经被使用了,而值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_18

然后再来看看使用「状态压缩」的话,一些基本的操作该如何进行:

假设变量 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 存放了「当前数的使用情况」,当我们需要检查值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 的数是否被使用时,可以使用位运算 ​​​a = (state >> k) & 1​​​,来获取 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 中第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 位的二进制表示,如果 ​​​a​​​ 为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 代表值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 的数字已被使用,如果为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_25

定义 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_26 为考虑前 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 个数,且当前选择方案为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19

一个显然的初始化条件为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_29,代表当我们不考虑任何数(【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_30)的情况下,一个数都不被选择(【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_31)为一种合法方案。

不失一般性的考虑 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_26

我们可以通过枚举当前位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 是选哪个数,假设位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 所选数值为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20,首先 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20

  • 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 中的第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 位为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java
  • 要么 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 能被 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 整除,要么 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 能被 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20

那么根据状态定义,位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 选了数值 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20,通过位运算我们可以直接得出决策位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 之前的状态是什么:【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_47,代表将 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 的二进制表示中的第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_25

最终的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_26 为当前位置 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 选择的是所有合法的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20

【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_54

一些细节:由于给定的数值范围为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_55,但实现上为了方便,我们使用 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 从右往左的第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_25 位表示数值 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 选择情况,第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 位表示数值 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_18 的选择情况 ... 即对选择数值 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_20 做一个 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_62

代码:

class Solution {
public int countArrangement(int n) {
int mask = 1 << n;
int[][] f = new int[n + 1][mask];
f[0][0] = 1;
for (int i = 1; i <= n; i++) {
// 枚举所有的状态
for (int state = 0; state < mask; state++) {
// 枚举位置 i(最后一位)选的数值是 k
for (int k = 1; k <= n; k++) {
// 首先 k 在 state 中必须是 1
if (((state >> (k - 1)) & 1) == 0) continue;
// 数值 k 和位置 i 之间满足任一整除关系
if (k % i != 0 && i % k != 0) continue;
// state & (~(1 << (k - 1))) 代表将 state 中数值 k 的位置置零
f[i][state] += f[i - 1][state & (~(1 << (k - 1)))];
}
}
}
return f[n][mask - 1];
}
}
  • 时间复杂度:共有 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_63 的状态需要被转移,每次转移复杂度为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_64,整体复杂度为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_65
  • 空间复杂度:【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_66

状态压缩 DP(优化)

通过对朴素的状压 DP 的分析,我们发现,在决策第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 位的时候,理论上我们应该使用的数字数量也应该为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05

但这一点在朴素状压 DP 中并没有体现,这就导致了我们在决策第 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05 位的时候,仍然需要对所有的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 进行计算检查(即使是那些二进制表示中 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 的出现次数不为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05

因此我们可以换个思路进行枚举(使用新的状态定义并优化转移方程)。

定义 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_73 为当前选择数值情况为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19

这样仍然有 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java_75 的初始化条件,最终答案为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_76

不失一般性考虑 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_73

从当前状态 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 进行出发,检查 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 中的每一位 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 作为最后一个被选择的数值,这样仍然可以确保方案数「不重不漏」的被统计,同时由于我们「从小到大」对 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 进行枚举,因此计算 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_73

同样的,我们仍然需要确保 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 中的那一位作为最后一个的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java

因此我们需要一个计算 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java 的个数的方法,这里使用 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_87

最终的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_73

【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_89

代码:

class Solution {
int getCnt(int x) {
int ans = 0;
while (x != 0) {
x -= (x & -x); // lowbit
ans++;
}
return ans;
}
public int countArrangement(int n) {
int mask = 1 << n;
int[] f = new int[mask];
f[0] = 1;
// 枚举所有的状态
for (int state = 1; state < mask; state++) {
// 计算 state 有多少个 1(也就是当前排序长度为多少)
int cnt = getCnt(state);
// 枚举最后一位数值为多少
for (int i = 0; i < n; i++) {
// 数值在 state 中必须是 1
if (((state >> i) & 1) == 0) continue;
// 数值(i + 1)和位置(cnt)之间满足任一整除关系
if ((i + 1) % cnt != 0 && cnt % (i + 1) != 0) continue;
// state & (~(1 << i)) 代表将 state 中所选数值的位置置零
f[state] += f[state & (~(1 << i))];
}
}
return f[mask - 1];
}
}
  • 时间复杂度:共有 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_90 的状态需要被转移,每次转移复杂度为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_整除_64,整体复杂度为 【面试高频题】难度 3/5,状态压缩 DP 及其优化_状态压缩_66
  • 空间复杂度:【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_93

总结

不难发现,其实两种状态压缩 DP 的思路其实是完全一样的。

只不过在朴素状压 DP 中我们是显式的枚举了考虑每一种长度的情况(存在维度 【面试高频题】难度 3/5,状态压缩 DP 及其优化_后端_05),而在状压 DP(优化)中利用则 【面试高频题】难度 3/5,状态压缩 DP 及其优化_算法_19 中的 【面试高频题】难度 3/5,状态压缩 DP 及其优化_Java

最后

这是我们「刷穿 LeetCode」系列文章的第 ​​No.525​​ 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先把所有不带锁的题目刷完。

在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。

为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:​​github.com/SharingSour…​​ 。

在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

更多更全更热门的「笔试/面试」相关资料可访问排版精美的 ​​合集新基地​​

标签:int,复杂度,mask,数值,面试,state,高频,DP
From: https://blog.51cto.com/acoier/5927376

相关文章

  • 【面试高频题】难度 2/5,回溯算法经典运用
    题目描述这是LeetCode上的​​93.复原IP地址​​,难度为中等。Tag:「回溯」、「DFS」有效​​IP​​​地址正好由四个整数(每个整数位于​​0​​​到​​255......
  • SAK-TC364DP-64F300W AA汽车MCU、SAK-TC375TP-96F300W AA特点概述
    1、SAK-TC364DP-64F300WAA汽车32位微控制器MCU封装:LQFP144批次:新年份说明:SAK-TC364DP-64F300WAA属于AURIX™TC36xDP家族。AURIX™第二代(TC3xx)在性能、内存大小、连接......
  • 2022.12.09深圳大头兄弟面试记录
    大头兄弟2022/12/9没回答好的问题:1px问题算法-->动态规划-->n个台阶回流和重绘父子组件的生命周期-->子组件的mount执行在父组件的mount之前diff算法Arr......
  • 代码实现WordPress自动关键词keywords与描述description
    以下代码实现的是以标签为关键词;以摘要为描述,如果没有填写摘要,那就自动截取文章前200字为描述。代码原创者未知,如果是你原创的,麻烦告知~~代码实现WordPress自动关键词与描......
  • 小林笔记【面试】
    小林笔记【面试】​​前言​​​​推荐​​​​小林笔记【面试】​​​​最后​​推荐​​https://xiaolincoding.com/​​小林笔记【面试】​​操作系统笔记【面试】​​​......
  • iOS面试题及答案大总结
    1.写一个NSString类的实现+(id)initWithCString:(c*****tchar*)nullTerminatedCStringencoding:(NSStringEncoding)encoding;+(id)stringWithCString:(c*****tch......
  • Docker 面试题
    Docker常见面试题NamespaceCgroupsNamespaceDocker容器这个听起来玄而又玄的概念,实际上是在创建容器进程时,指定了这个进程所需要启用的一组Namespace参数。这样,......
  • Android 轻量级存储方案的前世今生【SharedPreferences、MMKV、Jetpack DataStore】
    背景对于Android轻量级存储方案,有大多数人都很熟悉的SharedPreferences;也有基于mmap的高性能组件MMKV,底层序列化/反序列化使用protobuf实现,性能高,稳定性强;还有Jetp......
  • #yyds干货盘点# LeetCode程序员面试金典:分割链表
    题目:给你一个链表的头节点 head​ 和一个特定值 x​ ,请你对链表进行分隔,使得所有 小于 x​ 的节点都出现在 大于或等于 x 的节点之前。你不需要 保留 每个分区......
  • 考研向|动归|dp|矩阵连乘
    原理+手算【矩阵连乘应用3】https://www.bilibili.com/video/BV1XY4y1w7EN/?share_source=copy_web&vd_source=265987ccd804703830248514dc36023b......