LeetCode刷题:DFS与回溯2

自顶向下

40.组合总和2:避免重复搜索

在这里插入图片描述
自顶向下类型,需要避免重复搜索
可以用set,但是会超时

class Solution {
    
    
    Set<List<Integer>> result = new HashSet<>();
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        Arrays.sort(candidates);
        List<List<Integer>> res = new ArrayList<>();
        if(candidates.length == 0) {
    
    
            res.addAll(result);
            return res;
        }
        dfs(candidates, target, 0, 0, new ArrayList<>());
        res.addAll(result);
        return res;
    }
 
    private void dfs(int[] candidates, int target, int curResult, int index, List<Integer> path) {
    
    
        // success
        if(target == curResult) {
    
    
 
            result.add(new ArrayList<>(path));
            return;
        }
 
        // fail
        if(index == candidates.length) {
    
    
            return;
        }
 
        // not choose
        dfs(candidates, target, curResult, index + 1, path);
 
        //choose
        path.add(candidates[index]);
        dfs(candidates, target, curResult + candidates[index], index + 1, path);
        path.remove(path.size() - 1);
    }
}

核心思路:排序 + dfs + 避免重复情况

public class Solution {
    
    
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
    
    
        int len = candidates.length;
        List<List<Integer>> res = new ArrayList<>();
        if (len == 0) {
    
    
            return res;
        }
 
        // 关键步骤,排序
        Arrays.sort(candidates);
 
        Deque<Integer> path = new ArrayDeque<>(len);
        dfs(candidates, len, 0, target, path, res);
        return res;
    }
    
    private void dfs(int[] candidates, int len, int begin, int target, Deque<Integer> path, List<List<Integer>> res) {
    
    
        if (target == 0) {
    
    
            res.add(new ArrayList<>(path));
            return;
        }
        for (int i = begin; i < len; i++) {
    
    
            // 大剪枝:减去 candidates[i] 小于 0,减去后面的 candidates[i + 1]、candidates[i + 2] 肯定也小于 0,因此用 break
            if (target - candidates[i] < 0) {
    
    
                break;
            }
 
            // 小剪枝:同一层相同数值的结点,从第 2 个开始,候选数更少,结果一定发生重复,因此跳过,用 continue。核心,重复字符的第一个就把包含重复字符的所有工况搜索完毕,后续的字符不需要任何搜索。
            if (i > begin && candidates[i] == candidates[i - 1]) {
    
    
                continue;
            }
 
            path.addLast(candidates[i]);
            // 调试语句 ①
            // System.out.println("递归之前 => " + path + ",剩余 = " + (target - candidates[i]));
 
            // 因为元素不可以重复使用,这里递归传递下去的是 i + 1 而不是 i
            dfs(candidates, len, i + 1, target - candidates[i], path, res);
 
            path.removeLast();
            // 调试语句 ②
            // System.out.println("递归之后 => " + path + ",剩余 = " + (target - candidates[i]));
        }
    }
 
    public static void main(String[] args) {
    
    
        int[] candidates = new int[]{
    
    10, 1, 2, 7, 6, 1, 5};
        int target = 8;
        Solution solution = new Solution();
        List<List<Integer>> res = solution.combinationSum2(candidates, target);
        System.out.println("输出 => " + res);
    }
}
 

01背包:DFS-》记忆化搜索-》动态规划

参考

记忆化搜索,DFS版本实际上上述DFS解法-》记忆化搜索就是开一个状态空间,记录每一次当前规模问题的解,方便今后去取用。要明确内存空间的意义,该问题memo[index][capacity]意思是当前选取/不选取(两种结果的确认)第index物品时,容量为capacity时的最优解。
原因是:dfs在自底向上的过程中,在不断回溯时会问自己许多重复问题,在当前某个capacity下选择或不选择某个index时的最佳结果会是怎么样,这个时候就要存放最优解出来。
虽然不知道什么时候,但是大规模问题下某个小规模问题是一定求出来了。就比如一直不选择当前,走到了最后,那么就存入了momo(0,0).memo(0,5)等等。
这个状态空间是一定够了。
在这里插入图片描述
状态空间理解:在容量为n的时候,选择与不选择m列时候的最佳价值总和
在这里插入图片描述

自底向上的问题:需要存储小规模问题的解,并且利用中间的memo进行存储

public class KnapSack01 {
    
    
    private static int[][] memo;
 
