日常刷题记录
南蛮图腾
算法思路
这题是个分治问题最小的问题是需要画出像这样一个的图形
只需要把原问题分解为无数个这样小的图形合成即可。
用一个数组保存每个位置的字符,每次从最左下角的字符进行处理填充完整个图形需要的字符然后找到下一个需要处理的位置即可。
代码实现
def build(a, b, n):
if n == 1:
g[a][b] = g[a - 1][b + 1] = '/'
g[a][b + 1] = g[a][b + 2] = '_'
g[a][b + 3] = g[a - 1][b + 2] = '\\'
else:
build(a, b, n - 1)
build(a - (1 << (n - 1)), b + (1 << (n - 1)), n - 1)
build(a, b + (1 << n), n - 1)
n=int(input())
h = 1 << n
w = 1 << (n + 1)
g = [[' ' for _ in range(w + 1)] for _ in range(h + 1)]
build(h, 1, n)
for i in range(1, h + 1):
print(''.join(g[i][1:w + 1]))
地毯填补问题
算法思路
这题也是一个很经典的分治问题。题目让我们把这四种不同的拼图填到整个矩阵中,而且不能覆盖到女王所处的坐标。我们可以把女王所在的坐标认为是一个障碍物,在填补的时候我们总是把大矩阵平均隔成四块矩阵,然后往目标矩阵的中心去填补,将L形拼图的空缺部分朝向障碍物所在的方向,这样一来,分割后的四个矩阵内部都有了一个障碍物,
这样子大问题就化成了四个子问题,通过递归,当矩阵的边长变成1说明无法再分隔,直接退出递归。递归函数需要调用的参数有五个,分别是:
- 当前矩阵的左上角坐标
x
,y
; - 障碍物坐标
X
,Y
; - 矩阵的边长
n
代码实现
def fill(x,y,X,Y,n):
if n==1:
return
if X<=x+n//2-1 and Y<=y+n//2-1:
print(x+n//2,y+n//2,1)
fill(x,y,X,Y,n//2)
fill(x,y+n//2,x+n//2-1,y+n//2,n//2)
fill(x+n//2,y,x+n//2,y+n//2-1,n//2)
fill(x+n//2,y+n//2,x+n//2,y+n//2,n//2)
elif X<=x+n//2-1 and Y>=y+n//2:
print(x+n//2,y+n//2-1,2)
fill(x,y,x+n//2-1,y+n//2-1,n//2)
fill(x,y+n//2,X,Y,n//2)
fill(x+n//2,y,x+n//2,y+n//2-1,n//2)
fill(x+n//2,y+n//2,x+n//2,y+n//2,n//2)
elif X>=x+n//2 and Y<=y+n//2-1:
print(x+n//2-1,y+n//2,3)
fill(x,y,x+n//2-1,y+n//2-1,n//2)
fill(x,y+n//2,x+n//2-1,y+n//2,n//2)
fill(x+n//2,y,X,Y,n//2)
fill(x+n//2,y+n//2,x+n//2,y+n//2,n//2)
else:
print(x+n//2-1,y+n//2-1,4)
fill(x,y,x+n//2-1,y+n//2-1,n//2)
fill(x,y+n//2,x+n//2-1,y+n//2,n//2)
fill(x+n//2,y,x+n//2,y+n//2-1,n//2)
fill(x+n//2,y+n//2,X,Y,n//2)
k=int(input())
X,Y=map(int, input().split())
fill(1,1,X,Y,1<<k)
外星密码
算法思路
递归实现,一次性读取整个字符串,然后从字符串的头上一个个读取并删除,如果碰到左括号,那就把后面的数字读取来,如果是字母那就继续读,直到遇到右括号就返回读到的字符串,与前面的数字相乘(加倍操作)后再拼接到答案字符串。
代码实现
def f():
s = ""
while len(text) > 0:
c = text.pop(0)
if c == '[':
n=0
while text[0].isdigit():
n = n * 10 + int(text.pop(0))
s1 = f()
s += s1 * n
elif c == ']':
return s
else:
s += c
return s
text = list(input().strip())
print(f())
全排列问题
算法思路
深度优先搜索
- 递归:dfs 函数使用递归来尝试所有可能的排列。每次递归都会尝试在当前位置放置一个未使用的数字,然后递归到下一个位置。
- 回溯:在递归完成后,会撤销标记
use[i] = 0
使得当前数字可以在其他排列中使用。 - 建立两个数组,一个用于存放当前方案,另一个用于存放每一个数的使用情况。每一次搜索往方案中添加一个数,都要从前往后枚举,如果枚举的这个数没有被使用,那么就加到方案中然后更新使用状态,接着从第x+1个数开始向后搜索,然后要把这个数的使用状态撤回,改成未使用,以便回溯到上一层进行其他可能的排列尝试。如果
x+1
超过了最后一个数,说明搜索完成,那就输出当前方案。
代码实现
#include<bits/stdc++.h>
using namespace std;
int n;
int ans[9];//保存当前的方案
int use[9];//表示每个数是否被用过
void dfs(int x){//X表示当前搜索到那个数
if(x>n){//如果N位都搜索完了,就输出方案并返回
for(int i=1;i<=n;i++)
printf("% 5d",ans[i]);//输出方案
puts("");
return;
}
for(int i=1;i<=n;i++)//从小到大枚举
if(!use[i]){//判断这个数是否用过
ans[x]=i;//保存到方案中
use[i]=1;//标记这个数被使用了
dfs(x+1);//进行下一步搜索
use[i]=0;//撤销标记
}
}
int main(){
scanf("%d",&n);//输入
dfs(1);//从第一个数开始搜索;
}
组合的输出
算法思路
深度优先搜索
使用一个递归函数来逐步构建组合,在每一步选择一个新的数字并加入到当前组合中,然后递归调用自己生成下一个元素的组合。完成组合后,撤销最后一次选择,回到上一步继续尝试其他可能的组合,直到所有组合都被生成并输出。
代码实现
#include <bits/stdc++.h>
using namespace std;
void combine(int start, int n, int r, vector<int>& current) {
if (current.size() == r) {
// 如果组合的大小已经达到 r,输出当前组合
for (int num : current) {
printf("% 3d", num);
}
cout << endl;
return;
}
for (int i = start; i <= n; i++) {
current.push_back(i); // 加入当前数字
combine(i + 1, n, r, current); // 递归调用
current.pop_back(); // 撤销选择
}
}
int main() {
int n, r;
cin >> n >> r;
vector<int> current; // 用于存储当前组合
combine(1, n, r, current); // 从 1 开始生成组合
}
kotori和素因子
算法思路
题目的意思可以概括为:从n个装有正整数ai的素数因子的盒子中各取出一个数且互不相同,并使得总和相同。由于数据范围很小,所以可以打表直接搞出2-1000之内的所有素数,然后用试除法找到每个正整数的素因子,用二维数组存放。接下来就是要找到最优的取法,可以使用回溯算法来实现。集合visited用来记录被取出的素因子,每一次搜索都会访问一个盒子,把盒子中符合的素因子加入集合然后往下一层搜索,然后又将其从集合中移除回溯到上一层,当所有盒子中都有一个元素被取出后计算总和并用全局变量minsum来记录下最小的那个情况。minsum初始化为float(‘inf’),如果搜索结束之后值未变动说明找不到符合的情况于是输出-1其余情况输出minsum的值。
代码实现
primes=[2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191, 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997]
n=int(input())
nums=list(map(int, input().split()))
yz=[[]for i in range(n)]
for num in nums:
for k in primes:
if k>num:
break
if num%k==0:
yz[nums.index(num)].append(k)
def dfs(yz, x, visited):
global minsum
if x == len(yz):
minsum = min(minsum,sum(visited))
return
for num in yz[x]:
if num not in visited:
visited.add(num)
dfs(yz, x + 1, visited)
visited.remove(num)
minsum = float('inf')
dfs(yz, 0, set())
print(minsum) if minsum != float('inf') else print(-1)
小雷的神奇电脑
算法思路
题目让我们从序列中找任意一对数,取他们的二进制的后m位然后进行同或运算,找出可以得到的同或的最大值。这题考察的是位运算,具体步骤如下:
-
排序将相似的值放在一起,这样可以利用相邻的数对更有效地找到同或的最大值。在许多情况下,两个相邻的数对的同或结果较大,因为它们在二进制表示上可能具有更多不同的位。
-
通过
for i in range(1, n)
遍历排序后的整数列表,比较每对相邻的整数x
和y
。 -
ans
用于记录x
和y
在每一位上相等的情况所形成的值。 -
x >> j & 1
和y >> j & 1
是分别取x
和y
在第j
位的位值,判断它们是否相等。 -
如果相等,则将
1 << j
(即 2 的 j 次方)累加到ans
中。 -
通过
res = max(res, ans)
更新结果res
,以保存所有相邻整数对中最大值。
代码实现
n,m=map(int, input().split())
a=[]
while len(a)<n:
a+=list(map(int, input().split()))
a.sort()
res=0
for i in range(1,n):
x=a[i-1]
y=a[i]
ans=0
for j in range(m):
if x>>j&1==y>>j&1:
ans+=1<<j
res=max(res,ans)
print(res)
岗位分配
算法思路
这是一个数学题。首先要了解一个公式:
将n个人分配到k个地方(不一定要全分配,可以都不分配,n个人之间无差别)
总情况数等于
C
(
k
n
+
k
)
C\binom{k}{n+k}
C(n+kk)
那么这题就好做了,先把固定的人排进去,再到剩下的人中选择,直接套公式
代码实现
from math import comb
n, m = map(int, input().split())
a = m + n - sum(map(int, input().split()))
print(comb(a, n))
乘除法
算法思路
总共有两种操作,一种是乘法(增大)一种是除法(减小)。
因为乘法和除法的计算优先级一样的,所以只要按下面的方法操作,不用考虑先后
- 当前这个数小于目标数时就可以选择乘5
- 当前这个数大于目标数时,如果能整除6就除6,否则无解
代码实现
n=int(input())
for i in range(n):
x,y=map(int,input().split())
r=0
while x!=y:
if(x>y and x%6!=0):
r=-1
break
if(x<y):
x=x*5
r=r+1
if(x>y and x%6==0):
x=x//6
r=r+1
print(r)
不想想背景的gcd
算法思路
要做这题首先得知道下面这个恒等式
g
c
d
(
a
+
b
,
b
)
=
g
c
d
(
a
,
b
)
gcd(a+b,b)=gcd(a,b)
gcd(a+b,b)=gcd(a,b)
所以原式就可以化简了
设
x
=
b
j
+
c
k
x=bj+ck
x=bj+ck
原式就变成了
g
c
d
(
x
+
a
1
,
x
+
a
2
,
…
,
x
+
a
n
)
gcd(x+a_1,x+a_2, \ldots,x+a_n)
gcd(x+a1,x+a2,…,x+an)
由于
x
+
a
2
=
x
+
a
1
+
(
a
2
−
a
1
)
x+a_2=x+a_1+(a_2-a_1)
x+a2=x+a1+(a2−a1)
所以原式就变成了
g
c
d
(
x
+
a
1
,
x
+
a
1
+
(
a
2
−
a
1
)
,
…
)
gcd(x+a_1,x+a_1+(a_2-a_1),\ldots)
gcd(x+a1,x+a1+(a2−a1),…)
等于
g
c
d
(
x
+
a
1
,
a
2
−
a
1
,
a
3
−
a
1
,
…
,
a
n
−
a
1
)
gcd(x + a_1, a_2 - a_1, a_3 - a_1, \ldots, a_n - a_1)
gcd(x+a1,a2−a1,a3−a1,…,an−a1)
后面的部分与j,k无关,这样子问题就解决了,注意取绝对值!
代码实现
import math
t = 1
while t > 0:
t -= 1
N=int(input())
A = list(map(int, input().split()))
B = list(map(int, input().split()))
C = list(map(int, input().split()))
g = 0
for i in range(N - 1):
g = math.gcd(g, abs(A[i + 1] - A[i]))
mn = float('inf')
mx = float('-inf')
for i in range(N):
for j in range(N):
c = math.gcd(g, A[0] + B[i] + C[j])
mn = min(mn, c)
mx = max(mx, c)
print(f"{mn} {mx}")
AtCoder Beginner Contest 366 记录
AtCoder Beginner Contest 366
这次还是只做出了前四题。
A - Election 2
总结
给你总票数,和两个人目前的票数,判断按照目前的票数能否断定投票结果。按照最极端的情况思考,把剩下的票都给票数少的人,如果在这种极端情况下原本票数多的人还能获胜,那就证明是可以的
代码实现
n,t,a=map(int, input().split())
rest=n-a-t
if max(a,t)>min(a,t)+rest:
print("Yes")
else:
print("No")
B - Vertical Writing
总结
把输入的单词,从右到左竖着输出,如果右边的单词比当前单词长就得补上星号。我采用字符串转列表的方式,从后往前遍历单词,每次都尝试将每个单词的首元素弹出并添加到当前这行,然后输出
代码实现
n=int(input())
lines=[]
lastlen=0
maxn=0
for _ in range(n):
s=input()
if len(s)<lastlen:
s+='*'*(lastlen-len(s))
lines.append(list(s))
lastlen=len(s)
maxn=max(maxn,len(s))
for i in range(maxn):
s=''
for j in range(n-1,-1,-1):
try:
s+=lines[j].pop(0)
except:
pass
print(s)
C - Balls and Bag Query
总结
这题卡了我好一会儿,直接用数组模拟会超时,换成set又影响到了逻辑。后面干脆搞了一个字典然后过了。
Q = int(input())
balls = {}
for _ in range(Q):
a = list(map(int, input().split()))
if a[0] == 1:
balls[a[1]] = balls.get(a[1], 0) + 1
elif a[0] == 2:
if balls.get(a[1], 0) > 0:
balls[a[1]] -= 1
if balls[a[1]] == 0:
del balls[a[1]]
elif a[0] == 3:
print(len(balls))
D - Cuboid Sum Query
总结
题目意思就是把这些数存到n * n * n的三维数组,然后对于每次询问给到的区间内求和,这道题硬算超时了,得换成三维前缀和。这题其实也是一个三维前缀和的模板题,相当于给你一个立方体,立方体内的每个位置上有一个数,求子立方体内每个位置上的数的总和。
n = int(input())
a = [[[0] * (n + 1) for _ in range(n + 1)] for _ in range(n + 1)]
P = [[[0] * (n + 1) for _ in range(n + 1)] for _ in range(n + 1)]
for i in range(1, n + 1):
for j in range(1, n + 1):
p = list(map(int, input().split()))
for k in range(1, n + 1):
a[i][j][k] = p[k - 1]
for i in range(1, n + 1):
for j in range(1, n + 1):
for k in range(1, n + 1):
P[i][j][k] = a[i][j][k] + P[i-1][j][k]+ P[i][j-1][k] + P[i][j][k-1] - P[i-1][j-1][k] - P[i-1][j][k-1] - P[i][j-1][k-1] + P[i-1][j-1][k-1]
for _ in range(int(input())):
i1, i2, j1, j2, k1, k2 = map(int, input().split())
result = (P[i2][j2][k2] - P[i1-1][j2][k2] - P[i2][j1-1][k2] - P[i2][j2][k1-1] + P[i1-1][j1-1][k2] + P[i1-1][j2][k1-1] + P[i2][j1-1][k1-1] - P[i1-1][j1-1][k1-1])
print(result)
算法学习笔记
python中的引用与副本
无意中发现的一个小细节,需要引起重视
a=[1,2,3]
b=[]
c=[]
d=[]
b=a#对a进行引用,a中元素若有更改,b也会跟着更改
c=a[:]#将a的副本赋值给c,b中元素更改不影响c中的数组
d=a#对a进行引用,a中元素若有更改,d也会跟着更改
a[0]=10#将a中第一个元素修改为10
print(b)
print(c)
a=[10,11,12]#将a重新赋值为一个新的列表 [10, 11, 12]。因为d 中保存的依然是旧的a列表的引用,所以没影响。
print(d)
#输出
#[10, 2, 3]
#[1, 2, 3]
#[10, 2, 3]
位运算
位运算是对整数的二进制位进行操作的运算。在 C++ 中,位运算符用于直接操作数据的位级别。以下是 C++ 中常用的位运算符及其说明:
按位与(&):
- 操作符:
&
- 描述:按位与运算符对两个操作数的每一位进行逻辑与操作,结果是每一位都为 1 才会得到 1,否则为 0。
- 示例:
int a = 12; // 二进制: 00001100 int b = 7; // 二进制: 00000111 int c = a & b; // 结果: 00000100 (4)
判断奇偶性
在二进制表示中:
- 偶数的最低有效位是
0
。 - 奇数的最低有效位是
1
。
因此,我们可以通过检查一个整数的最低有效位来判断其奇偶性。
- 使用按位与操作符判断
使用按位与运算符&
来检查整数的最低有效位:
- 对整数
n
执行n & 1
,如果结果为1
,则n
是奇数。 - 如果结果为
0
,则n
是偶数。
- 示例代码
下面是一个示例代码,演示如何使用位运算判断一个整数是不是奇数:
bool isOdd(int n) {
return (n & 1) == 1; // 如果最低有效位是1,则为奇数
}
- Python 示例
在 Python 中,使用相同的逻辑:
def isOdd(n):
return (n & 1) == 1
按位或(|):
- 操作符:
|
- 描述:按位或运算符对两个操作数的每一位进行逻辑或操作,只要有一位为 1,结果就是 1。
- 示例:
int a = 12; // 二进制: 00001100 int b = 7; // 二进制: 00000111 int c = a | b; // 结果: 00001111 (15)
按位异或(^):
- 操作符:
^
- 描述:按位异或运算符对两个操作数的每一位进行逻辑异或操作,如果两位不同,则结果为 1,相同则为 0。
- 示例:
int a = 12; // 二进制: 00001100 int b = 7; // 二进制: 00000111 int c = a ^ b; // 结果: 00001011 (11)
按位取反(~):
- 操作符:
~
- 描述:按位取反运算符对操作数的每一位取反,即 0 变为 1,1 变为 0。
- 示例:
int a = 12; // 二进制: 00001100 int b = ~a; // 结果: 11110011 (通常取决于系统中 int 的位数)
左移右移
- 左移(<<):
- 操作符:
<<
- 描述:左移运算符将操作数的所有位向左移动指定的位数,右边空出的位填充 0。
- 示例:
int a = 12; // 二进制: 00001100 int b = a << 2; // 结果: 00110000 (48)
- 操作符:
右移(>>):
- 操作符:
>>
- 描述:右移运算符将操作数的所有位向右移动指定的位数,左边的位会根据符号位填充(对于带符号整数,左边填充符号位;对于无符号整数,左边填充 0)。
- 示例:
int a = 12; // 二进制: 00001100 int b = a >> 2; // 结果: 00000011 (3)
左移(<<
)和右移(>>
)操作在编程中有多种用途,特别是在性能优化和底层编程中。这些位移操作常用于:
1. 快速乘法和除法
-
左移:左移操作可以用来快速实现乘法。例如,将一个整数左移
n
位等同于将其乘以2^n
。这在一些需要高效乘法操作的场景下很有用。int x = 5; // 二进制: 00000101 int result = x << 3; // 结果: 00101000 (40),即 5 * 2^3
-
右移:右移操作可以用来快速实现除法。例如,将一个整数右移
n
位等同于将其除以2^n
(对于无符号整数)。这种操作在需要高效除法操作的场景下也很有用。int x = 40; // 二进制: 00101000 int result = x >> 3; // 结果: 00000101 (5),即 40 / 2^3
2.位标志和位掩码
-
设置标志位:可以通过左移操作设置某一特定的标志位。例如,设置一个特定的标志位为 1,可以通过将
1
左移到相应的位置。int flag = 1 << 5; // 结果是 00100000 (32),设置第 5 位为 1
-
检查标志位:使用按位与操作和左移操作可以检查某一特定的标志位是否被设置。
int flags = 0b10101000; bool isSet = (flags & (1 << 3)) != 0; // 检查第 3 位是否为 1
位运算通常用于底层编程和性能优化,因为它们直接操作位,计算速度很快。
快速幂
快速幂算法(Fast Exponentiation Algorithm)是一种高效计算大整数幂的算法。其基本思想是通过减少计算次数来提高效率,尤其是在计算形如 a^b
时。它的时间复杂度为 (O(\log b)),相比于直接的逐次乘法,其效率高得多。
快速幂算法的基本思想
快速幂算法利用了幂的二进制表示来减少计算次数。假设我们要计算 (a^b),我们可以通过以下方式来实现:
- 二进制分解:将指数
b
分解为二进制形式。例如,b = 13
可以二进制表示为1101
,这意味着a^{13} = a^{(2^3 + 2^2 + 2^0)}
。 - 逐步计算:根据二进制表示进行逐步计算。对于每一位为1的二进制位,我们乘以当前的结果。
快速幂算法的实现
下面是用 C++ 实现的快速幂算法:
#include <bits/stdc++.h>
using namespace std;
// 快速幂算法计算 (a^b) % MOD
long long quickPower(long long a, long long b) {
long long ans = 1; // 结果初始化为 1
const long long MOD = 1000000007; // 取模的值
while (b > 0) { // 当指数 b 大于 0 时继续计算
if (b & 1) { // 如果 b 的当前最低位是 1 (即 b 是奇数)
ans = (ans * a) % MOD; // 更新结果 ans = (ans * a) % MOD
}
a = (a * a) % MOD; // 将底数 a 平方并对 MOD 取模
b >>= 1; // 将指数 b 右移 1 位 (相当于除以 2)
}
return ans; // 返回最终结果
}
int main() {
long long a, b;
cin >> a >> b; // 读取底数 a 和指数 b
cout << quickPower(a, b) << endl; // 输出 (a^b) % 1000000007
return 0;
}
解释
- 初始化:
result
初始化为 1。 - 取模:
a = a % mod
确保a
不会因为过大而影响计算。 - 主循环:
- 如果当前
b
的最低位为1,则将当前的a
乘到result
上。 - 将
a
自身平方,并取模。 - 将
b
右移一位(即除以 2)。
- 如果当前
这个算法通过减少乘法操作次数,大大提高了计算效率。在实际应用中,特别是在处理大数运算时,快速幂算法非常有用。
排列
这段 C++ 代码使用了 next_permutation
函数来生成和打印字符串的所有排列。
#include <bits/stdc++.h>
using namespace std;
int main() {
string s="bac";
do{
cout<<s<<endl;
}while(next_permutation(s.begin(),s.end()));
return 0;
}
next_permutation 函数
next_permutation
是 C++ 标准库中的一个函数,用于生成当前序列的下一个字典序排列。其定义在 <algorithm>
头文件中:
next_permutation(s.begin(), s.end())
:这个调用尝试将字符串s
按字典序生成下一个排列。如果当前排列已经是最后一个排列,则返回false
,否则返回true
。- 例如,对于字符串
"bac"
,next_permutation
会生成"bca"
,然后是"cab"
,最后是"acb"
,循环结束时返回false
,因为"acb"
是所有排列中的最后一个。
生成部分排列
如果要生成从 n
个元素中取 m
个元素的所有排列,可以参考下面的代码,通过回溯实现:
#include <bits/stdc++.h>
using namespace std;
// 生成从 1 到 n 取 m 个的所有排列
void print_permutations(vector<int>& nums, int m) {
sort(nums.begin(), nums.end());
vector<bool> selected(nums.size(), false);
function<void(int, int)> backtrack = [&](int start, int count) {
if (count == m) {
for (int i = 0; i < nums.size(); ++i) {
if (selected[i]) {
cout << nums[i] << " ";
}
}
cout << endl;
return;
}
for (int i = start; i < nums.size(); ++i) {
selected[i] = true;
backtrack(i + 1, count + 1);
selected[i] = false;
}
};
backtrack(0, 0);
}
int main() {
vector<int> nums = {1, 2, 3};
int m = 2; // 取 2 个元素
print_permutations(nums, m);
return 0;
}
深度优先搜索
深度优先搜索算法(Depth First Search,简称DFS):一种用于遍历或搜索树或图的算法。 沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过或者在搜寻时结点不满足条件,搜索将回溯到发现节点v的那条边的起始节点。整个进程反复进行直到所有节点都被访问为止。属于盲目搜索,最糟糕的情况算法时间复杂度为O(!n)。
回溯法是一种选优搜索法,又称为试探法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。
深度优先搜索模版
int check(参数)
{
if(满足条件)
return 1;
return 0;
}
void dfs(int step)
{
判断边界
{
相应操作
}
尝试每一种可能
{
满足check条件
标记
继续下一步dfs(step+1)
恢复初始状态(回溯的时候要用到)
}
}
深度优先搜索算法框架1
int Search(int k)
{
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果
if (到目的地) 输出解;
else Search(k+1);
恢复:保存结果之前的状态{回溯一步}
}
}
深度优先搜索算法框架2
int Search(int k)
{
if (到目的地) 输出解;
else
for (i=1;i<=算符种数;i++)
if (满足条件)
{
保存结果;
Search(k+1)
恢复:保存结果之前的状态{回溯一步}
}
}
n皇后问题
C++代码
#include <bits/stdc++.h>
using namespace std;
// 打印棋盘的所有解
void print_board(const vector<vector<int>>& board) {
for (const auto& solution : board) {
for (int x : solution) {
cout << setw(5) << x + 1; // 打印每一列的位置,+1 是为了使列号从 1 开始
}
cout << endl;
}
}
// 检查在 (row, col) 位置放置皇后是否有效
bool is_valid(const vector<int>& board, int row, int col) {
for (int r = 0; r < row; ++r) {
// 检查是否与其他皇后在同一列或对角线上
if (board[r] == col ||
board[r] - col == r - row ||
board[r] - col == row - r) {
return false;
}
}
return true;
}
// 使用回溯法找到所有解
void backtrack(int row, int n, vector<int>& board, vector<vector<int>>& solutions) {
if (row == n) {
// 所有皇后都成功放置,保存当前解
solutions.push_back(board);
return;
}
for (int col = 0; col < n; ++col) {
if (is_valid(board, row, col)) {
board[row] = col; // 放置皇后
backtrack(row + 1, n, board, solutions); // 递归处理下一行
board[row] = -1; // 回溯,撤销当前行的皇后
}
}
}
int main() {
int n;
cin >> n; // 输入皇后的数量
vector<vector<int>> solutions; // 存储所有解决方案
vector<int> board(n, -1); // 初始化棋盘,-1 表示没有放置皇后
backtrack(0, n, board, solutions); // 从第 0 行开始回溯
if (solutions.empty()) {
cout << "no solution!" << endl; // 如果没有解
} else {
print_board(solutions); // 打印所有解
}
return 0;
}
python代码
def is_valid(board, row, col):
for r in range(row):
if board[r] == col or board[r] - col == r - row or board[r] - col == row - r:
return False
return True
def backtrack(row):
if row == n:
solutions.append(board[:])
return
for col in range(n):
if is_valid(board, row, col):
board[row] = col
backtrack(row + 1)
board[row] = -1
n=int(input())
solutions = []
board = [-1] * n
backtrack(0)
if not solutions:
print("no solute!")
else:
for i in solutions:
print(" ".join(f"{x+1:4}" for x in i))
拆分自然数
C++代码
#include <bits/stdc++.h>
using namespace std;
int n;// 存储自然数
// 深度优先搜索函数,用于拆分自然数
void dfs(int x, int mx, vector<int> ans) {
// 当当前拆分和等于 n 时
if (x == n) {
// 输出拆分结果,每个元素后面跟一个加号,最后一个元素后面跟换行符
for (int i = 0; i < (int)(ans.size()); i++) {
cout << ans[i] << "+\n"[i == ans.size() - 1];
}
return;
}
// 尝试将 i 加入到当前拆分中
for (int i = 1; i < n; i++) {
if (x + i > n || i < mx) continue;// 如果当前数 i 加上已有的和 x 超过 n,或者 i 小于上一个加的数 mx,则跳过
ans.push_back(i);// 将当前数 i 加入到拆分结果中
dfs(x + i, i, ans);// 递归调用 DFS 继续拆分
ans.pop_back();// 移除当前数 i,回溯
}
}
int main() {
cin >> n;
dfs(0, 0, {});// 从和为 0 开始,初始没有任何元素
}
python代码
def dfs(x,mx,ans):
if x==n:
print("+".join(ans))
return
for i in range(1,n):
if x+i>n or i<mx:
continue
else:
ans.append(str(i))
dfs(x+i,i,ans)
ans.pop()
n=int(input())
dfs(0,0,[])
数字分组求偶数和
题目描述
代码实现
def solution(numbers, index=0, current=[], ans=0):
if index == len(numbers):
if sum(current) % 2 == 0:
ans += 1
return ans
for number in str(numbers[index]): # 将数字转换为字符串以便遍历
current.append(int(number)) # 将字符转换为整数并添加到当前列表
ans = solution(numbers, index + 1, current, ans) # 递归调用
current.pop() # 回溯,移除最后一个元素
return ans
if __name__ == "__main__":
print(solution([123, 456, 789]) == 14)
print(solution([123456789]) == 4)
print(solution([14329, 7568]) == 10)