Leetcode算法系列四(递归、回溯与分治)

本文精选六道经典算法题目,涵盖递归、回溯与分治等核心算法,深入解析求子集、生成括号、N皇后等问题的实现方法。

1.6道经典 递归、回溯、分治的相关题目

预备知识:递归函数与回溯算法
例1-a: 求子集(medium) (回溯法、位运算法)
例1-b: 求子集2(medium) (回溯法)
例1-c: 组合数之和2(medium) (回溯法、剪枝)
例2:生成括号(medium) (递归设计)
例3:N皇后 (hard) (回溯法)
预备知识:分治算法与归并排序
例4:逆序数 (hard) (分治法、归并排序应用)

例1-a: 求子集(medium) (回溯法、位运算法)

  1. 子集
    给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
    说明:解集不能包含重复的子集。
    示例:
    输入: nums = [1,2,3]
    输出:
    [
    [3],
    [1],
    [2],
    [1,2,3],
    [1,3],
    [2,3],
    [1,2],
    []
    ]
public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result=new ArrayList<>();
        List<Integer> item=new ArrayList<>();
        result.add(item);
        generate(0,nums,item,result);
        return result;
    }

    //生成item和result
    public void generate(int i,int[] nums,List<Integer> item,List<List<Integer>> result){
        if(i>=nums.length){
            return;
        }
        item.add(nums[i]);
        System.out.println(nums[i]);
        result.add(new ArrayList<>(item));
        generate(i+1,nums,item,result);
        item.remove(item.size()-1);
        generate(i+1,nums,item,result);
    }
}


public class Solution {
    public List<List<Integer>> subsets(int[] nums) {
        List<List<Integer>> result=new ArrayList<>();
        int size=1<<nums.length;
        for(int i=0;i<size;i++){
            List<Integer> item=new ArrayList<>();
            for(int j=0;j<nums.length;j++){
                if((i&(1<<j))!=0){
                    item.add(nums[j]);
                }
            }
            result.add(item);
        }
        return result;
    }
}

例1-b: 求子集2(medium) (回溯法)

  1. 子集 II
    给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

输入: [1,2,2]
输出:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]

class Solution {
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        HashSet<List<Integer>> temp=new HashSet<>();
        List<List<Integer>> result=new ArrayList<>();
        List<Integer> item=new ArrayList<>();
        result.add(new ArrayList<>(item));
        Arrays.sort(nums);
        generate(0,nums,item,temp);
        for(List<Integer> iterator:temp){
            result.add(iterator);
        }
        return result;

     }

    public void generate(int i,int[] nums,List<Integer> item,HashSet<List<Integer>> temp){
        if(i==nums.length){
            return;
        }

        item.add(nums[i]);
        temp.add(new ArrayList<>(item));
        generate(i+1,nums,item,temp);
        item.remove(item.size()-1);
        generate(i+1,nums,item,temp);
    }
}

例1-c: 组合数之和2(medium) (回溯法、剪枝)

  1. 组合总和 II
    给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
    candidates 中的每个数字在每个组合中只能使用一次。
    说明:
    所有数字(包括目标数)都是正整数。
    解集不能包含重复的组合。
    示例 1:
    输入: candidates = [10,1,2,7,6,1,5], target = 8,
    所求解集为:
    [
    [1, 7],
    [1, 2, 5],
    [2, 6],
    [1, 1, 6]
    ]
    示例 2:
    输入: candidates = [2,5,2,1,2], target = 5,
    所求解集为:
    [
    [1,2,2],
    [5]
    ]
class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        List<List<Integer>> result=new ArrayList<>();
        HashSet<List<Integer>> temp=new HashSet<>();
        List<Integer> item=new ArrayList<>();
        int sum=0;
        Arrays.sort(candidates);
        generate(0,candidates,target,0,temp,item,result);
        return result;
    }

    public void generate(int i,int[] candidates,int target,int sum,HashSet<List<Integer>> temp,List<Integer> item,List<List<Integer>> result){
        if(i==candidates.length||sum>=target){
            return;
        }

        item.add(candidates[i]);
        sum+=candidates[i];
        if(sum==target&&!temp.contains(item)){
            temp.add(new ArrayList<>(item));
            result.add(new ArrayList<>(item));
        }
        generate(i+1,candidates,target,sum,temp,item,result);
        item.remove(item.size()-1);
        sum-=candidates[i];
        generate(i+1,candidates,target,sum,temp,item,result);
    }
}

例2:生成括号(medium) (递归设计)

  1. 括号生成
    数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
    示例:
    输入:n = 3
    输出:[
    “((()))”,
    “(()())”,
    “(())()”,
    “()(())”,
    “()()()”
    ]
public class Solution {
    public List<String> generateParenthesis(int n) {
        List<String> result=new ArrayList<>();
        generate("",n,n,result);
        return result;
    }

    public void generate(String item,int left,int right,List<String> result){
        if(left==0&&right==0){
            result.add(item);
            return;
        }
        if(left>0){
            generate(item+"(",left-1,right,result);    
        }
        if(right>left){
            generate(item+")",left,right-1,result);
        }
        
    }
}

例3:N皇后 (hard) (回溯法)

  1. N 皇后
    n 皇后问题研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。

