leetcode
leetcode 451 ~ 500
优美的排列

优美的排列

难度:

标签:

题目描述

假设有从 1 到 n 的 n 个整数。用这些整数构造一个数组 perm下标从 1 开始),只要满足下述条件 之一 ,该数组就是一个 优美的排列

  • perm[i] 能够被 i 整除
  • i 能够被 perm[i] 整除

给你一个整数 n ,返回可以构造的 优美排列 数量

 

示例 1:

输入:n = 2
输出:2
解释:
第 1 个优美的排列是 [1,2]:
    - perm[1] = 1 能被 i = 1 整除
    - perm[2] = 2 能被 i = 2 整除
第 2 个优美的排列是 [2,1]:
    - perm[1] = 2 能被 i = 1 整除
    - i = 2 能被 perm[2] = 1 整除

示例 2:

输入:n = 1
输出:1

 

提示:

  • 1 <= n <= 15

代码结果

运行时间: 32 ms, 内存: 16.0 MB


/*
题目思路:
使用Java Stream和Lambda表达式实现。
通过递归和流操作来生成排列并检查是否满足条件。
由于每个数字都必须被检查多次,因此我们仍然需要使用回溯法。
*/
 
import java.util.stream.IntStream;
 
public class SolutionStream {
    public int countArrangement(int n) {
        return countArrangement(n, 1, new boolean[n + 1]);
    }
 
    private int countArrangement(int n, int pos, boolean[] visited) {
        if (pos > n) {
            return 1;
        }
 
        return IntStream.range(1, n + 1)
                .filter(i -> !visited[i] && (i % pos == 0 || pos % i == 0))
                .map(i -> {
                    visited[i] = true;
                    int count = countArrangement(n, pos + 1, visited);
                    visited[i] = false;
                    return count;
                })
                .sum();
    }
}

解释

方法:

这个题解采用了回溯法来解决问题。首先,为了方便查找每个位置 i 可以放置哪些数字,创建了一个字典 match,其中 match[i] 包含所有可以放在位置 i 的数字(即满足 perm[i] 能被 i 整除或 i 能被 perm[i] 整除的数)。接着,使用 vis 集合来跟踪哪些数字已经在排列中被使用过。backtrack 函数从位置 1 到 n 递归地尝试所有可能的数字,如果构成了一个优美的排列,则增加结果计数器 self.res。

时间复杂度:

O(n!)

空间复杂度:

O(n^2)

代码细节讲解

🦆
在构建match字典时,为什么选择将所有满足条件的数字存储而不是直接在回溯过程中计算?
在构建`match`字典时预先存储每个位置可以放置哪些数字,是为了优化算法的效率。这种预处理方式可以避免在回溯过程中的重复计算,每到一个新的位置时,不需要再次计算哪些数字可以放在当前位置,只需要查询已经存储的信息即可。这样减少了计算量,加快了算法的执行速度。同时,这也使得我们的代码更加整洁、易于理解和维护。
🦆
如何确保在递归过程中使用的vis集合正确地跟踪了哪些数字已经被使用,以避免重复?
在递归函数`backtrack`中,我们使用了`vis`集合来跟踪哪些数字已经被使用。在每次递归调用前,如果一个数字`x`可以放在当前位置`index`并且尚未被使用,则将其加入到`vis`集合中表示这个数字已经被占用。在递归返回后,需要撤销这一选择,即从`vis`集合中移除数字`x`。这样,每次递归调用都保持了`vis`集合的正确性,确保了每个数字在排列中只被使用一次,从而避免了重复使用同一个数字。
🦆
在backtrack函数中,当index等于n+1时,为什么可以直接将结果计数器res增加1,而不需要进一步验证当前排列的有效性?
在`backtrack`函数的设计中,每一步递归都严格保证了选择的数字`x`满足优美排列的条件(`perm[i]`能被`i`整除或`i`能被`perm[i]`整除),这是通过`match[index]`确保的。因此,当`index`达到`n+1`时,意味着已经成功地放置了从1到n的所有数字,并且每个数字的放置都是有效的。所以,此时的排列已经是一个有效的优美排列,不需要进一步的验证,可以直接将结果计数器`res`增加1。

相关问题

优美的排列 II

给你两个整数 nk ,请你构造一个答案列表 answer ,该列表应当包含从 1nn 个不同正整数,并同时满足下述条件:

  • 假设该列表是 answer = [a1, a2, a3, ... , an] ,那么列表 [|a1 - a2|, |a2 - a3|, |a3 - a4|, ... , |an-1 - an|] 中应该有且仅有 k 个不同整数。

返回列表 answer 。如果存在多种答案,只需返回其中 任意一种

 

示例 1:

输入:n = 3, k = 1
输出:[1, 2, 3]
解释:[1, 2, 3] 包含 3 个范围在 1-3 的不同整数,并且 [1, 1] 中有且仅有 1 个不同整数:1

示例 2:

输入:n = 3, k = 2
输出:[1, 3, 2]
解释:[1, 3, 2] 包含 3 个范围在 1-3 的不同整数,并且 [2, 1] 中有且仅有 2 个不同整数:1 和 2

 

提示:

  • 1 <= k < n <= 104