Leetcode刷題學習筆記 – 解題技巧
Leetcode刷題學習筆記 -- 解題技巧
從後面往前統計
1010. Pairs of Songs With Total Durations Divisible by 60(Medium)
給你一個vector 例如題目給你一個input的長度,可以從length = 1開始推到n 解題不外乎就是探訪所有的可能,從所有的可能中找出答案。雖然是一個時間複雜度很差的方法,但是通常可以從中得到靈感,繼續減少探訪的次數。 給你一個兩個string beginWord和endWord,還有一個string list,求出最少幾次變換,可以從beginWord變成endWord。 或是,有效率的列舉所有可能。怎麼列舉所有可能而不會錯過任何一種,也是一種藝術。 題目給你一個vector 驗證給你的string中所有可能的ip address。 可以反推從答案的範圍中,選一個值(通常使用binary search),才可以降低搜尋的次數(O(logN), N為答案的範圍)。 可以從例外開始思考,看看有沒有想法。 答案正確比浪費的空間還重要。當沒有想法的時候不然浪費一點空間換來答案的正確性。 在原本的vector上,如果遇到0,就在0的右邊補上一個0。 有時候我會拘泥於比較複雜的方法,導致不容易寫出正確的程式。仔細想想,就跟上面的心得一樣,有時候正確性會比酷炫的方法或是浪費space的方法還重要。甚至暴力破解也會比沒解法或是解法錯誤來的好。 重新排列list從原本的L0->L1->L2->L3 變成 L0->L3->L1->L2 有時候很難想出一個好的解法,其實可以想想反向答案。 找出最大的subarray(連續的元素)和。因為是circular所以可以接到前面,只要元素不重疊。 一開始看到了minimum operrations,就使用了DP結果TLE。看了hints,因為只能從最左和最右刪除,所以最後只會剩下subarray,所以問題就可變成找出最長的subarray,則sz - subarray.size() 就會是minimal operations。 下面的解法是用prefix sum + hash table。 以下是two points的解法,個人覺得應該比prefix sum還慢但是leetcode判斷出來是比較快。 給你一個vector,允許你修改一個數值,問這個vector是不是non-decreasing array(就是後面的數值只會大於等於前面的數值)。 有時候題目要的答案不容易拼湊到,導致寫的程式碼過於奇怪,因為要一次就得到答案。其實只要有辦法拼湊回來,應該是先照程式方便寫的方向。 當沒有頭緒的時候,可以列出所有可能的情況。幫助整理思緒。例如:838. Push Dominoes,列出所有可能的骨牌排列: 所以程式碼如下:int numPairsDivisibleBy60(vector<int>& time) {
int rtn{0};
vector<int> cnt(60, 0);
for(auto& t : time) {
int tt = t % 60;
if(tt == 0) rtn += cnt[0];
else rtn += cnt[60 - t % 60];
cnt[tt]++;
}
return rtn;
}
從簡單的case開始延伸
22. Generate ParentheseMed
79. Word Search
探訪所有的可能(暴力破解/Burte force)
127. Word Ladder(Hard)
int ladderLength(string beginWord, string endWord, vector<string>& wordList) {
unordered_set<string> s(wordList.begin(), wordList.end());
if(!s.count(endWord)) return 0;
queue<string> q({beginWord});
int rtn = 0;
while(!q.empty()) {
for(int k = q.size(); k > 0; --k) {
string str = q.front(); q.pop();
if(str == endWord) return rtn + 1;
// 暴力搜尋,但是用set來快速檢查是不是在list裡面
for(int i = 0; i < str.length(); ++i) {
string newWord = str;
for(char c = 'a'; c <= 'z'; ++c) {
newWord[i] = c;
if(s.count(newWord) && newWord != str) {
q.push(newWord);
s.erase(newWord);
}
}
}
}
rtn++;
}
return 0;
}
1675. Minimize Deviation in Array(Hard)
int minimumDeviation(vector<int>& nums) {
set<int> s;
for(auto& n : nums)
s.insert(n & 1 ? n * 2 : n); // 所有最大的值集合(全部都是even)
int ans = *s.rbegin() - *s.begin(); // deviation的定義最大和最小的差
while((*s.rbegin() & 1) == 0) { // 只有even才可以變小
s.insert(*s.rbegin() / 2); // 放回變小的值,等於insert其他可能
s.erase(*s.rbegin()); // 因為s.rbegin()已經比過了,所以刪除
ans = min(ans, *s.rbegin() - *s.begin());
}
return ans;
}
93. Restore IP Addresses
class Solution {
bool check(string& s) {
if(s.size() == 1) return true;
else if(s.front() == '0') return false;
else if(stoi(s) > 255) return false;
return true;
}
public:
vector<string> restoreIpAddresses(string s) {
vector<string> rtn;
// 探訪所有可能的長度組合
for(int a = 1; a <= 3; ++a)
for(int b = 1; b <= 3; ++b)
for(int c = 1; c <= 3; ++c)
for(int d = 1; d <= 3; ++d) {
// 四個欄位的長度和必需為s.size()
if(a + b + c + d == s.size()) {
string A = s.substr(0, a);
string B = s.substr(a, b);
string C = s.substr(a + b, c);
string D = s.substr(a + b + c, d);
// 四個欄位比須通過檢查
if(!check(A) || !check(B) || !check(C) || !check(D)) continue;
rtn.push_back(A + "." + B + "." + C + "." + D);
}
}
return rtn;
}
};
從答案的範圍中,假設答案,驗證答案的正確性。
Leetcode刷題學習筆記–Binary Searchint left = ANS_MIN, right = ANS_MAX;
while(left < right) {
int mid = left + (right - left) / 2;
// 計算由mid得到的答案是否正確
// 由題目設計下一個left和right的位置
}
從例外開始思考
例如 556. Next Greater Element III,從完全沒有答案的例子開始,什麼樣的情況是沒有答案?回推怎樣的情況才會開始有答案。犧牲space換來答案的正確
1089. Duplicate Zeros
例如:[1,0,2,3,0,4,5,0] -> [1,0,0,2,3,0,0,4]// 比起浪費的空間,答案正確比較重要
// 使用比較大的空間來儲存答案
// 可以套過pointer的方法來不使用到額外的空間
void duplicateZeros(vector<int>& arr) {
int sz = arr.size();
int i = sz - 1, j = i + count(arr.begin(), arr.end(), 0);
for(;i >= 0; --i) {
if(arr[i] == 0) {
if(j < sz)
arr[j] = arr[i];
j--;
}
if(j < sz)
arr[j] = arr[i];
j--;
}
}
方法簡單不會出錯即可
143. Reorder List
void reorderList(ListNode* head) {
ListNode *p = head, *np;
stack<ListNode *> st;
while(p) {
st.push(p);
p = p->next;
}
p = head;
for(int i = st.size() / 2; i > 0; --i) {
np = p->next;
p->next = st.top(); st.pop();
p->next->next = np;
p = np;
}
p->next = nullptr;
}
想想答案的反向
918. Maximum Sum Circular Subarray

