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