力扣刷题笔记,回溯算法

力扣刷题笔记,回溯算法

回溯算法是一种常用的解决问题的思想,其本质是不断“试错”,在正则表达式匹配、编译原理中的语法分析等实际的场景中都可以应用,还可以解决诸如数独、八皇后、图的着色、全排列等数学问题。
其模板程序如下:

const visited = {}
function dfs(i) {
    if (满足特定条件){
        // 返回结果 or 退出搜索空间
    }

    visited[i] = true // 将当前状态标为已搜索
    dosomething(i) // 对i做一些操作
    for (根据i能到达的下个状态j) {
        if (!visited[j]) { // 如果状态j没有被搜索过
            dfs(j)
        }
    }
    undo(i) // 恢复i
}

下面是回溯算法知识点的部分题解:

39. 组合总和

class Solution {
    List<List<Integer>> ans;
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        ans = new ArrayList<>();
        dfs(candidates, target, new ArrayList<>(),0);
        return ans;
    }

/**
 * 依次查看数组中的所有元素,每次分为两种情况:考虑和不考虑,如果不考虑即直接跳过,下标直接+1,
 *        对于考虑的情况,因为元素可以重复使用,所以下一次的下标不变。
 */
    public void dfs(int[] candidates, int target, List<Integer> combine, int idx) {
        if (idx ==candidates.length) {
            return ;
        }
        if (target == 0) {
            ans.add(new ArrayList<Integer>(combine));
            return;
        }

        // 考虑当前元素
        if (target - candidates[idx] >= 0) {
            combine.add(candidates[idx]);
            dfs(candidates, target - candidates[idx],combine, idx);
            combine.remove(combine.size() - 1);
        }

        // 跳过当前元素
        dfs(candidates, target, combine, idx + 1);
    }
}

47. 全排列2

if (i > 0 && nums[i] == nums[i - 1] && !vis[i - 1]) {
    continue;
}

这个条件可以保证在已经排序的含有重复元素的中,每次填入的数字一定是这个数组中“从左到右第一个未被填入的数字”。假设我们有 33 个重复数排完序后相邻,那么我们一定保证每次都是拿从左往右第一个未被填过的数字,即整个数组的状态其实是保证了 [未填入,未填入,未填入] 到 [填入,未填入,未填入],再到 [填入,填入,未填入],最后到 [填入,填入,填入] 的过程的,因此可以达到去重的目标。

131. 分割回文串

class Solution {
    List<List<String>> ans;
    List<String> list;
    int n;
    boolean[][] f;
    public List<List<String>> partition(String s) {
        n = s.length();
        ans = new ArrayList<>();
        list = new ArrayList<>();
        f = new boolean[n][n];

        for (int i = 0; i < n; i++) {
            Arrays.fill(f[i],true);
        }

        for (int i = n - 1; i >= 0; i--) {
            for (int j = i + 1; j < n; j++) {
                f[i][j] = (s.charAt(i) == s.charAt(j)) && f[i + 1][j - 1];
                // System.out.println(i + ">" + j +">"+ (s.charAt(i) == s.charAt(j)) + ">"+ f[i + 1][j - 1] + ">" + f[i][j]);
            }
        }

        // for (int i = 0; i < n; i++) {
        //     for (int j = 0; j < n; j++) {
        //         System.out.print(f[i][j] + "   ");
        //     }
        //     System.out.println();
        // }
        dfs(s, 0);
        return ans;
    }

    public void dfs(String s, int idx) {
        if (idx ==n) {
            ans.add(new ArrayList<>(list));
            return ;
        }
        for (int i = idx; i < n; i++) {
            if (f[idx][i]) {
                list.add(s.substring(idx,i + 1));
                dfs(s, i + 1);
                list.remove(list.size() - 1);
            }
        }
    }
}

638. 大礼包

class Solution {
    HashMap<List<Integer>, Integer> memo;   // 记忆化搜索存储已经查找到的答案<需求列表,对应需求的价格>
    int n;                                  // 商品的数量

    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        memo = new HashMap<>();
        n = price.size();

