冉宝的每日一题-8月16日回溯法+ 动态规划压缩

526. 优美的排列

https://leetcode-cn.com/problems/beautiful-arrangement/

题目描述

设有从 1 到 N 的 N 个整数,如果从这 N 个数字中成功构造出一个数组,使得数组的第 i 位 (1 <= i <= N) 满足如下两个条件中的一个,我们就称这个数组为一个优美的排列。条件:

  1. 第 i 位的数字能被 i 整除
  2. 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
回溯法需要思考三个问题:

  1. 路径:也就是已经作出的选择
  2. 选择列表:当前可以做的选择
  3. 结束条件:到达决策树底层,无法再做选择的条件

代码框架:

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 由小写英文字母组成

感觉和回溯并无关系。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值