代码随想录一刷 回溯问题()

在 Java 中,当你使用 List<List<Integer>> res = new ArrayList<>() 这样的语句时,右侧的 new ArrayList<>() 实际上利用了 Java 的类型推断功能,这是 Java 7 引入的钻石操作符 (<>) 的特性。这里解释一下为什么不需要在右侧声明两层列表结构。

类型推断和钻石操作符

  1. 类型推断:Java 编译器可以从左侧的变量声明中推断出右侧的泛型类型。当你声明 List<List<Integer>> 时,编译器知道你需要的是一个 ArrayList,其元素类型为 List<Integer>

  2. 钻石操作符 (<>):Java 7 引入的这个特性允许你在创建泛型实例时不必重复泛型类型。你只需要在左侧声明完整的泛型类型,然后在右侧使用空的钻石操作符。编译器会自动填充正确的泛型类型。

理解列表嵌套

  • 外层列表:在 List<List<Integer>> 中,外层的 List 是容器,它将包含一些元素,这些元素本身也是列表。

  • 内层列表:内层的 List<Integer> 表示这些元素是整数列表。这些内层列表需要单独创建并添加到外层列表中。

实例创建

当你写 new ArrayList<>() 时,你实际上只是创建了外层的 ArrayList 实例,内层的 List<Integer> 实例需要在后续的操作中单独创建并添加。例如:

List<List<Integer>> res = new ArrayList<>();
List<Integer> innerList = new ArrayList<>();
innerList.add(1); // 添加一些整数到内层列表
res.add(innerList); // 将内层列表添加到外层列表

77. 组合 - 力扣(LeetCode) 

  1. temp的大小判定在每次进入下一层时也就是刚刚进入travelsal时操作
  2.  下一层travelsal中应添加i+1而不是start+1
class Solution {
    List<Integer> temp=new LinkedList<>();
    List<List<Integer>> result=new LinkedList<>();
    public List<List<Integer>> combine(int n, int k) {
     travelsal(1,n,k);
     return result;
    }
    public void travelsal(int start,int n,int k)
    {
         if(temp.size()==k)
            {
                result.add(new LinkedList<>(temp));
                return;
            }
           for(int i=start;i<=n;i++)
        {
           
            System.out.println(i);
            temp.add(i);
            travelsal(i+1,n,k);
            temp.removeLast();
        }
    }
}

 

本题可以进行剪枝

如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了

还需要元素个数:k-temp.size()  目前可选元素个数:n-i+1(左闭右闭) 

因此不等式 n-i+1>=k-temp.size()

i<=temp.size()+n+1-k

class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backtracking(1,n,k);
        return result;
            }
    public void backtracking(int start,int n,int k)
    {
        if(temp.size()==k)
        {
            //深度到达k 
            result.add(new ArrayList<>(temp));
            return;
        }
        //未到达k
        for(int i=start;i<=n+temp.size()+1-k;i++)
        {
            temp.add(i);
            backtracking(i+1,n,k);
            //到这里的时候已经添加完一个temp了
            temp.remove(temp.size()-1);
        }
        return;
    }
}

 216. 组合总和 III - 力扣(LeetCode)

class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backtracking(1,k,n);
        return result;
    }
    public void backtracking(int start,int k,int n)
    {
        //k是深度 9是宽度 n是还差多少到目标值
        if(temp.size()==k&&n==0)
        {
            result.add(new ArrayList<>(temp));//因为java中是引用,所以要new一个新的
            return;
        }
        if(n<=0){
            //k不满足要求
            return;}
        if(temp.size()==k)
        {
            //没达到要求的n
        }
    for(int i=start;i<=10-k+temp.size();i++)
      {   //剪枝不等式:k-tmp.size<=9-i+1
        // i<=10-k+tmp.size()
        n-=i;
        temp.add(i);
        backtracking(i+1,k,n);
        n+=i;
        temp.removeLast();
    }
    return;
}}

 17. 电话号码的字母组合 - 力扣(LeetCode)

