【LeetCode & 剑指offer刷题】回溯法与暴力枚举法题1:排列与组合
【LeetCode & 剑指offer 刷题笔记】目录(持续更新中...)
排列与组合
说明:排列组合方法很多,不限于文中的这些方法,可以在网上多看些解法,选择几种自己比较欣赏的解法。
1 Permutations I
Given a collection of
distinct
integers, return all possible permutations.
Example:
Permutations I问题
Input:
[1,2,3]
Output:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
Permutations II问题
Input:
[1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
/*问题: next_permutation ??单个元素如果大于9还适用吗??(有时间在讨论)
找下一个排列数,这里将排列位上的各位构成数字,下一种排列,其排列数大于输入排列数(如果输入的数为降序,已经最大,则约定下一个排列为升序排列,回到最小排列数),其最接近
要得到某个排列数下一个最接近的排列数
方法:具体见note
最小的排列数为增序排列,最大的排列数为降序排列,中间的为乱序,
(1) 先找第一个分割数pivot,其满足小于后一个相邻数,且最靠后(可以从后往前扫描)
(2) 再到后面找一个刚好大于此分割数的数changenum(从后往前扫描)
(3) 交换pivot与changenum
(4) 反序pivot后面的序列
例:
6 8 7 4 3 2
[6] 8 [7] 4 3 2 选中pivot和changenum
[7] 8 [6] 4 3 2 交换
7 [8 6 4 3 2]
7 [2 3 4 6 8] 反序pivot+1~end的序列
*/
//排列与组合问题示例:字符串的排列
//
返回一个数组所有可能的排列结果
(
数组中所有元素不同
) 方法一也适用于Permutations II
//
可以看成回溯法,也可以看成暴力枚举法
//
方法一:偷懒法
stl
中
next_permutation
函数
//O(n!), O(1)
#include
class
Solution
{
public
:
vector
<
vector
<
int
>>
permute
(
vector
<
int
>&
nums
)
{
vector
<
vector
<
int
>>
result
;
sort
(
nums
.
begin
(),
nums
.
end
());
//
排列成增序
do
{
result
.
push_back
(
nums
);
}
while
(
next_permutation
(
nums
.
begin
(),
nums
.
end
()));
//
若新排列按字典序大于旧者则为
true
。若抵达最后重排并重置范围为首个排列则为
false
return
result
;
}
};
//
方法二:自己实现
next_permutation
#include
class
Solution
{
public
:
vector
<
vector
<
int
>>
permute
(
vector
<
int
>&
nums
)
{
vector
<
vector
<
int
>>
result
;
sort
(
nums
.
begin
(),
nums
.
end
());
//
排列成增序
do
{
result
.
push_back
(
nums
);
}
while
(
nextPermutation
(
nums
));
//
与系统默认的函数参数不同
//
若新排列按字典序大于旧者则为
true
。若抵达最后重排并重置范围为首个排列则为
false
return
result
;
}
public
:
//O(n),O(1)
bool
nextPermutation
(
vector
<
int
>&
nums
)
{
if
(
nums
.
empty
()
||
nums
.
size
()==
1
)
return
false
;
//
异常情况处理
int
pivot
=
-
1
;
//
初始化枢轴
for
(
int
i
=
nums
.
size
()-
1
;
i
>=
1
;
i
--)
//从后往前扫描,找到分割数
{
if(nums[i-1] < nums[i])
{
pivot
=
i-1
;
break
;
}
}
if
(
pivot
==
-
1
)
//
说明排列数已经最大,不存在分割数
{
reverse
(
nums
.
begin
(),
nums
.
end
());
return
false
;
//
排列数已经最大时,返回
false
}
int
change
=
pivot
;
//
初始化
changenum
位置
for
(
int
i
=
nums
.
size
()-
1
;
i
>=
0
;
i
--)
//从后往前扫描,找changenum
{
if(nums[i] > nums[pivot])
{
change
=
i
;
break
;
}
}
swap
(
nums
[
pivot
],
nums
[
change
]);
//
交换
reverse
(nums.begin()+pivot+1, nums.end());
//
反序(注意这里是
begin()+pivot+1
,对应索引为
pivot+1
的位置)
return
true
;
//
存在下一个排列数
}
};
/*
方法三:递归法
*/
#include
class
Solution
{
public
:
vector
<
vector
<
int
>
>
permute
(
vector
<
int
>&
num
)
{
vector
<
vector
<
int
>>
result
;
if
(
num
.
empty
())
return
result
;
vector
<
int
>
path
;
//
中间结果
sort
(
num
.
begin
(),
num
.
end
());
//sort
之后递归函数才能按全排列顺序排列(好的初始顺序)
dfs
(
num
,
path
,
result
);
return
result
;
}
private
:
void
dfs
(
vector
<
int
>&
num
,
vector
<
int
>&
path
,
vector
<
vector
<
int
>>&
result
)
{
if
(
path
.
size
()
==
num
.
size
())
{
result
.
push_back
(
path
);
return
;
}
for
(
int
a
:
num
)
//
用于产生递归树某一层的多个分支,
if
语句来约束分支
{
if
(
find
(
path
.
begin
(),
path
.
end
(),
a
)
==
path
.
end
())
//
如果在当前路径上没有元素
a,
则将该元素
push
到路径
{
path
.
push_back
(
a
);
dfs
(
num
,
path
,
result
);
//
产生递归树的深度
path
.
pop_back
();
//
回溯,腾出空间,供一个分支
push
元素
}
}
}
};
2 Permutations II
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
Example:
Input:
[1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
//返回一个数组所有可能的排列结果(数组中存在重复元素)
/*
方法一:用next_permutation ,同问题Permutations 1
*/
/*
方法二:dfs
用一个map来统计各个元素出现的次数,通过这个容器约束各路径
与问题Permutations 1不同的是现在用计数器来约束,而之前用输入的num数组来约束选取(递归函数for循环部分)
*/
#include
class
Solution
{
public
:
vector
<
vector
<
int
>>
permuteUnique
(
vector
<
int
>&
nums
)
{
vector
<
vector
<
int
>>
result
;
if
(
nums
.
empty
())
return
result
;
vector
<
int
>
path
;
map
<
int
,
int
>
counter
;
// sort(str.begin(), str.end()); //
上面用了
map,
故这里无需先
sort
了
for
(
int
a
:
nums
)
counter
[
a
]++;
//
统计
nums
中各数出现的次数没有
key
的时候会自动创建
dfs
(
nums
,
counter
,
path
,
result
);
//
递归
return
result
;
}
private
:
void
dfs
(
vector
<
int
>&
nums
,
map
<
int
,
int
>&
counter
,
vector
<
int
>&
path
,
vector
<
vector
<
int
>>&
result
)
{
if
(
path
.
size
()
==
nums
.
size
())
//
到达树的末尾,将单路径数组
push
到结果向量中
{
result
.
push_back
(
path
);
return
;
}
for
(
auto
&
p
:
counter
)
//for
循环带来的是树宽度方向的延伸,即产生同一层的多个分支
{
if
(
p
.
second
>
0
)
//
如果该元素没有被取完(某个元素可能会出现多次)
{
path
.
push_back
(
p
.
first
);
p
.
second
--;
//
已经取了这个元素,统计数减一
dfs
(
nums
,
counter
,
path
,
result
);
//
继续往深度方向延伸
path
.
pop_back
();
//
回溯,给其他分支腾空间!!
p
.
second
++;
}
}
}
};
3 Combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 ... n.
Example:
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
//
产生所有可能组合(给
n
和
k,
返回从
1~n
中
k
个数的组合)
/*
递归法:画出递归树(数的组合数),然后设计递归函数
举例,
1~4
,
k=3
(为了避免重复,后面的数都必须必前面的大)
dfs(3) push and return
dfs(2) dfs(4) push and return
dfs(1) dfs(3) dfs(4) push and return
dfs(4) return
(递归子程序结束)
dfs(2)....
dfs(3)...
dfs(4)...
*/
class
Solution
{
public
:
vector
<
vector
<
int
>>
combine
(
int
n
,
int
k
)
{
vector
<
vector
<
int
>>
result
;
vector
<
int
>
path
;
if
(
n
<=
0
||
k
<=
0
)
return
result
;
dfs
(
n
,
k
,
1
,
path
,
result
);
return
result
;
}
private
:
//start
为每个父结点的子结点开始的数,子结点取
nums[start~end]
//
到树的末尾后
push
到
result
中
void
dfs
(
int
n
,
int
k
,
int
start
,
vector
<
int
>&
path
,
vector
<
vector
<
int
>>&
result
)
{
if
(path.size()
==
k
) //步数达到k后push路径,步数达不到k的,会运行到for循环,函数末尾而结束(递归到start = n时就会结束)
{
result
.
push_back
(
path
);
return
;
//
递归出口之一
}
for
(
int
i
=
start
;
i
<=
n
;
i
++)
//
产生父结点的多个子结点
{
path
.
push_back
(
i
);
dfs
(
n
,
k
,
i+1
,
path
,
result
);
//
深度方向和宽度方向都是以
i~end
扩展,而记录深度变化,通过
step
(为了避免重复,后面的数都必须必前面的大)某分支下一个step的start = i+1
path
.
pop_back
();
//
腾出空间
}
}
};
posted @
2019-01-06 17:06 wikiwen 阅读(
...) 评论(
...) 编辑 收藏