526. 优美的排列
https://leetcode-cn.com/problems/beautiful-arrangement/
题目描述
设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:
- 第 i 位的数字能被 i 整除
- i 能被第 i 位上的数字整除
现在给定一个整数 N,请问可以构造多少个优美的排列?
示例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 整除
说明:
N 是一个正整数,并且不会超过15。
思路:
这道题其实在公司看了题解了,但是其实自己之前没太写过回溯法,还是写一下吧
回溯法
回溯模板:
参考资料:https://zhuanlan.zhihu.com/p/138587283
回溯法需要思考三个问题:
- 路径:也就是已经作出的选择
- 选择列表:当前可以做的选择
- 结束条件:到达决策树底层,无法再做选择的条件
代码框架:
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.add(路径)
return
for 选择 in 选择列表:
做选择:
backtrack(路径,选择列表)
撤销选择
核心:for 循环里面的递归:在递归调用之前【做选择】,递归调用之后【撤销选择】。
backtrack
函数的作用类似一个指针,在树上游走,同时只要正确维护每个节点属性,走到树的底层,【路径】就是一个全排列。
遍历思路:-- 多叉树
def traverse(root:TreeNode):
for (child in root.children):
# 前序遍历需要的操作
traverse(child)
# 后续遍历需要的操作
前序遍历的代码在进入某一个节点之前的那个时间点执行,后序遍历代码在离开某个节点之后的那个时间点执行。
回想我们刚才说的,「路径」和「选择」是每个节点的属性,函数在树上游走要正确维护节点的属性,那么就要在这两个特殊时间点搞点动作:
我们只要在递归之前做出选择,在递归之后撤销刚才的选择,就能正确得到每个节点的选择列表和路径。
套用回溯法模版解题
回顾回溯法模版:
result = []
def backtrack(路径,选择列表):
if 满足结束条件:
result.append(路径)
return
for 选择 in 选择列表:
做选择:
backtrack(路径,选择列表)
撤销选择
因为这里不要求具体的路径,所以result 直接改成int型, result.append(路径)
⇒ result +=1
然后这题每个index的元素是有范围的,因为数字范围<=15,所以这道题的时间复杂度不会低。
数字范围 1- n , 位置 i 能放 数字 j的条件是 i % j == 0 or j%i ==0
然后可以选择的范围是能出现且没有选择过的数字。
终止条件,路径长度满足n。
回溯条件。
from collections import defaultdict
class Solution:
def countArrangement(self, n: int) -> int:
result = 0
vis = set()
ind_dict = defaultdict(list)
for i in range(1,n+1):
for j in range(1,n+1):
if i % j == 0 or j%i ==0:
ind_dict[i].append(j)
def backtrack(ind):
nonlocal result
if ind > n:
result += 1
return
for tmp in ind_dict[ind]:
if tmp not in vis:
vis.add(tmp)
backtrack(ind+1)
vis.discard(tmp)
backtrack(1)
return result
时间复杂度:O(n!)
空间复杂度:
O
(
n
2
)
O(n^2)
O(n2) ,可获得的矩阵,栈
O
(
n
)
O(n)
O(n)
动态规划法
因为题目保证了排列的长度最多15,可以用数位为n的二进制mask表述排列中数字被选取的情况,若mask中的第i为表示1,则第数字i+1已经被选去。
以n=4,mask =
(
0110
)
2
(0110)_2
(0110)2 为例,表示数字 2、3已经被选去,并且以任意顺序排列在排列中的前两个位置。
用 f[mask] 表示状态为 mask时的可行方案数,答案为 2^n-1.
状态转移方程:
KaTeX parse error: Expected '}', got '&' at position 27: …sum_{i\in mask &̲ (i+1 | num(mas…
x|y 表示 x可以整除y。
状态转移方程的含义:
想要计算 f[mask]时,祝需要在前 num(mask)-1 都放置了数字的情况下,考虑第 num(mask)需要放置的数字,累加到f[mask]即可。
感觉太复杂了,算了。
加一题回溯法的题目练手吧。
剑指 Offer II 020. 回文子字符串的个数
https://leetcode-cn.com/problems/a7VOhD/
给定一个字符串 s ,请计算这个字符串中有多少个回文子字符串。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
示例 1:
输入:s = "abc"
输出:3
解释:三个回文子串: "a", "b", "c"
示例 2:
输入:s = "aaa"
输出:6
解释:6个回文子串: "a", "a", "a", "aa", "aa", "aaa"
提示:
- 1 <= s.length <= 1000
- s 由小写英文字母组成
感觉和回溯并无关系。