class Solution {
    List<String> result=new ArrayList<>();
    StringBuilder temp=new StringBuilder();
    public List<String> letterCombinations(String digits) {
        //digits是数字串 164
        if(digits==null||digits.length()==0) return result;
        String[] numString={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
        backtracking(digits,numString,0);
        return result;
    }
    public void backtracking(String digits,String[] numString,int num)
    {
        //nums是深度 与digits.length()比较 用于遍历深度
        if(num==digits.length())
        {
            result.add(temp.toString());
            return;
        }
          //取出numstring中digtis[num]对应的字符串 abc
        String str=numString[digits.charAt(num)-'0'];
        for(int i=0;i<str.length();i++)
        {
            temp.append(str.charAt(i));
            backtracking(digits,numString,num+1);
             temp.deleteCharAt(temp.length() - 1);
        }
        return;
    }

}

39. 组合总和 - 力扣(LeetCode)

startindex的作用是实现树同层剪枝 ,树枝不剪枝。而本题深度不做限制,条件是数组中的最小值大于target,或者也可以写一个sum来记录

class Solution {
    public List<List<Integer>> result=new ArrayList<>();
    public List<Integer> temp=new ArrayList<>();
    int min=Integer.MAX_VALUE;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        for(int i=0;i<candidates.length;i++)
        {
            if(candidates[i]<min)
            {
                min=candidates[i];
            }
        }
        backtracking(candidates,target,0);
        return result;
    }
    public void backtracking(int[] candidates,int target,int startindex)
    {
        //满足条件
        if(target==0)
        {
            result.add(new ArrayList<>(temp));
            return;
        }
        if(min>target)
        {
            //查找失败 最小的都大于剩下的目标值
            return;
        }
        // if()
        for(int i=startindex;i<candidates.length;i++)
        {
            target-=candidates[i];
            temp.add(candidates[i]);
            backtracking(candidates,target,i);//向更深处遍历可选i 但是横向遍历时会变成i+1 满足了树层剪枝
            target+=candidates[i];
            temp.removeLast();
        }
        return;
    }
}

 40. 组合总和 II - 力扣(LeetCode)

  • used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
  • used[i - 1] == false,说明同一树层candidates[i - 1]使用过

为什么 used[i - 1] == false 就是同一树层呢,因为同一树层,used[i - 1] == false 才能表示,当前取的 candidates[i] 是从 candidates[i - 1] 回溯而来的。

而 used[i - 1] == true,说明是进入下一层递归,去下一个数,所以是树枝上,如图所示:

这里我们这里直接用startIndex来去重也是可以的, 就不用used数组了。 

class Solution {
    public List<List<Integer>> result=new ArrayList<>();
    public List<Integer> temp=new ArrayList<>();
    int min=Integer.MAX_VALUE;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        if(candidates==null||candidates.length==0) return result;
         Arrays.sort(candidates); // 对数组进行排序
        for(int i=0;i<candidates.length;i++)
        {
            if(candidates[i]<min)
            {
                min=candidates[i];
            }
        }
        backtracking(candidates,target,0);
        return result;
    }
    public void backtracking(int[] candidates,int target,int startindex)
    {
        //满足条件
        if(target==0)
        {
            result.add(new ArrayList<>(temp));
            return;
        }
        if(min>target)
        {
            //查找失败 最小的都大于剩下的目标值
            return;
        }
        // if()
        for(int i=startindex;i<candidates.length;i++)
        {
            if (i > startindex && candidates[i] == candidates[i - 1]) {
                continue;
            }
            target-=candidates[i];
            temp.add(candidates[i]);
            backtracking(candidates,target,i+1);//向更深处遍历
            target+=candidates[i];
            temp.removeLast();
        }
        return;
    }
}

用HashSet: 不用clear是因为我们在backtraking里每进入一次都声明一个新的set,因此同一个set只能限定一个backtracking里的同层相同元素

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    LinkedList<Integer> path = new LinkedList<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort( candidates );
        if( candidates[0] > target ) return result;
        backtracking(candidates,target,0,0);
        return result;
    }

    public void backtracking(int[] candidates,int target,int sum,int startIndex){
        if( sum > target )return;
        if( sum == target ){
            result.add( new ArrayList<>(path) );
        }
        HashSet<Integer> hashSet = new HashSet<>();
        for( int i = startIndex; i < candidates.length; i++){
            if( hashSet.contains(candidates[i]) ){
                continue;
            }
            hashSet.add(candidates[i]);
            path.add(candidates[i]);
            sum += candidates[i];
            backtracking(candidates,target,sum,i+1);
            path.removeLast();
            sum -= candidates[i];
        }
    }
}

131. 分割回文串 - 力扣(LeetCode)

