LeetCode中級演算法題目總結(1)
這是一篇筆記型Blog,主要存一下最近練的程式碼的筆記。LeetCode的程式碼,在雲端,複習起來麻煩,就這樣存下來。
目前的練習為LeetCode中級演算法與每日模擬賽.
沒事刷一刷LeetCode還是可以提高一下基本的程式碼能力的。
LeetCode15 三數之和
題目
給定一個包含 n 個整數的陣列 nums,判斷 nums 中是否存在三個元素 a,b,c ,使得 a + b + c = 0 ?找出所有滿足條件且不重複的三元組。
注意:答案中不可以包含重複的三元組。
例如, 給定陣列 nums = [-1, 0, 1, 2, -1, -4],
滿足要求的三元組集合為:
[
[-1, 0, 1],
[-1, -1, 2]
]
C++程式碼
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> vec;
sort(nums.begin(),nums.end());
for(int k=0;k<nums.size();k++)
{
if(k>0 && nums[k]==nums[k-1])
continue ;
int i,j;
for(i=k+1,j=nums.size()-1;i<j;)
{
if(i>k+1 && nums[i]==nums[i-1])
{
i++;
continue;
}
if(j<nums.size()-1 && nums[j]==nums[j+1 ])
{
j--;
continue;
}
int sum = nums[i]+nums[j]+nums[k];
if(sum==0)
{
vector<int> m_vec;
m_vec.push_back(nums[k]);
m_vec.push_back(nums[i]);
m_vec.push_back(nums[j]);
vec.push_back(m_vec);
j--;
i++;
}
else if(sum<0) i++;
else if(sum>0) j--;
}
}
return vec;
}
};
體會
此題直接窮舉一定爆時間
採用暴力+雙指標解法
首先對資料進行排序。
第一次遍歷確定一個數字n,則剩下兩個數字的和必須是-n,這樣才滿足條件。確定兩個指標i、j,從左至右,從右至左依次遍歷。
單個指標遍歷的過程中,如果重複直接跳過,防止結果中出現重複的數字。
如果sum為0時,得到答案,儲存,繼續遍歷。
如果sum小於0,則證明當前情況的左指標小了,左指標++。
如果sum大於0,則證明當前情況的右指標大了,右指標–。
此題嘗試了二重暴力+二分,時間沒問題,但是結果總出錯,不知道為什麼。
LeetCode73 矩陣置零
題目
給定一個 m x n 的矩陣,如果一個元素為 0,則將其所在行和列的所有元素都設為 0。請使用原地演算法。
樣例1:
輸入:
[
[1,1,1],
[1,0,1],
[1,1,1]
]
輸出:
[
[1,0,1],
[0,0,0],
[1,0,1]
]
樣例2:
輸入:
[
[0,1,2,0],
[3,4,5,2],
[1,3,1,5]
]
輸出:
[
[0,0,0,0],
[0,4,5,0],
[0,3,1,0]
]
C++程式碼
class Solution {
public:
void setZeroes(vector<vector<int>>& matrix) {
bool Row[matrix.size()]={false};
bool Col[matrix[0].size()]={false};
if (matrix.size()==1 && matrix[0].size()==1) return ;
for(int i=0;i<matrix.size();i++)
{
for(int j=0;j<matrix[0].size();j++)
{
if(matrix[i][j]==0){
Row[i]=true;
Col[j]=true;
}
}
}
for(int i=0;i<matrix.size();i++)
{
for(int j=0;j<matrix[0].size();j++)
{
if(Row[i]) matrix[i][j]=0;
else if(Col[j]) matrix[i][j]=0;
}
}
return ;
}
};
體會
這裡使用的演算法,空間複雜度O(m + n) ,並不是最優演算法。
最優演算法空間複雜度為常數級別
注意幾個細節就好
if (matrix.size()==1 && matrix[0].size()==1) return ;
這句話用來判斷[[1]]這樣的特殊情況
用於儲存0位的數字這樣開闢
bool Row[matrix.size()]={false};
bool Col[matrix[0].size()]={false};
不要用vec去儲存0,不然難以匹配座標,用兩個陣列一一對應就好。
常數級別空間複雜度的演算法並不複雜
就需要巧妙地利用原來矩陣的空間,這裡利用第一行和第一列儲存額外資訊:
1 先判斷號第一行和第一列是否需要全部置零
2 有任何一個該行或者該列的元素為零那麼這個第一行或者第一列的元素必然是零,就儲存這個零,最後用來判斷整個矩陣的這一行或者這一列是否需要置零。
3 最後再根據前面判斷,決定是否把這個第一行和第一列置零。
LeetCode49 字謎分組
題目
給定一個字串陣列,將字母異位詞組合在一起。字母異位詞指字母相同,但排列不同的字串。
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"],
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
說明:
所有輸入均為小寫字母。
不考慮答案輸出的順序。
C++程式碼
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string,vector<string>> str_map;
vector<vector<string>> res;
for(auto str : strs)
{
string tmp=str;
sort(tmp.begin(),tmp.end());
str_map[tmp].push_back(str);
}
for(auto val : str_map)
{
//sort(val.second.begin(),val.second.end());
res.push_back(val.second);
}
return res;
}
};
體會
題目邏輯很清晰
自己手動實現了一個演算法,但是最後一組資料沒有過啊!!!難受!!!等一下附上程式碼。
這個題目的思路很清晰,利用Map,將每次排序過的字串作為Key,將剩下的字串作為Val存放進去,最後整體存放在一個二維Vector裡面就可以了。
附一下差一組資料沒過的程式碼
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
vector<vector<string>> res;
vector<string> fir_str;
fir_str.push_back(strs[0]);
res.push_back(fir_str);
for(int i=1;i<strs.size();i++)
{
string tmp = strs[i];
sort(tmp.begin(),tmp.end());
bool flag=false;
for (int j = 0; j < res.size(); j++) {
string m_tmp = res[j][0];
sort(m_tmp.begin(), m_tmp.end());
if (tmp == m_tmp) {
res[j].push_back(strs[i]);
flag = true;
break;
}
}
if(!flag)
{
vector<string> new_str;
new_str.push_back(strs[i]);
res.push_back(new_str);
}
sort(res.begin(),res.end());
}
return res;
}
};
LeetCode75 顏色排序
題目
給定一個包含紅色、白色和藍色,一共 n 個元素的陣列,原地對它們進行排序,使得相同顏色的元素相鄰,並按照紅色、白色、藍色順序排列。
此題中,我們使用整數 0、 1 和 2 分別表示紅色、白色和藍色。
注意:
不能使用程式碼庫中的排序函式來解決這道題。
示例:
輸入: [2,0,2,1,1,0]
輸出: [0,0,1,1,2,2]
進階:
一個直觀的解決方案是使用計數排序的兩趟掃描演算法。
首先,迭代計算出0、1 和 2 元素的個數,然後按照0、1、2的排序,重寫當前陣列。
你能想出一個僅使用常數空間的一趟掃描演算法嗎?
C++程式碼
騷操作法:
void sortColors(vector<int>& nums) {
map<int,vector<int>> m;
for(int i=0;i<nums.size();i++)
{
int k=nums[i];
m[k].push_back(k);
}
nums.erase(nums.begin(),nums.end());
for(auto val : m)
{
for(int i=0;i<val.second.size();i++)
nums.push_back(val.second[i]);
}
}
快速排序:
class Solution {
public:
void qsort(vector<int>& nums,int l,int r)
{
if(l>r) return;
int mid = nums.size()/2;
int i=l;
int j=r;
int flag = nums[l];
while(i!=j)
{
while(i<j&&nums[j]>=flag)
j--;
while(i<j&&nums[i]<=flag)
i++;
int tmp;
tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
nums[l] =nums[i];
nums[i] = flag;
qsort(nums,i+1,r);
qsort(nums,l,i-1);
}
void sortColors(vector<int>& nums) {
qsort(nums,0,nums.size()-1);
}
};
體會
用map可以直接排序有點意思
熟練一下快排
LeetCode347 Top K Frequent Elements
題目
給定一個非空的整數陣列,返回其中出現頻率前 k 高的元素。
例如,
給定陣列 [1,1,1,2,2,3] , 和 k = 2,返回 [1,2]。
注意:
你可以假設給定的 k 總是合理的,1 ≤ k ≤ 陣列中不相同的元素的個數。
你的演算法的時間複雜度必須優於 O(n log n) , n 是陣列的大小。
C++程式碼
class Solution {
public:
vector<int> topKFrequent(vector<int>& nums, int k) {
map<int,int> m;
vector<int> res;
for(int i=0;i<nums.size();i++)
{
m[nums[i]] +=1;
}
vector<pair<int,int>> vtMap;
for(auto it=m.begin();it!=m.end();it++)
vtMap.push_back(make_pair(it->first,it->second));
sort(vtMap.begin(),vtMap.end(),cmp_by_val);
for(int i=0;i<k;i++)
res.push_back(vtMap[i].first);
return res;
}
static bool cmp_by_val(pair<int,int> &a,pair<int,int> &b)
{
return a.second>b.second;
}
};
體會
一道說難不難,說簡單不簡單的題。
熟練使用stl很重要。
這道題利用到了map通過val排序的方法,將map轉化成vector
LeetCode215 陣列中的第K個最大元素
題目
在未排序的陣列中找到第 k 個最大的元素。請注意,你需要找的是陣列排序後的第 k 個最大的元素,而不是第 k 個不同的元素。
示例 1:
輸入: [3,2,1,5,6,4] 和 k = 2
輸出: 5
示例 2:
輸入: [3,2,3,1,2,4,5,5,6] 和 k = 4
輸出: 4
說明:
你可以假設 k 總是有效的,且 1 ≤ k ≤ 陣列的長度。
C++程式碼
class Solution {
public:
int partition(vector<int>&nums, int low, int high)//找樞紐
{
int first = low;
int last = high;
int key = nums[first];//用字表的第一個記錄作為樞軸
while (first != last)
{
while (nums[last] >= key && first < last)
last--;
swap(nums[first], nums[last]);
while (nums[first] <= key && first < last)
first++;
swap(nums[first], nums[last]);
}
return first;//返回一個樞紐
}
int find_k(vector<int>& nums,int l,int r,int k)
{
int index=partition(nums,l,r);
int length = r-index+1;
if(length == k) //計算後面一段的長度,如果等於k表示找到了第k大
return nums[index];
else if(length > k)//如果後面的一段的長度大於k,證明第k大的數在後面一段
return find_k(nums,index+1,r,k);
else if(length <k)//如果後面的一段的長度小於k,證明第k大的數在前面一段
return find_k(nums,l,index-1,k-length);//k-length 去掉已經被劃分的數字
}
int findKthLargest(vector<int>& nums, int k) {
return find_k(nums,0,nums.size()-1,k);
}
};
體會
利用快排思想查詢第k大的數字,注意把patition和find_k分開寫,寫到一起容易出錯。
時間複雜度:
該演算法的平均時間複雜度為O(N)(詳細的推導過程看演算法導論9.2節),最壞情況為N^2,即每次劃分把陣列變為為(n-1) 和1的兩斷。
LeetCode425 電話號碼的字母組合
題目
給定一個僅包含數字 2-9 的字串,返回所有它能表示的字母組合。
給出數字到字母的對映如下(與電話按鍵相同)。注意 1 不對應任何字母。
大概是這樣的:
base[10]={“”,”“,”abc”,”def”,”ghi”,”jkl”,”mno”,”pqrs”,”tuv”,”wxyz”};
示例:
輸入:"23"
輸出:["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"].
說明:
儘管上面的答案是按字典序排列的,但是你可以任意選擇答案輸出的順序。
C++程式碼
class Solution {
public:
vector<string> letterCombinations(string digits) {
if(digits.length()<=0)
{
vector<string> v;
return v;
}//此處為題目要求
vector<string> res;
int index=0;
string str="";
letterCombinationsSearch(digits,str,index,res);
return res;
}
void letterCombinationsSearch(string digits,string str,int index, vector<string> &res)//注意啊,這裡一定是取地址!!!
{
if(index==digits.size())
{
res.push_back(str);//搜尋觸底 將當前的字串存入結果中
return;
}
string base[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
for(int i=0;i< base[digits[index]-'0'].size();i++)//自行領悟
{
str +=base[digits[index]-'0'][i];//將base陣列對應位置的每個字母都放進字串。
letterCombinationsSearch(digits,str,index+1,res);//遞迴,尋找下一個字母。
str.pop_back();//ad找完之後,彈出d,準備放入e
}
}
};
體會
整個問題就是一個dfs+回溯。
舉個例子,比如“234”,先一口氣找到adg,然後彈出g,找到adh,彈出h,找到adi,彈出i,彈出d,找到aeg,以此類推。
LeetCode46 全排列
題目
給定一個沒有重複數字的序列,返回其所有可能的全排列。
示例:
輸入: [1,2,3]
輸出:
[
[1,2,3],
[1,3,2],
[2,1,3],
[2,3,1],
[3,1,2],
[3,2,1]
]
C++程式碼
class Solution {
public:
void permute_search(vector<int>&nums,vector<bool>&used,vector<int>&tmp,vector<vector<int>>&res)
{
if(tmp.size()==nums.size())
{
res.push_back(tmp);
return;
}
for(int i=0;i<nums.size();i++)
{
if(!used[i])
{
tmp.push_back(nums[i]);//存入一個沒有用過的數字
used[i]=true;//用過的數字標記為true
permute_search(nums,used,tmp,res);
tmp.pop_back();//彈出剛剛用過的數字
used[i]=false;//將所對應的使用狀態改為false
}
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<vector<int>> res;
int index=-1;
vector<int> tmp;
vector<bool> used(nums.size(), false);
permute_search(nums,used,tmp,res);
return res;
}
};
體會
一道非常簡單dfs+回溯題目,建立一個used陣列用來儲存當前每個數字的使用狀態。核心部分都寫了註釋,這裡就不贅述了。
LeetCode78 子集
題目
給定一組不含重複元素的整數陣列 nums,返回該陣列所有可能的子集(冪集)。
說明:解集不能包含重複的子集。
示例:
輸入: nums = [1,2,3]
輸出:
[
[3],
[1],
[2],
[1,2,3],
[1,3],
[2,3],
[1,2],
[]
]
C++程式碼
class Solution {
public:
void subsets_search(vector<int>& nums,vector<int>&tmp,int index,vector<vector<int>> &res)
{
if(index==nums.size())
{
return ;
}
for(int i=index;i<nums.size();i++)
{
tmp.push_back(nums[i]);//將臨時陣列增加一個數字
res.push_back(tmp);//將臨時陣列放入答案陣列中
subsets_search(nums,tmp,i+1,res);//遞迴呼叫,注意這裡是i+1,不是index+1,因為使用index會出現1 3已被放入,index=2,遞迴index=3,將3也放入tmp,這樣會出現1 3 3 的情況。
tmp.pop_back();//將臨時陣列減少一個數字,用於回溯
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> tmp;//建立一個臨時陣列用於增加刪除數字
vector<vector<int>>res;//結果陣列
res.push_back(tmp);
int index=0;
subsets_search(nums,tmp,index,res);
return res;
}
};
體會
簡單回溯題,重點已經在註釋中寫的很清晰了。
這個題比較有意思的地方就在於每次都要把tmp都放入res中。
注意遞迴呼叫引數是i+1,不是index+1,因為使用index會出現1 3已被放入,index=2,遞迴index=3,將3也放入tmp,這樣會出現1 3 3 的情況。
LeetCode202 快樂數
題目
編寫一個演算法來判斷一個數是不是“快樂數”。
一個“快樂數”定義為:對於一個正整數,每一次將該數替換為它每個位置上的數字的平方和,然後重複這個過程直到這個數變為 1,也可能是無限迴圈但始終變不到 1。如果可以變為 1,那麼這個數就是快樂數。
示例:
輸入: 19
輸出: true
解釋:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1
C++程式碼
騷操作法:
class Solution {
public:
bool isHappy(int n) {
while(1)
{
int k=0;
while(n)
{
k+=(n%10)*(n%10);
n/=10;
}
if(k==1) return true;
if(k==4) return false;
n = k;
}
}
};
常規解法:
class Solution {
public:
bool isHappy(int n) {
vector<int> vec;
while(1)
{
int k=0;
while(n)
{
k+=(n%10)*(n%10);
n/=10;
}
if(k==1) return true;
for(int i=0;i<vec.size();i++)
{
if(k==vec[i]) return false;
}
vec.push_back(k);
n = k;
}
}
};
體會
有意思的一道題!
先說常規解法:
所有的不開心數最後都無法得到1,意味著他們一定會陷入一個有重複數字出現的迴圈,所以只要使用一個vector儲存一下所有出現的數字,有相同的就返回false。
再來一波騷操作:
不是快樂數的數稱為不快樂數(unhappy number),所有不快樂數的數位平方和計算,最後都會進入 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 的迴圈中。
所以只需要判斷一下4就可以了。