上图为 8 皇后问题的一种解法。
给定一个整数 n,返回所有不同的 n 皇后问题的解决方案。
每一种解法包含一个明确的 n 皇后问题的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。

示例:
输入:4
输出:[
[".Q…", // 解法 1
“…Q”,
“Q…”,
“…Q.”],
["…Q.", // 解法 2
“Q…”,
“…Q”,
“.Q…”]
]
解释: 4 皇后问题存在两个不同的解法。
提示:
皇后彼此不能相互攻击,也就是说:任何两个皇后都不能处于同一条横行、纵行或斜线上。

import java.util.ArrayList;
import java.util.List;

public class Solution {
    public List<List<String>> solveNQueens(int n) {
        List<List<String>> result=new ArrayList<>();//结果
        List<String> local=new ArrayList<>();
        int[][] mark=new int[n][n];
        generate(0,n,result,local,mark);
        return result;
    }
//按行放置n皇后
    void generate(int k,int n,List<List<String>> result,List<String> local,int [][] mark){
        if(k==n){
            result.add(new ArrayList<>(local));
            return;
        }

        for(int i=0;i<n;i++){
            if(mark[k][i]==0){
                int markTemp[][]=new int[n][n];
                for(int x=0;x<n;x++){
                    for(int y=0;y<n;y++){
                        markTemp[x][y]=mark[x][y];
                    }
                }
                String temp="";
                for(int j=0;j<n;j++){
                    if(j==i){
                        temp=temp+"Q";
                    }else{
                        temp=temp+".";
                    }
                }
                local.add(temp);
                mark[k][i]=1;
                bulidMark(k,i,n,mark);//标记皇后辐射的位置
                generate(k+1,n,result,local,mark);
                local.remove(local.size()-1);
                mark=markTemp;
            }
        }
    }

    void bulidMark(int x,int y,int n,int [][] mark){
        int xDirection[]={-1,-1,-1,0,0,1,1,1};
        int yDirection[]={-1,0,1,-1,1,-1,0,1};

        for(int i=0;i<n;i++){
            for(int j=0;j<8;j++){
                int newX=x+i*xDirection[j];
                int newY=y+i*yDirection[j];

                if(newX>=0&&newY>=0&&newX<n&&newY<n){
                    mark[newX][newY]=1;
                }
            }
        }
    }

}

例4:逆序数 (hard) (分治法、归并排序应用)

  1. 计算右侧小于当前元素的个数
    给定一个整数数组 nums,按要求返回一个新数组 counts。数组 counts 有该性质: counts[i] 的值是 nums[i] 右侧小于 nums[i] 的元素的数量。

示例:
输入:nums = [5,2,6,1]
输出:[2,1,1,0]
解释:
5 的右侧有 2 个更小的元素 (2 和 1)
2 的右侧仅有 1 个更小的元素 (1)
6 的右侧有 1 个更小的元素 (1)
1 的右侧有 0 个更小的元素

提示:
0 <= nums.length <= 10^5
-10^4 <= nums[i] <= 10^4

public class Solution {
    public List<Integer> countSmaller(int[] nums) {
        List<Integer> count=new ArrayList<>();
        if(nums.length==0){
            return count;
        }
        //Pair<Integer,Integer> pair=new Pair<>();
        //List<Integer> count=new ArrayList<>();
        List<Pair<Integer,Integer>> vec=new ArrayList<>();
        for(int i=0;i<nums.length;i++){
            count.add(0);
            vec.add(new Pair<>(nums[i],i));
        }
        mergeSort(vec,count);
        return count;
    }
    public void mergeSort(List<Pair<Integer,Integer>> vec,List<Integer> count){
        if(vec.size()<2){
            return;
        }
        int mid=vec.size()/2;
        List<Pair<Integer,Integer>> vec1=new ArrayList<>();
        List<Pair<Integer,Integer>> vec2=new ArrayList<>();
        for(int i=0;i<mid;i++){
            vec1.add(vec.get(i));
        }
        for(int i=mid;i<vec.size();i++){
            vec2.add(vec.get(i));
        }
        mergeSort(vec1,count);
        mergeSort(vec2,count);
        vec.clear();
        mergeTwo(vec1,vec2,vec,count);
    }

    public void mergeTwo(List<Pair<Integer,Integer>> vec1,List<Pair<Integer,Integer>> vec2,List<Pair<Integer,Integer>> vec,List<Integer> count){
        int i=0;
        int j=0;
        while(i<vec1.size()&&j<vec2.size()){
            if(vec1.get(i).getKey()<=vec2.get(j).getKey()){
                vec.add(vec1.get(i));
                int count_i=vec1.get(i).getValue();
                count.set(count_i,count.get(count_i)+j);
                //if(count_i==1)System.out.println(vec1.get(i)+","+(count_j+j));
                i++;
            }else{
                vec.add(vec2.get(j));
                j++;
            }
        }
        while(i<vec1.size()){
            vec.add(vec1.get(i));
            int count_i=vec1.get(i).getValue();
            count.set(count_i,count.get(count_i)+j);
            //if(count_i==1)System.out.println(vec1.get(i)+","+(count_j+j));
            i++;
        }
        while(j<vec2.size()){
            vec.add(vec2.get(j));
            j++;
        }

    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值