更多干貨就在我的個(gè)人博客 BlackBlog.tech 歡迎關(guān)注!
也可以關(guān)注我的csdn博客:黑哥的博客
謝謝大家!
很久沒有進(jìn)行練習(xí)了,手都生疏了不少。前一段時(shí)間完成30道DFS題目的練習(xí),現(xiàn)在開始BFS,預(yù)計(jì)做完BFS分類中的所有中等難度題。
LeetCode中的分類并不是非常的標(biāo)準(zhǔn),BFS的題目中很多用DFS也可以解答,只要能夠解出,不一定絕對(duì)要使用LeetCode建議的算法。
給個(gè)目錄:
LeetCode102 二叉樹的層次遍歷
LeetCode103 二叉樹的鋸齒形層次遍歷
LeetCode127 單詞接龍
LeetCode787 K 站中轉(zhuǎn)內(nèi)最便宜的航班
LeetCode752 打開轉(zhuǎn)盤鎖
LeetCode200 島嶼的個(gè)數(shù)
LeetCode199 二叉樹的右視圖
LeetCode210 課程表 II
LeetCode279 完全平方數(shù)
LeetCode310 最小高度樹
LeetCode863 二叉樹中所有距離為 K 的結(jié)點(diǎn)
LeetCode133 克隆圖
LeetCode102 二叉樹的層次遍歷
題目
給定一個(gè)二叉樹,返回其按層次遍歷的節(jié)點(diǎn)值。(即逐層地,從左到右訪問所有節(jié)點(diǎn))。
例如:
給定二叉樹: [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回其層次遍歷結(jié)果:
[
[3],
[9,20],
[15,7]
]
C++代碼
class Solution {
public:
map<int,vector<int>> hash; //新建一個(gè)map用于存儲(chǔ)結(jié)果
vector<vector<int>> levelOrder(TreeNode* root) {
dfs(root,0);//dfs開始
vector<vector<int>> res; //res存儲(chǔ)最終結(jié)果
for(auto key:hash){
res.push_back(key.second); //將map轉(zhuǎn)換為vector
}
return res;//返回結(jié)果
}
void dfs(TreeNode* root, int depth){
if(!root) return;//如果是空節(jié)點(diǎn) 直接返回
hash[depth].push_back(root->val); //同一層次的節(jié)點(diǎn)放入一個(gè)vector中
dfs(root->left,depth+1);//遍歷左子樹
dfs(root->right,depth+1);//遍歷右子樹
}
};
體會(huì)
DFS+記錄深度,非常經(jīng)典的樹的遍歷題目,熟悉DFS應(yīng)該在三分鐘內(nèi)可以A掉,具體內(nèi)容請(qǐng)看注釋。
LeetCode103 二叉樹的鋸齒形層次遍歷
題目
給定一個(gè)二叉樹,返回其節(jié)點(diǎn)值的鋸齒形層次遍歷。(即先從左往右,再從右往左進(jìn)行下一層遍歷,以此類推,層與層之間交替進(jìn)行)。
例如:
給定二叉樹 [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
返回鋸齒形層次遍歷如下:
[
[3],
[20,9],
[15,7]
]
C++代碼
class Solution {
public:
map<int,vector<int>> hash;
vector<vector<int>> zigzagLevelOrder(TreeNode* root) {
dfs(root,0);
vector<vector<int>> res;
for(auto key:hash){
if(key.first%2){ //偶數(shù)層反轉(zhuǎn)數(shù)組
reverse(key.second.begin(),key.second.end());
res.push_back(key.second);
}
else res.push_back(key.second); //奇數(shù)層正常放入
}
return res;
}
void dfs(TreeNode* root, int depth){
if(!root) return;//如果當(dāng)前節(jié)點(diǎn)為空 返回
hash[depth].push_back(root->val); //同一層節(jié)點(diǎn)放在一個(gè)vector中
dfs(root->left,depth+1); //遍歷左子樹
dfs(root->right,depth+1); //遍歷右子樹
}
};
體會(huì)
本題與上一題思路完全一致,唯一的區(qū)別在于偶數(shù)層節(jié)點(diǎn)需要反向輸出達(dá)到最后的效果,奇數(shù)層節(jié)點(diǎn)正常輸出。
LeetCode127 單詞接龍
題目
給定兩個(gè)單詞(beginWord 和 endWord)和一個(gè)字典,找到從 beginWord 到 endWord 的最短轉(zhuǎn)換序列的長度。轉(zhuǎn)換需遵循如下規(guī)則:
每次轉(zhuǎn)換只能改變一個(gè)字母。
轉(zhuǎn)換過程中的中間單詞必須是字典中的單詞。
說明:
如果不存在這樣的轉(zhuǎn)換序列,返回 0。
所有單詞具有相同的長度。
所有單詞只由小寫字母組成。
字典中不存在重復(fù)的單詞。
你可以假設(shè) beginWord 和 endWord 是非空的,且二者不相同。
示例 1:
輸入:
beginWord = "hit",
endWord = "cog",
wordList = ["hot","dot","dog","lot","log","cog"]
輸出: 5
解釋: 一個(gè)最短轉(zhuǎn)換序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog",
返回它的長度 5。
示例 2:
輸入:
beginWord = "hit"
endWord = "cog"
wordList = ["hot","dot","dog","lot","log"]
輸出: 0
解釋: endWord "cog" 不在字典中,所以無法進(jìn)行轉(zhuǎn)換。
C++代碼
class Solution {
public:
int min_length = INT_MAX; //記錄最短路徑
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
queue<pair<string,int>> que;//建立一個(gè)隊(duì)列 記錄當(dāng)前字符串與路徑長度
que.push({beginWord,1});//放入起始字符串 路徑長度為1
vector<int> visited(wordList.size(),0); //用來記錄wordList的訪問情況
while(!que.empty()){
auto tmp = que.front(); //取出隊(duì)首元素
que.pop(); //出隊(duì)
if(tmp.first==endWord && tmp.second<min_length){ //如果隊(duì)首元素與最終字符串相同 且 路徑長度小于min_length 則修改min_length
min_length = tmp.second;
}
//尋找當(dāng)前隊(duì)首字符串的下一個(gè)變換字符串
for(int i=0;i<wordList.size();i++){
if(visited[i]) continue; //如果當(dāng)前字符串被訪問過 則跳過
else if (cmp(tmp.first,wordList[i])!=1) continue; //如果當(dāng)前字符串與隊(duì)首字符串變換字符數(shù)量不為1 則跳過
else { //否則新的字符串入隊(duì) 并修改當(dāng)前長度
que.push({wordList[i],tmp.second+1});
visited[i] = 1;
}
}
}
if(min_length==INT_MAX) return 0; //如果當(dāng)前長度為INT_MAX證明沒有找到 返回0
else return min_length; //否則返回當(dāng)前最小長度
}
//判斷src與dst兩個(gè)字符串不相同字符的個(gè)數(shù)
int cmp(string src,string dst){
int count = 0;
for(int i=0;i<src.length();i++){
if(src[i]!=dst[i]) count ++;
}
return count;
}
};
體會(huì)
這個(gè)題目與POJ上的一道題目非常類似,本題采用BFS算法。首先創(chuàng)建一個(gè)隊(duì)列,隊(duì)列中需要包括當(dāng)前的字符串與路徑長度,向隊(duì)列中存入beginWord,之后重復(fù)如下過程:遍歷wordList尋找與隊(duì)首字符串僅一個(gè)字母不同的字符串,尋找到之后存入隊(duì)列中,直到尋找到endWord,比較當(dāng)前的長度與min_length,選小的作為min_length。最后返回min_length。
LeetCode787 K 站中轉(zhuǎn)內(nèi)最便宜的航班
題目
有 n 個(gè)城市通過 m 個(gè)航班連接。每個(gè)航班都從城市 u 開始,以價(jià)格 w 抵達(dá) v。
現(xiàn)在給定所有的城市和航班,以及出發(fā)城市 src 和目的地 dst,你的任務(wù)是找到從 src 到 dst 最多經(jīng)過 k 站中轉(zhuǎn)的最便宜的價(jià)格。 如果沒有這樣的路線,則輸出 -1。
示例 1:
輸入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 1
輸出: 200
示例 2:
輸入:
n = 3, edges = [[0,1,100],[1,2,100],[0,2,500]]
src = 0, dst = 2, k = 0
輸出: 500
C++代碼
class Solution {
public:
int min_value = INT_MAX;
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int K) {
queue<vector<int>> que; //建立一個(gè)隊(duì)列 隊(duì)列存儲(chǔ)的是 價(jià)值和 與 路徑。 數(shù)組的第一個(gè)元素為當(dāng)前路徑的價(jià)值和,后面的元素為當(dāng)前的路徑。
que.push({0,src}); //起點(diǎn)入隊(duì)
while(!que.empty()){ //如果隊(duì)列不為空
auto tmp = que.front(); //取出隊(duì)首元素
que.pop();
if(tmp[tmp.size()-1]==dst){ //如果當(dāng)前路徑已經(jīng)碰到終點(diǎn)
if(tmp.size()-1<=K+2 && tmp[0]<min_value) min_value = tmp[0]; //且當(dāng)前路徑的長度小于K+2(中轉(zhuǎn)點(diǎn)+起點(diǎn)+終點(diǎn))且當(dāng)前路徑的價(jià)值小于min_value 更新 min_value。
}
if(tmp.size()>K+2 || tmp[0]>min_value) continue;//剪枝 如果當(dāng)前路徑的長度>K+2或者當(dāng)前路徑的價(jià)值大于min_value都不在進(jìn)行搜索
for(auto key:flights){ //遍歷flights
if(tmp[tmp.size()-1]==key[0] && find(tmp.begin()+1,tmp.end(),key[1])==tmp.end()){ //find函數(shù)用來保證當(dāng)前路徑中沒有重復(fù)節(jié)點(diǎn)
vector<int> new_vec; //創(chuàng)建一個(gè)新的臨時(shí)vec
new_vec.insert(new_vec.end(),tmp.begin(),tmp.end()); //將tmp的內(nèi)容全部放入new_vec
new_vec[0]+= key[2]; //new_vec的價(jià)值 = tmp的價(jià)值 + 新節(jié)點(diǎn)的價(jià)值
new_vec.push_back(key[1]); //將新的節(jié)點(diǎn)放入new_vec中
que.push(new_vec);//將new_vec放入que
}
}
}
if(min_value==INT_MAX) return -1; //如果min_value等于INT_MAX證明沒有找到 返回-1
else return min_value; //否則返回最小長度
}
};
體會(huì)
經(jīng)典BFS算法,這個(gè)題需要考慮一個(gè)問題。題目中雖然說了沒有環(huán)路,但是兩個(gè)節(jié)點(diǎn)間有往返的情況。對(duì)于這種情況我們?cè)贐FS的過程中需要存儲(chǔ)整個(gè)路徑,用來判斷是否會(huì)出現(xiàn)重復(fù)節(jié)點(diǎn)。由于K有的時(shí)候會(huì)非常大,搜索的深度會(huì)非常深,這時(shí)候要注意剪枝。如果當(dāng)前路徑的長度>K+2或者當(dāng)前路徑的價(jià)值大于min_value都不在進(jìn)行搜索,這樣剪枝可以讓效率成倍的提升。
LeetCode752 打開轉(zhuǎn)盤鎖
題目
你有一個(gè)帶有四個(gè)圓形撥輪的轉(zhuǎn)盤鎖。每個(gè)撥輪都有10個(gè)數(shù)字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每個(gè)撥輪可以自由旋轉(zhuǎn):例如把 '9' 變?yōu)? '0','0' 變?yōu)?'9' 。每次旋轉(zhuǎn)都只能旋轉(zhuǎn)一個(gè)撥輪的一位數(shù)字。
鎖的初始數(shù)字為 '0000' ,一個(gè)代表四個(gè)撥輪的數(shù)字的字符串。
列表 deadends 包含了一組死亡數(shù)字,一旦撥輪的數(shù)字和列表里的任何一個(gè)元素相同,這個(gè)鎖將會(huì)被永久鎖定,無法再被旋轉(zhuǎn)。
字符串 target 代表可以解鎖的數(shù)字,你需要給出最小的旋轉(zhuǎn)次數(shù),如果無論如何不能解鎖,返回 -1。
示例 1:
輸入:deadends = ["0201","0101","0102","1212","2002"], target = "0202"
輸出:6
解釋:
可能的移動(dòng)序列為 "0000" -> "1000" -> "1100" -> "1200" -> "1201" -> "1202" -> "0202"。
注意 "0000" -> "0001" -> "0002" -> "0102" -> "0202" 這樣的序列是不能解鎖的,
因?yàn)楫?dāng)撥動(dòng)到 "0102" 時(shí)這個(gè)鎖就會(huì)被鎖定。
示例 2:
輸入: deadends = ["8888"], target = "0009"
輸出:1
解釋:
把最后一位反向旋轉(zhuǎn)一次即可 "0000" -> "0009"。
示例 3:
輸入: deadends = ["8887","8889","8878","8898","8788","8988","7888","9888"], target = "8888"
輸出:-1
解釋:
無法旋轉(zhuǎn)到目標(biāo)數(shù)字且不被鎖定。
示例 4:
輸入: deadends = ["0000"], target = "8888"
輸出:-1
C++代碼
class Solution {
public:
int openLock(vector<string>& deadends, string target) {
string beginStr = "0000"; //初始字符
set<string> dead(deadends.begin(),deadends.end()); //將deadends轉(zhuǎn)化為set 用來提高檢索速度
set<string> visited; //用來記錄已經(jīng)訪問過的號(hào)碼
char flag_1 = '9'+1; //用來處理9->0
char flag_2 = '0'-1; //用來處理0->9
int min_length = INT_MAX;//用來記錄最短路徑的長度
visited.insert(beginStr); //將初始字符設(shè)置為訪問
queue<pair<int,string>> que; //建立一個(gè)隊(duì)列 隊(duì)列中記錄當(dāng)前節(jié)點(diǎn)與深度
que.push({0,beginStr});//放入其實(shí)節(jié)點(diǎn)
if(dead.count("0000")) return -1; //如果dead中有0000 直接返回-1
while(!que.empty()){
auto tmp = que.front(); //取出隊(duì)列中第一個(gè)元素
que.pop();
if(tmp.second == target && tmp.first<min_length){ //如果當(dāng)前節(jié)點(diǎn)就是target 且深度小于當(dāng)前的min_length,更新min_length。
min_length = tmp.first;
}
if(tmp.first>min_length) continue; //剪枝 如果當(dāng)前深度大于min_length則不需要再搜索
for(int i=0;i<4;i++){ //對(duì)于密碼中的每一位做+1 -1 操作
string str_plus = tmp.second;
string str_subs = tmp.second;
str_plus[i]+=1;
str_subs[i]-=1;
if(str_plus[i]==flag_1){ //9->0
str_plus[i] = '0';
}
if(str_subs[i]==flag_2){ //0->9
str_subs[i] = '9';
}
if(!dead.count(str_plus) && !visited.count(str_plus)) { //如果dead與visited中都不存在 str_plus,則將其放入隊(duì)列。
que.push({tmp.first+1,str_plus});
visited.insert(str_plus);
}
if(!dead.count(str_subs) && !visited.count(str_subs)) { //如果dead與visited中都不存在 str_subs,則將其放入隊(duì)列。
que.push({tmp.first+1,str_subs});
visited.insert(str_subs);
}
}
}
return min_length == INT_MAX? -1: min_length; //返回最終結(jié)果
}
};
體會(huì)
經(jīng)典BFS+剪枝。將問題轉(zhuǎn)化為一個(gè)圖,每一個(gè)節(jié)點(diǎn)都與其他8個(gè)節(jié)點(diǎn)相連接,0000與0001、0009、0010、0090、0100、0900、1000、9000這個(gè)八個(gè)節(jié)點(diǎn)相連,以此類推。使用BFS逐層遍歷這個(gè)圖,找到答案并記錄下最小路徑。這個(gè)題直接BFS肯定會(huì)爆時(shí)間,因?yàn)槊恳粋€(gè)數(shù)字都會(huì)產(chǎn)生8種變化,不做剪枝數(shù)據(jù)量將非常大。使用visited數(shù)組記錄訪問過的數(shù)據(jù),防止重復(fù)。使用set代替vector,set查詢復(fù)雜度為O(1),vector查詢復(fù)雜度為O(n),要快不少。
LeetCode200 島嶼的個(gè)數(shù)
題目
給定一個(gè)由 '1'(陸地)和 '0'(水)組成的的二維網(wǎng)格,計(jì)算島嶼的數(shù)量。一個(gè)島被水包圍,并且它是通過水平方向或垂直方向上相鄰的陸地連接而成的。你可以假設(shè)網(wǎng)格的四個(gè)邊均被水包圍。
示例 1:
輸入:
11110
11010
11000
00000
輸出: 1
示例 2:
輸入:
11000
11000
00100
00011
輸出: 3
C++代碼
class Solution {
public:
int numIslands(vector<vector<char>>& grid) {
int count = 0;
for(int i=0;i<grid.size();i++){
for(int j=0;j<grid[i].size();j++){
if(grid[i][j]=='1') { //如果找到1 將這個(gè)點(diǎn)作為種子進(jìn)行涂色
dfs(grid,i,j);
count++;
}
}
}
return count;
}
void dfs(vector<vector<char>>& grid,int x,int y){
if(x<0||x>grid.size()-1||y<0||y>grid[0].size()-1||grid[x][y]=='0'||grid[x][y]=='2') return; //越界或者碰到0或2 都直接返回
grid[x][y] = '2'; //將當(dāng)前節(jié)點(diǎn)染色
dfs(grid,x+1,y); //對(duì)周圍四個(gè)點(diǎn)做相同操作
dfs(grid,x-1,y);
dfs(grid,x,y+1);
dfs(grid,x,y-1);
}
};
體會(huì)
DFS水題。可以把這個(gè)題轉(zhuǎn)化為一個(gè)涂色問題,找到一個(gè)1就將這個(gè)區(qū)域全涂成2,遍歷整個(gè)數(shù)組,看一功能涂多少個(gè)區(qū)域。
LeetCode199 二叉樹的右視圖
題目
給定一棵二叉樹,想象自己站在它的右側(cè),按照從頂部到底部的順序,返回從右側(cè)所能看到的節(jié)點(diǎn)值。
示例:
輸入: [1,2,3,null,5,null,4]
輸出: [1, 3, 4]
解釋:
1 <---
/ \
2 3 <---
\ \
5 4 <---
C++代碼
class Solution {
public:
map<int,int> hash; //key是層 value是最右側(cè)的數(shù)
vector<int> rightSideView(TreeNode* root) {
vector<int> res;//用于記錄最后的結(jié)果
dfs(root,0); //從root開始dfs
for(auto key:hash){
res.push_back(key.second);//將hash中的結(jié)果按層
}
return res;
}
void dfs(TreeNode* root,int depth){
if(!root) return;
hash[depth] = root->val; //因?yàn)槭遣捎孟刃虮闅v,所以每次都更新hash 最后的結(jié)果就是最右側(cè)的數(shù)據(jù)
dfs(root->left,depth+1);//dfs左子樹
dfs(root->right,depth+1);//dfs右子樹
}
};
體會(huì)
直接按先序遍歷DFS,記錄一下每一層的節(jié)點(diǎn)就好,水題。
LeetCode210 課程表 II
題目
現(xiàn)在你總共有 n 門課需要選,記為 0 到 n-1。
在選修某些課程之前需要一些先修課程。 例如,想要學(xué)習(xí)課程 0 ,你需要先完成課程 1 ,我們用一個(gè)匹配來表示他們: [0,1]
給定課程總量以及它們的先決條件,返回你為了學(xué)完所有課程所安排的學(xué)習(xí)順序。
可能會(huì)有多個(gè)正確的順序,你只要返回一種就可以了。如果不可能完成所有課程,返回一個(gè)空數(shù)組。
示例 1:
輸入: 2, [[1,0]]
輸出: [0,1]
解釋: 總共有 2 門課程。要學(xué)習(xí)課程 1,你需要先完成課程 0。因此,正確的課程順序?yàn)?[0,1] 。
示例 2:
輸入: 4, [[1,0],[2,0],[3,1],[3,2]]
輸出: [0,1,2,3] or [0,2,1,3]
解釋: 總共有 4 門課程。要學(xué)習(xí)課程 3,你應(yīng)該先完成課程 1 和課程 2。并且課程 1 和課程 2 都應(yīng)該排在課程 0 之后。因此,一個(gè)正確的課程順序是 [0,1,2,3] 。另一個(gè)正確的排序是 [0,2,1,3] 。
說明:
輸入的先決條件是由邊緣列表表示的圖形,而不是鄰接矩陣。詳情請(qǐng)參見圖的表示法。
你可以假定輸入的先決條件中沒有重復(fù)的邊。
提示:
這個(gè)問題相當(dāng)于查找一個(gè)循環(huán)是否存在于有向圖中。如果存在循環(huán),則不存在拓?fù)渑判颍虼瞬豢赡苓x取所有課程進(jìn)行學(xué)習(xí)。
通過 DFS 進(jìn)行拓?fù)渑判?- 一個(gè)關(guān)于Coursera的精彩視頻教程(21分鐘),介紹拓?fù)渑判虻幕靖拍睢?br>
拓?fù)渑判蛞部梢酝ㄟ^ BFS 完成。
C++代碼
class Solution {
public:
vector<int> findOrder(int numCourses, vector<pair<int, int>>& prerequisites) {
vector<int> in(numCourses,0); //記錄入度
map<int,vector<int>> linkGraph; //圖的臨接表
vector<int> res; //最終結(jié)果
queue<int> que; //BFS所用隊(duì)列
//構(gòu)建圖的鄰接表 計(jì)算每個(gè)節(jié)點(diǎn)的入度
for(auto key:prerequisites){
linkGraph[key.second].push_back(key.first);
in[key.first]++;
}
//將所有的起點(diǎn)都放入que中,因?yàn)閿?shù)據(jù)中存在圖不聯(lián)通的情況
for(int i=0;i<in.size();i++){
if(in[i]==0) que.push(i);
}
//BFS
while(!que.empty()){
auto tmp = que.front();
if(find(res.begin(),res.end(),tmp)==res.end() && in[tmp]==0)res.push_back(tmp); //如果當(dāng)前節(jié)點(diǎn)沒有被添加且入度為0 證明這門課的先修課都完成了,可以將該課添加進(jìn)res
que.pop();
//遍歷鄰接表 尋找該節(jié)點(diǎn)的鄰居
for(auto key:linkGraph[tmp]){
//!!!!!注意順序
in[key]--; //該節(jié)點(diǎn)所有的鄰居的入度--
if(in[key]==0) //如果鄰居的入度為0 則該鄰居可以作為起點(diǎn)放入que中
que.push(key);
}
}
//如果所有節(jié)點(diǎn)的入度都為0 證明這個(gè)圖拓?fù)淇膳?輸出res 否則輸出[]
if(*max_element(in.begin(),in.end())==0) return res;
else return {};
}
};
體會(huì)
本題與課程表無太大區(qū)別,都是考察圖的拓?fù)渑判颉1绢}只是記錄課程路徑即可,將que中的數(shù)據(jù)逐個(gè)放入res中,即是答案。
幾個(gè)注意點(diǎn):
1、圖的拓?fù)渑判虿捎肂FS則記錄入度,采用DFS則記錄出度。
2、圖可能不聯(lián)通,所以要將所有的起點(diǎn)都放入que中
3、去除一點(diǎn)時(shí),先將其鄰居點(diǎn)的入度-1,再判斷鄰居入度是否為0,就是我打了一堆!那里。(卡了我好久T_T)
4、res中可能會(huì)有重復(fù),記得去重。
課程表解答請(qǐng)點(diǎn)擊
LeetCode279 完全平方數(shù)
題目
給定正整數(shù) n,找到若干個(gè)完全平方數(shù)(比如 1, 4, 9, 16, ...)使得它們的和等于 n。你需要讓組成和的完全平方數(shù)的個(gè)數(shù)最少。
示例 1:
輸入: n = 12
輸出: 3
解釋: 12 = 4 + 4 + 4.
示例 2:
輸入: n = 13
輸出: 2
解釋: 13 = 4 + 9.
C++代碼
class Solution {
public:
int numSquares(int n) {
//DP[i]表示 i可以表示為DP[i]個(gè)平方數(shù)的和
vector<int> dp(n+1,INT_MAX); //初始化一個(gè)DP數(shù)組 全部初始化為INT_MAX
for(int i=1;i*i<=n;i++)
dp[i*i] = 1; //將所有的平方數(shù)都置為1
for(int i=1;i<n;i++){ //i為普通數(shù)
for(int j=1;i+j*j<=n;j++){ //j為平方數(shù)的根
dp[i+j*j] = min(dp[i]+1,dp[i+j*j]); //狀態(tài)轉(zhuǎn)移方程
}
}
return dp[n]; //最終結(jié)果
}
};
體會(huì)
首先使用DFS進(jìn)行了嘗試,但是爆時(shí)間了。
DP的思路:一個(gè)非平方數(shù)可以看成是一個(gè)平方數(shù)+一個(gè)非平方數(shù)。DP[i]表示i可以表示為DP[i]個(gè)平方數(shù)的和,則非平方數(shù)都可以看作是i+jj,i表示一個(gè)普通數(shù),j表示一個(gè)平方數(shù)的根。狀態(tài)轉(zhuǎn)移方程為dp[i+jj] = min(dp[i]+1,dp[i+jj]),求出能夠組成i的最小個(gè)數(shù)。所有的平方數(shù)都只需要自己就可以組成自己,所以是1,其他初始化為無窮。
例子:
n=12
dp[2] = dp[1+11] = min(dp[1]+1,dp[2]) = 2
dp[8] = dp[4+22] = min(dp[4]+1,dp[8]) = 2
dp[12] = dp[8+22] = min(dp[8]+1,dp[12]) = 3
本題參考博客請(qǐng)點(diǎn)擊
本題也可以使用四平方定理求解,但個(gè)人覺得技巧性太強(qiáng)。
LeetCode310 最小高度樹
對(duì)于一個(gè)具有樹特征的無向圖,我們可選擇任何一個(gè)節(jié)點(diǎn)作為根。圖因此可以成為樹,在所有可能的樹中,具有最小高度的樹被稱為最小高度樹。給出這樣的一個(gè)圖,寫出一個(gè)函數(shù)找到所有的最小高度樹并返回他們的根節(jié)點(diǎn)。
格式
該圖包含 n 個(gè)節(jié)點(diǎn),標(biāo)記為 0 到 n - 1。給定數(shù)字 n 和一個(gè)無向邊 edges 列表(每一個(gè)邊都是一對(duì)標(biāo)簽)。
你可以假設(shè)沒有重復(fù)的邊會(huì)出現(xiàn)在 edges 中。由于所有的邊都是無向邊, [0, 1]和 [1, 0] 是相同的,因此不會(huì)同時(shí)出現(xiàn)在 edges 里。
示例 1:
輸入: n = 4, edges = [[1, 0], [1, 2], [1, 3]]
0
|
1
/ \
2 3
輸出: [1]
示例 2:
輸入: n = 6, edges = [[0, 3], [1, 3], [2, 3], [4, 3], [5, 4]]
0 1 2
\ | /
3
|
4
|
5
輸出: [3, 4]
說明:
根據(jù)樹的定義,樹是一個(gè)無向圖,其中任何兩個(gè)頂點(diǎn)只通過一條路徑連接。 換句話說,一個(gè)任何沒有簡單環(huán)路的連通圖都是一棵樹。
樹的高度是指根節(jié)點(diǎn)和葉子節(jié)點(diǎn)之間最長向下路徑上邊的數(shù)量。
C++代碼
class Solution {
public:
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
if(n==1) return{0}; //如果n=1 直接返回[0]
map<int,vector<int>> hash; //hash用于存儲(chǔ)圖
vector<int> degree(n,0); //degree存儲(chǔ)每一個(gè)節(jié)點(diǎn)的度
for(auto key:edges){ //將edges的內(nèi)容存儲(chǔ)在hash中
hash[key.first].push_back(key.second);
hash[key.second].push_back(key.first);
}
for(auto key:hash){ //計(jì)算每個(gè)節(jié)點(diǎn)的度
degree[key.first] = key.second.size();
}
while (hash.size()>2){ //如果剩余節(jié)點(diǎn)個(gè)數(shù)大于2
vector<int> leaves; //leaves表示葉節(jié)點(diǎn),即 度數(shù)為1 的節(jié)點(diǎn)
for(int i=0;i<degree.size();i++){
if(degree[i]==1) leaves.push_back(i); //將所有度數(shù)為1的節(jié)點(diǎn)存入leaves
}
for(auto leaf:leaves){ //刪除leaf,并將leaf的鄰居節(jié)點(diǎn)的度數(shù)-1
degree[leaf]=0;
for(auto key:hash[leaf])
degree[key]--;
hash.erase(leaf);
}
}
vector<int> res; //將hash中剩下的1個(gè)或2個(gè)節(jié)點(diǎn)轉(zhuǎn)存到vector中
for(auto key:hash)
res.push_back(key.first);
return res;
}
};
體會(huì)
蠻有技巧的一個(gè)題目。
首先拿到這個(gè)題目,第一個(gè)想法就是BFS,將每個(gè)節(jié)點(diǎn)都做為樹的根節(jié)點(diǎn),最后找到樹高度最小的根節(jié)點(diǎn),思路很清晰。但是無奈超時(shí)了,最后5個(gè)數(shù)據(jù)怎么優(yōu)化都過不去,無奈換了思路。
本題思路:一個(gè)圖肯定存在一個(gè)最小高度樹,所有度為1的節(jié)點(diǎn)必為葉節(jié)點(diǎn)。我們從葉節(jié)點(diǎn)向內(nèi)搜索,每次都將葉節(jié)點(diǎn)刪去,并將其鄰居節(jié)點(diǎn)的度數(shù)-1,直到最后僅有一個(gè)或兩個(gè)節(jié)點(diǎn)即可,這一個(gè)或兩個(gè)節(jié)點(diǎn)就是最小高度樹的根節(jié)點(diǎn)。
若一個(gè)圖的最大路徑為奇數(shù)個(gè)節(jié)點(diǎn),則最小高度樹的根節(jié)點(diǎn)只有1個(gè)。
若一個(gè)圖的最大路徑為偶數(shù)個(gè)節(jié)點(diǎn),則最小高度樹的根節(jié)點(diǎn)只有2個(gè)。
算法流程:
1.以某種方式儲(chǔ)存圖,同時(shí)記錄每個(gè)節(jié)點(diǎn)的度
2.刪除度為1的節(jié)點(diǎn),并把與之相連的節(jié)點(diǎn)度數(shù)減1
3.重復(fù)步驟2直到剩下1或2個(gè)節(jié)點(diǎn)
BFS代碼 求大佬優(yōu)化
vector<int> findMinHeightTrees(int n, vector<pair<int, int>>& edges) {
map<int,vector<int>> hash;
for(auto key:edges){ //存儲(chǔ)圖
hash[key.first].push_back(key.second);
hash[key.second].push_back(key.first);
}
map<int,vector<int>> res; //key表示深度 value表示能達(dá)到當(dāng)前深度的根節(jié)點(diǎn)
int min_length = INT_MAX;
for(auto key:hash){
queue<pair<int,int>> que; //初始化一個(gè)隊(duì)列
vector<int> visited(n,0); //初始化visited防止循環(huán)訪問
que.push({key.first,0}); //放入第一個(gè)節(jié)點(diǎn)
visited[key.first]=1; //將第一個(gè)節(jié)點(diǎn)設(shè)置為被訪問
while(!que.empty()){
auto tmp = que.front(); //取出隊(duì)列中的第一個(gè)節(jié)點(diǎn)
que.pop();
visited[tmp.first]=1; //當(dāng)前節(jié)點(diǎn)設(shè)置為訪問
if(tmp.second>min_length) break; //如果當(dāng)前的長度已經(jīng)大于最小長度 直接彈出
if(find(visited.begin(),visited.end(),0)==visited.end()&&tmp.second<=min_length){ //如果所有的節(jié)點(diǎn)都被訪問 切當(dāng)長度小于min_length 修改min_length
min_length = tmp.second;
res[tmp.second].push_back(key.first);
}
for(auto neighbor:hash[tmp.first]){ //將當(dāng)前節(jié)點(diǎn)的neighbor依次入隊(duì)
if(!visited[neighbor]) {
que.push({neighbor,tmp.second+1});
}
}
}
}
return res[min_length];
}
LeetCode863 二叉樹中所有距離為 K 的結(jié)點(diǎn)
題目
給定一個(gè)二叉樹(具有根結(jié)點(diǎn) root), 一個(gè)目標(biāo)結(jié)點(diǎn) target ,和一個(gè)整數(shù)值 K 。返回到目標(biāo)結(jié)點(diǎn) target 距離為 K 的所有結(jié)點(diǎn)的值的列表。 答案可以以任何順序返回。
示例 1:
輸入:root = [3,5,1,6,2,0,8,null,null,7,4], target = 5, K = 2
輸出:[7,4,1]
解釋:
所求結(jié)點(diǎn)為與目標(biāo)結(jié)點(diǎn)(值為 5)距離為 2 的結(jié)點(diǎn),
值分別為 7,4,以及 1
提示:
給定的樹是非空的,且最多有 K 個(gè)結(jié)點(diǎn)。
樹上的每個(gè)結(jié)點(diǎn)都具有唯一的值 0 <= node.val <= 500 。
目標(biāo)結(jié)點(diǎn) target 是樹上的結(jié)點(diǎn)。
0 <= K <= 1000.
C++代碼
class Solution {
public:
map<int,vector<int>> graph;
int graph_size=0;
vector<int> distanceK(TreeNode* root, TreeNode* target, int K) {
dfs(root);
//BFS尋找第K近的節(jié)點(diǎn)
queue<pair<int,int>> que; //BFS所用隊(duì)列
vector<int> res; //res用于存儲(chǔ)最終結(jié)果
vector<int> visited(graph_size+1,0);
que.push({target->val,0}); //將第一個(gè)節(jié)點(diǎn)放入隊(duì)列
visited[target->val]=1; //將第一個(gè)節(jié)點(diǎn)設(shè)置為被訪問
while(!que.empty()){
auto tmp = que.front(); //取出隊(duì)列中的第一個(gè)節(jié)點(diǎn)
que.pop();
if(tmp.second==K) { //如果當(dāng)前節(jié)點(diǎn)的深度已經(jīng)達(dá)到K 將該節(jié)點(diǎn)放入res
res.push_back(tmp.first);
continue;
}
for(auto key:graph[tmp.first]){ //將該節(jié)點(diǎn)的鄰居節(jié)點(diǎn)依次放入隊(duì)列中 并將visited設(shè)置為被訪問
if(!visited[key]){
que.push({key,tmp.second+1});
visited[key]=1;
}
}
}
cout<<graph_size;
return res;
}
void dfs(TreeNode* root){ //dfs將樹轉(zhuǎn)化為圖
if(!root)return;
//建立根節(jié)點(diǎn)與左子節(jié)點(diǎn) 右節(jié)點(diǎn)的雙向連接 寫得有點(diǎn)蠢 見諒
if(root->val>graph_size) graph_size = root->val;
if(root->left) graph[root->val].push_back(root->left->val);
if(root->right) graph[root->val].push_back(root->right->val);
if(root->left) graph[root->left->val].push_back(root->val);
if(root->right) graph[root->right->val].push_back(root->val);
dfs(root->left);
dfs(root->right);
}
};
體會(huì)
DFS+BFS,沒想到?jīng)]有爆時(shí)間。
使用DFS將樹轉(zhuǎn)化為圖,之后使用BFS算法從target開始,尋找距離target為K的節(jié)點(diǎn)。兩個(gè)搜索組合即可。
LeetCode133 克隆圖
題目
克隆一張無向圖,圖中的每個(gè)節(jié)點(diǎn)包含一個(gè) label (標(biāo)簽)和一個(gè) neighbors (鄰接點(diǎn))列表 。
OJ的無向圖序列化:
節(jié)點(diǎn)被唯一標(biāo)記。
我們用 # 作為每個(gè)節(jié)點(diǎn)的分隔符,用 , 作為節(jié)點(diǎn)標(biāo)簽和鄰接點(diǎn)的分隔符。
例如,序列化無向圖 {0,1,2#1,2#2,2}。
該圖總共有三個(gè)節(jié)點(diǎn), 被兩個(gè)分隔符 # 分為三部分。
第一個(gè)節(jié)點(diǎn)的標(biāo)簽為 0,存在從節(jié)點(diǎn) 0 到節(jié)點(diǎn) 1 和節(jié)點(diǎn) 2 的兩條邊。
第二個(gè)節(jié)點(diǎn)的標(biāo)簽為 1,存在從節(jié)點(diǎn) 1 到節(jié)點(diǎn) 2 的一條邊。
第三個(gè)節(jié)點(diǎn)的標(biāo)簽為 2,存在從節(jié)點(diǎn) 2 到節(jié)點(diǎn) 2 (本身) 的一條邊,從而形成自環(huán)。
我們將圖形可視化如下:
1
/ \
/ \
0 --- 2
/ \
\_/
C++代碼
class Solution {
public:
map<int,UndirectedGraphNode*> hash;
UndirectedGraphNode *cloneGraph(UndirectedGraphNode *node) {
return dfs(node);
}
UndirectedGraphNode* dfs(UndirectedGraphNode* node) {
if(!node) return node; //如果當(dāng)前節(jié)點(diǎn)為NULL 返回NULL
if(hash.count(node->label)) return hash[node->label]; //如果當(dāng)前節(jié)點(diǎn)被拷貝 返回
UndirectedGraphNode* new_node = new UndirectedGraphNode(node->label); //創(chuàng)建一個(gè)新的節(jié)點(diǎn)
hash[node->label] = new_node; //將當(dāng)前節(jié)點(diǎn)存在hash中
for(auto tmp:node->neighbors){ //遍歷當(dāng)前節(jié)點(diǎn)的鄰居節(jié)點(diǎn) 對(duì)每一個(gè)鄰居節(jié)點(diǎn)dfs
new_node->neighbors.push_back(dfs(tmp));
}
return new_node;
}
};
體會(huì)
開始拿到這個(gè)題真的愣了一下,其實(shí)就是一個(gè)無向圖的遍歷問題,使用DFS算法遍歷,每次遍歷到一個(gè)新的節(jié)點(diǎn),進(jìn)行深拷貝。