        // 筛选可以使价格降低的大礼包
        List<List<Integer>> filteredSpecial = new ArrayList<>();
        //逐个检查所有的大礼包:大礼包中的商品数量>0并且其价格低于单独购买的价格
        for (List<Integer> sp : special) {      // sp:n+1;前n个元素是大礼包中第i个商品的价格,第n+1个是大礼包的价格
            int totalCount = 0, // 大礼包中的商品数量
                TotalPrice = 0; // 单独购买大礼包中所有商品需要的价格
            for (int i = 0; i < n; i++) {
                totalCount += sp.get(i);
                TotalPrice += sp.get(i) * price.get(i);
            }
            if (totalCount > 0 && TotalPrice > sp.get(n)) {
                filteredSpecial.add(sp);
            }
        }//至此,special中所有可以使价格降低的大礼包都被放入filteredSpecial中

        // 记忆化搜索确定最便宜的价格
        return dfs(price, special, needs);

    }

    public int dfs(List<Integer> price, List<List<Integer>> filteredSpecial, List<Integer> curNeeds) {
        if (!memo.containsKey(curNeeds)){
            int minPrice = 0;       // 购买所需商品的价格
            for (int i = 0; i < n; i++) {
                minPrice += curNeeds.get(i) * price.get(i); // 不购买任何大礼包
            }
            for (List<Integer> sp : filteredSpecial) {  // 遍历所有大礼包
                int specialPrice = sp.get(n);   // 当前大礼包的价格
                List<Integer> nextNeeds = new ArrayList<>();
                for (int i = 0; i < n; i++) {
                    if (sp.get(i) > curNeeds.get(i)) {  // 大礼包中某个商品的价格超过需求,不能购买
                        break;
                    }
                    nextNeeds.add(curNeeds.get(i) - sp.get(i)); // 剩余的需求
                }
                if (nextNeeds.size() == n) {    // 当前大礼包可以购买
                    minPrice = Math.min(minPrice,   // 不购买当前大礼包的价格
                                        dfs(price, filteredSpecial, nextNeeds) + specialPrice); // 购买当前大礼包的价格
                }
            }
            // 当前需求已经考虑结束,可以将其价格加入结果集中
            memo.put(curNeeds, minPrice);
        }
        return memo.get(curNeeds);
    }
}

784. 字母大小全排列

第一次做这个题目的时候因为粗心 把题目要求搞错,所以实际解决的问题是字符串元素的所有重排列。

class Solution {
    List<String> ans;
    boolean[] visited;
    public List<String> letterCasePermutation(String s) {
        ans = new ArrayList<>();
        visited = new boolean[s.length()];
        dfs(s, new StringBuffer());
        return ans;
    }

    public void dfs(String s, StringBuffer sb) {
        if (sb.length() == s.length()){
            ans.add(sb.toString());
        }
        for (int i = 0; i < s.length(); i++) {
            if (!visited[i]) {
                sb.append(s.charAt(i));
                visited[i] = true;
                dfs(s, sb);
                sb.deleteCharAt(sb.length() - 1);
                visited[i] = false;
            }
        }
    }
}
这里是这个问题的正确解答
class Solution {
    List<String> ans;
    boolean[] visited;
    public List<String> letterCasePermutation(String s) {
        ans = new ArrayList<>();
        visited = new boolean[s.length()];
        dfs(s, new StringBuffer(), 0);
        return ans;
    }

    public void dfs(String s, StringBuffer sb, int index) {
        if (index == s.length()) {
            String string = sb.toString();
            ans.add(string);
            return;
        }
        char ch = s.charAt(index);
        if (Character.isDigit(ch)) {
            sb.append(ch);
            dfs(s, sb, index + 1);
        } else {
            sb.append(ch);
            dfs(s, sb, index + 1);
            sb.deleteCharAt(sb.length() - 1);
            sb.append(change(ch));
            dfs(s, sb, index + 1);
        }
        sb.deleteCharAt(sb.length() - 1);
    }

    public char change(char c) {
        if (c >= 65 && c <= 90) {
            return (char) (c + 32);
        } else {
            return (char) (c - 32);
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值