从零开始的 dfs(全排列,回溯)
文章目录
46. 全排列
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
class Solution {
static int n;
static boolean[] visit;
static List<List<Integer>> res;
public List<List<Integer>> permute(int[] nums) {
n=nums.length;
res=new ArrayList<>();
Deque<Integer> deque=new ArrayDeque<>();
visit=new boolean[n];
dfs(deque,0,nums);
return res;
}
static void dfs(Deque<Integer> deque,int len,int[] nums)
{
if(len==n)
{
res.add(new ArrayList<>(deque));
return;
}
for(int i=0;i<n;i++)
{
if(visit[i]==false)
{
visit[i]=true;
deque.addLast(nums[i]);
dfs(deque,len+1,nums);
visit[i]=false;
deque.removeLast();
}
}
return ;
}
}
47. 全排列 II
给定一个可包含重复数字的序列 nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
解题思路
这题和上一题要求去重,要解决重复问题,我们只要设定一个规则,保证在填第 i 个数的时候重复数字只会被填入一次即可。而在本题解中,我们选择对原数组排序,保证相同的数字都相邻,然后每次填入的数一定是这个数所在重复数集合中「从左往右第一个未被填过的数字」,即如下的判断条件:
if(i>0 && nums[i]==nums[i-1] && visit[i-1]==false)
continue;
class Solution {
static int n;
static boolean[] visit;
static List<List<Integer>> res;
public List<List<Integer>> permuteUnique(int[] nums) {
n=nums.length;
res=new ArrayList<>();
Deque<Integer> deque=new ArrayDeque<>();
visit=new boolean[n];
Arrays.sort(nums);
dfs(deque,0,nums);
return res;
}
static void dfs(Deque<Integer> deque,int len,int[] nums)
{
if(len==n)
{
res.add(new ArrayList<>(deque));
return;
}
for(int i=0;i<n;i++)
{
if(i>0 && nums[i]==nums[i-1] && visit[i-1]==false)
continue;
if(visit[i]==false)
{
visit[i]=true;
deque.addLast(nums[i]);
dfs(deque,len+1,nums);
visit[i]=false;
deque.removeLast();
}
}
return ;
}
}
77. 组合
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution {
static List<List<Integer>> res;//答案
static boolean[] visit;//标记
public List<List<Integer>> combine(int n, int k) {
res=new ArrayList<>();
visit=new boolean[n+1];//[1,n]
Deque<Integer> deque=new ArrayDeque<>();
dfs(deque,0,k,n,1);
return res;
}
static void dfs(Deque<Integer> deque,int s,int k,int n,int c)
{
if(s==k)
{
res.add(new ArrayList<>(deque));
return;
}
for(int i=c;i<=n;i++)
{
if(visit[i]==false)
{
visit[i]=true;
deque.addLast(i);
dfs(deque,s+1,k,n,i+1);
deque.removeLast();
visit[i]=false;
}
}
return;
}
}
78. 子集
给你一个整数数组 nums
,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
class Solution {
static boolean[] visit;//标记数组
static List<List<Integer>> res;//结果
static int n;//测试样例长度
public List<List<Integer>> subsets(int[] nums) {
Deque<Integer> deque=new ArrayDeque<>();
res=new ArrayList<>();
n=nums.length;
visit=new boolean[n];
//遍历长度从0到n-1
for(int i=0;i<=n;i++)
{
Arrays.fill(visit,false);
dfs(deque,0,i,nums,0);
}
return res;
}
static void dfs(Deque<Integer> deque,int s,int k,int[] nums,int c)
{
if(s==k)
{
res.add(new ArrayList<>(deque));
return;
}
for(int i=c;i<n;i++)
{
if(visit[i]==false)
{
visit[i]=true;
deque.addLast(Integer.valueOf(nums[i]));
dfs(deque,s+1,k,nums,i+1);
deque.removeLast();
visit[i]=false;
}
}
return;
}
}
79. 单词搜索
给定一个 m x n
二维字符网格 board
和一个字符串单词 word
。如果 word
存在于网格中,返回 true
;否则,返回 false
。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例 1:
输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "ABCCED"
输出:true
class Solution {
static int n,m,l;//方格大小
static boolean f;//答案
static int[] dx=new int[]{0,0,1,-1};
static int[] dy=new int[]{1,-1,0,0};//方向数组
static boolean[][] visit;//标记数组
public boolean exist(char[][] board, String word) {
n=board.length;
m=board[0].length;
l=word.length();
f=false;
visit=new boolean[n][m];
for(int i=0;i<n;i++)
{
for(int j=0;j<m;j++)
{
for(boolean[] b:visit)
{
Arrays.fill(b,false);
}
if(board[i][j]==word.charAt(0))
{
visit[i][j]=true;
dfs(board,0,i,j,word);
if(f==true)
{
return true;
}
}
}
}
return false;
}
static void dfs(char[][] board,int s,int i,int j,String word)
{
if(s==l-1)
{
f=true;
return;
}
for(int q=0;q<4;q++)
{
int x=dx[q]+i;
int y=dy[q]+j;
if (x>=0 && x<n && y>=0 && y<m && visit[x][y]==false && board[x][y] == word.charAt(s + 1)) {
visit[x][y]=true;
dfs(board, s + 1, x, y, word);
visit[x][y]=false;
}
}
return;
}
}
1219. 黄金矿工
你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n
的网格 grid
进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0
。
为了使收益最大化,矿工需要按以下规则来开采黄金:
- 每当矿工进入一个单元,就会收集该单元格中的所有黄金。
- 矿工每次可以从当前位置向上下左右四个方向走。
- 每个单元格只能被开采(进入)一次。
- 不得开采(进入)黄金数目为
0
的单元格。 - 矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。
示例 1:
输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
[5,8,7],
[0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。
class Solution {
static int[] dx={0,0,1,-1};
static int[] dy={1,-1,0,0};//方向向量
static int n,m,max;
static boolean[][] visit;//标记
public int getMaximumGold(int[][] grid) {
n=grid.length;
m=grid[0].length;
visit=new boolean[n][m];
max=0;
for(int i=0;i<n;i++) {
for (int j = 0; j < m; j++) {
if (grid[i][j] != 0) {
for (boolean[] a : visit) {
Arrays.fill(a, false);
}
visit[i][j]=true;
max=Math.max(max,grid[i][j]);
dfs(grid, i, j, grid[i][j]);
}
}
}
return max;
}
static void dfs(int[][] grid,int i,int j,int s)
{
for(int t=0;t<4;t++)
{
int x=dx[t]+i;
int y=dy[t]+j;
if(x>=0 && x<n && y>=0 && y<m && grid[x][y]!=0 && visit[x][y]==false)
{
visit[x][y]=true;
s+=grid[x][y];
max=Math.max(max,s);
dfs(grid,x,y,s);
s-=grid[x][y];
visit[x][y]=false;
}
}
return;
}
}
设想有个机器人坐在一个网格的左上角,网格 r 行 c 列。机器人只能向下或向右移动,但不能走到一些被禁止的网格(有障碍物)。设计一种算法,寻找机器人从左上角移动到右下角的路径。
网格中的障碍物和空位置分别用 1
和 0
来表示。
返回一条可行的路径,路径由经过的网格的行号和列号组成。左上角为 0 行 0 列。如果没有可行的路径,返回空数组。
示例 1:
输入:
[
[0,0,0],
[0,1,0],
[0,0,0]
]
输出: [[0,0],[0,1],[0,2],[1,2],[2,2]]
解释:
输入中标粗的位置即为输出表示的路径,即
0行0列(左上角) -> 0行1列 -> 0行2列 -> 1行2列 -> 2行2列(右下角)
**说明:**r 和 c 的值均不超过 100。
class Solution {
static int n,m;
static Deque<List<Integer>> deque;
static int[] dx=new int[]{0,1};
static int[] dy=new int[]{1,0};
static boolean[][] dp;
static int min;
public List<List<Integer>> pathWithObstacles(int[][] obstacleGrid) {
n=obstacleGrid.length;
m=obstacleGrid[0].length;
dp=new boolean[n][m];
for(boolean[] a:dp)
{
Arrays.fill(a,true);
}
deque=new ArrayDeque<>();
if(obstacleGrid[0][0]==1 || obstacleGrid[n-1][m-1]==1)
{
return deque.stream().toList();
}
min=Integer.MAX_VALUE;
List<Integer> l=new ArrayList<>();
l.add(0);
l.add(0);
deque.add(l);
if(!dfs(obstacleGrid,0,0))
{
deque.removeFirst();
}
return deque.stream().toList();
}
static boolean dfs(int[][] obstacleGrid,int i, int j)
{
//从左上角到右下角,边走边记录当前位置,回退时撤销当前位置
//因为只能从下走或者从右走,不存在走过已经走过的格子,所以不需要标记数组
if(dp[i][j]==false)
{
return false;
}
if(i==n-1 && j==m-1)
{
return true;
}
for(int t=0;t<2;t++)
{
int x=dx[t]+i;
int y=dy[t]+j;
if(x<n && y<m && obstacleGrid[x][y]==0)
{
List<Integer> list=new ArrayList<>();
list.add(x);
list.add(y);
deque.addLast(list);
if(dfs(obstacleGrid,x,y))
{
return true;
}
else
deque.removeLast();
}
}
dp[i][j]=false;
return false;
}
}
494. 目标和
给你一个非负整数数组 nums
和一个整数 target
。
向数组中的每个整数前添加 '+'
或 '-'
,然后串联起所有整数,可以构造一个 表达式 :
- 例如,
nums = [2, 1]
,可以在2
之前添加'+'
,在1
之前添加'-'
,然后串联起来得到表达式"+2-1"
。
返回可以通过上述方法构造的、运算结果等于 target
的不同 表达式 的数目。
示例 1:
输入:nums = [1,1,1,1,1], target = 3
输出:5
解释:一共有 5 种方法让最终目标和为 3 。
-1 + 1 + 1 + 1 + 1 = 3
+1 - 1 + 1 + 1 + 1 = 3
+1 + 1 - 1 + 1 + 1 = 3
+1 + 1 + 1 - 1 + 1 = 3
+1 + 1 + 1 + 1 - 1 = 3
class Solution {
int res=0;
public int findTargetSumWays(int[] nums, int target) {
dfs(nums,0,0,target);
return res;
}
void dfs(int[] nums,int i,int s,int target)
{
if(i==nums.length)
{
if(s==target)
res++;
return;
}
dfs(nums,i+1,s+nums[i],target);
dfs(nums,i+1,s-nums[i],target);
return;
}
}
491. 递增子序列
给你一个整数数组 nums
,找出并返回所有该数组中不同的递增子序列,递增子序列中 至少有两个元素 。你可以按 任意顺序 返回答案。
数组中可能含有重复元素,如出现两个整数相等,也可以视作递增序列的一种特殊情况。
示例 1:
输入:nums = [4,6,7,7]
输出:[[4,6],[4,6,7],[4,6,7,7],[4,7],[4,7,7],[6,7],[6,7,7],[7,7]]
class Solution {
static int n;
static List<List<Integer>> res;
public List<List<Integer>> findSubsequences(int[] nums) {
res=new ArrayList<>();
n=nums.length;
Deque<Integer> deque=new ArrayDeque<>();
for(int i=2;i<=n;i++)
{
dfs(nums,deque,i,0);
}
return res;
}
static void dfs(int[] nums,Deque<Integer> deque,int k,int c)
{
if(deque.size()==k)
{
res.add(new ArrayList<>(deque));
return;
}
Set<Integer> set=new HashSet<>();
for(int i=c;i<n;i++)
{
if(set.contains(nums[i]))
{
continue;
}
set.add(nums[i]);
if(deque.size()==0 || (deque.size()!=0 && deque.getLast()<=nums[i])) {
deque.addLast(nums[i]);
dfs(nums, deque, k, i + 1);
deque.removeLast();
}
}
return;
}
}
LCR 157. 套餐内商品的排列顺序
某店铺将用于组成套餐的商品记作字符串 goods
,其中 goods[i]
表示对应商品。请返回该套餐内所含商品的 全部排列方式 。
返回结果 无顺序要求,但不能含有重复的元素。
示例 1:
输入:goods = "agew"
输出:["aegw","aewg","agew","agwe","aweg","awge","eagw","eawg","egaw","egwa","ewag","ewga","gaew","gawe","geaw","gewa","gwae","gwea","waeg","wage","weag","wega","wgae","wgea"]
class Solution {
boolean[] visit;
int n;
Set<String> set;
public String[] goodsOrder(String goods) {
set=new HashSet<>();
n=goods.length();
visit=new boolean[n];
Deque<Character> str=new ArrayDeque<>();
dfs(goods,str,0);
return set.toArray(new String[0]);
}
void dfs(String good, Deque<Character> str, int count)
{
if(count==n)
{
String s="";
for(Character ch:str)
{
s+=ch;
}
set.add(s);
return;
}
for(int i=0;i<n;i++)
{
if(visit[i]==false)
{
visit[i]=true;
str.addLast(good.charAt(i));
dfs(good,str,count+1);
str.removeLast();
visit[i]=false;
}
}
}
}
17. 电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
class Solution {
List<String> res=new ArrayList<>();
String[] str={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
public List<String> letterCombinations(String digits) {
StringBuilder sb=new StringBuilder();
if(digits.length()==0)
{
return res;
}
dfs(digits,sb,0);
return res;
}
void dfs(String digits,StringBuilder sb,int index)
{
//判断结束条件
if(index==digits.length())
{
res.add(sb.toString());
return;
}
String s=str[digits.charAt(index)-'0'];
//选择s中的一个字符
for(int i=0;i<s.length();i++)
{
sb.append(s.charAt(i));
dfs(digits,sb,index+1);
//回溯
sb.deleteCharAt(index);
}
return;
}
}