leetcode_OJ WC91 解題心得
- Contest time: Jul 1,2018
PA. 860. Lemonade Change 水題
- 思路:水題,用map儲存每一種硬幣與其對應的數量,注意十五元也能用三個五塊找錢即可
- Time complexity = O(N), Space complexity (with auxilary map structure) = O(N)
#define FORI(n) for(int i = 0; i < n; ++ i) class Solution { public: bool lemonadeChange(vector<int>& bl) { map<int ,int>mymap; FORI(bl.size()) { mymap[bl[i]]++; if(bl[i] == 10) { if(mymap[5] == 0) { return 0; } else { mymap[5]--; } } else { if(mymap[5] > 0 && mymap[10] > 0) { mymap[5]--; mymap[10]--; } else if(mymap[5] >=3 && mymap[10] == 0) { mymap[5] -= 3; } else if(mymap[5] == 0 || mymap[10] == 0) { return 0; } } } return 1; } };
PB. 863. All Nodes Distance K in Binary Tree 經典樹圖論題,DFS加上set, map 實作應用
- 找出一個二元樹中,從指定結點出發,給定一距離K,找出所有距離同為K的節點,相當重要又經典!
- Geekforgeeks ref
- 題意:找出一個二元樹中,從一個點出發,找出所有距離該點為K的節點(包含父節點也算)
- 思路:首先DFS用map儲存每一個child-parent node pair,接著從該點出發,走訪兩種CASE
- 左右子樹(每走過一個點就將K-1,代表一個距離的減少),直到K==0 代表指定的距離到了,便可以停下。
- 父節點,則是將child對應的perent以 parent_ptr = map[current_node]取出,再行走訪,因為方才走訪過的點會被放入set中,因此換到父結點的時候便不會又走回來現在的點,一舉兩得
- 走訪的同時也不忘了使用set 儲存visited path以防重複走訪,也就是標記過已經進行DFS過的節點。
- 分析: Time complexity = O(N), Space complexity = O(N) where N is # of nodes in that BT.
/** * Definition for a binary tree node. * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: map<TreeNode* , TreeNode* > child_par_pair; //[child parent] pair for accessing the relationship b/w the child and parent set<int> visited; vector<int> res; //store the result vector<int> distanceK(TreeNode* root, TreeNode* target, int dist_K) { if(root == NULL) //return nothing if the { return vector<int>(1,0); } dfs_parent(root); dfs_relation(target, dist_K); return res; } void dfs_parent(TreeNode* root) { if(root == NULL) { return; } if(root->left) { child_par_pair[root->left] = root; dfs_parent(root->left); } if(root->right) { child_par_pair[root->right] = root; dfs_parent(root->right); } } void dfs_relation(TreeNode* root, int dist_K) { if(root == NULL) { return; } for(set<int>::iterator it = visited.begin(); it != visited.end(); ++it) { if(*it == root->val)//the current-visiting node has been traversed before, so we just quit { return; } } visited.insert(root->val); //push the current visited node for dfs mark what node has been traversed to prevent duplicated traversing if(dist_K == 0) //dist_K reached { res.push_back(root->val); return; } if(root->left) //traverse left child` { dfs_relation(root->left, dist_K - 1); } if(root->right) //traverse left child` { dfs_relation(root->right, dist_K - 1); } TreeNode* parent_node_to_traverse = child_par_pair[root]; if(parent_node_to_traverse) { dfs_relation(parent_node_to_traverse, dist_K - 1); } } };
PC.861. Score After Flipping Matrix 貪心算法
- 同步刊載於leetcode discussion thread
- 題意:數個二進位數字以矩陣的row表示,每一次我們能toggle整個row或column使得整個row或column的全部元素XOR with 1,問最大的二進位數字是多少(亦即將每個row的二進位數字加總)
- 思路:基本的貪心演算法題(因為我們希望局部解愈大愈好,每一次都讓該col or row的組合情形能最大),因此有以下兩種情形
- 對於橫排由於 2^n > sum(2^n-1 + 2^n-2 + ....... + 1),因此改變最左邊的MSB會將剩餘的都設為一來的好,所以反轉使得MSB = 1是好的
- 對於直排亦即每一個位數(即column而言,把1變得比0多也是好事),因此對於col我們可以記錄該col有多少個零以及一,倘若零的個數大於一的個數,則反轉整個col使得一的個數比較多。
綜合以上兩點便能得到以下的code,其中迴圈跳出條件為,每一個二進位數字矩陣,每一個col的一的個數均大於零的個數(局部最佳解),每一個row的MSB都是1(源自於方才的數學計算式)
* 分析: Time complexity = O(row * col), Space complexity (with auxiliary vector of pair) = O(col)
/* Algorithm design: Greedy algorithm is used in this problem. Since 2^n > sum(2^n-1 + 2^n-2 + ....... + 1), so the msb(most significant bit) has more power than all the other. Steps: 1. We may toggle the whole column if cnt_0 is greater than cnt_1 in such row to make # of 1s more than 0s. 2. Than we toggle the whole row if the first 3. For loop termination, we check 2 flags First we check if all of the columns that 1s are greater than 0s. Than we check if all of the rows that the msb is all 1 to max the value. */ class Solution { public: int matrixScore(vector<vector<int>>& arr) { int flag_1 = 0, flag_2 = 0, cnt_0 = 0, cnt_1 = 0; vector<pair<int, int>>col_data_pair; while(1) { //data structure col_data_pair.clear(); col_data_pair.resize(arr[0].size()); flag_1 = 1; flag_2 = 1; //statistical data of 1 and 0 of each column for(int i = 0; i < arr[0].size(); i++) { cnt_0 = cnt_1 = 0; for(int j = 0; j < arr.size(); j++) { if(arr[j][i]) { cnt_1++; } else { cnt_0++; } } //check the first flag of 1s and 0s if(cnt_0 > cnt_1) { flag_1 = 0; } col_data_pair[i].first = cnt_0; col_data_pair[i].second = cnt_1; } //check the second flag of MSB for(int i = 0; i < arr.size(); i++) { if(arr[i][0] == 0) { flag_2 = 0; } } if(flag_1 == 1 && flag_2 == 1) { break; } //toggle the column if such column's 0 more than 1 for(int i = 0; i < col_data_pair.size(); i++) { if(col_data_pair[i].first > col_data_pair[i].second) { for(int j = 0; j < arr.size(); j++) { arr[j][i] ^= 1; } } } //toggle the row if such row's arr[0][col] = 0, if so, toggle the whole row for(int i = 0; i < arr.size(); i++) { if(arr[i][0] == 0) { for(int j = 0; j < arr[i].size(); j++) { arr[i][j] ^= 1; } } } } return binary_sum(arr); } int binary_sum(vector<vector<int>>& arr) { unsigned int sum = 0; for(int i = 0; i < arr.size(); i++) { string str_bin(""); for(int j = 0; j < arr[i].size(); j++) { str_bin += arr[i][j] + '0'; } sum += (unsigned int) stoi(str_bin, nullptr, 2); } return sum; } };
PD 862. Shortest Subarray with Sum at Least K deque應用
- TLE VERSION
- 思路:精簡之後的暴力解,雖然使用了滑動視窗,但是時間並沒有壓在O(N^2),基本上還是近乎查找每一個所有可能的subarray而已。
- 分析: Time complexity O(N^2) Space complexity O(1) (only some helper variables)
/* TLE in very large testcases(vector with length more than 30000), testcases 83/93 the algorithm is implemented in the sliding-window. need to optimize to O(N) */ class Solutio { public: int shortestSubarray(vector<int>& arr, int target_val) { if(arr.size() == 1) { return 1; } int start_idx = 0, end_idx = 1, cur_sum = 0; unsigned int min_len = 9999999; while(arr.size() > 1) { cur_sum = 0; for(int i = start_idx; i <= end_idx; i++) { cur_sum += arr[i]; } if(cur_sum < target_val) { if(end_idx != arr.size() - 1)//keep adding the elements, extending the right side of the window { end_idx++; } else if(end_idx == arr.size() - 1 && start_idx < end_idx) //shrink the subarray, try to toss away some negative values { if(cur_sum - arr[start_idx] >= target_val) //if successfully toss away enough negative value, then the min_len can be updated { min_len = min((unsigned int)end_idx - start_idx, min_len); } start_idx++; //shrink the left side of the window } } else //if the target value is reached, then we can try to shrink the left side of the window to try to make the window narrower { min_len = min((unsigned int)end_idx - start_idx + 1, min_len); if(start_idx < end_idx) //shrink the leftside { start_idx++; } else if(start_idx == end_idx) //moving forward to prevent locked situation { start_idx++; end_idx++; } if(cur_sum - arr[start_idx - 1] >= target_val) //update the min value if the shrinked sliding window still satisfies the criteria { min_len = min((unsigned int)end_idx - start_idx + 1, min_len); } } //I think the TLE mainly due to this part since my time complexity is not O(N) but O(N^2) if(start_idx == arr.size() - 1 && end_idx == arr.size() - 1 ) //if we reach the end of the vector, pop the last element, to do again for the smaller array. { arr.pop_back(); start_idx = 0; end_idx = 1; } } return (min_len == 9999999) ? -1 : min_len; //check the return answer } };
- AC VERSION
- 改進: 採用deque(和queue一樣FCFS的資料結構,但是可以頭尾都出去的雙隊列),採用prefix_sum來找到哪個區間的prefix_sum 是負的,那代表經過那段區間只會變小,可以捨棄他。
- 思路: 首先將各個array到該點的prefix_sum寫出來,其中prefix_sum[i] = sum(arr[0] + arr[1] + ... arr[i - 1]) (加總到前一項的和),接著,由於 prefix_sum[j] - prefix_sum[i] 代表從第i項到第j - 1項目的區間和,再以以下程式碼求解(具體更詳細的思路請見程式碼的註解)
- 分析: Time complexity: O(N) Space complexity O(N) (auxiliary deque)
- 備註:下面的printf可以自己放測資跑跑看,比較好理解流程唷 :D photo
class Solution { public: int shortestSubarray(vector<int>& arr, int target) { int prefix_sum[arr.size() + 1] = {0}; for(int i = 1; i <= arr.size(); i++) { prefix_sum[i] = prefix_sum[i - 1] + arr[i - 1]; } int ans = 0x7fffffff; deque<int> window_left; for(int end = 0; end <= arr.size(); end++) { printf("end now %d back ", end); while(!window_left.empty() && prefix_sum[end] - prefix_sum[window_left.back()] <= 0) { printf("%d ", window_left.back()); window_left.pop_back(); } printf(" front while loop 2 front "); while(!window_left.empty() && prefix_sum[end] - prefix_sum[window_left.front()] >= target) //區間和大於等於木雕哦要愈短愈好,所以從deque的front(比較早進來的<也就是比較左邊的,向右縮減,最後期望縮短到最小又能符合大於等於目標值(亦即,這個區間(prefix_sum[j] - prefix_sum[i] 代表從第i項到第j - 1項目的區間和)能符合 prefix_sum[end] - prefix_sum[window_left.front()] >= target)的長度,每次就向右邊縮短一格(見下方) { int beg = window_left.front(); printf("%d ", window_left.front()); window_left.pop_front();//目前這一格的長度找出後,向右邊縮短一格,接著重新跑這個while迴圈,看看能不能繼續符合他的目標數值要求,可以的話就在繼續縮短,不可以就跳離迴圈 ans = min(ans, end - beg); //找出比較短的長度 } printf("back \n"); window_left.push_back(end); } return ans == 0x7fffffff ? -1 : ans; } };