dfs:状态庞大方案很少。搜索是思想,可以用递归实现也可以用迭代实现。
//对于每个字母遍历他包含的字符c,当前str=str+c,然后递归的遍历下一个字符。
//传参:全局变量字母表,当前str,当前索引,传入的字符串
//递归出口:index等于digits.length(index可以用temp的长度计算出来)
List<String> output = new ArrayList<String>();
String []stringList={
"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
if(digits.length()!=0)
helper("",digits);
return output;
}
public void helper(String tempStr,String digits){
int index=tempStr.length();
if(index==digits.length())
output.add(tempStr);
else{
for(int i=0;i<stringList[digits.charAt(index)-'1'].length();i++)
helper(tempStr+stringList[digits.charAt(index)-'1'].charAt(i),digits);
}
}
样例2345678
state={}
for 每个数字
for c =每个数字的备选字母
for s= state中的所有字符串
s+=c;
state.add(s);
List<String> output = new ArrayList<String>();
String []stringList={
"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
if(digits.length()==0)
return output;
else{
for(int i=0;i<stringList[digits.charAt(0)-'1'].length();i++)
output.add(String.valueOf(stringList[digits.charAt(0)-'1'].charAt(i)));
for(int j= 1;j<digits.length();j++){
int length = output.size();
for(int i=0;i<stringList[digits.charAt(j)-'1'].length();i++){
for(int k = 0;k<length;k++){
String temp = output.get(k);
if(temp.length()==j){
temp+=stringList[digits.charAt(j)-'1'].charAt(i);
output.add(temp);
}
}
}
}
}
List<String> res = new ArrayList<String>();
for(String s:output){
if(s.length()==digits.length())
res.add(s);
}
return res;
}
List<String> output = new ArrayList<String>();
String []stringList={
"","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
if(digits.length()==0)
return output;
else{
for(int i=0;i<stringList[digits.charAt(0)-'1'].length();i++)
output.add(String.valueOf(stringList[digits.charAt(0)-'1'].charAt(i)));
for(int j= 1;j<digits.length();j++){
List<String>now = new ArrayList<String>();;
for(int i=0;i<stringList[digits.charAt(j)-'1'].length();i++){
for(String s:output){
s+=stringList[digits.charAt(j)-'1'].charAt(i);
now.add(s);
}
}
output=now;
}
}
return output;
}
1.枚举起点
2.从起点开始搜索下一个点的位置(不能往回走,用标记数组标一下)
3.在枚举过程中,要保证和目标单词匹配
4.需要有一个回溯的步骤,恢复操作前的初始状态。这里就是标记数组的复原。(恢复现场)
boolean res=false;
public boolean exist(char[][] board, String word) {
char [][]flag =new char[board.length][board[0].length];
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(word.charAt(0)==board[i][j]){
flag[i][j]=1;
helper(board,word,1,i,j,flag);
flag[i][j]=0;
}
}
}
return res;
}
public boolean helper(char[][]board,String word, int index,int i,int j,char[][]flag){
if(res==true)
return res;
if(index == word.length()){
res=true;
return res;
}
if(i-1>=0&&board[i-1][j]==word.charAt(index)&&flag[i-1][j]==0){
flag[i-1][j]=1;
helper(board,word,index+1,i-1,j,flag);
flag[i-1][j]=0;
}
if(i+1<board.length&&board[i+1][j]==word.charAt(index)&&flag[i+1][j]==0){
flag[i+1][j]=1;
helper(board,word,index+1,i+1,j,flag);
flag[i+1][j]=0;
}
if(j-1>=0&&board[i][j-1]==word.charAt(index)&&flag[i][j-1]==0){
flag[i][j-1]=1;
helper(board,word,index+1,i,j-1,flag);
flag[i][j-1]=0;
}
if(j+1<board[0].length&&board[i][j+1]==word.charAt(index)&&flag[i][j+1]==0){
flag[i][j+1]=1;
helper(board,word,index+1,i,j+1,flag);
flag[i][j+1]=0;
}
return res;
}
1.搜索顺序,不重复且不遗漏
样例 1 2 3 。没有重复数字。
枚举每个位置上放哪个数。(或者枚举每个数放到哪个位置上)。
LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
ArrayList<Integer> temp= new ArrayList<Integer>();
int []nums;
boolean []flag;
public List<List<Integer>> permute(int[] nums) {
if(nums == null || nums.length == 0)
return res;
this.nums=nums;
flag=new boolean[nums.length];
helper();
return res;
}
public void helper() {
if(temp.size()==nums.length)
res.add((ArrayList<Integer>)temp.clone());
//这里一定要用clone()浅拷贝,不然后续修改就修改掉了。
//这里还有一个坑,就是声明时一定要写成ArrayList而不是List,否则无法使用clone()函数
else{
for(int i=0;i<nums.length;i++)
if(flag[i]==false){
flag[i]=true;
temp.add(nums[i]);
helper();
temp.remove(Integer.valueOf(nums[i]));
flag[i]=false;
}
}
}
存在重复数。
1.通过排序把相同的数放在一起
2.在遍历过程中重复数只使用一次
3.DFS的状态用temp的size隐含。temp的size代表了枚举到第几个数
4.暂时不考虑用set去重的方法。
public LinkedList<List<Integer>> res = new LinkedList<List<Integer>>();
public ArrayList<Integer> temp= new ArrayList<Integer>();
public int []nums;
public boolean []flag;
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums == null || nums.length == 0)
return res;
this.nums=nums;
flag=new boolean[nums.length];
Arrays.sort(nums);
helper();
return res;
}
public void helper() {
int last=nums[0]-1;
if(temp.size()==nums.length)
res.add((ArrayList<Integer>)temp.clone());//浅拷贝
else{
for(int i=0;i<nums.length;i++)
//这里要保证的是每一轮对每个位置选出的数不重复。
if(flag[i]==false&&nums[i]!=last){
flag[i]=true;
temp.add(nums[i]);
last=nums[i];
int tail=temp.size();
helper();
//恢复现场
temp.remove(temp.size()-1);
flag[i]=false;
}
}
}
枚举每个元素,选择进入子集或者不选择,更新状态继续DFS。通过索引记录状态,退出递归。
public ArrayList<Integer>curr= new ArrayList<>();
public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
if(nums==null||nums.length==0)
return result;
helper(nums,0);
return result;
}
public void helper(int []nums,int currIdx){
if(currIdx==nums.length){
result.add((ArrayList<Integer>)curr.clone());
}else{
helper(nums,currIdx+1);
curr.add(nums[currIdx]);
helper(nums,currIdx+1);
curr.remove(curr.size()-1);
}
}
二进制优化。对索引进行二进制表示。
样例 1 2 3
000表示空集,001表示第一个元素…111表示全集{1 2 3}。
1.遍历0~2的N次方,对应各个子集。
2.利用移位和逻辑运算,把二进制位为1对应位置的nums[j]填入curr。
3.把curr写入ress
public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
public List<List<Integer>> subsets(int[] nums) {
if(nums==null||nums.length==0)
return result;
for(int i=0;i< 1<<nums.length;i++){
ArrayList<Integer>curr= new ArrayList<>();
for(int j=0;j<nums.length;j++)
if(((i>>j)&1)==1)
curr.add(nums[j]);
result.add(curr);
}
return result;
}
存在重复数字
public ArrayList<Integer>curr= new ArrayList<>();
public ArrayList<List<Integer>>result = new ArrayList<List<Integer>>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
if(nums==null||nums.length==0)
return result;
Arrays.sort(nums);//排序 相同的放在一起
helper(nums,0);//用索引表示状态
return result;
}
public void helper(int []nums,int currIdx){
if(currIdx==nums.length)
result.add((ArrayList<Integer>)curr.clone());
else{
int k=0;
//双指针计数当前索引处值的个数(最少是1)
while(currIdx+k<nums.length&&nums[currIdx+k]==nums[currIdx])
k++;
//放入0--k个
for(int i=0;i<=k;i++){
helper(nums,currIdx+k);//先递归进去,可以把空包含在内。
curr.add(nums[currIdx]);
}
for(int i=0;i<=k;i++)
curr.remove(curr.size()-1);
}
}
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
代码
public List<List<Integer>> res=new ArrayList<>();
public ArrayList<Integer> temp = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
int sum=0;
helper(k,1,n,sum);
return res;
}
public void helper(int k,int index,int n,int sum){
if(k==0||index>9){
if(sum==n&&k==0)
res.add((ArrayList<Integer>)temp.clone());
return ;
}
for(int i=index;i<=9;i++){
temp.add(i);
helper(k-1,i+1,n,sum+i);
temp.remove(temp.size()-1);
}
}
核心还是枚举顺序!
枚举每行的皇后在每列的情况,行默认不同,则值考虑列和对角线是否可用。
2N-1条对角线,2N-1条反对角线。(y=x+b,y=-x+b)
转换为数组索引x+y x-y+n。避免出现负数。
public int n,res=0;
public boolean [] d,ud,col;
public int totalNQueens(int n) {
//用递归次数代表行
//还需要记录列的情况和两个对角线的情况。
this.n=n;
d=new boolean[2*n];
ud= new boolean[2*n];
col = new boolean[n];
helper(0);
return res;
}
public void helper(int row){
if(row ==n){
res++;
return ;
}
for(int i=0;i<n;i++){
if(col[i]==false&&d[i+row]==false&&ud[row -i+n]==false){
col[i]=true;
d[i+row]=true;
ud[row -i+n]=true;
helper(row +1);
col[i]=false;
d[i+row]=false;
ud[row -i+n]=false;
}
}
}
八皇后和解数独都是精确优化问题(目前已经被十字链表完美解决,但是写起来十分麻烦)
每一行1-9是否已经使用row[9][9](前一个代表行,后一个代表数字)
每一列、每个九宫格同上cell[3][3][9]。