1.1 全组合(无重复)
题目:给定一个数组(不存在重复),输出所有可能的组合,不限定顺序(LeetCode 78)。 样例:[1,2,3]=>[[],[1],[2],[3],[1,2],[1,3],[2,3],[1,2,3]] 技巧:因为是不存在重复的,典型的采用递归即可,即每次取定第一个开始的节点,然后从该节点后面开始放数据和出数据 代码:
class Solution {
public List < List < Integer > > subsets ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, new ArrayList < > ( ) , 0 ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, List < Integer > subList, int index) {
if ( index> nums. length) {
return ;
}
list. add ( new ArrayList < > ( subList) ) ;
for ( int i= index; i< nums. length; i++ ) {
subList. add ( nums[ i] ) ;
backtrack ( nums, list, subList, i+ 1 ) ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
1.2 全组合(重复)
题目:同上,但是存在重复(LeetCode 90)。 样例:[1,2,2]=>[[],[1],[1,2],[1,2,2],[2],[2,2]] 技巧:因为存在重复,所以我们必然得先排序,然后每次选定一个开始的节点,考虑到会存在重复,所以我们在访问完第一个重复的节点后,需要跳过重复的节点来选其为第一个节点。最后,就是递归重复1.1的放数据和出数据了。 代码:
class Solution {
public List < List < Integer > > subsetsWithDup ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
Arrays . sort ( nums) ;
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, new ArrayList < > ( ) , 0 ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, List < Integer > subList, int index) {
if ( index> nums. length) {
return ;
}
list. add ( new ArrayList < > ( subList) ) ;
for ( int i= index; i< nums. length; i++ ) {
if ( i> index&& nums[ i] == nums[ i- 1 ] ) {
continue ;
}
subList. add ( nums[ i] ) ;
backtrack ( nums, list, subList, i+ 1 ) ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
1.3 全排列(无重复)
题目:给定数组,其中不包含重复数据,需要输出所有可能的全排列,不限定顺序(LeetCode 46)。 样例:[1,2,3]=>[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1] 技巧:
解法1:考虑到无重复数据,我们只需每次选定一个数作为最开始的节点,然后for循环的选择第2个节点,但是我们需要用一个标识位数组来记录已经访问过的节点(即这些节点已经在前面放进去了)。然后递归的就是放数据和改标识位。 解法2:考虑到无重复数据,最初,我们可以选定下标为0的为第一部分的第一个节点数据,然后选择后面的每一个节点跟其交换,从而形成一个排列。同理,这样后面部分同理采用递归的方式来选择当前部分第一个节点,依旧选择后面一个节点跟其交换。 对比:
解法1:简单,且容易理解和写出来。 解法2:速度更快,因为通过交换的方式减少了数据的放入和放出。 代码: 解法1:
class Solution {
public List < List < Integer > > permute ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, new ArrayList < > ( ) , new boolean [ nums. length] ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, List < Integer > subList, boolean [ ] visited) {
if ( subList. size ( ) == nums. length) {
list. add ( new ArrayList < > ( subList) ) ;
} else {
for ( int i= 0 ; i< nums. length; i++ ) {
if ( visited[ i] ) {
continue ;
}
visited[ i] = true ;
subList. add ( nums[ i] ) ;
backtrack ( nums, list, subList, visited) ;
subList. remove ( subList. size ( ) - 1 ) ;
visited[ i] = false ;
}
}
}
}
class Solution {
public List < List < Integer > > permute ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, 0 ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, int index) {
if ( index== nums. length) {
list. add ( getList ( nums) ) ;
} else {
for ( int i= index; i< nums. length; i++ ) {
swap ( nums, index, i) ;
backtrack ( nums, list, index+ 1 ) ;
swap ( nums, i, index) ;
}
}
}
private List < Integer > getList ( int [ ] nums) {
List < Integer > list= new ArrayList < > ( ) ;
for ( int num: nums) {
list. add ( num) ;
}
return list;
}
private void swap ( int [ ] nums, int i, int j) {
int tmp= nums[ i] ;
nums[ i] = nums[ j] ;
nums[ j] = tmp;
}
}
1.4 全排列(重复)
题目:同上,但是存在重复数据(LeetCode 47)。 样例:[1,1,2]=>[[1,1,2],[1,2,1],[2,1,1]] 技巧:
解法1:同1.3的解法1,因为存在重复数据,我们需要对数据进行排序,然后每次选定第一个没有访问的过重复作为第一个节点。最后,就是按照上面解法1的做法了(如果同前面一样,并且前面没有访问过的话,说明该节点为重复的,则不能选择)。 解法2:同1.3的解法2,我们需要每次都跳过同第一个节点一样的来进行交换(因为后面节点跟第一个节点一样,两者交换的排列一样的)。 代码: 解法1:
class Solution {
public List < List < Integer > > permuteUnique ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
Arrays . sort ( nums) ;
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, new ArrayList < > ( ) , new boolean [ nums. length] ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, List < Integer > subList, boolean [ ] visited) {
if ( subList. size ( ) == nums. length) {
list. add ( new ArrayList < > ( subList) ) ;
} else {
for ( int i= 0 ; i< nums. length; i++ ) {
if ( visited[ i] || ( i> 0 && nums[ i] == nums[ i- 1 ] && ! visited[ i- 1 ] ) ) {
continue ;
}
subList. add ( nums[ i] ) ;
visited[ i] = true ;
backtrack ( nums, list, subList, visited) ;
visited[ i] = false ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
}
class Solution {
public List < List < Integer > > permuteUnique ( int [ ] nums) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, 0 ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, int index) {
if ( index== nums. length) {
list. add ( new ArrayList < > ( asList ( nums) ) ) ;
} else {
for ( int i= index; i< nums. length; i++ ) {
if ( ! needSwap ( nums, index, i) ) {
continue ;
}
swap ( nums, index, i) ;
backtrack ( nums, list, index+ 1 ) ;
swap ( nums, i, index) ;
}
}
}
private boolean needSwap ( int [ ] nums, int index, int i) {
for ( int start= index; start< i; start++ ) {
if ( nums[ start] == nums[ i] ) {
return false ;
}
}
return true ;
}
private List < Integer > asList ( int [ ] nums) {
List < Integer > list= new ArrayList < > ( ) ;
for ( int num: nums) {
list. add ( num) ;
}
return list;
}
private void swap ( int [ ] nums, int i, int j) {
int tmp= nums[ i] ;
nums[ i] = nums[ j] ;
nums[ j] = tmp;
}
}
1.5 全组合和(无重复)
题目:给定数组和target,我们需要找出满足子数组和等于target的所有满足条件的子数组,且不限定顺序,其中每个数字可以重复使用多次(LeetCode 39)。 样例:[2,3,6,7],target=7 => [[2,2,3],[7]] 说明:每个数字>=0,数组长度<=30,无重复数字。 技巧:同上面解题思路类似,只是这里我们可以对每个数组使用多次,我们每次递归需要把当前剩余的差值(target-nums[i])作为target当作参数传入。 代码:
class Solution {
public List < List < Integer > > combinationSum ( int [ ] candidates, int target) {
if ( candidates== null || candidates. length== 0 || target== 0 ) {
return new ArrayList < > ( ) ;
}
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( candidates, list, new ArrayList < > ( ) , target, 0 ) ;
return list;
}
public void backtrack ( int [ ] candidates, List < List < Integer > > list, List < Integer > subList, int remain, int index) {
if ( remain< 0 ) {
return ;
}
if ( remain== 0 ) {
list. add ( new ArrayList < > ( subList) ) ;
} else {
for ( int i= index; i< candidates. length; i++ ) {
subList. add ( candidates[ i] ) ;
backtrack ( candidates, list, subList, remain- candidates[ i] , i) ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
}
1.6 全组合(重复)
题目:同上,但是数组中的每个数字我们只能使用一次。 样例:[10,1,2,7,6,1,5],target=8 => [[1,1,6],[1,2,5],[1,7],[2,6]] 技巧:因为存在重复,而且每个数我们只能用一次,所以我们需要 排序,并且跳过重复数作为第一个选定的节点,然后就是递归进行同样的步骤即可。 代码:
class Solution {
public List < List < Integer > > combinationSum2 ( int [ ] nums, int target) {
if ( nums== null || nums. length== 0 ) {
return new ArrayList < > ( ) ;
}
Arrays . sort ( nums) ;
List < List < Integer > > list= new ArrayList < > ( ) ;
backtrack ( nums, list, new ArrayList < > ( ) , target, 0 ) ;
return list;
}
private void backtrack ( int [ ] nums, List < List < Integer > > list, List < Integer > subList, int remain, int index) {
if ( remain< 0 ) {
return ;
}
if ( remain== 0 ) {
list. add ( new ArrayList < > ( subList) ) ;
} else {
for ( int i= index; i< nums. length; i++ ) {
if ( i> index&& nums[ i] == nums[ i- 1 ] ) {
continue ;
}
subList. add ( nums[ i] ) ;
backtrack ( nums, list, subList, remain- nums[ i] , i+ 1 ) ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
}
1.7 所有回文组合
题目:给定字符串,我们需要找出所有满足回文(正反对称的)的子字符串。 样例:“aab”=>[[“a”,“a”,“b”],[“aa”,“b”]] 技巧:同1.1的全组合类型,我们需要选定开始的第一个节点,然后分别追加判断是否满足回文。同理,接着递归进行数据放入和放出。 代码:
class Solution {
public List < List < String > > partition ( String s) {
if ( s== null || s. length ( ) == 0 ) {
return new ArrayList < > ( ) ;
}
List < List < String > > list= new ArrayList < > ( ) ;
backtrack ( s. toCharArray ( ) , list, new ArrayList < > ( ) , 0 ) ;
return list;
}
private void backtrack ( char [ ] chs, List < List < String > > list, List < String > subList, int index) {
if ( index== chs. length) {
list. add ( new ArrayList < > ( subList) ) ;
} else {
for ( int i= index; i< chs. length; i++ ) {
if ( isPalindrome ( chs, index, i) ) {
subList. add ( getString ( chs, index, i) ) ;
backtrack ( chs, list, subList, i+ 1 ) ;
subList. remove ( subList. size ( ) - 1 ) ;
}
}
}
}
private boolean isPalindrome ( char [ ] chs, int start, int end) {
while ( start< end) {
if ( chs[ start++ ] != chs[ end-- ] ) {
return false ;
}
}
return true ;
}
private String getString ( char [ ] chs, int start, int end) {
StringBuilder str= new StringBuilder ( ) ;
for ( int i= start; i<= end; i++ ) {
str. append ( chs[ i] ) ;
}
return str. toString ( ) ;
}
}
2.小结
针对全排列:如果第一时间想不到交换的方式,可以采用从0->length的全排列探测法来放入数据,同时采用visited标识位数组来记录已经访问过的节点。 针对全组合:每次递归的下标即当前访问节点的i的下一个i+1(因为是从当前节点的后面开始选节点),所以不能使用第一个index(index仅表示当前递归部分的第一个节点,这种是在全排列交换数据的时候使用的)。
3.参考地址