473. 火柴拼正方形
leetcode题目链接:https://leetcode.cn/problems/matchsticks-to-square
leetcode AC记录:
思路:计算数组总和,如果不能被4整除,则直接返回false。总和除以4作为边长,使用大小为4的数组变量edges保存结果集,遍历每一根火柴放置到4条边上,如果放置火柴后累加和小于等于边长值,则进行回溯,放置下一根火柴,时间复杂度为O(4^n),理由是每个火柴都有4种选择,这是一种解法。还有一种解法是遍历每一个火柴,尝试组成当前的边,组成后边的个数加1,然后用剩余的火柴组成剩余的边,但是时间复杂度太高,大约等于n!。
代码如下:
public boolean makesquare(int[] matchsticks) {
int sum = 0;
for(int i = 0;i < matchsticks.length;i++) {
sum += matchsticks[i];
}
if(sum % 4 != 0) {
return false;
}
int edgeLength = sum / 4;
Arrays.sort(matchsticks);
return hs(matchsticks, new int[4], matchsticks.length-1, edgeLength);
}
public boolean hs(int[] matchsticks, int[] edges, int index, int edgeLength) {
if(index == -1) {
return true;
}
for(int i = 0;i < edges.length;i++) {
edges[i] += matchsticks[index];
if(edges[i] <= edgeLength) {
if(hs(matchsticks, edges, index-1, edgeLength)) {
return true;
}
}
edges[i] -= matchsticks[index];
}
return false;
}
332. 重新安排行程
leetcode题目链接:https://leetcode.cn/problems/reconstruct-itinerary
leetcode AC记录:
思路:关键在于如何选择数据结构,使用map维护起点和终点的映射,使用treemap维护终点的次数(为了排序),使用回溯法遍历map,然后取出来输出。
代码如下:
public List<String> findItinerary(List<List<String>> tickets) {
Map<String, TreeMap<String, Integer>> map = new HashMap<>(tickets.size() + 1);
for(List<String> ticket : tickets) {
String start = ticket.get(0);
String end = ticket.get(1);
TreeMap<String, Integer> countMap = map.getOrDefault(start, new TreeMap<>());
countMap.put(end, countMap.getOrDefault(end, 0) + 1);
map.put(start, countMap);
}
List<String> res = new ArrayList<>(tickets.size() + 1);
res.add("JFK");
hs(tickets.size() + 1, "JFK", map, res);
return res;
}
public boolean hs(int length, String start, Map<String, TreeMap<String, Integer>> map, List<String> res) {
if(res.size() == length) {
return true;
}
Map<String, Integer> countMap = map.get(start);
if(countMap == null) {
return false;
}
for(Map.Entry<String, Integer> countEntry : countMap.entrySet()) {
Integer count = countEntry.getValue();
if(count > 0) {
countEntry.setValue(--count);
res.add(countEntry.getKey());
if(hs(length, countEntry.getKey(), map, res)) {
return true;
} else {
res.remove(res.size()-1);
countEntry.setValue(++count);
}
}
}
return false;
}
51. N 皇后
leetcode题目链接:https://leetcode.cn/problems/n-queens/
leetcode AC记录:
思路:主要是校验当前放置n皇后是否合法,然后使用回溯逐行放置即可。
代码如下:
public List<List<String>> solveNQueens(int n) {
List<List<String>> res = new ArrayList<>(16);
char[][] path = new char[n][n];
for(int i = 0;i < n;i++) {
for(int j = 0;j < n;j++) {
path[i][j] = '.';
}
}
hs(res, path, 0, n);
return res;
}
public void hs(List<List<String>> res, char[][] path, int x, int n) {
if(x >= n) {
res.add(Arrays.stream(path).map(String::valueOf).collect(Collectors.toList()));
return;
}
for(int j = 0; j < n;j++) {
path[x][j] = 'Q';
if(isValid(path, x, j, n)) {
hs(res, path, x + 1, n);
}
path[x][j] = '.';
}
}
/**
* 判断当前放置的棋子是否合法
*/
public boolean isValid(char[][] path, int x, int y, int n) {
//遍历每列
int num = 0;
for(int j = 0;j < n; j++) {
if(path[x][j] == 'Q') {
num++;
}
if(num > 1) {
return false;
}
}
//遍历每列
num = 0;
for(int i = 0;i < n;i++) {
if(path[i][y] == 'Q') {
num++;
}
if(num > 1) {
return false;
}
}
//遍历左斜线
int xbegin = x;
int ybegin = y;
while(xbegin > 0 && ybegin > 0) {
xbegin--;
ybegin--;
}
num = 0;
while(xbegin < n && ybegin < n) {
if(path[xbegin][ybegin] == 'Q') {
num++;
}
if(num > 1) {
return false;
}
xbegin++;
ybegin++;
}
//遍历右斜线
xbegin = x;
ybegin = y;
while(xbegin > 0 && ybegin + 1< n) {
xbegin--;
ybegin++;
}
num = 0;
while(xbegin < n && ybegin >= 0) {
if(path[xbegin][ybegin] == 'Q') {
num++;
}
if(num > 1) {
return false;
}
xbegin++;
ybegin--;
}
return true;
}
37. 解数独
leetcode题目链接:https://leetcode.cn/problems/sudoku-solver/
leetcode AC记录:
思路:这个题比较难,二维回溯,两个for循环遍历数独数组,遇到字符“.”放置1-9数字,然后进行回溯,如果合法直接返回true,如果1-9用完了没有合适的,返回false。
代码如下:
public void solveSudoku(char[][] board) {
hs(board);
}
public boolean hs(char[][] board) {
for(int i = 0;i < 9;i++) {
for(int j = 0;j < 9;j++) {
if(board[i][j] != '.') {
continue;
} else {
for(int k = 1;k <= 9;k++) {
board[i][j] = (char)(k + '0');
if(isValid(board, i, j)) {
if(hs(board)) {
return true;
}
}
board[i][j] = '.';
}
return false;
}
}
}
return true;
}
public boolean isValid(char[][] board, int x, int y) {
int[] nums = new int[9];
//判断所在行是否有效
for(int j = 0;j < 9;j++) {
if(board[x][j] >= '1' && board[x][j] <= '9') {
nums[board[x][j] - '1']++;
if(nums[board[x][j] - '1'] > 1) {
return false;
}
}
}
//判断所在列是否有效
Arrays.fill(nums, 0);
for(int i = 0;i < 9;i++) {
if(board[i][y] >= '1' && board[i][y] <= '9') {
nums[board[i][y] - '1']++;
if(nums[board[i][y] - '1'] > 1) {
return false;
}
}
}
//判断所在方块是否有效
Arrays.fill(nums, 0);
int beginX = 0, endX = 3, beginY = 0 , endY = 3;
if(x >= 3 && x < 6) {
beginX = 3;
endX = 6;
} else if(x >= 6 && x < 9) {
beginX = 6;
endX = 9;
}
if(y >= 3 && y < 6) {
beginY = 3;
endY = 6;
} else if(y >= 6 && y < 9) {
beginY = 6;
endY = 9;
}
for(int i = beginX;i < endX;i++) {
for(int j = beginY;j < endY;j++) {
if(board[i][j] >= '1' && board[i][j] <= '9') {
nums[board[i][j] - '1']++;
if(nums[board[i][j] - '1'] > 1) {
return false;
}
}
}
}
return true;
}
842. 将数组拆分成斐波那契序列
leetcode题目链接:力扣
leetcode AC记录:
代码如下:
public List<Integer> splitIntoFibonacci(String num) {
List<Integer> res = new ArrayList<>(16);
boolean flag = hs(res, num, 0);
return flag ? res : new ArrayList<>();
}
public boolean hs(List<Integer> res, String num, int beginIndex) {
if(res.size() > 2 && beginIndex == num.length()) {
return true;
}
for(int i = 1;beginIndex + i <= num.length();i++) {
String str = num.substring(beginIndex, beginIndex + i);
if(str.length() > 1 && str.charAt(0) == '0') {
break;
}
long valueLong = Long.parseLong(str);
if(valueLong > Integer.MAX_VALUE) {
break;
}
int value = (int) valueLong;
if(res.size() < 2) {
res.add(value);
if(hs(res, num, beginIndex + i)) {
return true;
}
res.remove(res.size()-1);
} else {
int lastSum = res.get(res.size()-1) + res.get(res.size()-2);
if(value == lastSum) {
res.add(value);
if(hs(res, num, beginIndex + i)) {
return true;
}
res.remove(res.size()-1);
} else if(value > lastSum) {
break;
}
}
}
return false;
}
306. 累加数
leetcode题目链接:https://leetcode.cn/problems/additive-number/
leetcode AC记录:
思路:使用回溯算法,需要注意的是判断是否是斐波纳切数列,case里字符串转为int后会超出限制,所以使用大数相加进行判断,java里采用BigInteger。
代码如下:
public boolean isAdditiveNumber(String num) {
if(num.length() < 3) {
return false;
}
List<BigInteger> res = new ArrayList<>(num.length());
return hs(res, num, 0);
}
public boolean hs(List<BigInteger> res, String num, int begin) {
if(begin == num.length() && res.size() > 2) {
return true;
}
for(int i = begin + 1; i <= num.length();i++) {
String str = num.substring(begin, i);
if(str.length() > 1 && str.charAt(0) == '0') {
continue;
}
BigInteger value = new BigInteger(str);
if(res.size() < 2) {
res.add(value);
if(hs(res, num, i)) {
return true;
}
res.remove(res.size() - 1);
} else {
BigInteger lastOneEle = res.get(res.size()-1);
BigInteger lastTwoEle = res.get(res.size()-2);
if(lastOneEle.add(lastTwoEle).equals(value)) {
res.add(value);
if(hs(res, num, i)) {
return true;
}
res.remove(res.size()-1);
}
}
}
return false;
}
980. 不同路径 III
leetcode题目链接:https://leetcode.cn/problems/unique-paths-iii/
leetcode AC记录:
思路:使用回溯法,找到并记录开始和结束的下标、空格的数量。
结束条件为:开始位置下标和结束位置下标重合,并且空格的数量等于空格的总数,则进行结果收集;如果空格的数量小于空格总数,开始结束下标重合不收集结果并且直接返回。
从开始位置进行回溯,如果开始位置的下标超出范围或者开始位置的值为-1,直接返回;如果符合条件,则将该位置记录为已访问,并且回溯上下左右四个方向。
回溯的方法需要的参数:结果集,原数组,是否访问数组,开始位置,结束位置,空格数量,空格总数。
代码如下:
public int uniquePathsIII(int[][] grid) {
//找到开始和结束的下标
int beginx = 0;
int beginy = 0;
int endx = 0;
int endy = 0;
int blankTotal = 0;
for(int i = 0;i < grid.length;i++) {
for(int j = 0;j < grid[0].length;j++) {
if(grid[i][j] == 1) {
beginx = i;
beginy = j;
} else if(grid[i][j] == 2) {
endx = i;
endy = j;
} else if(grid[i][j] == 0) {
blankTotal++;
}
}
}
int[] res = new int[1];
boolean[][] used = new boolean[grid.length][grid[0].length];
hs(res, beginx, beginy, endx, endy, 0, blankTotal, used, grid);
return res[0];
}
public void hs(int[] res, int beginx, int beginy, int targetx, int targety, int blankCount, int blankTotal, boolean[][] used, int[][] grid) {
if(beginx == targetx && beginy == targety && blankCount == blankTotal) {
res[0]++;
return;
} else if(beginx == targetx && beginy == targety && blankCount < blankTotal) {
return;
}
if(beginx < 0 || beginx >= grid.length || beginy < 0 || beginy >= grid[0].length || used[beginx][beginy] || grid[beginx][beginy] == -1) {
return;
}
used[beginx][beginy] = true;
int currentBlankCount = grid[beginx][beginy] == 0 ? blankCount + 1 : blankCount;
hs(res, beginx-1, beginy, targetx, targety, currentBlankCount, blankTotal, used, grid);
hs(res, beginx+1, beginy, targetx, targety, currentBlankCount, blankTotal, used, grid);
hs(res, beginx, beginy-1, targetx, targety, currentBlankCount, blankTotal, used, grid);
hs(res, beginx, beginy+1, targetx, targety, currentBlankCount, blankTotal, used, grid);
used[beginx][beginy] = false;
}