int maxSubarraySumCircular(vector<int>& nums) {
int total, maxsum , curmax, minsum, curmin;
total = maxsum = curmax = minsum = curmin = nums[0];
for(int i = 1; i < nums.size(); ++i) {
curmax = max(curmax + nums[i], nums[i]);
maxsum = max(maxsum, curmax);
curmin = min(curmin + nums[i], nums[i]);
minsum = min(minsum, curmin);
total += nums[i];
}
// 因為如果maxsum為負數,minsum會更負。導致total - minsum會更大
return maxsum > 0 ? max(maxsum, total - minsum) : maxsum;
}
1658. Minimum Operations to Reduce X to Zero
time complexity : $O(N)$
space complexity : $O(N)$int minOperations(vector<int>& nums, int x) {
int sz = nums.size();
int total = accumulate(nums.begin(), nums.end(), 0);
if(x > total) return -1;
if(x == total) return sz;
int target = total - x;
unordered_map<int, int> m;
m[0] = -1;
int sum = 0, maxlen = 0;
for(int i = 0; i < sz; ++i) {
sum += nums[i];
if(sum >= target && m.count(sum - target))
maxlen = max(maxlen, i - m[sum - target]);
m[sum] = i;
}
return maxlen > 0 ? sz - maxlen : -1;
}
int minOperations(vector<int>& nums, int x) {
int sz = nums.size();
int total = accumulate(nums.begin(), nums.end(), 0);
if(x > total) return -1;
if(x == total) return sz;
int target = total - x, left = 0, sum = 0, maxlen = 0;
for(int right = 0; right < sz; ++right) {
sum += nums[right];
while(sum > target)
sum -= nums[left++];
if(sum == target)
maxlen = max(maxlen, right - left + 1);
}
return maxlen > 0 ? sz - maxlen : -1;
}
從沒有例外,推到題目的情況
665. Non-decreasing Array
int cnt = 0;
for(int i = 1; i < nums.size(); ++i) {
if(nums[i] < nums[i - 1]) {
if(cnt) return false;
cnt++;
}
}

bool checkPossibility(vector<int>& nums) {
int cnt = 0;
for(int i = 1; i < nums.size(); ++i) {
if(nums[i] < nums[i - 1]) {
if(cnt) return false;
if(i == 1 || nums[i - 2] <= nums[i]) nums[i - 1] = nums[i];
else nums[i] = nums[i - 1];
cnt++;
}
}
// time : O(N)
// space : O(1)
return true;
}
拼湊題目要的答案
2096. Step-By-Step Directions From a Binary Tree Node to Another
bool find(TreeNode *root, int val, string& path) {
if(!root) return false;
if(root->val == val) return true;
bool rtn = false;
if(find(root->left, val, path)) {
path.push_back('L');
rtn = true;
}
if(find(root->right, val, path)) {
path.push_back('R');
rtn = true;
}
return rtn;
}
string getDirections(TreeNode* root, int startValue, int destValue) {
string sp, dp;
find(root, startValue, sp);
find(root, destValue, dp);
// remove the common part before LCA to root
while(!sp.empty() && !dp.empty() && sp.back() == dp.back()) {
sp.pop_back();
dp.pop_back();
}
return string(sp.size(), 'U') + string(dp.rbegin(), dp.rend());
}
列出所有的case
string pushDominoes(string dominoes) {
string rtn{""};
string d = 'L' + dominoes + 'R';
for(int i = 0, j = 1; j < d.size(); ++j) {
if(d[j] == '.') continue;
if(i > 0) rtn += d[i];
int len = j - i - 1;
// case 1
if(d[i] == d[j]) rtn += string(len, d[i]);
// case 2
else if(d[i] == 'L' && d[j] == 'R') rtn += string(len, '.');
// case 3
else rtn += string(len / 2, 'R') + string(len % 2, '.') + string(len / 2, 'L');
i = j;
}
return oss.str();
}