一、组合:同一个元素不能重复使用
1.1 组合
77:给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。
class Solution {
LinkedList<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combine(int n, int k) {
backtracking(n,k,1);
return result;
}
public void backtracking(int n,int k,int startindex){
if(path.size()==k){
//不能直接放path,因为传进去是引用,后面path继续修改会影响之前的path结果
result.add(new LinkedList<>(path));
return ;
}
//减枝优化:还需要在n个元素中再找k-path.size个元素,所以i的位置最多是n-(k-path.size())+1
for(int i =startindex;i<=n-(k-path.size())+1;i++){
path.offer(i);
backtracking(n,k,i+1);
path.pollLast();
}
}
}
1.2 组合之和
216:找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
与上题相比,多了个sum的限制
class Solution {
LinkedList<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
backtracking(k,n,1);
return result;
}
public void backtracking(int k,int sum,int startindex){
if(sum<0||path.size()>k)
return ;
if(sum==0&&path.size()==k){
result.add(new LinkedList(path));
return ;
}
for(int i=startindex;i<10;i++){
path.add(i);
sum -= i;
backtracking(k,sum,i+1);
sum += i;
path.pollLast();
}
}
}
二、组合进阶
2.1 组合总和:candidates无重复元素,可以重复拿取同一个元素
39:给你一个无重复元素的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有不同组合 ,并以列表形式返回。你可以按任意顺序返回这些组合。candidates 中的同一个数字可以无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。
class Solution {
LinkedList<Integer> path = new LinkedList<>();
List<List<Integer>> result = new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
backtracking(candidates,target,0);
return result;
}
public void backtracking(int[] candidates,int target,int startindex){
if(target<0)
return;
if(target==0){
result.add(new LinkedList(path));
return ;
}
for(int i=startindex;i<candidates.length;i++){
path.offer(candidates[i]);
//下一层 下标从i开始,代表可以重复选取相同元素
backtracking(candidates,target-candidates[i],i);
path.pollLast();
}
}
}
2.2 组合总和:candidates有重复元素,不能重复拿取同一个元素
40:给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的每个数字在每个组合中只能使用一次。
class Solution {
List<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
boolean[] used = new boolean[candidates.length];
Arrays.sort(candidates);
backtracking(candidates,target,0,used);
return result;
}
public void backtracking(int[] candidates,int target,int startindex,boolean[] used){
if(target<0)
return ;
if(target==0){
result.add(new LinkedList(path));
return ;
}
for(int i=startindex;i<candidates.length;i++){
//说明是同一层之前的元素
if(i>0&&candidates[i]==candidates[i-1]&&used[i-1]==false)
continue;
//used[i]!=false,说明是上下层关系,
used[i] = true;
path.offer(candidates[i]);
backtracking(candidates,target-candidates[i],i+1,used);
path.pollLast();
used[i] = false;
}
}
}
三、排列
3.1 全排列
给定一个不含重复数字的数组 nums ,返回其所有可能的全排列 。你可以按任意顺序返回答案。
class Solution {
List<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return result;
}
public void backtracking(int[] nums,boolean[] used){
if(path.size()==nums.length){
result.add(new LinkedList(path));
return;
}
//下标从0-nums.length-1
for(int i=0;i<nums.length;i++){
//上一层用过了
if(used[i]==true)
continue;
//这一层要用,让下一层不要用
used[i]=true;
path.offer(nums[i]);
backtracking(nums,used);
path.pollLast();
//清除标记,让同一层的兄弟儿子可以用
used[i]=false;
}
return ;
}
}
3.2 全排列II
47/剑指offer38:给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
class Solution {
List<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backtracking(nums,used);
return result;
}
public void backtracking(int[] nums,boolean[] used){
if(path.size()==nums.length){
result.add(new LinkedList(path));
return ;
}
//下标从0-nums.length-1
for(int i=0;i<nums.length;i++){
//左边兄弟和自己元素相等
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false)
continue;
//上一层用过这个元素
if(used[i]==true)
continue;
used[i]=true;
path.offer(nums[i]);
backtracking(nums,used);
path.pollLast();
used[i]=false;
}
return ;
}
}
四、分割字符串
4.1 分割字符串
131:给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是回文串 。返回 s 所有可能的分割方案。 回文串是正着读和反着读都一样的字符串。
class Solution {
List<List<String>> result = new LinkedList<>();
LinkedList<String> path = new LinkedList<>();
public List<List<String>> partition(String s) {
backtracking(s,0);
return result;
}
public void backtracking(String s,int startindex){
//如果已经截取完,加入到结果
if(startindex>=s.length()){
result.add(new LinkedList(path));
return ;
}
//从startindex开始截取,找到[startindex,i]是回文,递归往下一层
for(int i=startindex;i<s.length();i++){
if(isPalindrome(s,startindex,i)){
path.offer(s.substring(startindex,i+1));
backtracking(s,i+1);
path.pollLast();
}
}
}
boolean isPalindrome(String s,int start,int end){
while(start<end){
if(s.charAt(start)!=s.charAt(end))
return false;
start++;
end--;
}
return true;
}
}
4.2 复原IP地址
93:给定一个只包含数字的字符串 s ,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在 s 中插入 '.' 来形成。你不能重新排序或删除 s 中的任何数字。你可以按 任何 顺序返回答案。
class Solution {
List<String> result = new LinkedList<>();
public List<String> restoreIpAddresses(String s) {
backtracking(s,0,"",0);
return result;
}
public void backtracking(String s,int startindex,String path,int count){
if(count>4)
return ;
if(startindex>=s.length()&&count==4){
result.add(path.substring(0,path.length()-1));
return ;
}
for(int i=startindex;i<s.length();i++){
if(isValid(s,startindex,i)){
backtracking(s,i+1,path + s.substring(startindex,i+1)+".",count+1);
}
}
}
public boolean isValid(String s,int start,int end){
if(end-start>2)
return false;
if(end-start>0&&s.charAt(start)=='0')
return false;
if(Integer.parseInt(s.substring(start,end+1))>255)
return false;
return true;
}
}
4.3 单词拆分II
140:给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,在字符串中增加空格来构建一个句子,使得句子中所有的单词都在词典中。返回所有这些可能的句子。
class Solution {
List<String> result = new LinkedList<>();
List<String> wordDict = new LinkedList();
public List<String> wordBreak(String s, List<String> wordDict) {
this.wordDict = wordDict;
backtracking(s,0,"");
return result;
}
public boolean isValid(String s,int start,int end){
String tmp = s.substring(start,end+1);
return wordDict.contains(tmp);
}
public void backtracking(String s,int startindex,String path){
if(startindex==s.length()){
path = path.substring(0,path.length()-1);
result.add(path);
}
for(int i=startindex;i<s.length();i++){
if(isValid(s,startindex,i)){
backtracking(s,i+1,path+s.substring(startindex,i+1)+" ");
}
}
}
}
五、子集
78:给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。 解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
与组合类似,不同的是没有终止条件,沿途的记录都放入结果,for循环结束就终止
class Solution {
List<List<Integer>> result = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> subsets(int[] nums) {
backtracking(nums,0);
return result;
}
public void backtracking(int[] nums,int startindex){
//没有写终止,沿途的记录都放入结果,for循环结束就终止
result.add(new LinkedList(path));
for(int i=startindex;i<nums.length;i++){
path.offer(nums[i]);
backtracking(nums,i+1);
path.pollLast();
}
return ;
}
}
六、深度遍历
6.1 岛屿数量
200:给你一个由 '1'(陆地)和 '0'(水)组成的的二维网格,请你计算网格中岛屿的数量。 岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。 此外,你可以假设该网格的四条边均被水包围。
class Solution {
public int numIslands(char[][] grid) {
int count = 0;
int m = grid.length;
int n = grid[0].length;
for(int i =0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]=='1'){//遇到陆地开始深度遍历
dfs(grid,i,j);
count++;
}
}
}
return count;
}
public void dfs(char[][] grid,int row,int col){
if(!inArea(grid,row,col))
return ;
//以及遍历过的地方,或者是水域直接返回
if(grid[row][col]=='0'||grid[row][col]=='2')
return ;
//深度遍历到的地方标记为2
grid[row][col] = '2';
dfs(grid,row+1,col);
dfs(grid,row-1,col);
dfs(grid,row,col-1);
dfs(grid,row,col+1);
}
public boolean inArea(char[][] grid,int row,int col){
return row>=0&&row<grid.length&&col>=0&&col<grid[0].length;
}
}
6.2 岛屿的最大面积
695:根据上一题求,岛屿的最大面积
class Solution {
public int maxAreaOfIsland(int[][] grid) {
int m = grid.length;
int n = grid[0].length;
int max = 0;
for(int i=0;i<m;i++){
for(int j=0;j<n;j++){
if(grid[i][j]==1){
max =Math.max(max,dfs(grid,i,j));
}
}
}
return max;
}
public int dfs(int[][]grid,int row,int col){
if(row<0||col<0||row>grid.length-1||col>grid[0].length-1)
return 0;
if(grid[row][col]==0||grid[row][col]==2)
return 0;
//染色,标记为岛屿
grid[row][col]=2;
return 1+dfs(grid,row+1,col)+dfs(grid,row-1,col)+dfs(grid,row,col+1)+dfs(grid,row,col-1);
}
}
6.3 单词搜索
79:给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。 单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
class Solution {
public boolean exist(char[][] board, String word) {
int[][] visited = new int[board.length][board[0].length];
for(int i=0;i<board.length;i++){
for(int j=0;j<board[0].length;j++){
if(dfs(board,i,j,word,0,visited))
return true;
}
}
return false;
}
public boolean dfs(char[][] board,int row,int col,String word,int k,int[][] visited){
if(row<0||row>board.length-1||col<0||col>board[0].length-1||visited[row][col]==1){
return false;
}
if(board[row][col]!=word.charAt(k))
return false;
if(board[row][col]==word.charAt(k)&&k==word.length()-1){
return true;
}
//标记这个点已经访问过,下一层不能访问
visited[row][col] = 1;
boolean up = dfs(board,row-1,col,word,k+1,visited);
boolean down = dfs(board,row+1,col,word,k+1,visited);
boolean left = dfs(board,row,col-1,word,k+1,visited);
boolean right = dfs(board,row,col+1,word,k+1,visited);
//擦除标记,自己走完让别的路径方法可以走
visited[row][col] = 0;
return up||down||left||right;
}
}