Leetcode——回溯法常考算法整理
Leetcode——回溯法常考算法整理
- Preface
Definition
First, let’s see the definition of backtracking given by Wikipedia:
Backtrack is a general algorithm for finding all (or some) solutions to some computational problems, notably constraint satisfaction problems, that incrementally builds candidates to the solutions, and abandons a candidate (“backtracks”) as soon as it determines that the candidate cannot possibly be completed to a valid solution.
The classic textbook example of the use of backtracking is the eight queens puzzle, that asks for all arrangements of eight chess queens on a standard chessboard so that no queen attacks any other. In the common backtracking approach, the partial candidates are arrangements of k queens in the first k rows of the board, all in different rows and columns. Any partial solution that contains two mutually attacking queens can be abandoned.
So, in short, backtracking is notably useful to deal with ‘constraint satisfaction problems’– we must find solutions that satisfy certain constraints–
We can know from the definition that, the name of ‘backtrack’ comes from the way it searches for solutions: ‘it incrementally builds candidates to the solutions, and abandons a candidate (“backtracks”) as soon as it determines that the candidate cannot possibly be completed to a valid solution.’
So how does the algorithm ‘determine whether a candidate cannot possibly be completed to a valid solution’? Clearly, this is when the constraint will play a role, i.e. the algorithm would judge whether the current candidate satisfies the constraint.
Another problem is: how to abandon a candidate (‘backtrack’)? Let’s move on!
Why & When to Use Backtrakcing
Backtrack is a good way to search for solutions that satisfy certain constraints, which makes it fit for corresponding problems.
In wiki, it also says:
Backtracking can be applied only for problems which admit the concept of a “partial candidate solution” and a relatively quick test of whether it can possibly be completed to a valid solution. It is useless, for example, for locating a given value in an unordered table. When it is applicable, however, backtracking is often much faster than brute force enumeration of all complete candidates, since it can eliminate a large number of candidates with a single test.
So according to the wiki, the problem that backtracking can solve must satisfy two conditions:
- partial candidate solution
- quick way to test whether a partial candidate can be completed to a valid solution
- What is partial candidate solution?
In my understanding, partial candidate solution refers to solution that is ‘complicate’, not just a simple number or character, but a combination/group of them that work together as the solution to the problem.
For example, in classical problem 8-Queens, we’re required to place 8 queens on an 8×8 chessboard such that no two queens attack each other, and we’d like to know how many possible solutions there are. Each solution consists of the position information of 8 queens. So we say position information of 8 queens together forms a possible solution to the problem, and when there are only i (i<8) queens on the chessboard, we say it’s a partial solution candidate.
Therefore, when we face with a problem, we can first decide whether we can solve it in using backtracking by analyzing if its solution could be broken apart into partial solutions. (We will better understand the concept of partial candidate solution through practicing on some Leetcode problems)
Besides, we can know that for problem the backtracking is applicable, it’s much more efficient than brute force enumeration, since it can eliminate a large number of candidates with a single test, and that is the key difference which make backtracking much more advantageous than brute force enumeration for the problem.
How to Use Backtracking
According to wiki’s description of the method:
The backtracking algorithm enumerates a set of partial candidates that, in principle, could be completed in various ways to give all the possible solutions to the given problem. The completion is done incrementally, by a sequence of candidate extension steps.
Conceptually, the partial candidates are represented as the nodes of a tree structure, the potential search tree. Each partial candidate is the parent of the candidates that differ from it by a single extension step; the leaves of the tree are the partial candidates that cannot be extended any further.
The backtracking algorithm traverses this search tree recursively, from the root down, in depth-first order. At each node c, the algorithm checks whether c can be completed to a valid solution. If it cannot, the whole sub-tree rooted at c is skipped (pruned). Otherwise, the algorithm (1) checks whether c itself is a valid solution, and if so reports it to the user; and (2) recursively enumerates all sub-trees of c. The two tests and the children of each node are defined by user-given procedures.
Therefore, the actual search tree that is traversed by the algorithm is only a part of the potential tree. The total cost of the algorithm is the number of nodes of the actual tree times the cost of obtaining and processing each node. This fact should be considered when choosing the potential search tree and implementing the pruning test.
To sum up, all possible solutions can be seen as the set of all nodes in a tree structure, and in backtracking, we would find all valid solutions by searching in DFS way.
For any specific node N, we would first determine whether N can be completed to a valid solution (i.e. whether N has broken the constraint). If it cannot, the whole sub-tree rooted at N is skipped (pruned, i.e. backtracking). Otherwise, the algorithm (1) checks whether c itself is a valid solution, and if so reports it to the user; and (2) recursively enumerates all sub-trees of c. The two tests and the children of each node are defined by user-given procedures.
- Time Cost
According to wiki:
Therefore, the actual search tree that is traversed by the algorithm is only a part of the potential tree. The total cost of the algorithm is the number of nodes of the actual tree times the cost of obtaining and processing each node. This fact should be considered when choosing the potential search tree and implementing the pruning test.
- Pseudocode
In order to apply backtracking to a specific class of problems, one must provide the data P for the particular instance of the problem that is to be solved, and six procedural parameters, root, reject, accept, first, next, and output. These procedures should take the instance data P as a parameter and should do the following:
- root(P): return the partial candidate at the root of the search tree.
- reject(P,c): return true only if the partial candidate c is not worth completing.
- accept(P,c): return true if c is a solution of P, and false otherwise.
- first(P,c): generate the first extension of candidate c.
- next(P,s): generate the next alternative extension of a candidate, after the extension s.
- output(P,c): use the solution c of P, as appropriate to the application.
The backtracking algorithm reduces the problem to the call bt(root(P)), where bt is the following recursive procedure:
procedure bt(c)
if reject(P,c) then return
if accept(P,c) then output(P,c)
s ← first(P,c)
while s ≠ Λ do
bt(s)
s ← next(P,s)
Details for Writing Code
You must ensure that the reject() method rejects candidate correctly.
- In next() method, if there is no possible way to further extend partial candidate, then remember to backtrack the candidate by one step and then return.
Leetcode Problems
N-Queens
The n-queens puzzle is the problem of placing n queens on an n×n chessboard such that no two queens attack each other.
Given an integer n, return all distinct solutions to the n-queens puzzle.
Each solution contains a distinct board configuration of the n-queens’ placement, where ‘Q’ and ‘.’ both indicate a queen and an empty space respectively.
Example:
Input: 4
Output: [
[".Q..", // Solution 1
"...Q",
"Q...",
"..Q."],
["..Q.", // Solution 2
"Q...",
"...Q",
".Q.."]
]
Explanation: There exist two distinct solutions to the 4-queens puzzle as shown above.
- My Answer
package medium2;
import java.util.ArrayList;
import java.util.List;
/**
* @author Tom Qian
* @email tomqianmaple@outlook.com
* @github https://github.com/bluemapleman
* @date 2018年6月22日
*/
public class NQueens
{
static List<List<String>> ans;
public static void main(String[] args)
{
List<List<String>> ans=solveNQueens(8);
System.out.println("size:"+ans.size());
// for(List<String> list:ans) {
// for(String row:list)
// System.out.println(row);
// System.out.println();
// }
}
public static List<List<String>> solveNQueens(int n) {
ans=new ArrayList<List<String>>();
String[][] c=new String[n][n];
for(int i=0;i<n;i++)
for(int j=0;j<n;j++)
c[i][j]=new String(".");
backtrack(c);
return ans;
}
public static void backtrack(String[][] c){
if(reject(c)) return;
if(accept(c)) {
List<String> list=new ArrayList<>();
for(int i=0;i<c.length;i++) {
String temp="";
for(int j=0;j<c.length;j++) {
temp+=c[i][j];
}
list.add(temp);
}
ans.add(list);
}
c=first(c);
while(c!=null) {
// output(c);
// System.out.println("---------");
backtrack(c);
c=next(c);
}
return;
}
//generate the next alternative extension of a candidate, after the extension s.
public static String[][] next(String[][] c){
int lastRow=-1;
for(int i=0;i<c.length;i++) {
boolean hasQueen=false;
for(int j=0;j<c.length;j++) {
if(c[i][j].equals("Q"))
hasQueen=true;
}
if(!hasQueen) {
lastRow=i-1;
break;
}
}
if(lastRow==-1)
lastRow=c.length-1;
for(int j=0;j<c.length-1;j++) {
if(c[lastRow][j].equals("Q")) {
c[lastRow][j]=".";
c[lastRow][j+1]="Q";
return c;
}
}
c[lastRow][c.length-1]=".";
return null;
}
//generate the first extension of candidate c.
public static String[][] first(String[][] c){
for(int i=0;i<c.length;i++) {
boolean hasQueen=false;
for(int j=0;j<c.length;j++) {
if(c[i][j].equals("Q"))
hasQueen=true;
}
if(!hasQueen) {
c[i][0]="Q";
return c;
}
}
return null;
}
public static boolean accept(String[][] c) {
int count=0;
int n=c.length;
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
if(c[i][j].equals("Q"))
count++;
}
}
if(count==c.length)
return true;
return false;
}
public static boolean reject(String[][] c) {
int n=c.length;
// check by row
for(int i=0;i<n;i++) {
boolean hasQueen=false;
for(int j=0;j<n;j++) {
if(c[i][j].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
// check by column
for(int j=0;j<n;j++) {
boolean hasQueen=false;
for(int i=0;i<n;i++) {
if(c[i][j].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
// check by diagram
// left bottom to right top
for(int i=0;i<n;i++) {
boolean hasQueen=false;
for(int x=0;x<=i;x++) {
if(c[i-x][x].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
for(int j=n-1;j>0;j--) {
boolean hasQueen=false;
for(int x=0;x<n-j;x++) {
if(c[n-1-x][j+x].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
// left top to right bottom
for(int i=n-1;i>=0;i--) {
boolean hasQueen=false;
for(int x=0;x<n-i;x++) {
if(c[i+x][x].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
for(int j=n-1;j>=0;j--) {
boolean hasQueen=false;
for(int x=0;x<n-j;x++) {
if(c[x][j+x].equals("Q")) {
if(hasQueen)
return true;
else
hasQueen=true;
}
}
}
return false;
}
public static void output(String[][] c) {
for(int i=0;i<c.length;i++) {
for(int j=0;j<c.length;j++)
System.out.print(c[i][j]+"-");
System.out.println();
}
}
}
PS: Clearly, I write the code according to pseudocode representing typical backtracking thought, and I think it’s a very convenient and efficient way to finish writing backtracking algorithmwe.
Permutations II
Given a collection of numbers that might contain duplicates, return all possible unique permutations.
Example:
Input: [1,1,2]
Output:
[
[1,1,2],
[1,2,1],
[2,1,1]
]
- My Answer
package medium2;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
/**
* @author Tom Qian
* @email tomqianmaple@outlook.com
* @github https://github.com/bluemapleman
* @date 2018年7月3日
*/
public class PermutationsII
{
// First, review the typical pseudocode of backtracking algorithm again:
/*
backtrack(c){
if(reject(c)) return;
if(accept(c)) add c to solution set;
// first extension of c.
s=first(c);
while(s!=null) {
backtrack(s);
// switch to next possible extension of c
s=next(s);
}
}
*/
// The solution is the all possible permutations of given numbers.
// So to use backtracking, we must first define what is partial solution candidate:
// the list of part of numbers from given integer array
List<List<Integer>> ans;
public List<List<Integer>> permuteUnique(int[] nums) {
ans=new ArrayList<List<Integer>>();
List<Integer> numList=new ArrayList<>();
Arrays.sort(nums);
for(Integer i:nums)
numList.add(i);
backtrack(new ArrayList<>(), numList);
return ans;
}
public void backtrack(List<Integer> c,List<Integer> numList) {
if(reject(c,numList)) return;
if(accept(c, numList)) {
List<Integer> list=new ArrayList<>();
for(int i=0;i<c.size();i++) {
list.add(c.get(i));
}
ans.add(list);
}
List<Integer> s=first(c, numList);
while(s!=null) {
// System.out.println(s);
// System.out.println(numList);
// System.out.println();
backtrack(s, numList);
s=next(s, numList);
}
}
public boolean reject(List<Integer> c,List<Integer> numList) {
return false;
}
public boolean accept(List<Integer> c,List<Integer> numList) {
if(numList.isEmpty()) {
return true;
}else
return false;
}
public List<Integer> first(List<Integer> c,List<Integer> numList){
if(!numList.isEmpty()){
c.add(numList.remove(0));
return c;
}
else
return null;
}
public List<Integer> next(List<Integer> c,List<Integer> numList){
boolean nextFlag=false;
for(int i=0;i<numList.size();i++) {
int ele=numList.get(i);
int lastEle=c.get(c.size()-1);
if(ele>lastEle) {
numList.remove(i);
numList.add(i,lastEle);
c.remove(c.size()-1);
c.add(ele);
nextFlag=true;
break;
}
}
if(nextFlag) {
return c;
}else {
numList.add(c.remove(c.size()-1));
Collections.sort(numList);
return null;
}
}
}
Combinations
Given two integers n and k, return all possible combinations of k numbers out of 1 … n.
Example:
Input: n = 4, k = 2
Output:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
- My Answer
package medium2;
import java.util.ArrayList;
import java.util.List;
/**
* @author Tom Qian
* @email tomqianmaple@outlook.com
* @github https://github.com/bluemapleman
* @date 2018年7月5日
*/
public class Combinations
{
List<List<Integer>> ans;
int n,k;
public List<List<Integer>> combine(int n, int k) {
ans=new ArrayList<>();
this.n=n;
this.k=k;
List<Integer> numList=new ArrayList<>();
for(int i=1;i<=n;i++) {
numList.add(i);
}
backtrack(new ArrayList<>(), numList);
return ans;
}
public void backtrack(List<Integer> c,List<Integer> numList) {
if(reject(c)) return;
if(accept(c)) {
List<Integer> list=new ArrayList<>();
for(Integer ele:c)
list.add(ele);
ans.add(list);
}
List<Integer> s=first(c,numList);
while(s!=null) {
backtrack(s, numList);
s=next(s,numList);
}
}
public boolean reject(List<Integer> c) {
for(int i=0;i<c.size()-1;i++) {
if(c.get(i)>c.get(i+1))
return true;
}
return false;
}
public boolean accept(List<Integer> c) {
if(c.size()==k)
return true;
else
return false;
}
public List<Integer> first(List<Integer> c,List<Integer> numList){
if(c.size()<k) {
if(c.isEmpty()) {
c.add(numList.remove(0));
return c;
}
else {
int lastEle=c.get(c.size()-1);
if(n>lastEle) {
c.add(lastEle+1);
return c;
}
else
return null;
}
}else
return null;
}
public List<Integer> next(List<Integer> c,List<Integer> numList){
int lastEle=c.get(c.size()-1);
if(n>lastEle) {
c.remove(c.size()-1);
c.add(lastEle+1);
return c;
}else {
c.remove(c.size()-1);
return null;
}
}
}
Sudoku Solver
Write a program to solve a Sudoku puzzle by filling the empty cells.
A sudoku solution must satisfy all of the following rules:
- Each of the digits 1-9 must occur exactly once in each row.
- Each of the digits 1-9 must occur exactly once in each column.
- Each of the the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.
Empty cells are indicated by the character ‘.’.
A sudoku puzzle…
…and its solution numbers marked in red.
Note:
- The given board contain only digits 1-9 and the character ‘.’.
- You may assume that the given Sudoku puzzle will have a single unique solution.
The given board size is always 9x9.
My Answer
class Solution {
// This solution from: https://leetcode.com/problems/sudoku-solver/discuss/15752/Straight-Forward-Java-Solution-Using-Backtracking
// Backtrack
int tempX,tempY;
boolean[][] cellsToBeFilled;
public void solveSudoku(char[][] board) {
if(board == null || board.length == 0)
return;
cellsToBeFilled=new boolean[board.length][board[0].length];
for(int i=0;i<board.length;i++) {
for(int j=0;j<board[0].length;j++) {
if(board[i][j]=='.') {
cellsToBeFilled[i][j]=true;
}
}
}
backtrack(board);
}
boolean findAnswerFlag=false;
public void backtrack(char[][] board) {
if(reject(board)) return;
if(accept(board)) {
findAnswerFlag=true;
return;
}
char[][] s=first(board);
while(s!=null) {
// outputMatrix(board);
backtrack(s);
if(findAnswerFlag)
return;
s=next(s);
}
}
public boolean reject(char[][] board) {
int n=board.length;
for(int i=0;i<n;i++) {
Set<Character> set=new HashSet<>();
for(int j=0;j<n;j++) {
if(board[i][j]!='.')
if(set.contains(board[i][j])) {
return true;
}else
set.add(board[i][j]);
}
}
for(int j=0;j<n;j++) {
Set<Character> set=new HashSet<>();
for(int i=0;i<n;i++) {
if(board[i][j]!='.')
if(set.contains(board[i][j])) {
return true;
}else
set.add(board[i][j]);
}
}
for(int x=0;x<2;x++) {
for(int y=0;y<2;y++) {
int startRow=x*3,startCol=y*3;
Set<Character> set=new HashSet<>();
for(int i=startRow;i<=startRow+2;i++) {
for(int j=startCol;j<=startCol+2;j++) {
if(board[i][j]!='.')
if(set.contains(board[i][j])) {
return true;
}else
set.add(board[i][j]);
}
}
}
}
return false;
}
public boolean accept(char[][] board) {
int n=board.length;
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
if(board[i][j]=='.') {
return false;
}
}
}
return true;
}
public char[][] first(char[][] board) {
int n=board.length;
boolean hasEmptyCellFlag=false;
for(int i=0;i<n;i++) {
for(int j=0;j<n;j++) {
if(board[i][j]=='.') {
board[i][j]='1';
tempX=i;tempY=j;
hasEmptyCellFlag=true;
break;
}
}
if(hasEmptyCellFlag)
break;
}
if(hasEmptyCellFlag)
return board;
else {
return null;
}
}
public char[][] next(char[][] board) {
int number=Character.digit(board[tempX][tempY], 10);
if(number<9) {
board[tempX][tempY]=Character.forDigit(number+1, 10);
return board;
}
else {
board[tempX][tempY]='.';
boolean findLastToBeFilledCellFlag=false;
for(int j=tempY-1;j>=0;j--) {
if(cellsToBeFilled[tempX][j]==true) {
tempY=j;
findLastToBeFilledCellFlag=true;
break;
}
}
if(!findLastToBeFilledCellFlag) {
boolean breakFlag=false;
for(int i=tempX-1;i>=0;i--) {
for(int j=board.length-1;j>=0;j--) {
if(cellsToBeFilled[i][j]==true) {
tempX=i;
tempY=j;
breakFlag=true;
break;
}
}
if(breakFlag)
break;
}
}
return null;
}
}
public void outputMatrix(char[][] board) {
for(int i=0;i<board.length;i++) {
for(int j=0;j<board.length;j++) {
System.out.print(board[i][j]+" ");
}
System.out.println();
}
System.out.println();
}
}
Leetcode——回溯法常考算法整理的更多相关文章
- Leetcode——二叉树常考算法整理
二叉树常考算法整理 希望通过写下来自己学习历程的方式帮助自己加深对知识的理解,也帮助其他人更好地学习,少走弯路.也欢迎大家来给我的Github的Leetcode算法项目点star呀~~ 二叉树常考算法 ...
- BFS与DFS常考算法整理
BFS与DFS常考算法整理 Preface BFS(Breath-First Search,广度优先搜索)与DFS(Depth-First Search,深度优先搜索)是两种针对树与图数据结构的遍历或 ...
- JS-常考算法题解析
常考算法题解析 这一章节依托于上一章节的内容,毕竟了解了数据结构我们才能写出更好的算法. 对于大部分公司的面试来说,排序的内容已经足以应付了,由此为了更好的符合大众需求,排序的内容是最多的.当然如果你 ...
- (排序算法整理)NEFU 30/32
版权声明:本文为博主原创文章,未经博主同意不得转载. https://blog.csdn.net/caihongshijie6/article/details/26165093 事实上, ...
- C语言入门100题,考算法的居多
入门题,考算法的居多,共同学习! 1. 编程,统计在所输入的50个实数中有多少个正数.多少个负数.多少个零. 2. 编程,计算并输出方程X2+Y2=1989的所有整数解. 3. 编程,输入一个10进制 ...
- LeetCode解题记录(贪心算法)(二)
1. 前言 由于后面还有很多题型要写,贪心算法目前可能就到此为止了,上一篇博客的地址为 LeetCode解题记录(贪心算法)(一) 下面正式开始我们的刷题之旅 2. 贪心 763. 划分字母区间(中等 ...
- 排序算法整理C++(初赛)
排序算法整理 常见考点 将一个乱掉的字符串排回有序(以交换为基本操作)的最少操作,就是冒泡排序. 排序算法的稳定性 排序算法的时间复杂度 排序算法的稳定性 稳定性是指排序前两个元素a1 = a2,a1 ...
- leetcode回溯算法--基础难度
都是直接dfs,算是巩固一下 电话号码的字母组合 给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合. 给出数字到字母的映射如下(与电话按键相同).注意 1 不对应任何字母. 思路 一直 ...
- leetcode 算法整理
一 字符串中的最大回文串(第5题) Given a string s, find the longest palindromic substring in s. You may assume that ...
随机推荐
- rpmbuild 实践
安装 rpmbuild 1 # yum install -y rpm-build 查看 rpmbuild 相关的宏和参数 12345678 # rpmbuild --showrc | grep --c ...
- 原创:Python爬虫实战之爬取代理ip
编程的快乐只有在运行成功的那一刻才知道QAQ 目标网站:https://www.kuaidaili.com/free/inha/ #若有侵权请联系我 因为上面的代理都是http的所以没写这个判断 代 ...
- web系统是否要前后端分离?
开发一个web管理系统,是否要采用如今流行的前后端分离模式? 首先要从为什么会出现前后端分离说起,前后端分离的目的. 1.让前端工程师(前端)和后端工程师(后端)们能够更加专注于自己的领域 传统的开发 ...
- Ⅱ:python入门
一.编程语言介绍 编程语言的分类: 机器语言 汇编语言 高级语言(编译型.解释型号) 总结: 执行效率:机器语言>汇编语言>高级语言(编译型>解释型) 开发效率:机器语言<汇编 ...
- Day 3 DP
1. P1015 精卫填海 01背包 + 判断 #include <iostream> using namespace std; , MAXV = ; int v, n, m, f[MAX ...
- js事件委托target
**看一看,瞧一瞧!** 话说要谈事件委托和target.那我们首先来看看什么是事件.话说什么是事件呢?一般的解释是比较重大.对一定的人群会产生一定影响的事情.而在JavaScript中就不是这样了, ...
- Windows10 JDK1.8安装及环境变量配置
一.下载JDK1.8: 下载地址:https://www.oracle.com/java/technologies/javase-jdk8-downloads.html 二.安装步骤: 我们通常选择 ...
- CSS的四种样式
行内式CSS样式 在标签内部使用的样式 <div id="one" style="width:50p"></div> 内嵌式CSS样式 ...
- scrapy 在爬取过程中抓取下载图片
先说前提,我不推荐在sarapy爬取过程中使用scrapy自带的 ImagesPipeline 进行下载,是在是太耗时间了 最好是保存,在使用其他方法下载 我这个是在 https://blog.csd ...
- etcdctl的使用
etcdctl是一个提供简洁命令的etcd客户端,使用etcdctl可以直接和etcd服务打交道,对etcd中的键值对进行增删改查. 安装etcdctl 下载etcdctl工具 下载地址:etcdct ...