class Solution {
    List<String> temp=new ArrayList<>();
    List<List<String>> result=new ArrayList<>();
    // Deque<String> deque = new LinkedList<>();
    public List<List<String>> partition(String s) {
        if(s==null||s.length()==0) return result;
        backTracking(s,0);
        return result;
    }
    private void backTracking(String s,int startIndex)
    {
        if(startIndex >=s.length()){
            //分割竖现超过了s的长度
            result.add(new ArrayList<>(temp));
            return;
        }
        for(int i=startIndex;i<s.length();i++)
        {
            //i=startindex就是处理竖线后的字符串
            if(isH(s,startIndex,i))
            {
                //提取字符串 左闭右开
                String str=s.substring(startIndex,i+1);
                temp.add(str);
            }else{
                continue;
                //不是回文串就不向下深度延伸了
            }

            backTracking(s,i+1);
            temp.removeLast();
        }
    }
    private boolean isH(String s,int startIndex,int end)
    {
        //一开始想的是用栈来处理 但是发现双指针更容易一些
        for(int i=startIndex,j=end;i<j;i++,j--)
        {
            if(s.charAt(i)!=s.charAt(j))
            {
                return false;
            }
        }
        return true;
    }
}

其实可以不需要加终止条件,因为startIndex >= nums.size(),本层for循环本来也结束了,本来我们就要遍历整棵树。

有的同学可能担心不写终止条件会不会无限递归?

并不会,因为每次递归的下一层就是从i+1开始的。

如果要写终止条件,注意:result.push_back(path);要放在终止条件的上面,如下

 90. 子集 II - 力扣(LeetCode)

class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        backtracking(nums,0);
        return result;
    }
    public void backtracking(int[] nums,int start)
    {
          //没有终止条件限定就是到达每一个叶子节点时都将temp放入结果
         result.add(new ArrayList(temp)); 
      
        for(int i=start;i<nums.length;i++)
        {
            if(i>start&&nums[i]==nums[i-1])
            {
                //i==start表示是同一枝 还未同层扩展
                continue;//同层不能有重复
            }else{
                    temp.add(nums[i]);
                    backtracking(nums,i+1);
                    temp.remove(temp.size()-1);//回溯
            }
        }
       return;
    }
}

491. 非递减子序列 - 力扣(LeetCode)

class Solution {
    List<List<Integer>> result=new ArrayList<>();
    List<Integer> temp=new ArrayList<>();
    public List<List<Integer>> findSubsequences(int[] nums) {
        // Arrays.sort(nums);
        backtracking(nums,0);
        return result;
    }
    public void backtracking(int[] nums,int startindex)
    {
        if(temp.size()>=2)//用temp.size()控制深度
        {
            //因为在每次操作的前面
            result.add(new ArrayList(temp));
        }
        //used是局部创建的 只负责同层去重 向下延伸到新的结点时 会重新new一个
        Set<Integer> used=new HashSet<>();//用used判定是否用过元素
        for(int i=startindex;i<nums.length;i++)
        {
            if(used.contains(nums[i]))//同层去重
            continue;
            if(temp.size()==0||nums[i]>=temp.get(temp.size()-1)){
             
                 temp.add(nums[i]);
                used.add(nums[i]); // 记录这个元素在本层已经使用过
                backtracking(nums,i+1);
                temp.removeLast();
            } 
            }
        return;
    }
}

 46. 全排列 - 力扣(LeetCode)

backtrack 函数的循环中,我们遍历整个 nums 数组。对于每个 nums[i],我们首先检查 used[i] 是否为 true。如果为 true这意味着 nums[i] 已经在当前排列中,因此我们跳过它,继续检查下一个数字。 

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> temp = new ArrayList<>();

    public List<List<Integer>> permute(int[] nums) {
        if (nums == null || nums.length == 0)
            return result;
        boolean[] used = new boolean[nums.length]; // 增加一个用于跟踪元素是否被使用的布尔数组
        backtrack(nums, used);
        return result;
    }

    private void backtrack(int[] nums, boolean[] used) {
        if (temp.size() == nums.length) {
            result.add(new ArrayList<>(temp)); // 完成一种排列
            return;
        }

        for (int i = 0; i < nums.length; i++) {
            if (!used[i]) { // 检查这个元素是否已经被使用
                used[i] = true;
                temp.add(nums[i]);
                backtrack(nums, used); // 继续递归填充下一个数字
                temp.remove(temp.size() - 1); // 回溯,移除最后一个元素,尝试其他可能性
                used[i] = false; // 标记为未使用
            }
        }
    }
}

 

 当遇到一个与前一个元素相同的元素时,只有在前一个元素已被使用的情况下才继续递归。这样可以防止生成重复的排列。前一个元素未被使用说明是同一层,不能

  • 28
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值