1. 组合
leetcode题目链接:77. 组合
给定两个整数
n
和k
,返回范围[1, n]
中所有可能的k
个数的组合。你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
示例 2:
输入:n = 1, k = 1 输出:[[1]]
解法:
class Solution {
// 成员变量可以优化为函数入参
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combine(int n, int k) {
// 参数校验
if(n <= 0 || k <= 0){
return null;
}
// 回溯算法
backTrack(n, k, 1);
return result;
}
public void backTrack(int n, int k, int startIndex){
// 剪枝操作,剩余可选元素已经达不到k个
// if(path.size() + n - startIndex + 1 < k){
// return;
// }
if(path.size() == k){
// 注意,这里要值复制
result.add(new ArrayList(path));
System.out.println("debug: "+String.valueOf(path));
return;
}
// 回溯算法的常规剪枝操作是在for循环里面控制i的最大值来实现
for(int i = startIndex; i <= path.size() + n - k + 1; i++){
path.add(i);
// 注意,这里startIndex是从i+1开始的
backTrack(n, k, i + 1);
path.remove(path.size() - 1);
}
}
}
2. 组合总和III
leetcode题目链接:216. 组合总和III
找出所有相加之和为
n
的k
个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
示例 1:
输入: k = 3, n = 7 输出: [[1,2,4]] 解释: 1 + 2 + 4 = 7 没有其他符合的组合了。
示例 2:
输入: k = 3, n = 9 输出: [[1,2,6], [1,3,5], [2,3,4]] 解释: 1 + 2 + 6 = 9 1 + 3 + 5 = 9 2 + 3 + 4 = 9 没有其他符合的组合了。
示例 3:
输入: k = 4, n = 1 输出: [] 解释: 不存在有效的组合。 在[1,9]范围内使用4个不同的数字,我们可以得到的最小和是1+2+3+4 = 10,因为10 > 1,没有有效的组合。
解法
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum3(int k, int n) {
if(k <= 0 || n <= 0){
return null;
}
backTrack(k, n, 1);
return result;
}
/**
* 递归方法
*/
public void backTrack(int k, int n, int startIndex){
if(path.size() == k && listSum(path) == n){
result.add(new ArrayList(path));
return;
}
if(path.size() == k){
return;
}
// 剪枝1,i取值的最大值做限制,使得总的元素个数不会少于k个
for(int i = startIndex; i <= 9 - (k-path.size()) + 1; i++){
// 剪枝2,如果此时path中的元素和已经大于n了,提前结束for循环
if(listSum(path) > n){
return;
}
path.add(i);
backTrack(k, n, i+1);
path.remove(path.size()-1);
}
}
/**
* 求list中所有元素之和
*/
public Integer listSum(List<Integer> path){
Integer result = 0;
for(Integer i : path){
result += i;
}
return result;
}
}
3. 组合总和II
leetcode题目链接:40. 组合总和II
给定一个候选人编号的集合
candidates
和一个目标数target
,找出candidates
中所有可以使数字和为target
的组合。
candidates
中的每个数字在每个组合中只能使用 一次 。注意:解集不能包含重复的组合。
示例 1:
输入: candidates =[10,1,2,7,6,1,5]
, target =8
, 输出: [ [1,1,6], [1,2,5], [1,7], [2,6] ]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5, 输出: [ [1,2,2], [5] ]
解法:
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> combinationSum2(int[] candidates, int target) {
if(candidates == null || candidates.length == 0 || target <= 0){
return null;
}
Arrays.sort(candidates);
Boolean[] used = new Boolean[candidates.length];
backTrack(candidates, target, 0, 0, used);
return result;
}
public void backTrack(int[] candidates, int target, int sum, int startIndex, Boolean[] used){
// 结果
if(sum == target){
result.add(new ArrayList(path));
return;
}
// 剪枝
if(sum > target){
return;
}
// 单次搜索
for(int i = startIndex; i < candidates.length; i++){
// 关键点!!!
// 使用used数组记录元素使用状态,来判断值形同的元素是否是在同一层循环中,同一层循环只会选取一个元素所以前一个元素的used必定为0
// 只有同一层循环,值相同的元素才需要去重,不同层不需要去重
if(i > 0 && candidates[i] == candidates[i-1] && (used[i-1] == null || !used[i-1])){
continue;
}
sum += candidates[i];
path.add(candidates[i]);
used[i] = true;
backTrack(candidates, target, sum, i+1, used);
sum -= candidates[i];
path.remove(path.size() - 1);
used[i] = false;
}
}
public List<List<Integer>> combinationSum2_timeout(int[] candidates, int target) {
if(candidates == null || candidates.length == 0 || target <= 0){
return null;
}
Arrays.sort(candidates);
backTrack(candidates, target, 0, 0);
return result;
}
public void backTrack(int[] candidates, int target, int sum, int startIndex){
// 结果
if(sum == target){
if(!listContain(result, path)){
result.add(new ArrayList(path));
}
return;
}
// 剪枝
if(sum > target){
return;
}
// 单次搜索
for(int i = startIndex; i < candidates.length; i++){
sum += candidates[i];
path.add(candidates[i]);
backTrack(candidates, target, sum, i+1);
sum -= candidates[i];
path.remove(path.size() - 1);
}
}
/**
* 判断list list 中是否包含了值相同的list(path)
* 此解法会超时
*/
public boolean listContain(List<List<Integer>> result, List<Integer> path){
for(List<Integer> list : result){
if(String.valueOf(list).equals(String.valueOf(path))){
return true;
}
}
return false;
}
}
4. 复原IP地址
leetcode题目链接:93. 复原IP地址
有效 IP 地址 正好由四个整数(每个整数位于
0
到255
之间组成,且不能含有前导0
),整数之间用'.'
分隔。
- 例如:
"0.1.2.201"
和"192.168.1.1"
是 有效 IP 地址,但是"0.011.255.245"
、"192.168.1.312"
和"192.168@1.1"
是 无效 IP 地址。给定一个只包含数字的字符串
s
,用以表示一个 IP 地址,返回所有可能的有效 IP 地址,这些地址可以通过在s
中插入'.'
来形成。你 不能 重新排序或删除s
中的任何数字。你可以按 任何 顺序返回答案。
示例 1:
输入:s = "25525511135" 输出:["255.255.11.135","255.255.111.35"]
示例 2:
输入:s = "0000" 输出:["0.0.0.0"]
示例 3:
输入:s = "101023" 输出:["1.0.10.23","1.0.102.3","10.1.0.23","10.10.2.3","101.0.2.3"]
解法:
class Solution {
List<String> result = new ArrayList<>();
List<String> path = new ArrayList<>();
public List<String> restoreIpAddresses(String s) {
if(s == null || s.length() == 0){
return null;
}
backTrack(s, 0);
return result;
}
public void backTrack(String s, int startIndex){
// 递归到最后,并且刚好分成了4组合法的整数,这是要找的结果
if(path.size() == 4 && startIndex == s.length()){
result.add(String.join(".", path));
return;
}
// 字符串遍历完了或者分割已经超过4组,后面不会有正确的结果了
if(startIndex == s.length() || path.size() > 4){
return;
}
// 单层搜索
// 剪枝:i <= startIndex+3subString最长取3位就可以,取长了无意义
for(int i = startIndex + 1; i <= s.length() && i <= startIndex+3; i++){
String subString = s.substring(startIndex, i);
if(valid(subString)){
path.add(subString);
backTrack(s, i);
path.remove(path.size() - 1);
}
}
}
/**
* 判断ip地址中的正数是否满足:每个整数位于 0 到 255 之间组成,且不能含有前导 0
*/
public boolean valid(String subString){
if(subString.startsWith("0") && subString.length() > 1){
return false;
}
// 注意这里长度判断一下,否则字符串太长转换int/long会报错
if(subString.length() > 3){
return false;
}
Long subNum = Long.parseLong(subString);
if(subNum >= 0 && subNum <= 255){
return true;
}
return false;
}
}
5. 子集II
leetcode题目链接:90. 子集II
给你一个整数数组
nums
,其中可能包含重复元素,请你返回该数组所有可能的 子集(幂集)。解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:
输入:nums = [1,2,2] 输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:
输入:nums = [0] 输出:[[],[0]]
解法:
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> subsetsWithDup(int[] nums) {
if(nums == null || nums.length == 0){
return null;
}
// 注意:因为要处理重复的元素,这里要先排好序,这样重复的元素必定会相邻
Arrays.sort(nums);
boolean[] used = new boolean[nums.length];
backTrack(nums, 0, used);
return result;
}
public void backTrack(int[] nums, int startIndex, boolean[] used){
result.add(new ArrayList<>(path));
for(int i = startIndex; i < nums.length; i++){
// 注意:这里处理了同一层for循环中,如果元素值已经处理过了,这里continue。
// 这里使用used数组来判定是不是同一层的for循环
if(i > 0 && nums[i] == nums[i-1] && !used[i-1]){
continue;
}
path.add(nums[i]);
used[i] = true;
backTrack(nums, i+1, used);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
6. 非递减子序列
leetcode题目链接: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]]
示例 2:
输入:nums = [4,4,3,2,1] 输出:[[4,4]]
解法:
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> findSubsequences(int[] nums) {
if(nums == null || nums.length == 0){
return null;
}
backTrack(nums, 0);
return result;
}
public void backTrack(int[] nums, int startIndex){
if(path.size() > 1){
result.add(new ArrayList<>(path));
}
// 使用set记录同一层for循环中哪些元素已经遍历过,用于查找相同值的元素是否已经遍历过
Set<Integer> set = new HashSet<>();
for(int i = startIndex; i < nums.length; i++){
// 注意:同一层for循环中,重复元素做去重操作
if(set.contains(nums[i])){
continue;
}
set.add(nums[i]);
if(path.size() == 0 || path.get(path.size() - 1) <= nums[i]){
path.add(nums[i]);
backTrack(nums, i+1);
path.remove(path.size() - 1);
}
}
}
}
7. 全排列II
leetcode题目链接:47. 全排列II
给定一个可包含重复数字的序列
nums
,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2] 输出: [[1,1,2], [1,2,1], [2,1,1]]
示例 2:
输入:nums = [1,2,3] 输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
解法:
class Solution {
List<List<Integer>> result = new ArrayList<>();
List<Integer> path = new ArrayList<>();
public List<List<Integer>> permuteUnique(int[] nums) {
if(nums == null || nums.length == 0){
return null;
}
boolean[] used = new boolean[nums.length];
backTrack(nums, used);
return result;
}
public void backTrack(int[] nums, boolean[] used){
if(path.size() == nums.length){
result.add(new ArrayList<>(path));
return;
}
// 注意:这里使用set记录同一层for循环选中的值,用于同一层for循环值相同的元素去重
Set<Integer> set = new HashSet<>();
for(int i = 0; i < nums.length; i++){
// 去重
if(set.contains(nums[i])){
continue;
}
if(used[i]){
continue;
}
// 真正选中的时候才add进set里面,上面continue的时候不add
set.add(nums[i]);
path.add(nums[i]);
used[i] = true;
backTrack(nums, used);
path.remove(path.size() - 1);
used[i] = false;
}
}
}
8. 解数独
leetcode题目链接:37. 解数独
编写一个程序,通过填充空格来解决数独问题。
数独的解法需 遵循如下规则:
- 数字
1-9
在每一行只能出现一次。- 数字
1-9
在每一列只能出现一次。- 数字
1-9
在每一个以粗实线分隔的3x3
宫内只能出现一次。(请参考示例图)数独部分空格内已填入了数字,空白格用
'.'
表示。
示例 1:
输入:board = [["5","3",".",".","7",".",".",".","."],["6",".",".","1","9","5",".",".","."],[".","9","8",".",".",".",".","6","."],["8",".",".",".","6",".",".",".","3"],["4",".",".","8",".","3",".",".","1"],["7",".",".",".","2",".",".",".","6"],[".","6",".",".",".",".","2","8","."],[".",".",".","4","1","9",".",".","5"],[".",".",".",".","8",".",".","7","9"]] 输出:[["5","3","4","6","7","8","9","1","2"],["6","7","2","1","9","5","3","4","8"],["1","9","8","3","4","2","5","6","7"],["8","5","9","7","6","1","4","2","3"],["4","2","6","8","5","3","7","9","1"],["7","1","3","9","2","4","8","5","6"],["9","6","1","5","3","7","2","8","4"],["2","8","7","4","1","9","6","3","5"],["3","4","5","2","8","6","1","7","9"]] 解释:输入的数独如上图所示,唯一有效的解决方案如下所示:
解法:
class Solution {
public void solveSudoku(char[][] board) {
backTrack(board);
}
// 注意:这个回溯只需要找到一个满足题意的结果就可以,不需要枚举校验所有的结果
// 所以设置返回值,截断for循环提前结束递归,否则会超时
public boolean backTrack(char[][] board){
for(int i = 0; i < 9; i++){
for(int j = 0; j < 9; j++){
if(board[i][j] == '.'){
for(char num = '1'; num <= '9'; num++){
if(valid(board, i, j, num)){
board[i][j] = num;
// 在for循环里面return,直接截断剩余的循环
if(backTrack(board)){
return true;
};
board[i][j] = '.';
}
}
// 每个num都不合适,说明找不到正确结果了
return false;
}
}
}
// 每一个i,j都填上了,说明九宫格填满了
return true;
}
public boolean valid(char[][] board, int row, int col, char num){
// 所在行没有num
for(int i = 0; i < 9; i++){
if(board[row][i] == num){
return false;
}
}
// 所在列没有num
for(int i = 0; i < 9; i++){
if(board[i][col] == num){
return false;
}
}
// 所在小方格没有num
int startRow = row/3*3;
int startCol = col/3*3;
for(int i = startRow; i < startRow + 3; i++){
for(int j = startCol; j < startCol + 3; j++){
if(board[i][j] == num){
return false;
}
}
}
return true;
}
}