    private static int solveKS(int[] w, int[] v, int index, int capacity) {
    
    
        //基准条件:如果索引无效或者容量不足,直接返回当前价值0
       // 也是最小规模问题的解
        if (index < 0 || capacity <= 0)
            return 0;
 
        //如果此子问题已经求解过,则直接返回上次求解的结果
        if (memo[index][capacity] != 0) {
    
    
            return memo[index][capacity];
        }
 
        //不放第index个物品所得价值
        int res = solveKS(w, v, index - 1, capacity);
        //放第index个物品所得价值(前提是:第index个物品可以放得下)
        if (w[index] <= capacity) {
    
    
            res = Math.max(res, v[index] + solveKS(w, v, index - 1, capacity - w[index]));
        }
        //添加子问题的解,便于下次直接使用
        memo[index][capacity] = res;
        return res;
    }
 
    public static int knapSack(int[] w, int[] v, int C) {
    
    
        int size = w.length;
        memo = new int[size][C + 1];
        return solveKS(w, v, size - 1, C);
    }
 
    public static void main(String[] args) {
    
    
        int[] w = {
    
    2, 1, 3, 2};
        int[] v = {
    
    12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}

在这里插入图片描述

/*再转到动态规划,推出转移方程:
dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
Dp[index][capacity]= Math.max(Dp[index][capacity], v[i] + dp[i - 1][capacity - w[i]]);
v[i] + dp[i - 1][capacity - w[i]]:选取后的最大值
Dp[index][capacity]:不选取的值,初始值为dp[index - 1][capacity]
*/
 
public class KnapSack01 {
    
    
    public static int knapSack(int[] w, int[] v, int C) {
    
    
        int size = w.length;
        if (size == 0) {
    
    
            return 0;
        }
 
        int[][] dp = new int[size][C + 1];
        //初始化第一行
        //仅考虑容量为C的背包放第0个物品的情况
        for (int i = 0; i <= C; i++) {
    
    
            dp[0][i] = w[0] <= i ? v[0] : 0;
        }
//填充其他行和列
        for (int i = 1; i < size; i++) {
    
    
            for (int j = 0; j <= C; j++) {
    
    
                dp[i][j] = dp[i - 1][j];
                if (w[i] <= j) {
    
    
                    dp[i][j] = Math.max(dp[i][j], v[i] + dp[i - 1][j - w[i]]);
                }
            }
        }
        return dp[size - 1][C];
    }
 
    public static void main(String[] args) {
    
    
        int[] w = {
    
    2, 1, 3, 2};
        int[] v = {
    
    12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}
 
 
public class KnapSack01 {
    
    
    public static int knapSack(int[] w, int[] v, int C) {
    
    
        int size = w.length;
        if (size == 0) {
    
    
            return 0;
        }
 
        int[] dp = new int[C + 1];
        //初始化第一行
        //仅考虑容量为C的背包放第0个物品的情况
        for (int i = 0; i <= C; i++) {
    
    
            dp[i] = w[0] <= i ? v[0] : 0;
        }
 
        for (int i = 1; i < size; i++) {
    
    
            for (int j = C; j >= w[i]; j--) {
    
    
                dp[j] = Math.max(dp[j], v[i] + dp[j - w[i]]);
            }
        }
        return dp[C];
    }
 
    public static void main(String[] args) {
    
    
        int[] w = {
    
    2, 1, 3, 2};
        int[] v = {
    
    12, 10, 20, 15};
        System.out.println(knapSack(w, v, 5));
    }
}

其他类型

394. 字符串解码:DFS

https://leetcode-cn.com/problems/decode-string/
在这里插入图片描述核心思路:不断深入,找到每一组能够彼此匹配的[],然后处理其中的内容。
难点:判断[]的彼此匹配、取[之前的数字

class Solution {
    
    
    public String decodeString(String s) {
    
    
        return dfs(s);
    }
    private String dfs(String tmp) {
    
    
        // succcess,已经完全解开
        if (!tmp.contains("[")) {
    
    
            return tmp;
        }
        // handle
        int leftNum = 0; // 可以理解为入度
        int rightNum = 0; // 理解为出度
        int firstIndex = tmp.indexOf("[");
        int lastIndex = 0;
        StringBuilder stringBuilder = new StringBuilder();
        for (int i = firstIndex; i < tmp.length(); i++) {
    
    
            if (tmp.charAt(i) == '[') {
    
    
                leftNum++;
            }
            if (tmp.charAt(i) == ']') {
    
    
                rightNum++;
            }
            if (leftNum == rightNum) {
    
    
            // 这个时候里面的就是可以处理的一整串字符
                int index = getIndex(tmp, firstIndex - 1);
                // 取之前的数字
                if(!Character.isDigit(tmp.charAt(index))) {
    
    
                    index ++;
                }
                int rightIndex = getIndex(tmp, i - 1);
                if(!Character.isDigit(tmp.charAt(rightIndex))) {
    
    
                    rightIndex ++;
                }
                if (index == 0) {
    
    
                    // 数字在第一位的时候
                    String handle = repeat(tmp.substring(firstIndex + 1 , rightIndex), tmp, firstIndex - 1);
                    stringBuilder.append(handle);
                } else {
    
    
                    stringBuilder.append(tmp.subSequence(0, index));
                    String handle = repeat(tmp.substring(firstIndex + 1 , rightIndex), tmp, firstIndex - 1);
                    stringBuilder.append(handle);
                }
                if(i + 1 < tmp.length()) {
    
    
                    // 右括号不在最后一位
                    stringBuilder.append(tmp.substring(i + 1));
                }
                String a = stringBuilder.toString();
                lastIndex = i;
                break;
            }
        }
        return dfs(stringBuilder.toString());
    }
}

395.至少有K个重复字符的最长子串

在这里插入图片描述

https://leetcode-cn.com/problems/longest-substring-with-at-least-k-repeating-characters/solution/jie-ben-ti-bang-zhu-da-jia-li-jie-di-gui-obla/
下面是详细讲解。

递归最基本的是记住递归函数的含义(务必牢记函数定义):本题的 longestSubstring(s, k) 函数表示的就是题意,即求一个最长的子字符串的长度,该子字符串中每个字符出现的次数都最少为 kk。函数入参 ss 是表示源字符串;kk 是限制条件,即子字符串中每个字符最少出现的次数;函数返回结果是满足题意的最长子字符串长度。

递归的终止条件(能直接写出的最简单 case):如果字符串 ss 的长度少于 kk,那么一定不存在满足题意的子字符串,返回 0;

调用递归(重点):如果一个字符 cc 在 ss 中出现的次数少于 kk 次,那么 ss 中所有的包含 cc 的子字符串都不能满足题意。所以,应该在 ss 的所有不包含 cc 的子字符串中继续寻找结果:把 ss 按照 cc 分割(分割后每个子串都不包含 cc),得到很多子字符串 tt;下一步要求 tt 作为源字符串的时候,它的最长的满足题意的子字符串长度(到现在为止,我们把大问题分割为了小问题(ss → tt))。此时我们发现,恰好已经定义了函数 longestSubstring(s, k) 就是来解决这个问题的!所以直接把 longestSubstring(s, k) 函数拿来用,于是形成了递归。

未进入递归时的返回结果:如果 ss 中的每个字符出现的次数都大于 kk 次,那么 ss 就是我们要求的字符串,直接返回该字符串的长度。

class Solution {
    
    
    public int longestSubstring(String s, int k) {
    
    
        if (s.length() < k) return 0;
        HashMap<Character, Integer> counter = new HashMap();
        for (int i = 0; i < s.length(); i++) {
    
    
            counter.put(s.charAt(i), counter.getOrDefault(s.charAt(i), 0) + 1);
        }
        for (char c : counter.keySet()) {
    
    
            if (counter.get(c) < k) {
    
    
                int res = 0;
                for (String t : s.split(String.valueOf(c))) {
    
    
                    res = Math.max(res, longestSubstring(t, k));
                }
                return res;
            }
        }
        return s.length();
    }
}

1254. 统计封闭岛屿的数目 在这里插入图片描述

只要有一个成功,这个陆地就不封闭了,整个工况解封。

class Solution {
    
    
    int val=1;
    public int closedIsland(int[][] grid) {
    
    
        int count=0;
        for(int i=0;i<grid.length;i++)
        {
    
    
            for(int j=0;j<grid[i].length;j++)
            {
    
    
            	// 搜索每一块陆地
                if(grid[i][j]==0) 
                {
    
       
                    val= 1;
                    dfs(grid,i,j);
                    count+=val;
                }
            }
        }
       return count;
    }
    private void dfs(int[][] grid,int i,int j)
    {
    
    
        if(i<0||i==grid.length||j<0||j==grid[0].length)
        {
    
    
            val=0; // 一旦连接到边即认为非孤立,只需要一次即可洗白
            return;
        }
        if(grid[i][j]!=0)return ; // 有了海域,此时即可返回
        grid[i][j]=1; // 标记为已经搜索,直接设置为海域即可
        dfs(grid,i+1,j); // 四个方向上
        dfs(grid,i-1,j);
        dfs(grid,i,j-1);
        dfs(grid,i,j+1);
    }
}

猜你喜欢

转载自blog.csdn.net/weixin_38370441/article/details/115247132
今日推荐