今日學習的視頻和文章
- 代碼隨想錄 四數相加
- 代碼隨想錄 贖金信
- 代碼隨想錄 三數之和
- 代碼隨想錄 四數之和II
LeetCode454 四數相加II
- 初見題目時的想法:由于只需要計算元組的個數,而不需要返回具體的元組,因此,只需要通過枚舉來求“和為零的四元組”的個數
n
。- 將四個整數數組分為兩組,用
nums1
和nums2
來求num[i] + nums[j]
的值及出現的次數,并保存在unordered_map
中。 - 然后再枚舉
nums3
和nums4
中的nums[k] + nums[l]
的和,如果其相反數為unordered_map
中保存的key
,則n += unordered_map[- nums[k] - nums[l]]
。 - 其實這里也有二分的思想,將四個數組平分成兩組,每組兩層循環,時間復雜度就是
O(n^2)
;如果兩組不平分,一組包含三個數組,另一組包含一個數組,那就會出現三層循環,時間復雜度就時O(n^3)
了。
- 將四個整數數組分為兩組,用
初見的題解代碼如下:
class Solution {
public:
int fourSumCount(vector<int>& nums1, vector<int>& nums2, vector<int>& nums3, vector<int>& nums4) {
int count = 0;
unordered_map<int, int> mapSum12;
for (auto& i : nums1) {
for (auto& j : nums2) {
mapSum12[i + j]++;
}
}
for (auto& k : nums3) {
for (auto& l : nums4) {
if (mapSum12.count(- k - l)) {
count += mapSum12[- k - l];
}
}
}
return count;
}
};
LeetCode383 贖金信
- 初見題目時的想法:
- 啪的就是一個
unordered_map
,很快啊。key
存字母,val
存出現次數,將magazine
中字母出現次數的信息存儲為一個unordered_map
。 - 然后遍歷
ransomNote
,每出現一個在unordered_map
中的字母,則該字母對應的val
值-1
,最后遍歷unordered_map
的val
值是否都大于等于零。
- 啪的就是一個
初見題目時的題解代碼
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
if (magazine.length() < ransomNote.length()) {
return false;
}
unordered_map<char, int> magazineMap;
for (auto& iter : magazine) {
magazineMap[iter]++;
}
for (auto& iter : ransomNote) {
if (--magazineMap[iter] < 0) {
return false;
}
}
return true;
}
};
- 看了講解之后:
- 還是不能太依賴
STL
- 遇到
key
值連續分布且范圍不是很大,且哈希函數非常簡單的情況,應該直接使用數組作為哈希表,這道題的key
在[a-z]
上連續分布,哈希函數為letter - 'a'
,也不用處理哈希碰撞。直接用數組作為哈希表,效率當然要比使用STL
高。
- 還是不能太依賴
使用數組作為哈希表的題解代碼如下:
class Solution {
public:
bool canConstruct(string ransomNote, string magazine) {
if (magazine.length() < ransomNote.length()) {
return false;
}
array<int, 26> magazineMap({ 0 });
for (auto& iter : magazine) {
magazineMap[iter - 'a']++;
}
for (auto& iter : ransomNote) {
if (--magazineMap[iter - 'a'] < 0) {
return false;
}
}
return true;
}
};
LeetCode15 三數之和
- 初見題目時的想法:先將數組非降序排序,用外層循環來枚舉
a
,內層循環來枚舉b
和c
。- 由于枚舉的數在數組中的位置應為
a < b < c
,且不能使用重復的元素,所以可以在內層循環中一起枚舉b
和c
。 - 在內層循環中,用
numSet
來保存可能的c
,當枚舉到可能的c
時,將當前的三元組以mutiset
的類型存入resSet
- 然后利用
set<mutiset<int>>
來進行結果的去重
- 由于枚舉的數在數組中的位置應為
初次嘗試的題解代碼,我沒有辦法自己完善去重的邏輯進行剪枝,嘗試使用STL實現,但最后還是沒能通過所有用例。邏輯是對的,但是方法確實不夠好,學了STL是為了使用更好的方法,把這段有一個測試用例超時的代碼貼上來提醒自己多學習多思考吧。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums[0] > 0) {
return res;
}
int n = nums.size();
set<multiset<int>> resSet;
for (int i = 0; i < n; i++) {
int a = nums[i];
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
unordered_set<int> numSet;
for (int j = i + 1; j < n; j++) {
int b = nums[j];
int c = 0 - nums[i] - nums[j];
if (numSet.find(c) != numSet.end()) {
resSet.insert({nums[i], nums[j], c});
numSet.erase(c);
}
else {
numSet.insert(nums[j]);
}
}
}
for (auto& iter : resSet) {
res.push_back({iter.begin(), iter.end()});
}
return res;
}
};
image.png
image.png
- 看了講解后的理解:
-
關鍵還是對于如何去重的理解
if (nums[i] == nums[i + 1]) continue; // 這會直接影響到結果集里一個元組中的元素,使得元組的中的元素不能重復 /* * 因為這樣剪枝的話,相當于當前枚舉到的元素和下一個未被枚舉的元素重復,因而跳過了這次的循環,這個邏輯和我們要的去重 * 是不一樣的 */
if (i > 0 && nums[i] == nums[i - 1]) continue; // 這樣才能正確讓結果集里的元組不重復 /* * 這樣剪枝的話,相當于上一個已經被枚舉了的元素的所在的元組已經插入了結果集,而當前枚舉的元素與上一個已經被枚舉的元素 * 相同,這就意味著接下來得到的元組是和上一個已經被枚舉了的元素所在的元組是一樣的,所以跳過本次循環可以對結果集中的元 * 組進行去重 */
-
這道題其實很考察編程的思維,對于待處理的數據的觀察、分析、思考,顯然我的能力還是不夠的。將數組元素排序后,就可以利用數組元素的非降序排列進行剪枝,如何利用數組元素的順序等特性進行剪枝,是我以后要多多學習思考的。
- 元素非降序排列,意味著
a <= b <= c
,那么a + b + c
可以有幾種情況呢?-
a > 0
,則由不等式的傳遞性c >= b >= a > 0
,顯然a + b + c > 0
,此時可以直接進行剪枝。 -
a + b + c > 0
時,應該如何調整其中的數讓a + b + c == 0
呢?縮小其中一個數,可以達到這個目的。 -
a + b + c < 0
時,應該放大其中一個數,讓a + b + c == 0
。
-
- 元素非降序排列,意味著
再結合代碼隨想錄的講解,思路是這樣的,外層循環枚舉
a
,然后內層循環用來枚舉b
和c
,我們規定a <= b <= c
,當a + b + c > 0
時,縮小c
;當a + b + c < 0
時,放大b
。這體現在代碼實現里就是雙指針朝彼此移動來枚舉三元組。
-
使用雙指針法的題解代碼如下:
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
if (nums[0] > 0) {
return res;
}
int n = nums.size();
for (int i = 0; i < n; i++) {
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
int left = i + 1;
int right = n - 1;
while (left < right) {
int sum = nums[i] + nums[left] + nums[right];
if (sum < 0) {
left++;
continue;
}
else if (sum > 0) {
right--;
continue;
}
else {
res.push_back({nums[i], nums[left], nums[right]});
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right - 1] == nums[right]) {
right--;
}
left++;
right--;
}
}
return res;
}
};
LeetCode18 四數之和
- 初見題目時的想法:
- 這道題可以轉化為已知的方法,即上一道題的雙指針法。
- 規定
a <= b <= c <= d
,第一層循環枚舉a
,第二層循環枚舉b
,然后第三層循環使用雙指針枚舉c
和d
。 - 不能沿用的剪枝方法:
-
a > target
,如果target
為負數,這種剪枝方法是不合邏輯的,負數相加越加越小。
-
初見題目時的題解
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n; i++) {
int a = nums[i];
if (i > 0 && nums[i] == nums[i - 1]){
continue;
}
for (int j = i + 1; j < n; j++) {
int b = nums[j];
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = n - 1;
while (left < right) {
int c = nums[left];
int d = nums[right];
long sum = (long)a + b + c + d;// 注意 int 相加可能會溢出
if (sum > target) {
right--;
continue;
}
else if (sum < target) {
left++;
continue;
}
else {
res.push_back({a, b, c, d});
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right - 1] == nums[right]) {
right--;
}
left++;
right--;
}
}
}
return res;
}
};
- 聽了講解之和添加的剪枝條件
- 根據
target
的正負來剪枝:target >= 0 && a > target
時直接break
。 - 還是只有
target
為非負時能進行剪枝,target >= 0 && a + b > target
- 根據
完整題解代碼
class Solution {
public:
vector<vector<int>> fourSum(vector<int>& nums, int target) {
vector<vector<int>> res;
sort(nums.begin(), nums.end());
int n = nums.size();
for (int i = 0; i < n; i++) {
int a = nums[i];
if (target >= 0 && a > target) {
break;
}
if (i > 0 && nums[i] == nums[i - 1]){
continue;
}
for (int j = i + 1; j < n; j++) {
int b = nums[j];
if (target >= 0 && a + b > target) {
break;
}
if (j > i + 1 && nums[j] == nums[j - 1]) {
continue;
}
int left = j + 1;
int right = n - 1;
while (left < right) {
int c = nums[left];
int d = nums[right];
long sum = (long)a + b + c + d;
if (sum > target) {
right--;
continue;
}
else if (sum < target) {
left++;
continue;
}
else {
res.push_back({a, b, c, d});
}
while (left < right && nums[left] == nums[left + 1]) {
left++;
}
while (left < right && nums[right - 1] == nums[right]) {
right--;
}
left++;
right--;
}
}
}
return res;
}
};