(Divide & Conquer)分治和回溯本質上就是特殊的遞歸, 找重複性 重複性有最近重複項(分治,回溯),和最優重複項(動態規劃);
不論分治,遞歸,回溯本質都是找重複性, 分解問題, 最後組合每個子問題的結果.
將一個字符串轉爲大寫字符串
Python 遞歸代碼模板
def recursion(level, param1, param2, ...):
#recursion terminator
if level > MAX_LEVEL:
process_result
return
# process logic in current level
process(level, data..)
# drill down
self.recursion(level + 1, par1, ...)
#reverse the current level status if needed
分治 代碼模板
def divide_conquer(problem, param1, param2, ...):
# recursion terminator
if problem is None:
print_result
return
# prepare data
data = prepare_Data(problem)
subproblems = split_problem(problem, data)
處理當前層邏輯:
如果是求n的階乘這裏就是寫成n * fac(n - 1),
如果是斐波那契寫成n - 1的遞歸結果加上n - 2的遞歸結果,
如果是組合左右括號,組裝左括號存在一個變量中,或者組裝右括號存起來,當然要判斷左右括號是否用完
# conquer subproblems
subresult1 = self.divide_conquer(subproblem[0], p1, ...)
subresult1 = self.divide_conquer(subproblem[1], p1, ...)
subresult1 = self.divide_conquer(subproblem[2], p1, ...)
調用函數下探到下一層解決更細節的子問題
# process and generate the final result
result = process_result(subresult1, subresult2, subresult3, ...)
#reverse the current level states
回溯
Pow(x, n)
一 暴力 時間複雜度 O(n)
result = 1
for i: 0 -> n {
result *= x;
}
二 分治
// template:
1 terminator
2 process(split problem)
3 drill down(subproblems), merge(sub result)
4 reverse states
思想:要算x的n次方結果,比如算2^10,不需要一個2一個2的乘起來乘10次,可以一分爲二,只要算2的5次方就夠了,將2的5次方乘以它自己,最後就變成2的10次方了,注意2的5次方乘以2的5次方是指數相加2的10次方,並不是2的25次方, 還有注意點就是2的5次方的算法,一分爲二,分爲2的2次方,那麼2的2次方乘以它自己爲2的4次方,發現漏掉一個2,所以如果指數是一個奇數的時候將其一分爲二乘以它自己會漏掉一個自己,這時要根據奇偶性補一個x,所以這個題由求問題 化解爲求其子問題
x^n -- > 2^10 : 2^5 -- > (2^2)*2
pow(x, n):
subproblem: subresult = pow(x, n/2)
merge:
if n % 2 == 1 {
// 奇數odd
result = subresult * subresult * x;
}else {
// 偶數even
result = subresult * subresult;
}
時間複雜度變爲了log(N)
所以如果N = 1024次的話,只需要10次,因爲1024減半512,減半256減半…
方法二 分治 :時間 O(logN)
double myPow(double x, int n) {
long long N = n;
if (N < 0) {
x = 1 / x;
N = -N;
}
return fastPow(x, N);
}
double fastPow(double x, long long n){
if (n == 0) return 1.0;
double half = fastPow(x, n / 2);
if (n % 2 == 0) {
return half * half;
}else {
return half * half * x;
}
}
上面寫報錯的原因:Java 代碼中 int32, n∈[−2147483648, 2147483647] , 因此當 n = -2147483648時 執行 n = − n 得到n = −2147483648 時 會因越界而賦值出錯
把int 改成long 是可以用同樣邏輯的
感覺下面這個n < 0 的情況 1/ resu應該就沒用上,因爲上面如果n < 0的話已經將n變爲正數了,是吧
實戰 子集
給定一組不含重複元素的整數數組 nums,返回該數組所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
方法一 :遞歸分治思想
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
if (nums == null) {return ans;}
dfs(ans, nums, new ArrayList<Integer>(), 0);
}
private void dfs(List<List<Integer>> ans, int[] nums, List<Integer> list, int index) {
// teminator
if (index == nums.length) {
ans.add(new ArrayList<Integer>(list));
return;
}
// not pick the number at this index,list不需要發生改變
dfs(ans, nums, list, index + 1);
list.add(nums[index]);
// pick the number at this index list發生改變了
dfs(ans, nums, list, index + 1);
// reverse the current state
/*這裏爲什麼要reverse,因爲我們在上面add給了list的一個數,改變了list參數,這個地方隨着遞歸一直在變,它不是本層的本地變量,不是本層的局部變量Local variable(每一層是隔開的),但這個參數改變了會影響上面幾層的函數,所以這裏就把剛纔添加到List中的數去掉,達到一致的.也可以不用reverse the current state 但是要把結果的每一層都複製出來通過這種辦法每次改變當前層list不會影響到上下面一層,因爲是每層拷貝一份出來的,這樣的話,因爲List爲容器相等於把裏面的每一個元素的reference都淺拷貝即指針,引用拷貝了一次類似於下面僞代碼
*/
list.remove(list.size() - 1);
}
僞代碼
private void dfs(List<List<Integer>> ans, int[] nums, List<Integer> list, int index) {
// teminator
if (index == nums.length) {
ans.add(new ArrayList<Integer>(list));
return;
}
// not pick the number at this index,不需要發生改變
dfs(ans, nums, list.clone(), index + 1);
list.add(nums[index]);
// pick the number at this index 發生改變了
dfs(ans, nums, list.clone(), index + 1);
}
方法二:迭代思想:
一開始是空數組,之後都是往之前已經產生過的子集合裏面加,
[[]] 遍歷所有nums 後 [[], [1], [2], [1, 2], [3], [1, 3], [2, 3], [1, 2, 3]]
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
backtrack(0, nums, res, new ArrayList<Integer>());
return res;
}
private void backtrack(int i, int[] nums, List<List<Integer>> res, ArrayList<Integer> tmp) {
res.add(new ArrayList<>(tmp));
for (int j = i; j < nums.length; j++) {
tmp.add(nums[j]);
backtrack(j + 1, nums, res, tmp);
tmp.remove(tmp.size() - 1);
}
}
電話號碼的字母組合
public List<String> letterCombinations(String digits) {
if (digits == null || digits.length() == 0) return new ArrayList();
Map<Character, String> map = new HashMap<Character, String>();
map.put('2', "abc");
map.put('3', "def");
map.put('4', "ghi");
map.put('5', "jkl");
map.put('6', "mno");
map.put('7', "pqrs");
map.put('8', "tuv");
map.put('9', "wxyz");
List<String> res = new LinkedList<String>();
search("", digits, 0, res, map);
return res;
}
private void search(String s, String digits, int i, List<String> res, Map<Character, String> map) {
if (i == digits.length()) {
res.add(s);
return;
}
String letter = map.get(digits.charAt(i));
for (int j = 0; j < letter.length(); j++) {
search(s + letter.charAt(j), digits, i + 1, res, map);
}
}