回溯的思想在于回这个字,以最经典的八皇后作为例子:
backTrack(当前层数,棋盘):
for: 当前层的每个位置
放置一个棋子到这个位置
backTrack(下一层, 棋盘) // 深入到下一层
取消将棋子放到这个位置 // 回溯,以便于尝试当前层的下一个位置
当然,回溯也有当前搜索的解空间的情况,比如对于棋盘就是目前已经放置的所有的位置,可以使用记忆化搜锁
下面的题目中0698就使用了
https://leetcode.cn/problems/partition-to-k-equal-sum-subsets/
1 解题思路:
2 1.2的时机例子如下:典型的多余的for循环:1取到1,2取到2,那么当最外层的2取到2,1又取到1,那不就是重复计算了嘛
// for every number, try bucket from 0 to k-1
void backTrack(vector<int>& nums, vector<int>& buckets, int tar) {
if(fin) {
return ;
}
bool tmp = true;
for(auto& buc : buckets) {
tmp = tmp && (buc == tar);
}
if(tmp) {
fin = true;
}
for(int i = 0; i < nums.size(); ++i) {
if(!used[i]) {
for(int buc = 0; buc < buckets.size(); ++buc) {
buckets[buc] += nums[i]; // put it into buc
used[i] = true;
if(buckets[buc] <= tar) {
backTrack(nums, buckets, tar);
}
buckets[buc] -= nums[i];
used[i] = false;
}
}
}
}
标准的带有返回值的写法:
class Solution {
public:
vector<int> used;
bool canPartitionKSubsets(vector<int>& nums, int k) {
vector<int> buckets(k, 0);
used.resize(nums.size(), false);
sort(nums.rbegin(), nums.rend());
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % k != 0) {
return false;
}
int tar = sum / k;
if(nums[0] > tar) {
return 0;
}
vector<unordered_set<int>> memo(nums.size()); // for node[i], memo[i][j]means: node[i] put into a bucket with value j will failed
return backTrack(nums, buckets, 0, tar, memo);
}
// for every number, try bucket from 0 to k-1
bool backTrack(vector<int>& nums, vector<int>& buckets, int st, int tar, vector<unordered_set<int>>& memo) {
// 所有数字都放进去了,成功了
if(st == nums.size()) {
return true;
}
for(int buc = 0; buc < buckets.size(); ++buc) {
if(0 == memo[st].count(buckets[buc])) {
// if(buckets[i]) {
buckets[buc] += nums[st]; // put it into buc
// used[st] = true;
if(buckets[buc] == tar || (tar - buckets[buc] >= nums.back())) {
// cout << "to buc: " << buc << endl;
if(backTrack(nums, buckets, st + 1, tar, memo)) {
return true; // 提前返回,因为已经找到一个结果
}
}
buckets[buc] -= nums[st];
// 如果nums[st] 放入 buckets[buc]失败了,那么后面值相同的桶都会失败,就不用放了
memo[st].insert(buckets[buc]);
}
}
// 说明放错桶的不是st,而是nums[0 : st-1]中的某个,于是把memo清除掉
memo[st].clear();
return false;
}
}
我写的回溯不喜欢带返回值,后面是带有返回值的:
class Solution {
public:
vector<int> used;
bool fin = false;
bool canPartitionKSubsets(vector<int>& nums, int k) {
vector<int> buckets(k, 0);
used.resize(nums.size(), false);
sort(nums.rbegin(), nums.rend());
int sum = accumulate(nums.begin(), nums.end(), 0);
if(sum % k != 0) {
return false;
}
int tar = sum / k;
if(nums[0] > tar) {
return 0;
}
int useCnt = 0;
vector<unordered_set<int>> memo(nums.size());
backTrack(nums, buckets, 0, tar, useCnt, memo);
return fin;
}
// for every number, try bucket from 0 to k-1
void backTrack(vector<int>& nums, vector<int>& buckets, int st, int tar, int& useCnt, vector<unordered_set<int>>& memo) {
if(fin) {
return ;
}
if(useCnt == nums.size()) {
bool tmp = true;
for(auto& buc : buckets) {
tmp = tmp && (buc == tar);
}
if(tmp) {
fin = true;
return;
}
}
if(useCnt == nums.size()) {
return; // return true就行
}
for(int buc = 0; buc < buckets.size(); ++buc) {
if(0 == memo[st].count(buckets[buc])) {
buckets[buc] += nums[st]; // put it into buc
++useCnt;
if(buckets[buc] == tar || (tar - buckets[buc] >= nums.back())) {
// cout << "to buc: " << buc << endl;
backTrack(nums, buckets, st + 1, tar, useCnt, memo);
}
buckets[buc] -= nums[st];
// 如果nums[st] 放入 buckets[buc]失败了,那么后面值相同的桶都会失败,就不用放了
memo[st].insert(buckets[buc]);
--useCnt;
}
}
// 说明放错桶的不是st,而是nums[0 : st-1]中的某个,于是把memo清除掉
memo[st].clear();
}
}
https://leetcode.cn/problems/unique-paths-iii/
class Solution {
public:
int m,n;
int dx[4] = {1, 0, -1, 0};
int dy[4] = {0, 1, 0, -1};
int tarSum = 0;
int edX, edY;
int backTrack(vector<vector<int>>& grid, vector<vector<bool>>& vis, int x, int y, int curSum) {
if(x == edX && y == edY) {
// cout << "yes: " << curSum << endl;
if(curSum == tarSum) {
return 1;
} else {
return 0;
}
}
int ways = 0;
for(int mv = 0; mv < 4; ++mv) {
int nextX = x + dx[mv];
int nextY = y + dy[mv];
if(0 <= nextX && nextX < m && 0 <= nextY && nextY < n && \
!vis[nextX][nextY] && \
-1 != grid[nextX][nextY]
) {
vis[nextX][nextY] = true;
ways += backTrack(grid, vis, nextX, nextY, curSum + 1);
vis[nextX][nextY] = false;
}
}
return ways;
}
int uniquePathsIII(vector<vector<int>>& grid) {
m = grid.size();
n = grid[0].size();
// just backTracking all
vector<vector<bool>> vis(m, vector<bool>(n, false));
int x, y;
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(1 == grid[i][j]) {
x = i; y = j;
} else if(2 == grid[i][j]) {
edX = i; edY = j;
++tarSum;
} else {
tarSum += (grid[i][j] == 0);
}
}
}
// cout << tarSum << endl;
int curSum = 0;
vis[x][y] = true;
return backTrack(grid, vis, x, y, curSum);
}
};
https://leetcode.cn/problems/number-of-squareful-arrays/
class Solution {
public:
void swap(int& a, int& b) {
int c = a;
a = b;
b = c;
}
bool judge(double tar) {
double x = sqrt(tar);
if(floor(x) == x) {
return true;
}
return false;
}
void print(vector<int>& vec) {
for(auto i : vec) {
cout << i <<" ";
}cout <<"\n";
}
unordered_set<string> rmDup;
// memo thah: cal[st][num] in pos st, whethter num has been visited
unordered_map<int, unordered_map<int, bool>> cal;
// st means : we are choosing a num for nums[st]
// and we should be sure that: nums[st] and nums[st-1] is able to sqrt
int permutationCheck(vector<int>& nums, int st) {
if(st == nums.size()) {
string tmp = "";
for(auto& i : nums) {
tmp += to_string(i);
}
if(rmDup.insert(tmp).second) {
return 1;
}
return 0;
}
int res = 0;
for(int i = st; i < nums.size(); ++i) {
swap(nums[i], nums[st]);
// cout << "level: " << st << " ";
// print(nums);
if(st == 0) {
if(!cal[st][nums[st]]) {
res += permutationCheck(nums, st + 1);
cal[st][nums[st]] = true;
}
} else {
if(judge(nums[st-1] + nums[st])) {
if(!cal[st][nums[st]]) {
res += permutationCheck(nums, st + 1);
cal[st][nums[st]] = true;
}
}
}
swap(nums[i], nums[st]);
}
// when return, the permutated prefix: nums[:st-1] will change, so we clear the memo
cal[st].clear();
return res;
}
int numSquarefulPerms(vector<int>& nums) {
int n = nums.size();
if(1 == n) {
return judge(nums.front());
}
// all sequence(permutation) and check each
return permutationCheck(nums, 0);
}
};
https://leetcode.cn/problems/tiling-a-rectangle-with-the-fewest-squares/
class Solution {
public:
vector<vector<bool>> board; // top left is 0, 0
int n, m; // n colums, m rows
int finRes = INT_MAX;
// start from left top
pair<int, pair<int, int>> findPos(vector<vector<bool>>& board) {
int x, y;
for(int i = 0; i < m; ++i) {
for(int j = 0; j < n; ++j) {
if(!board[i][j]) {
x = i; y = j;
// find len:
int u = x;
while(u < m && !board[u][y]) {
++u;
}
int len = u - x;
int v = y;
while(v < n && !board[x][v]) {
++v;
}
len = min(len, v - y);
return {len, {x, y}};
}
}
}
return {-1, {-1, -1}};
}
void fillSquareWith(vector<vector<bool>>& board, int x, int y, int len, bool content) {
for(int i = x; i < x + len; ++i) {
for(int j = y; j < y + len; ++j) {
board[i][j] = content;
}
}
}
void backTrack(vector<vector<bool>>& board, int& leftCnt, int curRes) {
if(finRes <= curRes) {
return ;
}
if(0 == leftCnt) {
finRes = min(finRes, curRes);
return ;
}
// find first zero pos along the boarder, or the next 1
auto p = findPos(board);
int len = p.first;
int x = p.second.first, y = p.second.second;
// cout << "found max len: " << len << " x/y " << x << " " << y << endl;
for(int l = len; l > 0; --l) {
fillSquareWith(board, x, y, l, true);
leftCnt -= l*l;
// cout <<"ready to fill: x/y/l: " << x << "/" << y << "/" << l << "/" << "left: " << leftCnt << endl;
// print(board);
backTrack(board, leftCnt, curRes + 1);
leftCnt += l*l;
fillSquareWith(board, x, y, l, false);
}
}
int tilingRectangle(int n, int m) {
this->n = m;
this->m = n;
// cout << "this: " << this->n << " " << this->m << endl;
board.resize(n, vector<bool>(m, 0));
int leftCnt = n*m;
int curRes = 0;
backTrack(board, leftCnt, curRes);
return finRes;
}
void print(vector<vector<bool>>& board) {
for(auto v : board) {
for(auto i : v) {
cout << i << " ";
}cout << "\n";
}cout << "\n ---------- ";
}
}
https://leetcode.cn/problems/maximum-score-words-formed-by-letters/
class Solution {
public:
vector<int> wordScore;
void printPool(unordered_map<char, int>& leftCharPool) {
for(auto& [c, cnt] : leftCharPool) {
cout << c << " " << cnt << endl;
}
cout << "---\n";
}
bool addOrRemove(string& word, unordered_map<char, int>& leftCharPool, bool add) {
// cout << "add/rm: " << word << endl;
if(add) {
for(auto& c : word) {
++leftCharPool[c];
}
} else {
unordered_map<char, int> curWord;
for(auto& c : word) {
++curWord[c];
}
for(auto& [c, cnt] : curWord) {
if(leftCharPool[c] < cnt) {
return false;
}
}
printPool(leftCharPool);
// remove the word from the pool
for(auto& [c, cnt] : curWord) {
// cout << "rm " << c << " : " << cnt << endl;
leftCharPool[c] -= cnt;
}
// printPool(leftCharPool);
}
return true;
}
int backTrack(vector<string>& words, int st, unordered_map<char, int>& leftCharPool) {
if(words.size() == st) {
return 0;
}
int curScore = INT_MIN;
// use it
if(addOrRemove(words[st], leftCharPool, false)) {
// cout << "using " << words[st] << endl;
// printPool(leftCharPool);
curScore = max(wordScore[st] + backTrack(words, st + 1, leftCharPool), curScore);
// add it back
addOrRemove(words[st], leftCharPool, true);
}
// not use it
// cout << "not using" << words[st] << endl;
// printPool(leftCharPool);
curScore = max(backTrack(words, st + 1, leftCharPool), curScore);
return curScore;
}
int maxScoreWords(vector<string>& words, vector<char>& letters, vector<int>& score) {
// check all subsets of words using status compression
// you may use status compression, but here we use backTrack
unordered_map<char, int> leftCharPool;
wordScore.resize(words.size(), 0);
int wIdx = 0;
for(auto& w : words) {
for(auto& c : w) {
wordScore[wIdx] += score[c - 'a'];
}
++wIdx;
}
for(auto& c : letters) {
++leftCharPool[c];
}
int st = 0;
return backTrack(words, 0, leftCharPool);
}
};
https://leetcode.cn/problems/word-rectangle-lcci/
class Solution {
public:
class Trie {
public:
vector<Trie*> curLevel;
bool isEnd = false;
Trie() : curLevel(26, nullptr) {
}
void buildTrie(vector<string>& words) {
for(auto& w : words) {
auto curNode = this;
for(auto& c : w) {
if(nullptr == curNode->curLevel[c - 'a']) {
curNode->curLevel[c - 'a'] = new Trie();
}
curNode = curNode->curLevel[c - 'a'];
}
curNode->isEnd = true;
}
}
bool preInTrie(string& w) {
auto curNode = this;
for(auto& c : w) {
auto nextLevel = curNode->curLevel[c - 'a'];
if(nullptr == nextLevel) {
return false;
}
curNode = nextLevel;
}
return true;
}
bool wordInTrie(string& w) {
auto curNode = this;
for(auto& c : w) {
auto nextLevel = curNode->curLevel[c - 'a'];
if(nullptr == nextLevel) {
return false;
}
curNode = nextLevel;
}
return curNode->isEnd;
}
};
void ableToMakeRes(vector<string>& hori, vector<string>& vert) {
// cout << "> hori.size() " << hori.size() << endl;
if(0 == vert[0].size()) {
// cout << "> vert empty: " << endl;
return;
}
for(auto& v : vert) {
if(!root->wordInTrie(v)) {
// cout << "> word failed: " << v;
return ;
}
}
int curSquare = vert[0].size() * vert.size();
// cout << "> curSquare: " << vert[0].size() * vert.size() << endl;
// cout << "> finSquare: " << finSquare << endl;
if(curSquare > finSquare) {
// cout << "updated!\n";
finSquare = curSquare;
resVec = hori;
}
}
// try all possible permutation of rect with width = w
void backTrack(
vector<string>& curWords,
vector<string>& hori,
vector<string>& vert,
vector<string>& resVec
) {
int width = curWords[0].size();
// cur found square bigger then all possilbe res from curWords
if(finSquare > width * maxWordLen) return;
// check if we obtain a res
ableToMakeRes(hori, vert);
for(auto& word : curWords) {
hori.push_back(word);
int vertCnt = 0;
bool failed = false;
for(int i = 0; i < width; ++i) {
vert[i].push_back(word[i]);
++vertCnt;
if(!root->preInTrie(vert[i])) {
failed = true;
break;
}
}
// cout << "trying: " << word << " | with hori = " << hori.size() << endl;
if(!failed) {
// cout << "choosing: " << word << endl;
// next level
backTrack(curWords, hori, vert, resVec);
}
// backTrack
// cout << "failed on: " << word << endl;
hori.pop_back();
for(int i = 0; i < vertCnt; ++i) {
vert[i].pop_back();
}
}
}
unique_ptr<Trie> root;
int finSquare = INT_MIN;
vector<string> resVec;
int maxWordLen = -1;
vector<string> maxRectangle(vector<string>& words) {
root = make_unique<Trie>();
root->buildTrie(words);
map<int, vector<string>, std::greater<int>> lenWords;
for(auto& w : words) {
lenWords[w.size()].push_back(w);
maxWordLen = max(maxWordLen, static_cast<int>(w.size()));
}
for(auto& [w, curWords] : lenWords) {
vector<string> hori;
vector<string> vert(w, "");
// cout << "w is: " << w <
backTrack(curWords, hori, vert, resVec);
}
return resVec;
}
};