深度優先搜索解題算法2
題目1:重新安排行程
給定一個機票的字符串二維數組 [from, to],子數組中的兩個成員分別表示飛機出發和降落的機場地點,對該行程進行重新規劃排序。所有這些機票都屬於一個從JFK(肯尼迪國際機場)出發的先生,所以該行程必須從 JFK 出發
如果存在多種有效的行程,你可以按字符自然排序返回最小的行程組合。例如,行程 [“JFK”, “LGA”] 與 [“JFK”, “LGB”] 相比就更小,排序更靠前
示例 1:
輸入: [[“JFK”,“SFO”],[“JFK”,“ATL”],[“SFO”,“ATL”],[“ATL”,“JFK”],[“ATL”,“SFO”]]
輸出: [“JFK”,“ATL”,“JFK”,“SFO”,“ATL”,“SFO”]
解釋: 另一種有效的行程是 [“JFK”,“SFO”,“ATL”,“JFK”,“ATL”,“SFO”]。但是它自然排序更大更靠後
解題思路:深度優先搜索
class Solution {
public:
unordered_map<string,multiset<string>>map;
vector<string> res;
vector<string> findItinerary(vector<vector<string>>& tickets) {
for(auto& t:tickets){
map[t[0]].insert(t[1]);
}
dfs("JFK");
return vector<string>(res.rbegin(),res.rend());
}
void dfs(string from){
while(map[from].size()!=0){
string next = *map[from].begin();
map[from].erase(map[from].begin());
dfs(next);
}
res.push_back(from);
}
};
題目2:打家劫舍
在上次打劫完一條街道之後和一圈房屋後,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之爲“根”。 除了“根”之外,每棟房子有且只有一個“父“房子與之相連。一番偵察之後,聰明的小偷意識到“這個地方的所有房屋的排列類似於一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。
示例 1:
輸入: [3,2,3,null,3,null,1]
3
/ \
2 3
\ \
3 1
輸出: 7
解釋: 小偷一晚能夠盜取的最高金額 = 3 + 3 + 1 = 7.
示例 2:
輸入: [3,4,5,1,3,null,1]
3
/ \
4 5
/ \ \
1 3 1
輸出: 9
解釋: 小偷一晚能夠盜取的最高金額 = 4 + 5 = 9.
解題思路:深度優先搜索和相鄰關係,每個節點有偷和不偷兩種可能,如果偷,則子節點不可以偷,但可以偷孫子節點,如果不偷當前節點,則可以偷子節點.
C++ 參考代碼
class Solution {
public:
int rob(TreeNode* root) {
if(root == NULL) return 0;
//偷取該節點
int s1 = root->val;
if(root->left != NULL){
s1 += rob(root->left->left) +\
rob(root->left->right);
}
if(root->right != NULL){
s1 += rob(root->right->left) +\
rob(root->right->right);
}
//不偷取該節點
int s0 = 0;
s0 += rob(root->left) + rob(root->right);
return max(s0,s1);
}
};
避免重複計算,用容器保存已經計算過的值
class Solution {
public:
unordered_map<TreeNode*,int>map;
int rob(TreeNode* root) {
if(root == NULL) return 0;
//偷取該節點
if(map.count(root)){
return map[root];
}
int s1 = root->val;
if(root->left != NULL){
s1 += rob(root->left->left) +\
rob(root->left->right);
}
if(root->right != NULL){
s1 += rob(root->right->left) +\
rob(root->right->right);
}
//不偷取該節點
int s0 = 0;
s0 += rob(root->left) + rob(root->right);
map[root] = max(s0,s1);
return map[root];
}
};
題目3:員工的重要性
給定一個保存員工信息的數據結構,它包含了員工唯一的id,重要度 和 直系下屬的id。
比如,員工1是員工2的領導,員工2是員工3的領導。他們相應的重要度爲15, 10, 5。那麼員工1的數據結構是[1, 15, [2]],員工2的數據結構是[2, 10, [3]],員工3的數據結構是[3, 5, []]。注意雖然員工3也是員工1的一個下屬,但是由於並不是直系下屬,因此沒有體現在員工1的數據結構中。
現在輸入一個公司的所有員工信息,以及單個員工id,返回這個員工和他所有下屬的重要度之和。
示例 1:
輸入: [[1, 5, [2, 3]], [2, 3, []], [3, 3, []]], 1
輸出: 11
解釋:
員工1自身的重要度是5,他有兩個直系下屬2和3,而且2和3的重要度均爲3。因此員工1的總重要度是 5 + 3 + 3 = 11
解題思路:通過遍歷找到指定 id 的員工重要性,然後迭加
class Employee {
public:
int id;
int importance;
vector<int> subordinates;
};
class Solution {
public:
int importance = 0;
int getImportance(vector<Employee*> employees, int id) {
int index;
for(int i = 0;i<employees.size();++i){
if(employees[i] == nullptr) return importance;
if(id == employees[i]->id){
index = i;
importance += employees[i]->importance;
}
}
for(int i = 0;i<employees[index]->subordinates.size();++i){
getImportance(employees,employees[index]->subordinates[i]);
}
return importance;
}
};
題目4:火柴正方形
還記得童話《賣火柴的小女孩》嗎?現在,你知道小女孩有多少根火柴,請找出一種能使用所有火柴拼成一個正方形的方法。不能折斷火柴,可以把火柴連接起來,並且每根火柴都要用到。
輸入爲小女孩擁有火柴的數目,每根火柴用其長度表示。輸出即爲是否能用所有的火柴拼成正方形
示例 1:
輸入: [1,1,2,2,2]
輸出: true
解釋: 能拼成一個邊長爲2的正方形,每邊兩根火柴。
示例 2:
輸入: [3,3,3,3,4]
輸出: false
解釋: 不能用所有火柴拼成一個正方形
解題思路:對於給定的若干根火柴,將它們分成四組,每一根火柴恰好屬於其中的一組;每一組火柴的長度之和都相同,等於所有火柴長度之和的四分之一
使用深度優先搜索枚舉出所有的分組情況,並對於每一種情況,判斷是否滿足上述的條件
我們依次對每一根火柴進行搜索,當搜索到第 i 根火柴時,我們可以把它放到四組中的任意一種。對於每一種放置方法,我們繼續對第 i + 1 根火柴進行遞歸搜索。當我們搜索完全部的 N 根火柴後,再判斷每一組火柴的長度之和是否都相同
C++ 參考代碼
class Solution {
public:
bool makesquare(vector<int>& nums) {
// 先判斷簡單的情況,如果 nums 元素個數小於4
int sum = 0;
for(auto&e:nums){
sum+=e;
}
if(nums.size()<4 || sum % 4 != 0) {
return false;
}
int target = sum / 4;
// 降序排序
sort(nums.begin(),nums.end(),greater<int>());
vector<int>partition(4,0);
return dfs(target,partition,0,nums);
}
bool dfs(int target,vector<int>&partition,int index,vector<int>&nums){
if(index == nums.size()){
return (partition[0]==partition[1] &&
partition[1] == partition[2] &&
partition[2] == partition[3]);
}
for(int i = 0;i<4;++i){
if(partition[i] + nums[index] > target){
continue;
}
partition[i] +=nums[index];
if(dfs(target,partition, index + 1,nums)){
return true;
}
partition[i] -= nums[index];
}
return false;
}
};
題目5:目標和
給定一個非負整數數組,a1, a2, …, an, 和一個目標數,S。現在你有兩個符號 + 和 -。對於數組中的任意一個整數,你都可以從 + 或 -中選擇一個符號添加在前面。
返回可以使最終數組和爲目標數 S 的所有添加符號的方法數。
示例 1:
輸入: nums: [1, 1, 1, 1, 1], S: 3
輸出: 5
解釋:
-1+1+1+1+1 = 3
+1-1+1+1+1 = 3
+1+1-1+1+1 = 3
+1+1+1-1+1 = 3
+1+1+1+1-1 = 3
一共有5種方法讓最終目標和爲3。
注意:
數組非空,且長度不會超過20。
初始的數組的和不會超過1000。
保證返回的最終結果能被32位整數存下
解題:暴力搜索法,枚舉出所有的可能性,我們可以把問題看做一個將目標值變爲 0 的情況個數
class Solution {
public:
int count = 0;
int findTargetSumWays(vector<int>& nums, int S) {
int len = nums.size();
dfs(nums,S,0,len);
return count;
}
void dfs(vector<int>&nums,long target,int i,int len){
if(i == len && target == 0){
count++;
return;
}
if(i == len) return;
dfs(nums,target - nums[i],i+1,len);
dfs(nums,target + nums[i],i+1,len);
}
};