├── .editorconfig ├── .gitattributes ├── Chapter04 ├── 04_01_Insertion_Sort.cpp ├── 04_02_Shell_Sort.cpp ├── 04_03_Selection_Sort.cpp ├── 04_04_Bubble_Sort.cpp ├── 04_05_Shaker_Sort.cpp ├── 04_06_Merge_Sort.cpp ├── 04_07_merge.cpp ├── 04_08_Heap_Sort.cpp ├── 04_09_Quick_Sort.cpp ├── 04_10_STL.cpp ├── 04_11_STL_Customization.cpp ├── 04_12_hIndex.cpp └── 04_13_物品堆疊.cpp ├── Chapter05 ├── 05_01_Sequential_Search.cpp ├── 05_02_Binary_Search.cpp ├── 05_03_Square_Root.cpp ├── 05_04_Interpolation_Search.cpp ├── 05_05_Gold_Search.cpp ├── 05_06_Fibonacci_Search.cpp ├── 05_07_STL.cpp ├── 05_08_peakIndexInMountainArray_Binary_Search.cpp ├── 05_08_peakIndexInMountainArray_Gold_Search.cpp ├── 05_08_peakIndexInMountainArray_Sequential_Search.cpp ├── 05_09_searchInsert_Loop.cpp ├── 05_09_searchInsert_Recursion.cpp ├── 05_10_firstBadVersion.cpp ├── 05_11_arrangeCoins.cpp ├── 05_12_基地台.cpp └── 05_13_圓環出口.cpp ├── Chapter06 ├── 06_01_Fibo_DC.cpp ├── 06_01_Fibo_DP.cpp ├── 06_02_Hanoi_Tower.cpp ├── 06_03_maxSubArray_Brute_Force.cpp ├── 06_03_maxSubArray_Brute_Force_Optimization.cpp ├── 06_03_maxSubArray_DC.cpp ├── 06_04_Selection.cpp ├── 06_05_majorityElement.cpp ├── 06_06_searchMatrix.cpp ├── 06_07_支點切割.cpp └── 06_08_反序數量.cpp ├── Chapter07 ├── 07_01_lemonadeChange.cpp ├── 07_02_coinChange.cpp ├── 07_03_中途休息.cpp ├── 07_04_eraseOverlapIntervals.cpp ├── 07_05_maximumUnits.cpp ├── 07_06_leastInterval.cpp ├── 07_07_minCostToMoveChips.cpp ├── 07_08_balancedStringSplit.cpp ├── 07_09_carPooling.cpp ├── 07_10_線段覆蓋長度.cpp └── 07_11_砍樹.cpp ├── Chapter08 ├── 08_01_Triple_Fibo.cpp ├── 08_02_minCostClimbingStairs.cpp ├── 08_02_minCostClimbingStairs_Optimization.cpp ├── 08_03_coinChange.cpp ├── 08_04_maxSubArray.cpp ├── 08_05_jobScheduling.cpp ├── 08_06_Cut_Rod_DC.cpp ├── 08_06_Cut_Rod_DP.cpp ├── 08_07_integerBreak.cpp ├── 08_08_01背包問題.cpp ├── 08_09_change_1D.cpp ├── 08_09_change_2D.cpp ├── 08_10_矩陣鏈乘.cpp ├── 08_11_LIS.cpp ├── 08_12_lengthOfLIS.cpp ├── 08_13_LCS.cpp ├── 08_13_LCS_輸出.cpp ├── 08_14_minDistance.cpp ├── 08_15_置物櫃出租.cpp ├── 08_16_勇者修煉.cpp └── 08_17_飛黃騰達.cpp ├── Chapter09 ├── 09_01_findJudge.cpp ├── 09_02_findCenter.cpp ├── 09_03_鄰接矩陣.cpp ├── 09_04_鄰接列表.cpp ├── 09_05_cloneGraph.cpp ├── 09_06_canFinish.cpp ├── 09_07_findOrder.cpp ├── 09_08_maximalNetworkRank.cpp └── 09_09_visit.cpp ├── Chapter10 ├── 10_01_BFS.cpp ├── 10_02_Connected_Component.cpp ├── 10_03_floodFill.cpp ├── 10_04_numIslands.cpp ├── 10_05_permute.cpp ├── 10_06_Shortest_Path.cpp ├── 10_07_shortestPathBinaryMatrix.cpp ├── 10_08_sumOfLeftLeaves.cpp ├── 10_09_numEnclaves.cpp └── 10_10_闖關遊戲.cpp ├── Chapter11 ├── 11_01_DFS.cpp ├── 11_02_canFinish.cpp ├── 11_03_isBipartite.cpp ├── 11_04_findOrder.cpp ├── 11_05_SCC.cpp ├── 11_06_NQueen.cpp ├── 11_07_KQueens.cpp ├── 11_08_maxAreaOfIsland.cpp ├── 11_09_countServers.cpp ├── 11_10_小群體.cpp ├── 11_11_血緣關係.cpp └── 11_12_樹狀圖分析.cpp ├── Chapter12 ├── 12_01_Kruskal.cpp ├── 12_02_minCostConnectPoints_Kruskal.cpp ├── 12_03_Prim.cpp └── 12_04_minCostConnectPoints_Prim.cpp ├── Chapter13 ├── 13_01_Edmonds_Karp.cpp └── 13_02_質數伴侶.cpp ├── Chapter14 ├── 14_01_Bellman_Ford.cpp ├── 14_02_SPFA.cpp ├── 14_03_findCheapestPrice_Bellman_Ford.cpp ├── 14_03_findCheapestPrice_SPFA.cpp ├── 14_04_DAG.cpp ├── 14_05_Dijkstra.cpp ├── 14_06_networkDelayTime.cpp ├── 14_07_maxProbability.cpp ├── 14_08_Floyd_Warshall.cpp └── 14_09_findTheCity.cpp └── 勘誤表 /.editorconfig: -------------------------------------------------------------------------------- 1 | # top-most EditorConfig file 2 | root = true 3 | 4 | [*] 5 | indent_style = tab 6 | indent_size = 4 7 | trim_trailing_whitespace = true -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /Chapter04/04_01_Insertion_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Insertion_Sort(int data[], int len){ 6 | for (int i = 1; i < len; i++) { 7 | // i is the index of X 8 | int value = data[i]; 9 | // j is the index of Y 10 | int j = i - 1; 11 | // shift elements > X to right 12 | while(value < data[j] && j >= 0) { 13 | data[j+1] = data[j]; 14 | j--; 15 | } 16 | // insert X to its position 17 | data[j+1] = value; 18 | } 19 | } 20 | 21 | int main(){ 22 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 23 | cout << "Origin data:"; 24 | for(int i = 0; i < 10; i++) 25 | cout << arr[i] << " "; 26 | cout << endl; 27 | 28 | Insertion_Sort(arr,10); 29 | 30 | cout << "Sorted data:"; 31 | for(int i = 0; i < 10; i++) 32 | cout << arr[i] << " "; 33 | cout << endl; 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /Chapter04/04_02_Shell_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Shell_Sort(int data[], int len) 6 | { 7 | // gap 從 len / 2 開始 8 | for (int gap = len/2; gap > 0; gap /= 2) 9 | { 10 | // 對各組組內進行插入排序法 11 | for (int i = gap; i < len; i += 1) 12 | { 13 | int value = data[i]; 14 | int j; 15 | for (j = i; 16 | j >= gap && data[j - gap] > value; 17 | j -= gap) 18 | data[j] = data[j - gap]; 19 | 20 | data[j] = value; 21 | } 22 | } 23 | } 24 | 25 | int main(){ 26 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 27 | cout << "Origin data:"; 28 | for(int i = 0; i < 10; i++) 29 | cout << arr[i] << " "; 30 | cout << endl; 31 | 32 | Shell_Sort(arr,10); 33 | 34 | cout << "Sorted data:"; 35 | for(int i = 0; i < 10; i++) 36 | cout << arr[i] << " "; 37 | cout << endl; 38 | 39 | return 0; 40 | } 41 | -------------------------------------------------------------------------------- /Chapter04/04_03_Selection_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Selection_Sort(int data[], int len) 6 | { 7 | for (int i = 0; i < len - 1; i++) { 8 | // 找到 data[i] ~ data[len-1] 中的最小值 9 | int minimal_index = i; 10 | for (int j = i + 1; j < len; j++) { 11 | if (data[j] < data[minimal_index]) { 12 | minimal_index = j; 13 | } 14 | } 15 | // 把最小值跟 data[i] 互換 16 | int temp = data[minimal_index]; 17 | data[minimal_index] = data[i]; 18 | data[i] = temp; 19 | } 20 | } 21 | 22 | int main(){ 23 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 24 | cout << "Origin data:"; 25 | for(int i = 0; i < 10; i++) 26 | cout << arr[i] << " "; 27 | cout << endl; 28 | 29 | Selection_Sort(arr,10); 30 | 31 | cout << "Sorted data:"; 32 | for(int i = 0; i < 10; i++) 33 | cout << arr[i] << " "; 34 | cout << endl; 35 | 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /Chapter04/04_04_Bubble_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Bubble_Sort(int data[], int len){ 6 | // 重複 len - 1 輪 7 | for(int i = 0; i < len - 1; i++){ 8 | // 每多做一輪就可以少做最後一筆資料 9 | // 因為每一輪中剩下的最大值都會被移到最右邊 10 | for(int j = 0; j < len - i - 1; j++){ 11 | // 如果左邊 data[j] 大於右邊 data[j+1] 就互換 12 | if(data[j] > data[j+1]){ 13 | int tmp = data[j]; 14 | data[j] = data[j + 1]; 15 | data[j + 1] = tmp; 16 | } 17 | } 18 | } 19 | } 20 | 21 | int main(){ 22 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 23 | cout << "Origin data:"; 24 | for(int i = 0; i < 10; i++) 25 | cout << arr[i] << " "; 26 | cout << endl; 27 | 28 | Bubble_Sort(arr,10); 29 | 30 | cout << "Sorted data:"; 31 | for(int i = 0; i < 10; i++) 32 | cout << arr[i] << " "; 33 | cout << endl; 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /Chapter04/04_05_Shaker_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Shaker_Sort(int data[], int len){ 6 | // left, right 紀錄左右兩端的位置 7 | int left = 0, right = len - 1, shift = 1; 8 | 9 | while(left < right) { 10 | // 對 left 到 right 的範圍從左到右進行冒泡排序法 11 | for(int i = left; i < right; i++) { 12 | // 左邊比右邊大,互換 13 | if(data[i] > data[i + 1]) { 14 | int tmp = data[i]; 15 | data[i] = data[i + 1]; 16 | data[i + 1] = tmp; 17 | // 記錄下最後互換的位置 18 | shift = i; 19 | } 20 | } 21 | right = shift; 22 | // 對 left 到 right 的範圍從右到左進行冒泡排序法 23 | for(int i = right - 1; i >= left; i--) { 24 | if(data[i] > data[i+1]) { 25 | int tmp = data[i]; 26 | data[i] = data[i + 1]; 27 | data[i + 1] = tmp; 28 | // 記錄下最後互換的位置 29 | shift = i + 1; 30 | } 31 | } 32 | left = shift; 33 | } 34 | } 35 | 36 | int main(){ 37 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 38 | cout << "Origin data:"; 39 | for(int i = 0; i < 10; i++) 40 | cout << arr[i] << " "; 41 | cout << endl; 42 | 43 | Shaker_Sort(arr,10); 44 | 45 | cout << "Sorted data:"; 46 | for(int i = 0; i < 10; i++) 47 | cout << arr[i] << " "; 48 | cout << endl; 49 | 50 | return 0; 51 | } -------------------------------------------------------------------------------- /Chapter04/04_06_Merge_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | void Merge(int data[], int start, int finish, int middle){ 7 | int size_left = middle - start + 1; 8 | int size_right = finish - middle; 9 | // 把資料分成左右半部 10 | int* left = (int *) malloc(sizeof(int) * (size_left + 1)); 11 | int* right = (int *) malloc(sizeof(int) * (size_right + 1)); 12 | 13 | // 把資料搬遷到暫存用的空間 14 | memcpy(left, data + start, sizeof(int) * (size_left)); 15 | memcpy(right, data + middle + 1, sizeof(int) * (size_right)); 16 | 17 | // 在左右兩邊的最末端新增無限大 18 | // 用整數的最大值代替無限大 19 | left[size_left] = 2147483647; 20 | right[size_right] = 2147483647; 21 | 22 | int left_index = 0, right_index = 0; 23 | 24 | // 把 left 與 right 兩陣列融合在一起 25 | for(int i = start; i <= finish; i++){ 26 | if(left[left_index] <= right[right_index]){ 27 | data[i] = left[left_index]; 28 | left_index++; 29 | } 30 | else{ 31 | data[i] = right[right_index]; 32 | right_index++; 33 | } 34 | } 35 | free(left); 36 | free(right); 37 | } 38 | 39 | void Merge_Sort(int data[], int start, int finish){ 40 | if(finish > start){ 41 | int middle = (finish+start) / 2; 42 | Merge_Sort(data, start, middle); 43 | Merge_Sort(data, middle + 1, finish); 44 | Merge(data, start, finish, middle); 45 | } 46 | } 47 | 48 | int main(){ 49 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 50 | cout << "Origin data:"; 51 | for(int i = 0; i < 10; i++) 52 | cout << arr[i] << " "; 53 | cout << endl; 54 | 55 | Merge_Sort(arr, 0, 9); 56 | 57 | cout << "Sorted data:"; 58 | for(int i = 0; i < 10; i++) 59 | cout << arr[i] << " "; 60 | cout << endl; 61 | 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /Chapter04/04_07_merge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | A. 題目 4 | 5 | 給定兩個已排序成升冪的整數陣列 nums1 與 nums2,且其長度分別為 m 與 n,請把這兩個陣列合併至 nums1,合併後須讓 nums1 內的資料亦成升冪排列。 6 | 7 | 為了順利合併,一開始 nums1 向量的長度即為 m+n,其中前 m 筆資料為 nums1 的資料,後面 n 筆資料被設定成 0 可以忽略。 8 | 9 | B. 出處 10 | https://leetcode.com/problems/merge-sorted-array/ 11 | 12 | */ 13 | 14 | class Solution { 15 | public: 16 | void merge(vector& nums1, int m, 17 | vector& nums2, int n) 18 | { 19 | vector data_1 = nums1; 20 | // i, j代表 nums1, nums2 各處理到第幾個元素 21 | int i = 0, j = 0; 22 | // k 代表合併到第幾個元素 23 | int k = 0; 24 | while(i < m && j < n){ 25 | // nums2[j] 的資料較小,把 nums2[j] 放入 nums1 26 | if(data_1[i] > nums2[j]) { 27 | nums1[k] = nums2[j]; 28 | j++; 29 | } 30 | // data_1[i] 的資料較小,把 data_1[i] 放入 nums1 31 | else{ 32 | nums1[k] = data_1[i]; 33 | i++; 34 | } 35 | k++; 36 | } 37 | // 如果 data_1 還剩資料 38 | while(i < m){ 39 | nums1[k] = data_1[i]; 40 | i++; 41 | k++; 42 | } 43 | // 如果 nums2 還剩資料 44 | while(j < n){ 45 | nums1[k] = nums2[j]; 46 | j++; 47 | k++; 48 | } 49 | } 50 | }; 51 | -------------------------------------------------------------------------------- /Chapter04/04_08_Heap_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | void Max_Heapify(int *data, int root, int len){ 6 | int left = 2 * root + 1; 7 | int right = 2 * root + 2; 8 | 9 | int largest = root; 10 | // 左節點為最大值 11 | if(left < len && data[left] > data[largest]){ 12 | largest = left; 13 | } 14 | // 右節點為最大值 15 | if(right < len && data[right] > data[largest]){ 16 | largest = right; 17 | } 18 | // 把最大值與根節點做互換 19 | if(largest != root){ 20 | // Swap data[root] and data[largest] 21 | int tmp = data[root]; 22 | data[root] = data[largest]; 23 | data[largest] = tmp; 24 | Max_Heapify(data, largest, len); 25 | } 26 | } 27 | 28 | void Min_Heapify(int *data, int root, int len){ 29 | int left = 2 * root + 1; 30 | int right = 2 * root + 2; 31 | 32 | int smallest = root; 33 | 34 | if(left < len && data[left] < data[smallest]){ 35 | smallest = left; 36 | } 37 | 38 | if(right < len && data[right] < data[smallest]){ 39 | smallest = right; 40 | } 41 | 42 | if(smallest != root){ 43 | // Swap data[root] and data[smallest] 44 | int tmp = data[root]; 45 | data[root] = data[smallest]; 46 | data[smallest] = tmp; 47 | Min_Heapify(data, smallest, len); 48 | } 49 | } 50 | 51 | void Build_Max_Heap(int *data, int len){ 52 | for(int i = len / 2 - 1; i >= 0; i--){ 53 | Max_Heapify(data, i, len); 54 | } 55 | } 56 | 57 | void Build_Min_Heap(int *data, int len){ 58 | for(int i = len / 2 - 1; i >= 0; i--){ 59 | Min_Heapify(data, i, len); 60 | } 61 | } 62 | 63 | void Print_Array(int *data, int len){ 64 | cout << "Array: "; 65 | for(int i=0;i0;i--){ 76 | // Step 2: Swap data[i] and data[0] 77 | int tmp = data[0]; 78 | data[0] = data[i]; 79 | data[i] = tmp; 80 | // Step 3: Max heapify again 81 | Max_Heapify(data, 0, i); 82 | } 83 | } 84 | 85 | int main(){ 86 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 87 | cout << "Origin data:"; 88 | for(int i = 0; i < 10; i++) 89 | cout << arr[i] << " "; 90 | cout << endl; 91 | 92 | Heap_Sort(arr,10); 93 | 94 | cout << "Sorted data:"; 95 | for(int i = 0; i < 10; i++) 96 | cout << arr[i] << " "; 97 | cout << endl; 98 | 99 | return 0; 100 | } -------------------------------------------------------------------------------- /Chapter04/04_09_Quick_Sort.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int Partition(int data[], int start, int finish){ 6 | // 選擇區間最右邊的值做為 Pivot 7 | int pivot = data[finish]; 8 | // 把比 Pivot 小的值依序移動到左側 9 | int p = start; 10 | for(int i = start; i < finish; i++){ 11 | if(data[i] < pivot){ 12 | int tmp = data[i]; 13 | data[i] = data[p]; 14 | data[p] = tmp; 15 | p++; 16 | } 17 | } 18 | int tmp = data[finish]; 19 | data[finish] = data[p]; 20 | data[p] = tmp; 21 | // 最後回傳 Pivot 所在的 index 22 | return p; 23 | } 24 | 25 | 26 | void Quick_Sort(int data[], int start, int finish){ 27 | if (start < finish) { 28 | // 取出 Pivot 後,會回傳 Pivot 最後所在的 index 29 | int pivot = Partition(data, start, finish); 30 | // 對 Pivot 的兩端再進行下一輪的 Quick Sort 31 | Quick_Sort(data, start, pivot - 1); 32 | Quick_Sort(data, pivot + 1, finish); 33 | } 34 | } 35 | 36 | int main(){ 37 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 38 | cout << "Origin data:"; 39 | for(int i = 0; i < 10; i++) 40 | cout << arr[i] << " "; 41 | cout << endl; 42 | 43 | Quick_Sort(arr, 0, 9); 44 | 45 | cout << "Sorted data:"; 46 | for(int i = 0; i < 10; i++) 47 | cout << arr[i] << " "; 48 | cout << endl; 49 | 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /Chapter04/04_10_STL.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for sort 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | vector data = {8,-5,-1,4,-3,6,2,-2,3,4}; 10 | 11 | // 預設由小到大排 12 | sort (data.begin(), data.end()); 13 | 14 | // 印出排序後的資料 15 | for (int i=0 ; i < data.size(); i++){ 16 | cout << data[i] << " "; 17 | } 18 | 19 | return 0; 20 | } 21 | -------------------------------------------------------------------------------- /Chapter04/04_11_STL_Customization.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for sort 4 | using namespace std; 5 | 6 | // 自訂的排序規則的函式 7 | bool compare(int a, int b){ 8 | return a > b; 9 | } 10 | 11 | int main() 12 | { 13 | vector data = {8,-5,-1,4,-3,6,2,-2,3,4}; 14 | 15 | // 傳入自訂的排序規則 16 | sort (data.begin(), data.end(), compare); 17 | 18 | // 印出排序後的資料 19 | for (int i = 0; i < data.size(); i++){ 20 | cout << data[i] << " "; 21 | } 22 | return 0; 23 | } 24 | -------------------------------------------------------------------------------- /Chapter04/04_12_hIndex.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #274. H 指數 H-Index 4 | 5 | A. 題目 6 | 給定一個整數陣列 citations,其中 citations[i] 代表某個研究者的第 i 篇公開論文被引用的總次數,請回傳該研究者的 H 指數。 7 | 根據維基百科,H 指數的定義如下:如果有一個研究者,他的所有 n 篇公開論文中至少有 h 篇個別至少被引用 h 次,且另外 n-h 篇被引用次數都不達 h 次,那這個研究者的 H 指數就是 h。 8 | 如果有數個值符合 h 值的定義,H 指數是其中最大的值。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/h-index/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | int hIndex(vector& citations){ 18 | // 把 citations 由大到小排 19 | sort (citations.begin(), citations.end(), greater()); 20 | 21 | // 從第一篇,最多 citation 的論文開始檢查 22 | for (int i = 0; i < citations.size(); i++){ 23 | // i+1 => 高於目前引用數的論文數 24 | // citations[i] => 目前論文的引用數 25 | // 根據題述條件,citations[i]>i+1 才要繼續迴圈 26 | if (i+1 > citations[i]){ 27 | return i; 28 | } 29 | } 30 | // 如果中間都沒有不符合條件而回傳,則回傳論文總篇數 31 | return citations.size(); 32 | } 33 | }; 34 | -------------------------------------------------------------------------------- /Chapter04/04_13_物品堆疊.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:物品堆疊 (2017/10/18 P4) 4 | 5 | A. 題目 6 | 7 | 某個自動化系統中以一個存取物品的子系統,該系統將 N 個物品堆在一個垂直的貨架上,每個物品各佔一層。 8 | 9 | 系統運作的方式如下:每次只會取用一個物品,取用前必須先將在其上方的物品貨架升高,取用後,必須將該物品放回,然後將剛才升起的貨架降回原始位置,才會繼續進行下一個物品的取用。 10 | 11 | 每一次升高某些物品所需要消耗的能量以這些物品的總重來計算,忽略貨架本身的重量以及其他消耗。 12 | 13 | 現在有 N 個物品,第 i 個物品的重量是 w(i),而需要取用的次數為 f(i),如何擺放這些物品,可以使消耗的能量最小? 14 | 15 | 舉例來說,若有兩個物品 w(1)=1、w(2)=2、f(1)=3、f(2)=4,代表物品 1 的重量是 1,需取用 3 次,物品 2 的重量是 2,需取用 4 次。 16 | 17 | B. 2017/10/18 APCS 實作題 #4 18 | 本題可以到 zerojudge 上提交程式碼,網址為: 19 | https://zerojudge.tw/ShowProblem?problemid=c471 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | #include // for sort 26 | 27 | using namespace std; 28 | 29 | // 自訂的排序函式 30 | // 物品 index 越小代表放在越上面 31 | bool compare(pair a, pair b){ 32 | return a.first * b.second < a.second * b.first; 33 | } 34 | 35 | int main(){ 36 | long long int n, sum = 0; 37 | // 儲存物品資訊的向量 38 | // 一個 pair 可以儲存關於該物品的兩個資料 39 | vector> objects; 40 | long long int total_cost = 0; 41 | 42 | // 取得物品數目並調整向量大小 43 | cin >> n; 44 | objects.resize(n); 45 | 46 | // 取得每個物品的重量 47 | for (int i = 0; i < n; i++){ 48 | cin >> objects[i].first; 49 | } 50 | 51 | // 取得每個物品的取用次數 52 | for (int i = 0; i < n; i++){ 53 | cin >> objects[i].second; 54 | } 55 | 56 | // 利用自訂規則排序 57 | sort(objects.begin(), objects.end(), compare); 58 | 59 | // 計算總消耗能量 60 | for (int i = 0; i < n - 1; i++){ 61 | // sum 是上面 i+1 件物品的總重量 62 | sum += objects[i].first; 63 | // 上面 i+1 件物品要移動 第 (i+1)+1 件物品的取用次數 64 | total_cost += sum*objects[i+1].second; 65 | } 66 | 67 | cout << total_cost << endl; 68 | 69 | return 0; 70 | } 71 | -------------------------------------------------------------------------------- /Chapter05/05_01_Sequential_Search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | // 傳入引數中,data[]是資料結構 6 | // len 是資料筆數、target 是要找的目標 7 | int Sequential_Search(int data[], int len, int target){ 8 | for (int i = 0; i < len; i++){ 9 | // 目前檢查的資料是目標資料時,回傳索引值 10 | if (data[i] == target) 11 | return i; 12 | // 找到陣列結尾仍然沒有找到時,回傳 -1 13 | else if (i == len-1) 14 | return -1; 15 | } 16 | } 17 | 18 | int main() 19 | { 20 | int target; 21 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 22 | cout << "Please enter the target you would like to search:" << endl; 23 | cin >> target; 24 | 25 | cout << "Index of " << target << " is " << Sequential_Search(arr, 10, target); 26 | 27 | return 0; 28 | } 29 | -------------------------------------------------------------------------------- /Chapter05/05_02_Binary_Search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int Binary_Search(int data[], int lower, int upper, int target){ 6 | if(upper < lower) 7 | return -1; 8 | int middle = (lower + upper) / 2; 9 | // 中位數 == target 10 | if(data[middle] == target) 11 | return middle; 12 | // 中位數 > target,搜尋左側 13 | else if(data[middle] > target) 14 | return Binary_Search(data, lower, middle - 1, target); 15 | // 中位數 < target,搜尋右側 16 | else if(data[middle] < target) 17 | return Binary_Search(data, middle + 1, upper, target); 18 | } 19 | 20 | int main() 21 | { 22 | int target; 23 | int arr[10] = {12,27,35,36,47,52,52,52,68,74}; 24 | cout << "Please enter the target you would like to search:" << endl; 25 | cin >> target; 26 | 27 | cout << "Index of " << target << " is " << Binary_Search(arr,0,9,target); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /Chapter05/05_03_Square_Root.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include // for abs 3 | 4 | using namespace std; 5 | 6 | // 傳入值 x 是要求根號的數字 7 | // lower 到 upper 是目前的區間 8 | // error 是可容許誤差 9 | double Square(double x, double lower, double upper, double error){ 10 | // 算出中間數值 middle 11 | double middle = (lower + upper) / 2; 12 | 13 | // 誤差小於容許值時,回傳 middle 14 | if(abs(middle * middle - x) < error) 15 | return middle; 16 | // 搜尋 middle 左邊 17 | else if(middle * middle > x) 18 | return Square(x, lower, middle, error); 19 | // 搜尋 middle 右邊 20 | else if(middle * middle < x) 21 | return Square(x, middle, upper, error); 22 | } 23 | 24 | int main() 25 | { 26 | double x,e; 27 | cout << "Please enter x(x>1) and e:" << endl; 28 | cin >> x >> e; 29 | 30 | // 下界也可傳入 1,如 Square(x, 1, x, e) 31 | cout << Square(x,0,x,e); 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /Chapter05/05_04_Interpolation_Search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int Interpolation_Search(int data[], int lower, int upper, int target){ 6 | // 邊界條件:區間內沒有資料 7 | if(upper < lower) 8 | return -1; 9 | int upper_data = data[upper]; 10 | int lower_data = data[lower]; 11 | 12 | if(upper >= lower && target >= lower_data && target <= upper_data){ 13 | // 透過公式算出目標最可能在的索引值 index 14 | int index = lower + (target - lower_data) * (upper - lower) / (upper_data - lower_data); 15 | // 與二分搜尋法一樣分成三種情況處理 16 | // data[index] 正好就是目標 17 | if(data[index] == target) 18 | return index; 19 | // data[index] 比目標的值大 20 | // 尋找左側區間 21 | else if(data[index] > target) 22 | return Interpolation_Search(data, lower, index - 1, target); 23 | // data[index] 比目標的值小 24 | // 尋找右側區間 25 | else if(data[index] < target) 26 | return Interpolation_Search(data, index + 1, upper, target); 27 | } 28 | return -1; 29 | } 30 | 31 | int main() 32 | { 33 | int target; 34 | int arr[10] = {12,27,35,36,47,52,52,52,68,74}; 35 | cout << "Please enter the target you would like to search:" << endl; 36 | cin >> target; 37 | 38 | cout << "Index of " << target << " is " << Interpolation_Search(arr,0,9,target); 39 | 40 | return 0; 41 | } 42 | -------------------------------------------------------------------------------- /Chapter05/05_05_Gold_Search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int gold_1(int lower,int upper){ 6 | return lower + (upper - lower) * 0.382; 7 | } 8 | 9 | int gold_2(int lower,int upper){ 10 | return lower + (upper - lower) * 0.618; 11 | } 12 | 13 | int Gold_Search(int data[], int len) { 14 | // 極值不會出現在開頭或結尾 15 | // i 從 1 到 len - 2 16 | int lower = 1; 17 | int upper = len - 2; 18 | int cut_1 = gold_1(lower, upper); 19 | int cut_2 = gold_2(lower, upper); 20 | while(cut_2 > cut_1){ 21 | if(data[cut_1] > data[cut_2]){ 22 | upper = cut_2; 23 | cut_2 = cut_1; 24 | cut_1 = gold_2(lower, upper); 25 | } 26 | else{ 27 | lower = cut_1; 28 | cut_1 = cut_2; 29 | cut_2 = gold_1(lower, upper); 30 | } 31 | } 32 | for(int i = lower; i <= upper; i++){ 33 | if(data[i] > data[i-1] && data[i] > data[i + 1]) 34 | return i; 35 | } 36 | return -1; 37 | } 38 | 39 | int main() 40 | { 41 | int arr[10] = {56,86,104,128,135,124,109,96,81,72}; 42 | cout << "Index of maximum is: " << Gold_Search(arr, 10); 43 | 44 | return 0; 45 | } 46 | -------------------------------------------------------------------------------- /Chapter05/05_06_Fibonacci_Search.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int Fibonacci_Search(int data[], int len, int target) 6 | { 7 | int fibo_1 = 0; 8 | int fibo_2 = 1; 9 | int fibo_3 = fibo_1 + fibo_2; 10 | 11 | // 先找大於等於陣列長度 len 的最小費氏數 12 | while (fibo_3 < len) { 13 | fibo_1 = fibo_2; 14 | fibo_2 = fibo_3; 15 | fibo_3 = fibo_1 + fibo_2; 16 | } 17 | 18 | // 目前所在的索引值 19 | int current = fibo_1 - 1; 20 | current = current >= 0 ? current : 0; 21 | 22 | // 目標在 fibo_1 - 1右側,須加上 m 23 | if (data[current] < target) { 24 | current = len - fibo_2; 25 | } 26 | 27 | while (fibo_3 > 0) { 28 | current = current >= 0 ? current : 0; 29 | current = current < len ? current : len - 1; 30 | // 找到目標,回傳索引值 current 31 | if(data[current] == target) 32 | return current; 33 | // 目標資料在 current 的右側 34 | // current 往右移動 fibo_1 35 | else if (data[current] < target) { 36 | fibo_3 = fibo_2; 37 | fibo_2 = fibo_1; 38 | fibo_1 = fibo_3 - fibo_2; 39 | current += fibo_1; 40 | } 41 | // 目標資料在 current的左側 42 | // current 往左移動 fibo_1 43 | else if (data[current] > target) { 44 | fibo_3 = fibo_2; 45 | fibo_2 = fibo_1; 46 | fibo_1 = fibo_3 - fibo_2; 47 | current -= fibo_1; 48 | } 49 | } 50 | // 找不到資料,回傳 -1 51 | return -1; 52 | } 53 | 54 | 55 | int main() 56 | { 57 | int target; 58 | int arr[10] = {12,27,35,36,47,52,52,52,68,74}; 59 | cout << "Please enter the target you would like to search:" << endl; 60 | cin >> target; 61 | 62 | cout << "Index of " << target << " is " << Fibonacci_Search(arr, 10, target); 63 | 64 | return 0; 65 | } 66 | -------------------------------------------------------------------------------- /Chapter05/05_07_STL.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int main() 8 | { 9 | vector data = {8, 9, 6, 5, 3, 6, 7, 4, 2, 1}; 10 | // 預設從小排到大 11 | sort(data.begin(), data.end()); 12 | 13 | auto iter_upper = upper_bound(data.begin(), data.end(), 5); 14 | auto iter_lower = lower_bound(data.begin(), data.end(), 5); 15 | 16 | cout << "Data: "; 17 | for(int i : data){ 18 | cout << i << " "; 19 | } 20 | 21 | cout << endl; 22 | cout << "Upper bound of 5:" << *iter_upper << endl; 23 | cout << "Lower bound of 5:" << *iter_lower << endl; 24 | 25 | cout << "Index of upper bound: " << iter_upper - data.begin() << endl; 26 | cout << "Index of lower bound: " << iter_lower - data.begin() << endl; 27 | 28 | return 0; 29 | } 30 | -------------------------------------------------------------------------------- /Chapter05/05_08_peakIndexInMountainArray_Binary_Search.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #852. 山形陣列中的最大值 Peak Index in a Mountain Array 4 | 5 | A. 題目 6 | 如果一個陣列 arr 符合以下條件,就把它叫做「山形陣列」: 7 | a. arr.length≥3 8 | b. 存在某個 i,且 0arr[i+1]>⋯>arr[arr.length-1] 11 | 12 | 給定一個山形陣列 arr,回傳所有符合條件的 i,使得 13 | arr[0]<⋯arr[i+1]>⋯>arr[arr.length-1] 14 | 15 | B. 出處 16 | https://leetcode.com/problems/peak-index-in-a-mountain-array/ 17 | 18 | */ 19 | 20 | class Solution{ 21 | public: 22 | int peakIndexInMountainArray (vector& arr){ 23 | int lower = 1; 24 | int upper = arr.size() - 2; 25 | while (upper >= lower){ 26 | // 避免溢位,不直接相加除以 2 27 | int middle = lower + (upper-lower)/2; 28 | // 找到目標值 29 | if (arr[middle] > arr[middle - 1] && 30 | arr[middle] > arr[middle + 1]){ 31 | return middle; 32 | } 33 | // middle 位在左邊山坡上,要往右邊區間找 34 | else if (arr[middle] < arr[middle + 1]){ 35 | lower = middle + 1; 36 | } 37 | // 這次的 middle 位在右邊山坡上,要往左邊區間找 38 | else { 39 | upper = middle - 1; 40 | } 41 | } 42 | // leetcode 要求處理例外 43 | return -1; 44 | } 45 | }; 46 | -------------------------------------------------------------------------------- /Chapter05/05_08_peakIndexInMountainArray_Gold_Search.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #852. 山形陣列中的最大值 Peak Index in a Mountain Array 4 | 5 | A. 題目 6 | 如果一個陣列 arr 符合以下條件,就把它叫做「山形陣列」: 7 | a. arr.length≥3 8 | b. 存在某個 i,且 0arr[i+1]>⋯>arr[arr.length-1] 11 | 12 | 給定一個山形陣列 arr,回傳所有符合條件的 i,使得 13 | arr[0]<⋯arr[i+1]>⋯>arr[arr.length-1] 14 | 15 | B. 出處 16 | https://leetcode.com/problems/peak-index-in-a-mountain-array/ 17 | 18 | */ 19 | class Solution{ 20 | // 回傳黃金切割的索引值 21 | // 也可以選擇不使用 0.382 和 0.618,改用數學算出實際精確值 22 | int gold_1(int lower, int upper){ 23 | return lower + (upper - lower) * 0.382; 24 | } 25 | int gold_2(int lower, int upper){ 26 | return lower + (upper - lower) * 0.618; 27 | } 28 | public: 29 | int peakIndexInMountainArray(vector& arr){ 30 | // 極值不會出現在開頭或結尾 31 | // i 從 1 到 arr.size()-2 32 | int lower = 1; 33 | int upper = arr.size() - 2; 34 | int cut_1 = gold_1(lower, upper); 35 | int cut_2 = gold_2(lower, upper); 36 | // cut_1 和 cut_2 切出的區間有資料時可以繼續執行 37 | while (cut_2 > cut_1){ 38 | // 如果極值在 cut_2 左邊 39 | // cut_2 變為上界 40 | // cut_1 變為下一輪的 cut_2 41 | // 新切出下一輪的 cut_1(區間為原先的 [lower, cut_2]) 42 | if (arr[cut_1] > arr[cut_2]){ 43 | upper = cut_2; 44 | cut_2 = cut_1; 45 | cut_1 = gold_1(lower, upper); 46 | } 47 | // 如果極值在 cut_1 右邊 48 | // cut_1 變為下界 49 | // cut_2 變為下一輪的 cut_1 50 | // 新切出下一輪的 cut_2(區間為原先的 [cut_1, upper]) 51 | else { 52 | lower = cut_1; 53 | cut_1 = cut_2; 54 | cut_2 = gold_2(lower, upper); 55 | } 56 | } 57 | // 因為這題不能無限切割下去(索引值必須是整數) 58 | // 所以上面迴圈只能先限縮 lower 和 upper 的值 59 | // 仍然要在這個區間內尋找極值 60 | // 因為極值不會出現在開頭及尾端,要調整 lower 和 upper 61 | lower = (lower > 0 ? lower : 1); 62 | upper = (upper < arr.size() - 1 ? 63 | upper : arr.size() - 2); 64 | for (int i = lower; i <= upper; i++){ 65 | if(arr[i] > arr[i - 1] && arr[i] > arr[i + 1]) 66 | return i; 67 | } 68 | return -1; 69 | } 70 | }; 71 | -------------------------------------------------------------------------------- /Chapter05/05_08_peakIndexInMountainArray_Sequential_Search.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #852. 山形陣列中的最大值 Peak Index in a Mountain Array 4 | 5 | A. 題目 6 | 如果一個陣列 arr 符合以下條件,就把它叫做「山形陣列」: 7 | a. arr.length≥3 8 | b. 存在某個 i,且 0arr[i+1]>⋯>arr[arr.length-1] 11 | 12 | 給定一個山形陣列 arr,回傳所有符合條件的 i,使得 13 | arr[0]<⋯arr[i+1]>⋯>arr[arr.length-1] 14 | 15 | B. 出處 16 | https://leetcode.com/problems/peak-index-in-a-mountain-array/ 17 | 18 | */ 19 | 20 | class Solution{ 21 | public: 22 | int peakIndexInMountainArray(vector& arr){ 23 | // 極值不會出現在開頭或結尾 24 | // i 從 1 到 arr.size()-2 25 | for (int i = 1; i < arr.size() - 1; i++){ 26 | if (arr[i] > arr[i - 1] && arr[i] > arr[i + 1]) 27 | return i; 28 | } 29 | // leetcode 要求處理例外 30 | return -1; 31 | } 32 | }; 33 | -------------------------------------------------------------------------------- /Chapter05/05_09_searchInsert_Loop.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #35. 找尋插入位置 Search Insert Position 4 | 5 | A. 題目 6 | 給定一個已排序的整數陣列(數字不重複)和目標值,如果目標在陣列中,回傳其索引值,如果不在陣列中,則回傳其應該插入在哪個索引值,才能使陣列仍然符合排序。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/search-insert-position/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int searchInsert(vector& nums, int target){ 16 | int lower = 0; 17 | int upper = nums.size()-1; 18 | // 記錄要插入的位置 19 | int position; 20 | while(upper >= lower){ 21 | int middle = lower + (upper - lower) / 2; 22 | if (nums[middle] == target){ 23 | return middle; 24 | } 25 | else if (nums[middle] > target){ 26 | upper = middle - 1; 27 | position = middle; 28 | } 29 | else { 30 | lower = middle + 1; 31 | position = middle + 1; 32 | } 33 | } 34 | // 沒有找到 target 35 | return position; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter05/05_09_searchInsert_Recursion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #35. 找尋插入位置 Search Insert Position 4 | 5 | A. 題目 6 | 給定一個已排序的整數陣列(數字不重複)和目標值,如果目標在陣列中,回傳其索引值,如果不在陣列中,則回傳其應該插入在哪個索引值,才能使陣列仍然符合排序。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/search-insert-position/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | // 使用二分搜尋法 15 | int binary_search(vector& nums, int lower, int upper, int target){ 16 | // 沒有找到時,回傳應該插入的位置 lower 17 | if (upper < lower) 18 | return lower; 19 | int middle = lower + (upper - lower) / 2; 20 | // 比較 nums[middle] 與 target 的大小 21 | if (nums[middle] == target){ 22 | return middle; 23 | } 24 | else if (nums[middle] > target){ 25 | return binary_search(nums, lower, middle-1, target); 26 | } 27 | else { 28 | return binary_search(nums, middle+1, upper, target); 29 | } 30 | } 31 | 32 | public: 33 | int searchInsert(vector& nums, int target){ 34 | // 回傳二分搜尋的結果 35 | return binary_search(nums, 0, nums.size()-1, target); 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter05/05_10_firstBadVersion.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #278. 第一個錯誤版本 First Bad Version 4 | 5 | A. 題目 6 | 你是一個產品經理,且正在帶領一個團隊開發新產品,但最新版本的產品無法通過品管測試。因為每個版本都是修改先前的版本而成,所以只要有一個版本發生錯誤,後面的每個版本也都會是錯的。 7 | 假設有 n 個版本 [1, 2,..., n],而你想找到第一個發生錯誤的版本。有一個 API 會根據傳入的版本回傳該版本是否錯誤:bool isBadVersion(version)。 8 | 設計一個函式來找到第一個錯誤的版本,並最小化呼叫該 API 的次數。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/first-bad-version/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | int firstBadVersion(int n){ 18 | int lower = 0; 19 | int upper = n; 20 | // 使用二分搜尋 21 | while (lower <= upper){ 22 | int middle = lower + (upper - lower) / 2; 23 | // 檢查 middle 是否是錯誤的 24 | bool bad = isBadVersion(middle); 25 | // 如果 middle 是錯誤的,第一個錯誤版本在前面 26 | if (bad){ 27 | upper = middle - 1; 28 | } 29 | // 如果 middle 是對的,第一個錯誤版本在後面 30 | else { 31 | lower = middle + 1; 32 | } 33 | } 34 | // 回傳找到的第一個錯誤版本索引值 lower 35 | return lower; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter05/05_11_arrangeCoins.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #441. 安排硬幣 Arranging Coins 4 | 5 | A. 題目 6 | 你有 n 個硬幣,且你想要用這些硬幣來排出一個階梯的形狀。這個階梯有 k 個橫列,第 i 個橫列剛好會有 i 個硬幣,最下面的那個橫列可以不用填滿。 7 | 給定一個整數 n,回傳該階梯完整的「橫列」數。 8 | 9 | B. 出處 10 | https://leetcode.com/problems/arranging-coins/ 11 | 12 | */ 13 | 14 | class Solution{ 15 | // 避免溢位,用函式計算 middle 層可以放的硬幣數 16 | long long int sum(long long int middle){ 17 | return middle * (middle + 1) / 2; 18 | } 19 | 20 | public: 21 | int arrangeCoins(int n){ 22 | int lower = 0; 23 | // k 超過 2*sqrt(n) 時,k*(k+1)/2 一定超過 n 24 | // 因此初始的上界 upper 可以取 2*sqrt(n) 25 | // 若再向下取到 sqrt(2n) 會有邊界處理問題 26 | int upper = 2 * sqrt(n); 27 | while (upper > lower){ 28 | int middle = lower + (upper - lower) / 2; 29 | // middle 層正好放的下 n 個硬幣 30 | if (sum(middle) == n){ 31 | return middle; 32 | } 33 | // middle 層可以放超過 n 個硬幣 34 | else if (sum(middle) > n){ 35 | upper = middle; 36 | } 37 | // middle 層放不下 n 個硬幣 38 | else if (sum(middle) < n){ 39 | lower = middle + 1; 40 | } 41 | } 42 | return lower-1; 43 | } 44 | }; 45 | -------------------------------------------------------------------------------- /Chapter05/05_12_基地台.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:基地台 (2017/03/04 P4) 4 | 5 | A. 題目 6 | 7 | 為因應資訊化與數位化的發展趨勢,某市長想要在城市的一些服務點上提供無線網路服務,因此他委託電信公司架設無線基地台。某電信公司負責其中 N 個服務點,這 N個服務點位在一條筆直的大道上,它們的位置(座標)係以與該大道一端的距離 P[i]來表示,其中 i=0~N-1。由於設備訂製與維護的因素,每個基地台的服務範圍必須都一樣,當基地台架設後,與此基地台距離不超過 R (稱為基地台的半徑)的服務點都可以使用無線網路服務,也就是說每一個基地台可以服務的範圍是 D=2R(稱為基地台的直徑)。現在電信公司想要計算,如果要架設 K 個基地台,那麼基地台的最小直徑是多少才能使每個服務點都可以得到服務。 8 | 9 | 基地台架設的地點不一定要在服務點上,最佳的架設地點也不唯一,但本題只需要求最小直徑即可。以下是一個 N=5 的例子,五個服務點的座標分別是 1、2、5、7、8。 10 | 11 | 假設 K=1,最小的直徑是 7,基地台架設在座標 4.5 的位置,所有點與基地台的距離都在半徑 3.5 以內。假設 K=2,最小的直徑是 3,一個基地台服務座標 1 與 2 的點,另一個基地台服務另外三點。在 K=3 時,直徑只要 1 就足夠了。 12 | 13 | B. 題目出處 14 | 2017/03/04 APCS 實作題#4 15 | 本題可以到 zerojudge 上提交程式碼,網址為: 16 | https://zerojudge.tw/ShowProblem?problemid=c575 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | int N,K; 27 | vector City; 28 | 29 | // 檢查半徑 R 是否可行 30 | bool Covered(int R) { 31 | // available 為手上還剩幾個基地台堪用 32 | // limit 為目前的基地台最遠覆蓋到的位置 33 | int available = K, limit = -1; 34 | // 每個城市從左到右處理一次 35 | for (int i = 0; i < N; i++) { 36 | // 如果目前的城市覆蓋不到,就新增一個基地台 37 | if (limit < City.at(i)) { 38 | limit = (City.at(i) + R); 39 | available--; 40 | } 41 | // 如果基地台用罄就回傳 false 42 | if (available < 0) 43 | return false; 44 | } 45 | // 此半徑可行,基地台夠用,回傳 True 46 | return true; 47 | } 48 | 49 | int Binary_Search (int lower, int upper) { 50 | // 從 lower 到 upper 中找可行的半徑解 51 | while(true){ 52 | if(upper <= lower + 1){ 53 | if(Covered(lower)) 54 | return lower; 55 | else 56 | return upper; 57 | } 58 | int middle = (lower + upper) / 2; 59 | // 中位數可行,搜尋左半部 60 | if(Covered(middle)) 61 | upper = middle; 62 | // 中位數不可行,搜尋右半部 63 | else 64 | lower = middle + 1; 65 | } 66 | } 67 | 68 | int main () { 69 | ios::sync_with_stdio(false); 70 | cin.tie(NULL); 71 | 72 | cin >> N >> K; 73 | City.resize(N); 74 | for (int i = 0; i < N; i++) 75 | cin >> City.at(i); 76 | sort(City.begin(), City.end()); 77 | // 上界是最遠兩城市的距離 78 | // 下界是 1 79 | cout << Binary_Search(1, (City.back() - City.front())) << endl; 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /Chapter05/05_13_圓環出口.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | APCS:圓環出口 (2020/07/04 P4) 3 | 4 | A. 題目 5 | 6 | 給定 N 個房間排成一個環,其中編號 i 的房間可以走到編號 (i +1) mod N 的房間,i 的編號為 0 ~ N - 1,並且路徑為單向,且每次進入編號 i 的房 7 | 間可以獲得 pi 個點數,同時最一開始待的房間也可以獲得點數。 8 | 9 | 另外有 M 個任務,第 i 個任務需要蒐集到 qi 個點數,若一開始在編號 0 的房間,當點數達到 qi 時就可以完成第 i 個任務後並清空所有點數,清空 10 | 點數後會進到下一個房間,當收集完 M 把鑰匙時,最後會停在哪個編號的房間? 11 | 12 | B. 題目出處 13 | 14 | 2020/07/04 APCS 實作題#3 15 | 本題可以到 zerojudge 上提交程式碼,網址為: 16 | https://zerojudge.tw/ShowProblem?problemid=f581 17 | 18 | */ 19 | 20 | #include 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | int main () { 27 | ios::sync_with_stdio(false); 28 | cin.tie(0); 29 | 30 | int N, M; 31 | cin >> N >> M; 32 | 33 | // 輸入並計算前綴和 34 | vector p(N), prefix_sum(N, 0); 35 | for (int i = 0; i < N; i++){ 36 | cin >> p[i]; 37 | prefix_sum[i] += p[i]; 38 | if (i != 0) 39 | prefix_sum[i] += prefix_sum[i - 1]; 40 | } 41 | 42 | // 依序接受任務 43 | int q, last_room = -1; 44 | for (int i = 0; i < M; i++){ 45 | cin >> q; 46 | // last_room: 上次停留的房間 47 | // 本次目標為 q + prefix_sum[last_room] 48 | if (last_room != -1) 49 | q += prefix_sum[last_room]; 50 | // 若超出範圍則扣掉所有房間之總和 51 | if (q > prefix_sum[N - 1]) 52 | q -= prefix_sum[N - 1]; 53 | // 利用 lower_bound 找出可達成需求之最小索引值的房間 54 | last_room = lower_bound(prefix_sum.begin(), prefix_sum.end(), q) - prefix_sum.begin(); 55 | } 56 | cout << (last_room + 1) % N << endl; 57 | return 0; 58 | } 59 | -------------------------------------------------------------------------------- /Chapter06/06_01_Fibo_DC.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int Fibo (int n){ 5 | if (n <= 2) 6 | return 1; 7 | else 8 | return Fibo(n - 1) + Fibo(n - 2); 9 | } 10 | 11 | int main() 12 | { 13 | // 求費波那契數列的前一百項 14 | for (int i = 1; i <= 100; i++){ 15 | cout << Fibo(i) << " "; 16 | } 17 | return 0; 18 | } 19 | -------------------------------------------------------------------------------- /Chapter06/06_01_Fibo_DP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | int Fibo(int n){ 5 | // 宣告儲存每個費波那契數的陣列 6 | int *data = (int*) malloc(sizeof(int) * n); 7 | data[0] = 1; 8 | data[1] = 1; 9 | // 每個費波那契數是用兩個先前已經儲存的結果直接求和得到 10 | for (int i = 2; i < n; i++){ 11 | data[i] = data[i - 1] + data[i - 2]; 12 | } 13 | int result = data[n - 1]; 14 | free(data); 15 | return result; 16 | } 17 | 18 | int main() 19 | { 20 | // 求費波那契數列的前一百項 21 | for (int i = 1; i <= 100; i++){ 22 | cout << Fibo(i) << " "; 23 | } 24 | return 0; 25 | } 26 | -------------------------------------------------------------------------------- /Chapter06/06_02_Hanoi_Tower.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | using namespace std; 3 | 4 | // 把代表三根棍子的三個堆疊分為 from、to 和 others 5 | int Hanoi (int N, char from, char to, char others){ 6 | // 邊界條件:只有一個盤子(最大的盤子)要搬動 7 | if (N==1){ 8 | // 把這個盤子由 from 搬到 to 9 | cout << "Move " << N << " from " << from << " to " << to << endl; 10 | return 1; 11 | } 12 | // 一般情形 13 | else { 14 | // 把 n-1 個盤子從 from 移動到 others 15 | int step_1 = Hanoi(N-1, from, others, to); 16 | // 把 1 個盤子從 from 移動到 to 17 | int step_2 = 1; 18 | cout << "Move " << N << " from " << from << " to " << to << endl; 19 | // 把 n-1 個盤子從 others 移動到 to 20 | int step_3 = Hanoi(N-1, others, to, from); 21 | // 回傳需要移動的總次數 22 | return step_1 + step_2 + step_3; 23 | } 24 | } 25 | 26 | int main(){ 27 | // 讓使用者輸入 N,代表要移動 N 層河內塔 28 | int N; cout << "Please enter N:" << endl; 29 | cin >> N; 30 | int steps = Hanoi(N, 'A', 'C', 'B'); 31 | cout << "Steps:" << steps << endl; 32 | return 0; 33 | } 34 | -------------------------------------------------------------------------------- /Chapter06/06_03_maxSubArray_Brute_Force.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#53. 最大子數列 Maximum Subarray 4 | 5 | A. 題目: 6 | 給定一個整數陣列 nums,找到其中的連續子陣列,其元素總和為最大,並回傳該元素總和。 7 | 8 | B. 題目來源: 9 | https://leetcode.com/problems/maximum-subarray/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int maxSubArray(vector& nums){ 16 | int n = nums.size(); 17 | // Should use malloc or vector instead; 18 | int sum[n][n]; 19 | // -int_max 20 | int maximum = -2147483648; 21 | // 指定區間開始位置 22 | for (int start = 0; start < n; start++){ 23 | // 指定區間結束位置 24 | for (int finish = start ; finish < n; finish++){ 25 | sum[start][finish] = 0; 26 | // 計算區間的元素和 27 | for (int k = start; k <= finish; k++){ 28 | sum[start][finish] += nums[k]; 29 | } 30 | if (sum[start][finish] > maximum){ 31 | maximum = sum[start][finish]; 32 | } 33 | } 34 | } 35 | return maximum; 36 | } 37 | }; 38 | -------------------------------------------------------------------------------- /Chapter06/06_03_maxSubArray_Brute_Force_Optimization.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#53. 最大子數列 Maximum Subarray 4 | 5 | A. 題目: 6 | 給定一個整數陣列 nums,找到其中的連續子陣列,其元素總和為最大,並回傳該元素總和。 7 | 8 | B. 題目來源: 9 | https://leetcode.com/problems/maximum-subarray/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int maxSubArray(vector& nums){ 16 | int n = nums.size(); 17 | // 儲存每個從開頭開始的區間的和 18 | int sum_1_to_n[n]; 19 | // 儲存所有區間的和 20 | int sum[n][n]; 21 | int maximum = -2147483648; // -int_max 22 | // 先計算每個從開頭開始的區間的元素和,複雜度 O(n) 23 | for (int i = 0; i < n; i++){ 24 | if (i == 0) 25 | sum_1_to_n[i] = nums[0]; 26 | else 27 | sum_1_to_n[i] = sum_1_to_n[i - 1] + nums[i]; 28 | } 29 | // 利用 [0, finish] 和 [0, start-1] 兩個區間和的差 30 | // 計算其他區間的元素和,複雜度降到 O(n^2) 31 | for (int start = 0; start < n; start++){ 32 | for (int finish = start ; finish < n ; finish++){ 33 | // start!=0 34 | if (start){ 35 | // 這層的複雜度從 O(n) 優化成 O(1) 36 | sum[start][finish] = sum_1_to_n[finish] - sum_1_to_n[start - 1]; 37 | } 38 | // start==0 39 | else{ 40 | sum[start][finish] = sum_1_to_n[finish]; 41 | } 42 | if (sum[start][finish] > maximum){ 43 | maximum = sum[start][finish]; 44 | } 45 | } 46 | } 47 | return maximum; 48 | } 49 | }; 50 | -------------------------------------------------------------------------------- /Chapter06/06_03_maxSubArray_DC.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#53. 最大子數列 Maximum Subarray 4 | 5 | A. 題目: 6 | 給定一個整數陣列 nums,找到其中的連續子陣列,其元素總和為最大,並回傳該元素總和。 7 | 8 | B. 題目來源: 9 | https://leetcode.com/problems/maximum-subarray/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int maxSubArray(vector& nums){ 16 | // 取得整數陣列 nums 的長度 17 | int len = nums.size(); 18 | 19 | // 例外處理:陣列中沒有資料,回傳整數最小值 20 | if (len == 0) 21 | return -2147483648; 22 | // 例外處理:陣列中只有一筆資料,回傳該筆資料 23 | if (len == 1) 24 | return nums[0]; 25 | 26 | // 一般情況 27 | // 訂出 data_left 和 data_right 的範圍 28 | // 中間元素的迭代器 29 | auto middle = nums.begin() + len / 2; 30 | vector data_left(nums.begin(), middle); 31 | vector data_right(middle, nums.end()); 32 | 33 | // 分成三個子問題處理 34 | int max_left = maxSubArray(data_left); 35 | int max_right = maxSubArray(data_right); 36 | int max_center = maxCrossArray(nums); 37 | 38 | // 回傳最大值 39 | if (max_left >= max_center && 40 | max_left >= max_right) 41 | return max_left; 42 | else if (max_right >= max_center && 43 | max_right >= max_left) 44 | return max_right; 45 | else 46 | return max_center; 47 | } // end of maxSubArray 48 | 49 | int maxCrossArray(vector& nums){ 50 | int len = nums.size(); 51 | int middle = (len - 1) / 2; 52 | int max_center = nums[middle]; 53 | int index_left = -1, index_right = 1; 54 | int left_sum = 0, right_sum = 0; 55 | int max = -2147483648; 56 | 57 | while(middle + index_left >= 0){ 58 | left_sum += nums[middle + index_left]; 59 | if (left_sum > max) 60 | max = left_sum; 61 | index_left--; 62 | } 63 | 64 | // 如果往左會變大,就累加 65 | if(max > 0) 66 | max_center += max; 67 | max = -2147483648; 68 | 69 | while(middle + index_right < len){ 70 | right_sum += nums[middle + index_right]; 71 | if(right_sum > max) 72 | max = right_sum; 73 | index_right++; 74 | } 75 | 76 | // 如果往右會變大,就累加 77 | if(max > 0) 78 | max_center += max; 79 | 80 | return max_center; 81 | } // end of maxCrossArray 82 | }; // end of Solution 83 | -------------------------------------------------------------------------------- /Chapter06/06_04_Selection.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | // 互換函式 7 | void Swap(int &a, int &b){ 8 | int tmp = a; 9 | a = b; 10 | b = tmp; 11 | } 12 | 13 | int Partition(int data[], int start, int finish, int pivot){ 14 | int i; 15 | // 找到 Pivot 的索引值 i 16 | for (i = start; i <= finish; i++) 17 | if (data[i] == pivot) 18 | break; 19 | // 把 Pivot 移到最後面 20 | Swap(data[i], data[finish]); 21 | // 把比 Pivot 小的值依序移動到左側 22 | int p = start; 23 | for(int i = start; i < finish; i++){ 24 | if(data[i] < pivot){ 25 | Swap(data[i], data[p]); 26 | p++; 27 | } 28 | } 29 | Swap(data[finish], data[p]); 30 | // 最後回傳 Pivot 所在的 index 31 | return p; 32 | } 33 | 34 | // 取中位數:排序後取中間的索引值 35 | int Median(int data[], int len){ 36 | sort(data, data + len); 37 | return data[len / 2]; 38 | } 39 | 40 | int k_th_Smallest( int data[], int lower, int upper, int k){ 41 | // 確認 k 在範圍內 42 | if(k > 0 && k <= upper - lower + 1) 43 | { 44 | // 目標陣列的長度 45 | int len = upper - lower + 1; 46 | // 五個五個一組,再找其中位數 47 | int median[(len + 4) / 5]; 48 | int i; 49 | for(i = 0; i < len / 5; i++) 50 | median[i] = Median(data + lower + i * 5, 5); 51 | // 如果最後一組資料不足五個 52 | if(i * 5 < len) 53 | { 54 | median[i] = Median(data + lower + i * 5, len % 5); 55 | i++; 56 | } 57 | int MOM; 58 | // 如果只有一組資料,則 MOM 只能挑第一筆中位數 59 | if(i == 1) 60 | MOM = median[0]; 61 | // 否則從每組資料的中位數陣列裏頭找 MOM 62 | else 63 | MOM = k_th_Smallest(median, 0, i - 1, i / 2); 64 | 65 | // 利用 Partition 區分成三組 66 | // 比 MOM 大、比 MOM 小、跟 MOM 一樣 67 | // index: 最後 Pivot 的位置 68 | int index = Partition(data, lower, upper, MOM); 69 | // index - lower + 1: 比 MOM 小的資料有幾個 70 | // 第 k 小的資料在比 MOM 小的那組裏頭 71 | if(index - lower + 1 > k) 72 | return k_th_Smallest(data, lower, index - 1, k); 73 | // 第 k 小的資料在跟 MOM 一樣的那組裏頭 74 | if(index - lower + 1 == k) 75 | return data[index]; 76 | // 剩下的就是在比 MOM 大的那組裏頭 77 | // 新的 k: k - (index - lower + 1) 78 | int new_k = k - (index - lower + 1); 79 | return k_th_Smallest(data, index + 1, upper, new_k); 80 | } 81 | // 不符合規則,回傳 - 1 82 | return - 1; 83 | } 84 | 85 | int main() 86 | { 87 | int target; 88 | int arr[10] = {35,52,68,12,47,52,36,52,74,27}; 89 | cout << "Please enter the target you would like to search:" << endl; 90 | cin >> target; 91 | 92 | cout << "kth smallest of " << target << " is " << k_th_Smallest(arr,0,9,target); 93 | 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /Chapter06/06_05_majorityElement.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#169 多數元素 Majority Element 4 | 5 | A. 題目 6 | 給定一個長度為 n 的整數陣列 nums,回傳「多數元素」。這裡多數元素指的是「出現多於 ⌊n/2⌋ 次的元素」。測資中的每個陣列中都存在一個多數元素。 7 | 8 | B. 出處: 9 | https://leetcode.com/problems/majority-element/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | // 傳入整數陣列、下界的索引值與上界的索引值 15 | // 回傳介於傳入下界與上界間的區間中的多數元素 16 | int majority_range( 17 | vector& nums, int lower, int upper 18 | ) 19 | { 20 | // 邊界處理:傳入區間已經縮小到沒有值 21 | if (lower==upper) 22 | return nums[lower]; 23 | 24 | // 一般情形 25 | // 分成左右兩個子陣列遞迴處理 26 | int middle = lower + (upper - lower) / 2; 27 | int left_major = 28 | majority_range(nums, lower, middle); 29 | int right_major = 30 | majority_range(nums, middle + 1, upper); 31 | 32 | // 比較左邊與右邊多數元素 33 | // 相同時任意回傳一邊 34 | if (left_major == right_major) 35 | return right_major; 36 | 37 | // 不同時,回傳出現次數較多者 38 | int left_count = 0, right_count = 0; 39 | for (int i = lower; i <= middle; i++){ 40 | if (nums[i]==left_major) 41 | left_count++; 42 | } 43 | for (int i = middle + 1; i <= upper; i++){ 44 | if (nums[i] == right_major) 45 | right_count++; 46 | } 47 | return left_count > right_count ? 48 | left_major : right_major; 49 | } 50 | 51 | public: 52 | int majorityElement(vector& nums){ 53 | // 把整個 nums 陣列傳入函式處理 54 | return majority_range(nums, 0, nums.size()-1); 55 | } 56 | }; 57 | -------------------------------------------------------------------------------- /Chapter06/06_06_searchMatrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#240 Search a 2D Matrix II 4 | 5 | A. 題目 6 | 試寫一個有效率的演算法來搜尋大小為 m×n 的整數二維陣列中的某個目標值,若該值存在在陣列中,回傳 true,否則回傳 false。 7 | 8 | 該陣列有下列性質: 9 | a. 每個橫列中,整數由左到大遞增 10 | b. 每個直行中,整數由上到下遞增 11 | 12 | B. 出處: 13 | https://leetcode.com/problems/search-a-2d-matrix-ii/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | public: 19 | bool searchMatrix(vector>& matrix, int target){ 20 | // 例外處理:陣列是空的 21 | if (matrix.empty()) 22 | return false; 23 | if (matrix[0].empty()) 24 | return false; 25 | 26 | // 一般情形 27 | // 取得陣列大小 28 | int rows = matrix.size(); 29 | int cols = matrix[0].size(); 30 | // 從右上角的元素出發 31 | int row_now = 0; 32 | int col_now = cols - 1; 33 | 34 | // 超出左邊與下方邊界前繼續執行 35 | while (row_now < rows && col_now >= 0){ 36 | // 找到目標,回傳 true 37 | if (matrix[row_now][col_now] == target){ 38 | return true; 39 | } 40 | // 往左找 41 | else if (matrix[row_now][col_now] > target){ 42 | col_now--; 43 | } 44 | // 往下找 45 | else { 46 | row_now++; 47 | } 48 | } // end of while 49 | return false; 50 | } // end of searchMatrix 51 | }; // end of Solution 52 | -------------------------------------------------------------------------------- /Chapter06/06_07_支點切割.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int N, K, sum = 0; 9 | vector pre_torque, suf_torque; 10 | vector p; 11 | 12 | int cut(int depth, int left, int right) { 13 | // 遞迴深度超過 14 | if(depth >= K) 15 | return 0; 16 | 17 | int len = right - left + 1; 18 | // 陣列長度不足 3 19 | if(len < 3) 20 | return 0; 21 | 22 | // 計算左力矩 23 | long long int prefix = 0; 24 | pre_torque[left] = 0; 25 | for (int i = left + 1; i <= right; i++){ 26 | prefix += p[i - 1]; 27 | pre_torque[i] = pre_torque[i - 1] + prefix; 28 | 29 | } 30 | // 計算右力矩 31 | long long int suffix = 0; 32 | suf_torque[right] = 0; 33 | for (int i = right - 1; i >= left; i--){ 34 | suffix += p[i + 1]; 35 | suf_torque[i] = suf_torque[i + 1] + suffix; 36 | } 37 | // 找到最小力矩的點 38 | long long int minimum = 2e18; 39 | int min_index = -1; 40 | for (int i = left + 1; i < right; i++){ 41 | long long int total = 42 | abs(pre_torque[i] - suf_torque[i]); 43 | if(total < minimum){ 44 | min_index = i; 45 | minimum = total; 46 | } 47 | } 48 | return p[min_index] + 49 | // 左邊 50 | cut(depth + 1, left, min_index - 1) + 51 | // 右邊 52 | cut(depth + 1, min_index + 1, right); 53 | } 54 | 55 | int main () { 56 | ios::sync_with_stdio(false); 57 | cin.tie(NULL); 58 | 59 | cin >> N >> K; 60 | p.resize(N); 61 | 62 | // 輸入資料 63 | for (int i = 0; i < N; i++) 64 | cin >> p[i]; 65 | 66 | pre_torque.resize(N); 67 | suf_torque.resize(N); 68 | 69 | cout << cut(0, 0, N - 1) << endl; 70 | return 0; 71 | } 72 | -------------------------------------------------------------------------------- /Chapter06/06_08_反序數量.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | APCS:反序數量 (2018/06 P4) 3 | 4 | A. 題目 5 | 6 | 給定一陣列 p,若 i < j 但 p[i] > p[j],則稱 (p[i],p[j]) 為反序對,請試著陣列 p 之反序對數量。 7 | 8 | 比方說給定陣列 p 為 [5, 6, 2, 1 ,3],反序對為 (5, 2)、(5, 1)、(5, 3)、(6, 2)、(6, 1)、(6, 3)、(2, 1),共七個反序。 9 | 10 | 11 | B. 題目出處 12 | 13 | 2018/06 APCS 實作題#4 14 | 本題可以到一中電腦資訊研究社 Online Judge上提交程式碼 15 | 網址為: https://judge.tcirc.tw/ShowProblem?problemid=d064 16 | 17 | */ 18 | 19 | #include 20 | #include 21 | #include 22 | 23 | using namespace std; 24 | 25 | int N; 26 | vector p; 27 | 28 | long long int DC(vector& p){ 29 | if(p.size() < 2) 30 | return 0; 31 | long long int sum = 0; 32 | int max = -2147483648; 33 | int min = 2147483647; 34 | // 取出陣列最大與最小值 35 | for(int value : p){ 36 | if(value > max) 37 | max = value; 38 | if(value < min) 39 | min = value; 40 | } 41 | if(max == min) 42 | return 0; 43 | 44 | int medium = min + (max - min) / 2; 45 | 46 | vector smaller; 47 | vector bigger; 48 | // 依序取出資料 49 | for(int value : p){ 50 | if(value <= medium){ 51 | smaller.push_back(value); 52 | // value 在 bigger 的右側且又比 bigger 小 53 | // 故 bigger 的大小就是 value 的子序對數目 54 | sum += bigger.size(); 55 | } 56 | else 57 | bigger.push_back(value); 58 | } 59 | return sum + DC(smaller) + DC(bigger); 60 | } 61 | 62 | int main () { 63 | ios::sync_with_stdio(false); 64 | cin.tie(NULL); 65 | 66 | cin >> N; 67 | vector p(N); 68 | 69 | // 輸入資料 70 | for (int i = 0; i < N; i++) 71 | cin >> p[i]; 72 | cout << DC(p) << endl; 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /Chapter07/07_01_lemonadeChange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #860 檸檬水找零 Lemonade Change 4 | 5 | A. 題目 6 | 有個賣檸檬水的攤位,每杯檸檬水賣 5 元。有一些顧客排隊來買,而他們一次都只會點「一杯」。每個顧客點了他們的一杯檸檬水後,會用一枚面額 5 元、10 元或 20 元的硬幣來結帳。你必須正確找零,使得找零後的結果是每個顧客總共只付給你 5 元(顧客付 10 元時找 5 元,付 20 元時找 15 元)。 7 | 8 | 注意一開始手上並沒有任何零錢,所以有時會遇到無法找零的情形。只有在從頭到尾都能正確找零給每個顧客時,回傳 true。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/lemonade-change/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | bool lemonadeChange(vector& bills){ 18 | // 紀錄手上的不同硬幣個數 19 | // 開始時每種硬幣個數都是 0 20 | int five = 0; 21 | int ten = 0; 22 | int twenty = 0; 23 | int len = bills.size(); 24 | 25 | // 依序處理每位顧客的找錢流程 26 | for (int i = 0; i < len; i++){ 27 | // 第 i 個顧客使用的硬幣 28 | int coin = bills[i]; 29 | // 顧客使用 5 元時,手上增加一枚 5 元 30 | if (coin == 5){ 31 | five++; 32 | } 33 | // 顧客使用 10 元時 34 | else if (coin == 10){ 35 | // 手上有 5 元硬幣,能夠成功找錢 36 | if (five){ 37 | five--; // 找給顧客一枚 5 元 38 | ten++; // 手上增加一枚 10 元 39 | } 40 | // 不能成功找錢 41 | else { 42 | return false; 43 | } 44 | } 45 | // 顧客使用 20 元時 46 | else if (coin == 20){ 47 | // 5 元比較珍貴,優先把 10 元找掉 48 | // 但也要同時至少有一枚 5 元才能找 49 | if (ten && five){ 50 | ten--; 51 | five--; 52 | twenty++; 53 | } 54 | // 沒有 10 元時,才試著用 3 枚 5 元找錢 55 | else if (five >= 3){ 56 | five -= 3; 57 | twenty++; 58 | } 59 | // 不能成功找錢 60 | else { 61 | return false; 62 | } 63 | } // end of outer if 64 | } // end of for 65 | return true; 66 | } // end of lemonadeChange 67 | }; // end of Solution 68 | -------------------------------------------------------------------------------- /Chapter07/07_02_coinChange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #322 硬幣找零 Coin Change 4 | 注意本題如果使用貪婪演算法來解決,會得到「錯誤」的答案,但建議你仍可以試著先寫出對應的貪婪解法,並思考為何會出錯,下一章介紹動態規劃法就可以順利解決這個問題。 5 | 6 | A. 題目 7 | 給定一個整數陣列 coins 代表各種硬幣的面額,與一個整數 amount 代表要達到的總額。 8 | 9 | 回傳「要達到該總額最少需要的硬幣個數」,如果根據給定的各種硬幣面額無法剛好達到該總額,回傳 -1。每種硬幣的可用個數沒有上限。 10 | 11 | B. 出處 12 | https://leetcode.com/problems/coin-change/ 13 | 14 | */ 15 | 16 | class Solution{ 17 | public: 18 | int coinChange(vector& coins, int amount){ 19 | // 例外處理:amount 為 0 20 | if (amount == 0) 21 | return 0; 22 | // 一般情形 23 | // 把硬幣面額從大到小排 24 | sort(coins.begin(), coins.end(), greater()); 25 | int coin_count = 0; 26 | int len = coins.size(); 27 | 28 | // 從面額最大的硬幣開始盡量貼近總額 29 | for (int i = 0; i < len; i++){ 30 | // 當前處理的面額 31 | int value = coins[i]; 32 | // 目前面額硬幣最多可以使用的個數 33 | // 例如總額為 240 時,可以使用四個 50 塊 34 | coin_count += amount / value; 35 | // 更新 amount 36 | // 例如用了四個 50 塊後,amount 從 240 更新為 40 37 | amount %= value; 38 | // 可以找開時,回傳硬幣數目 39 | if (amount == 0) 40 | return coin_count; 41 | } // end of for 42 | return -1; 43 | } // end of coinChange 44 | }; // end of Solution 45 | -------------------------------------------------------------------------------- /Chapter07/07_03_中途休息.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include // for sort 4 | 5 | using namespace std; 6 | 7 | int main(){ 8 | 9 | int c, d, n; 10 | cout << "Please enter c,d,n:" << endl; 11 | cin >> c >> d >> n; 12 | vector gas_station(n); 13 | // 獲取每個加油站的位置 14 | for (int i = 0; i < n; i++){ 15 | cin >> gas_station[i]; 16 | } 17 | // 將加油站依預設規則由小到大排序 18 | sort(gas_station.begin(), gas_station.end()); 19 | // 記錄停靠的加油站數量 20 | int stops = 0; 21 | 22 | // 在終點(最後一格右邊)放一個加油站 23 | // 只要檢查是否有到這個加油站,就知道是否有走到終點 24 | gas_station.push_back(d); 25 | 26 | // 上一個有停靠的加油站是所有加油站中「第幾個」 27 | int last_stop_index = -1; 28 | // 上一個有停靠的加油站座標 29 | int last_stop_position = 0; 30 | 31 | // 尋找「可行範圍內最後面一個加油站」的邏輯是 32 | // 先找到「超過 c 距離外」,最近的一個加油站 33 | // 再往回退一個加油站(退回 c 的距離內) 34 | 35 | // 因為在終點處多加了一個加油站,總共要檢查 n+1 次 36 | for (int i = 0; i < n + 1; i++){ 37 | // 檢查和上次停靠的距離超過 c 後,第一個遇到的加油站 38 | if (gas_station[i] - last_stop_position > c){ 39 | // 上次停靠的加油站是第 i-1 個(i 的上一個) 40 | // 那麼 i-1 和 i 兩個相鄰的加油站間距離超過 c 41 | // 沒有可行解 42 | if (last_stop_index == i - 1){ 43 | cout << "No solution!" << endl; 44 | stops = -1; 45 | break; 46 | } 47 | // 如果上次停的加油站和第 i 個加油站間有加油站 48 | // 就退回這個加油站(可行距離內的最後一個加油站) 49 | last_stop_index = i - 1; 50 | last_stop_position = gas_station[i - 1]; 51 | 52 | // 停靠的加油站增加一個 53 | stops++; 54 | cout << "Stops @" << 55 | last_stop_position << endl; 56 | 57 | // 因為要停第 i - 1 個加油站 58 | // 停完後重新從第 i 個加油站檢查起 59 | i--; 60 | } // end of if 61 | } // end of for 62 | cout << "Stops = " << stops << endl; 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /Chapter07/07_04_eraseOverlapIntervals.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #435. 不重疊的區間 Non-overlapping Intervals 4 | 5 | A. 題目 6 | 給定一個含有多個區間的陣列 intervals,其中 intervals[i] = [start_i,end_i],回傳至少需要移除多少個區間,才能使剩下的區間不互相重疊。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/non-overlapping-intervals/ 10 | 11 | */ 12 | 13 | /* 14 | // 排序根據活動的「開始時間」 15 | // 不過預設排序方式正好與此相同,因此可以不特別加上此函式 16 | bool cmp(vector& interval_1, vector& interval_2){ 17 | return interval_1[0] < interval_2[0]; 18 | } 19 | */ 20 | 21 | class Solution{ 22 | public: 23 | int eraseOverlapIntervals(vector>& intervals){ 24 | // 依照活動的開始時間排序活動,可以用預設方式排序即可 25 | // sort(intervals.begin(), intervals.end(), cmp); 26 | sort(intervals.begin(), intervals.end()); 27 | 28 | // 紀錄刪除了多少個區間 29 | int delete_number = 0; 30 | // 把結束時間 finish 初始化成第一個活動的結束時間 31 | int finish = intervals[0][1]; 32 | 33 | for(vector& interval:intervals){ 34 | int next_start = interval[0]; 35 | int next_finish = interval[1]; 36 | // 兩相鄰活動沒有重疊,不需刪除活動 37 | if(next_start >= finish){ 38 | // 更新 finish 為目前檢查活動的結束時間 39 | finish = next_finish; 40 | } 41 | // 兩相鄰活動有重疊 42 | else{ 43 | // 一定需要刪除其中一個活動 44 | delete_number++; 45 | // 只保留兩者中「較早結束的活動」 46 | // 所以finish 更新為該活動的結束時間 47 | // 即 finish = min(finish, next_finish) 48 | finish = finish < next_finish ? finish : next_finish; 49 | } 50 | } 51 | // 迴圈第一次執行時,第一個活動會和自己比較到 52 | // 所以多計算了一次刪除,需要扣掉 53 | return --delete_number; 54 | } // end of eraseOverlapIntervals 55 | }; // end of Solution 56 | -------------------------------------------------------------------------------- /Chapter07/07_05_maximumUnits.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1710. 裝載最多單位 Maximum Units on a Truck 4 | 5 | A. 題目 6 | 本題要把一些盒子放到卡車上,給定一個陣列 boxTypes, 7 | boxTypes[i] = [numberOfBoxes_i,numberOfUnitsPerBox_i],其中 8 | 9 | a. numberOfBoxes_i 是這種盒子的總數 10 | b. numberOfUnitsPerBox_i 是每個這種盒子中裝有的單位數 11 | 12 | 另外,有一個整數 truckSize 是卡車上最多可以放的盒子數,只要還沒超過這個上限值,可以繼續選擇任一種盒子放到卡車上。 13 | 14 | 回傳可以放到卡車上的最大「單位」數。 15 | 16 | B. 出處 17 | https://leetcode.com/problems/maximum-units-on-a-truck/ 18 | 19 | */ 20 | 21 | // 依照 numberOfUnitsPerBox_i 從大到小排序 22 | bool cmp(vector& box_1, vector& box_2){ 23 | return box_1[1] > box_2[1]; 24 | } 25 | 26 | class Solution{ 27 | public: 28 | int maximumUnits(vector>& boxTypes, int truckSize){ 29 | sort(boxTypes.begin(), boxTypes.end(), cmp); 30 | int total = 0; 31 | 32 | // 從內含單位數最多的箱子種類開始處理 33 | for (vector& box : boxTypes){ 34 | if (truckSize == 0){ break; } 35 | // 取出目前這種箱子的資料 36 | int number = box[0]; 37 | int capacity = box[1]; 38 | 39 | // 可以把目前這種盒子全拿的情形 40 | if (truckSize >= number){ 41 | truckSize -= number; 42 | total += number * capacity; 43 | } 44 | // 剩餘空間不夠全拿的情形 45 | else { 46 | total += truckSize * capacity; 47 | truckSize = 0; 48 | } 49 | } // end of for 50 | return total; 51 | } // end of maximumUnits 52 | }; // end of Solution 53 | -------------------------------------------------------------------------------- /Chapter07/07_06_leastInterval.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #621 工作排程 Task Scheduler 4 | 5 | A. 題目 6 | 給定一個字元陣列 tasks 代表 CPU 需要完成的所有任務,不同的字元代表不同類型的任務,這些任務可以被以任意順序完成。 7 | 每個任務需要一個單位時間來完成,而在任一單位時間內,CPU 可以完成某一個工作,或者什麼也不做。 8 | 另外考慮一個非負整數 n,代表兩個相同種類(用同一個字元來表示)的任務間最少要間隔多久才能進行,也就是說,同樣的兩個任務間必須至少空出 n 單位的時間。 9 | 回傳 CPU 完成所有給定任務最少需要花費的總時間。 10 | 11 | B. 出處 12 | https://leetcode.com/problems/task-scheduler/ 13 | 14 | */ 15 | 16 | class Solution{ 17 | public: 18 | int leastInterval(vector& tasks, int n){ 19 | // 最多可能有 26 種字母 20 | int counts[26] = {0}; 21 | // 記錄每個字母的出現次數 22 | for (char c:tasks){ 23 | counts[c-'A']++; 24 | } 25 | 26 | // 找到出現次數最多的字母其出現次數 27 | int max_frequency = -1; 28 | for (int i = 0; i < 26; i++){ 29 | if (counts[i] > max_frequency){ 30 | max_frequency = counts[i]; 31 | } 32 | } 33 | 34 | // 找到與該出現次數相同的字母總數 35 | // 比如解題邏輯中,A、B 都出現 5 次的情形 36 | int max_frequency_words = 0; 37 | for (int i = 0; i < 26; i++){ 38 | if (counts[i]==max_frequency){ 39 | max_frequency_words++; 40 | } 41 | } 42 | 43 | // 即解題邏輯中的 taskAmount 44 | int len = tasks.size(); 45 | // 即 (n+1)(t-1)+counts 46 | int result = (n + 1) * (max_frequency-1) + 47 | max_frequency_words; 48 | return len > result ? len : result; 49 | } // end of leastInverval 50 | }; // end of Solution 51 | -------------------------------------------------------------------------------- /Chapter07/07_07_minCostToMoveChips.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1217. 移動籌碼到同一位置的最小成本 Minimum Cost to Move Chips to The Same Position 4 | 5 | A. 題目 6 | 總共有 n 個籌碼,position[i] 代表第 i 個籌碼的位置。 7 | 目標是移動所有籌碼到同一個位置上,每次動作可以把某個籌碼從 position[i] 移動到其他位置: 8 | 9 | a. 移動到 position[i]+2 或 position[i]-2,成本為 0 10 | b. 移動到 position[i]+1 或 position[i]-1,成本為 1 11 | 12 | 回傳要把所有籌碼移動到同一個位置需要的最小成本。 13 | 14 | B. 出處 15 | https://leetcode.com/problems/minimum-cost-to-move-chips-to-the-same-position/ 16 | 17 | */ 18 | 19 | class Solution{ 20 | public: 21 | int minCostToMoveChips(vector& position){ 22 | int len = position.size(); 23 | int odd_sum = 0; 24 | int even_sum = 0; 25 | 26 | // 計算奇數籌碼數與偶數籌碼數 27 | // 例如 [2,3,5] 代表共有三個籌碼 28 | // 第一個籌碼在 2、第二個籌碼在 3、第三個籌碼在 5 29 | for(int i : position){ 30 | if(i % 2){ 31 | odd_sum++; 32 | } 33 | else{ 34 | even_sum++; 35 | } 36 | } 37 | // 回傳兩者中較小者 38 | return odd_sum < even_sum ? odd_sum : even_sum; 39 | } // end of minCostToMoveChips 40 | }; // end of Solution 41 | -------------------------------------------------------------------------------- /Chapter07/07_08_balancedStringSplit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1221. 分割出平衡字串 Split a String in Balanced Strings 4 | 5 | A. 題目 6 | 平衡字串是指字串當中 'L' 和 'R' 出現次數相同者。給定一個平衡字串 s,把它切割為若干個平衡子字串,使得切割出的子字串個數最多。回傳該切割出的個數。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/split-a-string-in-balanced-strings/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int balancedStringSplit(string s){ 16 | int L = 0; 17 | int R = 0; 18 | int counts = 0; // 平衡字串的數目 19 | 20 | // 逐一檢查 s 中的字元 21 | for (char c : s){ 22 | if (c == 'L'){ 23 | L++; 24 | } 25 | else{ 26 | R++; 27 | } 28 | // 目前 L 與 R 個數相同時,counts 加一 29 | if (L == R && L){ 30 | L = R = 0; 31 | counts++; 32 | } 33 | } 34 | return counts; 35 | } // end of balancedStringSplit 36 | }; // end of Solution 37 | -------------------------------------------------------------------------------- /Chapter07/07_09_carPooling.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1094. 共享乘車 Car Pooling 4 | 5 | A. 題目 6 | 有一輛共可搭載 capacity 位乘客的車,這輛車只會往東邊開(不會轉彎,也不往西邊開)。 7 | 給定一個陣列 trips,其中 8 | trips[i] = [num_passengers,start_location,end_location] 9 | 10 | 代表第 i 個接駁處有幾位乘客搭車,以及這幾位乘客的上下車地點。地點是以距離出發地東邊的公里數表示。只有在可以成功搭載所有乘客並讓他們在目的地下車時,回傳 true。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/car-pooling/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | public: 19 | bool carPooling(vector>& trips, int capacity){ 20 | // 記錄每個座標位置的乘客「變動數」 21 | // 1001 是 LeetCode 給的測資限制 22 | vector passenger_change(1001,0); 23 | 24 | // 從每筆 trip 資料中,找到乘客會變動的座標 25 | // 乘客上車時,用正數標記該點乘客會「增加」幾人 26 | // 乘客下車時,用負數標記 27 | for (vector& trip : trips){ 28 | int num_passengers = trip[0]; 29 | int start_location = trip[1]; 30 | int end_location = trip[2]; 31 | passenger_change[start_location] += 32 | num_passengers; 33 | passenger_change[end_location] -= 34 | num_passengers; 35 | } 36 | 37 | // 透過 passenger_change 向量 38 | // 檢查過程中是否超過車的座位數 capacity 39 | for (int num : passenger_change){ 40 | // num 為正數(乘客上車),capacity 減少 41 | // num為負數時(乘客下車),capacity 增加 42 | capacity -= num; 43 | // 座位不夠時,回傳 false 44 | if (capacity < 0){ 45 | return false; 46 | } 47 | } 48 | return true; 49 | } // end of carPooling 50 | }; // end of Solution 51 | -------------------------------------------------------------------------------- /Chapter07/07_10_線段覆蓋長度.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:線段覆蓋長度 (2016/03/05 P3) 4 | 5 | A. 題目 6 | 給定一維座標上一些線段,求這些線段所覆蓋的長度,注意,重疊的部分只能算一次。例如給定四個線段,(5, 6)、(1, 2)、(4, 8)、和 (7, 9)。如下圖,線段覆蓋長度為6。 7 | 8 | B. 題目出處 9 | 2016/03/05 APCS 實作題 #3 10 | 本題可以到 zerojudge 上提交程式碼,網址為: 11 | https://zerojudge.tw/ShowProblem?problemid=b966 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | 21 | struct Segment { 22 | int L, R; 23 | }; 24 | 25 | // 依照線段的起始座標排序 26 | bool cmp(Segment &a, Segment &b){ 27 | return a.L < b.L; 28 | } 29 | 30 | int main () { 31 | ios::sync_with_stdio(false); 32 | cin.tie(0); 33 | int N; 34 | cin >> N; 35 | 36 | // 依序輸入資料 37 | vector data(N); 38 | for (int i = 0; i < N; i++){ 39 | int L, R; 40 | cin >> L >> R; 41 | data[i].L = L; 42 | data[i].R = R; 43 | } 44 | // 依照線段的起始座標排序 45 | sort(data.begin(), data.end(), cmp); 46 | // last_R: 上一個線段的結束座標 47 | int ans = 0, last_R = -1; 48 | for (int i = 0; i < N; i++) { 49 | // 如果現在的結束座標在上一個線段的結束座標前面 50 | // 則不需要處理目前的線段 51 | // L<-------------->R 52 | // L<---->R 53 | if (data[i].R < last_R) 54 | continue; 55 | // 如果現在的結束座標在上一個線段的結束座標後面 56 | // 則需要加上: 57 | // 現在結束座標 - max(上一個結束座標, 現在開始座標) 58 | // L<-------------->R 59 | // L<------->R 60 | // L<-------------->R 61 | // L<---->R 62 | ans += data[i].R - 63 | ((last_R > data[i].L) ? last_R : data[i].L); 64 | last_R = data[i].R; 65 | } 66 | cout << ans << endl; 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /Chapter07/07_11_砍樹.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:砍樹 (2020/01 P3) 4 | 5 | A. 題目 6 | 目前有 N 棵樹種在一排,砍樹可以選擇向左或向右倒下,但倒下皆不能超過林場的左右範圍,也不能壓到其它尚未砍除的樹木,若 p[i] 代表第 i 棵樹的位置,h[i] 代表數高,因此倒下的範圍是: p[i]±h[i],若此範圍內有樹則不能砍,但若尚未砍除的樹木剛好在端點則不算壓到,請不斷找到能滿足砍除條件的樹木,直到沒有樹木可以砍為止。 7 | 8 | B. 題目出處 9 | 2020/01 APCS 實作題 #3 10 | 本題可以到 zerojudge 上提交程式碼,網址為: 11 | https://zerojudge.tw/ShowProblem?problemid=h028 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | 21 | int main () { 22 | ios::sync_with_stdio(false); 23 | cin.tie(0); 24 | 25 | int N, L; 26 | cin >> N >> L; 27 | 28 | // 為處理邊界條件 29 | // 在 0、L 各插入一棵無限高的樹 30 | vector p(N + 2, 0); 31 | p[N + 1] = L; 32 | vector h(N + 2, 2147483647); 33 | 34 | // 輸入資料 35 | for (int i = 1; i < N + 1; i++) 36 | cin >> p[i]; 37 | for (int i = 1; i < N + 1; i++) 38 | cin >> h[i]; 39 | 40 | int sum = 0, max = 0; 41 | // 前一棵樹的索引值 42 | stack tree; 43 | tree.push(0); 44 | 45 | for(int i = 1; i < N + 1; i++){ 46 | // 可以往右倒 47 | if(p[i] + h[i] <= p[i + 1] || 48 | // 可以往左倒 49 | p[i] - h[i] >= p[tree.top()]) 50 | { 51 | sum++; 52 | max = max > h[i] ? max : h[i]; 53 | // 不斷往前檢查 54 | while(!tree.empty()){ 55 | int last = tree.top(); 56 | // 可以往右倒 57 | if(p[last] + h[last] <= p[i + 1]){ 58 | sum++; 59 | max = max > h[last] ? 60 | max : h[last]; 61 | tree.pop(); 62 | } 63 | else 64 | break; 65 | } 66 | } 67 | else 68 | tree.push(i); 69 | } 70 | cout << sum << endl; 71 | cout << max << endl; 72 | return 0; 73 | } 74 | -------------------------------------------------------------------------------- /Chapter08/08_01_Triple_Fibo.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | using namespace std; 4 | 5 | int main() 6 | { 7 | long long int triple_fibo[30]; 8 | for (int i = 0; i < 30; i++){ 9 | // 根據定義,前三項為 1 10 | if (i < 3) 11 | triple_fibo[i] = 1; 12 | else 13 | triple_fibo[i] = triple_fibo[i - 1] + 14 | triple_fibo[i - 2] + 15 | triple_fibo[i - 3]; 16 | } 17 | 18 | cout << triple_fibo[29] << endl; 19 | 20 | return 0; 21 | } 22 | -------------------------------------------------------------------------------- /Chapter08/08_02_minCostClimbingStairs.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #746. 爬階梯的最小成本 Min Cost Climbing Stairs 4 | 5 | A. 題目 6 | 給定一個整數陣列 cost,cost[i] 是踏到階梯上第 i 階後需付的成本,付出該成本後,就可以選擇再往上爬一或二階。 7 | 你可以選擇從 index 0 這階開始爬階梯,或者從 index 1 這階開始。回傳要爬完整個階梯到達上方的最小成本。 8 | 9 | B. 出處 10 | https://leetcode.com/problems/min-cost-climbing-stairs/ 11 | 12 | */ 13 | 14 | class Solution{ 15 | public: 16 | int minCostClimbingStairs(vector& cost){ 17 | int len = cost.size(); 18 | 19 | // 記錄到達每一階的最小成本 20 | // 因為在最後面加上一階,所以長度是 len + 1 21 | vector Step(len + 1); 22 | 23 | // 前兩階的最小成本直接取該階需付的成本 24 | Step[0] = cost[0]; 25 | Step[1] = cost[1]; 26 | 27 | // 把加在最後面的一階成本設為 0 28 | cost.push_back(0); 29 | 30 | for (int i = 2; i <= len; i++){ 31 | // 選擇前兩階中成本較小者,再加上該階自己的成本 32 | Step[i] = (Step[i - 1] < Step[i - 2] ? 33 | Step[i - 1] : Step[i - 2]) 34 | + cost[i]; 35 | } 36 | return Step[len]; 37 | } // end of minCostClimbingStairs 38 | }; // end of Solution 39 | -------------------------------------------------------------------------------- /Chapter08/08_02_minCostClimbingStairs_Optimization.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #746. 爬階梯的最小成本 Min Cost Climbing Stairs 4 | 5 | A. 題目 6 | 給定一個整數陣列 cost,cost[i] 是踏到階梯上第 i 階後需付的成本,付出該成本後,就可以選擇再往上爬一或二階。 7 | 你可以選擇從 index 0 這階開始爬階梯,或者從 index 1 這階開始。回傳要爬完整個階梯到達上方的最小成本。 8 | 9 | B. 出處 10 | https://leetcode.com/problems/min-cost-climbing-stairs/ 11 | 12 | */ 13 | 14 | class Solution{ 15 | public: 16 | int minCostClimbingStairs(vector& cost){ 17 | int len = cost.size(); 18 | int step_1 = cost[0]; 19 | int step_2 = cost[1]; 20 | int step_3; 21 | cost.push_back(0); 22 | 23 | for (int i = 2; i <= len; i++){ 24 | step_3 = step_1 < step_2 ? step_1 : step_2; 25 | step_3 += cost[i]; 26 | 27 | // 接下來,更新 step_1 和 step_2 的值 28 | // 使得算下一階時,能用到新的 step_1 和 step_2 29 | // step_2 -> step_1 30 | step_1 = step_2; 31 | // step_3 -> step_2 32 | step_2 = step_3; 33 | } 34 | // 回傳的是迴圈執行完後 step_3 的值 35 | return step_3; 36 | } // end of minCostClimbingStairs 37 | }; // end of Solution 38 | -------------------------------------------------------------------------------- /Chapter08/08_03_coinChange.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #322. 找錢問題 Coin Change 4 | 5 | A. 題目 6 | 給定一個整數陣列 𝑐𝑜𝑖𝑛𝑠 代表不同面額的硬幣,與一個整數 𝑎𝑚𝑜𝑢𝑛𝑡 代表要找的金額。回傳要組成該金額最少需要的硬幣枚數,如果給定的硬幣面額無法組成該要找的金額,回傳 -1。假設每種硬幣使用的數量不受限制。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/coin-change/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int coinChange(vector& coins, int amount){ 16 | // 儲存 n 元需要最少硬幣枚數的陣列 17 | // 初始值設定為 -1,用來判斷是否算過 18 | vector min_coin(amount + 1, -1); 19 | min_coin[0] = 0; 20 | 21 | // 從 n = 1 開始往後算到 amount 22 | for (int i = 1; i <= amount; i++){ 23 | // min_coin[i] = 24 | // min( 25 | // min_coin[i - coins[0]], 26 | // min_coin[i - coins[1]], 27 | // min_coin[i - coins[2]], 28 | // ... 29 | // ) + 1 30 | // int_max, C++ 中常用來代表無限大 31 | int min = 2147483647; 32 | // 目前這個 min_coin[current] 的值 33 | int coin_now; 34 | 35 | for(int coin : coins){ 36 | // 除了最後一枚 coin 外,要找的金額 37 | int current = i - coin; 38 | 39 | // i - coin < 0,沒有這種找法 40 | // coin_now 設為最大值,確保 min 不被改變 41 | if(current < 0) 42 | coin_now = 2147483647; 43 | // i - coin >= 0,可以找找看 44 | else{ 45 | // 如果此時沒辦法找開 46 | if (min_coin[current] == -1) 47 | coin_now = 2147483647; 48 | // 可以找開 49 | else 50 | coin_now = min_coin[current]; 51 | } 52 | // 取最小的硬幣數 53 | min = coin_now < min ? coin_now : min; 54 | } 55 | 56 | // 如果 min 還是初始值,代表無法找開,回傳 -1 57 | if(min == 2147483647){ 58 | min_coin[i] = -1; 59 | } 60 | else{ 61 | // 否則是 min 枚硬幣加上最後那枚 62 | min_coin[i] = min + 1; 63 | } 64 | } // end of for 65 | return min_coin[amount]; 66 | } // end of coinChange 67 | }; // end of Solution 68 | -------------------------------------------------------------------------------- /Chapter08/08_04_maxSubArray.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #53. 最大子數列 Maximum Subarray 4 | 5 | A. 題目 6 | 給定一個整數陣列 nums,找到最大子數列(至少包含一個元素)使得其和為最大,並回傳該和。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/maximum-subarray/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int maxSubArray(vector& nums){ 16 | int len = nums.size(); 17 | // 例外處理:只有一個元素時,回傳該元素值 18 | if(len == 1) 19 | return nums[0]; 20 | 21 | // 開出新陣列 DP 22 | vector DP(len); 23 | // DP[0] 與第一個元素的值相同 24 | DP[0] = nums[0]; 25 | 26 | // 算出 DP 陣列中的值 27 | for (int i = 1; i < len; i++){ 28 | // DP 陣列中前一個值 29 | int before_now = DP[i - 1]; 30 | // 目前處理的新元素值 31 | int now = nums[i]; 32 | // 目前處理的元素往左延伸可以得到的最大值 33 | int add_before = now + before_now; 34 | // 決定是否往左延伸 35 | DP[i] = add_before > now ? add_before : now; 36 | } 37 | 38 | // 負無限大 39 | int max = -2147483648; 40 | // 找出 DP 陣列中的最大值 41 | for (int i = 0; i < len; i++){ 42 | max = max > DP[i] ? max : DP[i]; 43 | } 44 | return max; 45 | } // end of maxSubArray 46 | }; // end of Solution 47 | -------------------------------------------------------------------------------- /Chapter08/08_05_jobScheduling.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1235. 最大收益的工作排程 Maximum Profit in Job Scheduling 4 | 5 | A. 題目 6 | 給定 n 項工作,每個工作開始時間與結束時間分別為 startTime[i] 到 endTime[i],收益為 profit[i]。 7 | 給定三個陣列 startTime、endTime 與 profit,回傳工作間佔用時間不重疊的前提下,能夠達到的最大收益。 8 | 如果一個工作結束時間是 X,允許同時選擇一個開始時間是 X 的工作。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/maximum-profit-in-job-scheduling/ 12 | 13 | */ 14 | 15 | // 存放工作資訊的結構 16 | typedef struct{ 17 | int start; 18 | int finish; 19 | int profit; 20 | } job; 21 | 22 | // 根據工作的結束時間排序,早結束的在前面 23 | bool cmp(job* a, job* b){ 24 | return a->finish < b->finish; 25 | } 26 | 27 | class Solution{ 28 | public: 29 | int jobScheduling(vector& startTime, vector& endTime, vector& profit){ 30 | int len = startTime.size(); 31 | // 存放工作資訊的向量,使用指標,排序時會較快 32 | vector jobs(len); 33 | 34 | // 把工作資訊存放入向量中 35 | for (int i = 0; i < len; i++){ 36 | jobs[i] = new job{startTime[i], endTime[i], profit[i]}; 37 | } 38 | 39 | // 把工作根據結束時間排序 40 | sort(jobs.begin(), jobs.end(), cmp); 41 | // 到每一次工作為止的最大利潤,有 len 個活動 42 | vector max_profit(len + 1, 0); 43 | 44 | for (int i = 1; i <= len; i++){ 45 | // 取出當前這筆工作的資料 46 | int start = jobs[i - 1]->start; 47 | int finish = jobs[i - 1]->finish; 48 | int profit = jobs[i - 1]->profit; 49 | 50 | // 第一個活動的特例 51 | if(i == 1){ 52 | max_profit[i] = profit; 53 | continue; 54 | } 55 | 56 | // 若選擇當前這個工作可得的收益 57 | // 需往前找到上一個可行的工作 58 | int choose = profit; 59 | for(int j = i - 2; j >= 0; j--){ 60 | if(jobs[j]->finish <= start){ 61 | choose += max_profit[j + 1]; 62 | break; 63 | } 64 | } 65 | 66 | // 若不選擇當前這個工作可得的為上一個的收益 67 | int not_choose = max_profit[i - 1]; 68 | 69 | // 先比較選與不選兩個選項 70 | int max_choose = choose > not_choose ? choose : not_choose; 71 | 72 | // 再來要比較同一時間結束的不同工作中有較大收益者 73 | max_profit[i] = max_profit[i] > max_choose ? max_profit[i] : max_choose; 74 | } 75 | // 回傳最後一個工作結束時間的 max_profit 76 | return max_profit.back(); 77 | }; // end of jobScheduling 78 | }; // end of Solution 79 | -------------------------------------------------------------------------------- /Chapter08/08_06_Cut_Rod_DC.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | // *p:價格陣列 7 | // p_len:p 的長度(即木頭種類) 8 | // p[0]:長度 1 的價格、p[1]:長度 2 的價格、... 9 | // n:木頭長度 10 | int Cut_Rod(int* p, int p_len, int n){ 11 | if(n == 0) 12 | return 0; 13 | int revenue = -2147483648; 14 | for(int i = 0; i < p_len; i++){ 15 | if(n >= i + 1){ 16 | // 注意 p[0] 對應長度 1、p[1] 對應長度 2 17 | int revenue_i = p[i] + 18 | Cut_Rod(p, p_len, n - i - 1); 19 | revenue = revenue > revenue_i ? 20 | revenue : revenue_i; 21 | } 22 | } 23 | return revenue; 24 | } 25 | 26 | int main(){ 27 | int p[5] = {10, 22, 35, 45, 56}; 28 | int len; 29 | cout << "Please enter len:" << endl; 30 | cin >> len; 31 | 32 | cout << "Max Profit = " << Cut_Rod(p, 5, len) << endl; 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /Chapter08/08_06_Cut_Rod_DP.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | // *p:價格陣列 7 | // p_len:p 的長度(即木頭種類) 8 | // p[0]:長度 1 的價格、p[1]:長度 2 的價格、... 9 | // n:木頭長度 10 | int Cut_Rod(int* p, int p_len, int n){ 11 | if (n == 0) 12 | return 0; 13 | // 儲存每種長度木頭的最大價值 14 | int revenue_array[n + 1] = {0}; 15 | for (int i = 1; i <= n; i++){ 16 | // 初始化為負無限大 17 | int max_revenue = -2147483648; 18 | for (int j = 0; j < p_len; j++){ 19 | // 要切割的長度為 j + 1 20 | // 切割長度大於目前有的木頭長度時 21 | if (j + 1 > i) break; 22 | // 一般情形,注意 p[0] 對應長度 1 的價格 23 | int revenue_j = 24 | p[j] + revenue_array[i - j - 1]; 25 | max_revenue = max_revenue > revenue_j ? 26 | max_revenue : revenue_j; 27 | } 28 | // 每次都記錄下算出的(長度 i 的木頭)結果 29 | revenue_array[i] = max_revenue; 30 | } 31 | return revenue_array[n]; 32 | } 33 | 34 | int main(){ 35 | int p[5] = {10, 22, 35, 45, 56}; 36 | int len; 37 | cout << "Please enter len:" << endl; 38 | cin >> len; 39 | 40 | cout << "Max Profit = " << Cut_Rod(p, 5, len) << endl; 41 | return 0; 42 | } 43 | -------------------------------------------------------------------------------- /Chapter08/08_07_integerBreak.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #343. 拆分整數 Integer Break 4 | 5 | A. 題目 6 | 給定一個整數 n,將它拆分為 k 個正整數的和,其中 k≥2。找到可使這些拆分出的整數乘積最大的拆分方法,並回傳該乘積。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/integer-break/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int integerBreak(int n){ 16 | // 儲存中間結果的陣列 17 | vector DP(n + 1, 1); 18 | 19 | for (int i = 2; i < n + 1; i++){ 20 | int max = -2147483648; 21 | for (int j = 1; j < i; j++){ 22 | // i - j 繼續拆分與不拆分兩者間,取較大的 23 | int now = j * DP[i - j] > j * (i - j) ? 24 | j * DP[i - j] : j * (i - j); 25 | max = now > max ? now : max; 26 | } 27 | // 儲存得到的結果 28 | DP[i] = max; 29 | } 30 | return DP[n]; 31 | } // end of integerBreak 32 | }; // end of Solution 33 | -------------------------------------------------------------------------------- /Chapter08/08_08_01背包問題.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | int main() 7 | { 8 | // 背包容量上限 9 | int weight_limit = 10; 10 | vector weight{4, 5, 2, 6, 3, 7}; 11 | vector value{6, 7, 5, 12, 5, 18}; 12 | // 物品數目 13 | int number = weight.size(); 14 | // 宣告 (number + 1) * (weight_limit + 1) 的二維陣列 15 | vector> DP( 16 | number + 1, 17 | vector(weight_limit + 1, 0) 18 | ); 19 | // 物品重量與價值前插入 0 20 | weight.insert(weight.begin(), 0); 21 | value.insert(value.begin(), 0); 22 | 23 | for(int j = 1; j <= weight_limit; j++){ 24 | for(int i = 1; i <= number; i++){ 25 | if(j < weight[i]) 26 | // 背包容量不足,不挑第 i 項物品 27 | DP[i][j] = DP[i - 1][j]; 28 | else{ 29 | // 不挑第 i 項物品 30 | int not_take = DP[i - 1][j]; 31 | // 不挑第 i 項物品 32 | int take = DP[i - 1][j - weight[i]] 33 | + value[i]; 34 | // 從挑與不挑中選價值大的 35 | DP[i][j] = not_take > take ? 36 | not_take : take; 37 | } 38 | } 39 | } 40 | 41 | for(int i = 0; i <= number; i++){ 42 | for(int j = 0; j <= weight_limit; j++){ 43 | cout << DP[i][j] << "\t"; 44 | } 45 | cout << endl; 46 | } 47 | 48 | cout << "Maximal value = " 49 | << DP[number][weight_limit] << endl; 50 | return 0; 51 | } 52 | -------------------------------------------------------------------------------- /Chapter08/08_09_change_1D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #518. 找錢問題2 Coin Change 2 4 | 5 | A. 題目 6 | 給定一個整數陣列 coins 代表每種硬幣的面額,與一個整數 amount 代表要找的金額。 7 | 8 | 回傳可以組成該金額的方法數,如果沒有任何方法可以組成該金額,回傳 0。 9 | 假設每種面額的硬幣數不限。 10 | 11 | B. 出處 12 | https://leetcode.com/problems/coin-change-2/ 13 | 14 | */ 15 | 16 | class Solution{ 17 | public: 18 | int change(int amount, vector& coins){ 19 | int len = coins.size(); 20 | vector DP(amount+1,0); 21 | DP[0] = 1; 22 | 23 | for (int coin:coins){ 24 | // i 要從 coin 開始,避免 i-coin<0 的情況 25 | for (int i = coin; i <= amount; i++){ 26 | DP[i] += DP[i - coin]; 27 | } 28 | } // end of for 29 | return DP[amount]; 30 | } // end of change 31 | }; // end of Solution 32 | -------------------------------------------------------------------------------- /Chapter08/08_09_change_2D.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #518. 找錢問題2 Coin Change 2 4 | 5 | A. 題目 6 | 給定一個整數陣列 coins 代表每種硬幣的面額,與一個整數 amount 代表要找的金額。 7 | 8 | 回傳可以組成該金額的方法數,如果沒有任何方法可以組成該金額,回傳 0。 9 | 假設每種面額的硬幣數不限。 10 | 11 | B. 出處 12 | https://leetcode.com/problems/coin-change-2/ 13 | 14 | */ 15 | 16 | class Solution{ 17 | public: 18 | int change(int amount, vector& coins){ 19 | int len = coins.size(); 20 | // 陣列大小為 len + 1 個 row 21 | // amount + 1 個 column 22 | vector> DP( 23 | len + 1, 24 | vector(amount + 1, 0) 25 | ); 26 | 27 | DP[0][0] = 1; 28 | 29 | // 用雙重迴圈逐一填上 DP 的值 30 | // 不用填第 0 個 row,因為值和預設值 0 相同 31 | for (int i = 1; i <= len; i++){ 32 | DP[i][0] = 1; // 第 0 個 column 填上 1 33 | 34 | for (int j = 1; j <= amount; j++){ 35 | // DP[i][j] = DP[i-1][j] + DP[i][j-coins[i-1]] 36 | // 不使用第 i 種硬幣 + 使用第 i 種硬幣 37 | DP[i][j] += DP[i-1][j]; 38 | if (j - coins[i - 1] >= 0){ 39 | DP[i][j] += DP[i][j - coins[i - 1]]; 40 | } 41 | } // end of inner for 42 | } // end of outer for 43 | // 回傳值中 len 代表考慮使用所有種類的硬幣 44 | // amount 代表要湊出 amount 元 45 | return DP[len][amount]; 46 | } // end of change 47 | }; // end of Solution 48 | -------------------------------------------------------------------------------- /Chapter08/08_10_矩陣鏈乘.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | void Matrix_Chain(vector dimension) { 7 | int len = dimension.size(); 8 | // 宣告 len * len 的二維陣列 9 | // DP[i][j] 代表 M(i,j) 10 | // M(i,j) = 從 i 到 j 運算最少的次數 11 | vector> DP(len, vector (len, 0)); 12 | 13 | // 第 k 輪 14 | for (int k = 1; k < len; k++){ 15 | // 第 i, j 筆資料 16 | for (int i = 1; i < len - k; i++){ 17 | int j = i + k; 18 | // 計算 DP[i][j],因需取最小值 19 | // 故初始化成 INT_MAX,替代無限大 20 | DP[i][j] = 2147483647; 21 | // i ~ j - 1, 計算最小值 22 | for (int k = i; k <= j-1; k++) 23 | { 24 | int tmp_1 = dimension[i - 1] * dimension[k] * dimension[j]; 25 | int tmp_2 = DP[i][k] + DP[k + 1][j] + tmp_1; 26 | if (tmp_2 < DP[i][j]){ 27 | DP[i][j] = tmp_2; 28 | } 29 | } 30 | } 31 | } 32 | // 印出動態規劃的二維陣列 33 | for(int i = 1; i < len; i++){ 34 | for(int j = 1; j < len; j++){ 35 | cout << DP[i][j] << "\t"; 36 | } 37 | cout << endl; 38 | } 39 | cout << "Result: " << DP[1][len - 1]; 40 | } 41 | 42 | int main() 43 | { 44 | vector dimension = {8, 3, 1, 9, 4, 3, 6, 10, 2}; 45 | // 印出所有維度 46 | cout << "Dimensions: "; 47 | for(int data : dimension) 48 | cout << data << " "; 49 | cout << endl; 50 | cout << "DP Table:" << endl; 51 | Matrix_Chain(dimension); 52 | return 0; 53 | } 54 | -------------------------------------------------------------------------------- /Chapter08/08_11_LIS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | int LIS(vector& nums) { 7 | int len = nums.size(); 8 | vector L(len); 9 | for(int i = 0; i < len; i++){ 10 | int max_L = 0; 11 | for(int j = 0; j < i; j++){ 12 | // 找出所有滿足 nums[j] < nums[i] 的 j 中 13 | // L[j] 最大者 14 | if(nums[j] < nums[i]){ 15 | if(L[j] > max_L){ 16 | max_L = L[j]; 17 | } 18 | } 19 | } 20 | // nums[i] 可接在長度為 max_L 的最大子序列以後 21 | L[i] = max_L + 1; 22 | } 23 | // 找出 L 中的最大值 24 | int LIS = 0; 25 | for(int data : L){ 26 | if(data > LIS) LIS = data; 27 | } 28 | return LIS; 29 | } 30 | 31 | int main() 32 | { 33 | vector nums = {2, 4, 7, 4, 5, 3, 8, 2, 9}; 34 | 35 | cout << "LIS: " << LIS(nums); 36 | return 0; 37 | } 38 | -------------------------------------------------------------------------------- /Chapter08/08_12_lengthOfLIS.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #300. 最長遞增子序列 Longest Increasing Subsequence 4 | 5 | A. 題目 6 | 給定一陣列,從中保留先後順序並挑選出一序列,使序列內的內容為嚴格遞增,並求此遞增子序列的最大可能長度。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/longest-increasing-subsequence/ 10 | 11 | */ 12 | 13 | class Solution { 14 | public: 15 | int lengthOfLIS(vector& nums) { 16 | int len = nums.size(); 17 | // 把動態規劃陣列初始化為無限大 18 | // DP[i]:長度為 i 的最大遞增子序列的最後數字 19 | vector DP(len + 1, 2147483647); 20 | DP[0] = -2147483648; 21 | for (int data : nums) { 22 | // 利用二分搜尋法找出 data 的 lower bound 23 | auto target = lower_bound( 24 | DP.begin(), DP.end(), data 25 | ); 26 | *target = data; 27 | } 28 | // 找出無限大出現在最左邊的哪個位置 29 | auto result = lower_bound( 30 | DP.begin(), DP.end(), 2147483647 31 | ); 32 | // 該位置跟 vector 的起點距離再 - 1即是所求 33 | return result - DP.begin() - 1; 34 | } 35 | }; 36 | -------------------------------------------------------------------------------- /Chapter08/08_13_LCS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | int LCS(string& S1, string& S2){ 8 | int n1 = S1.length(); 9 | int n2 = S2.length(); 10 | // 宣告一個大小為 (n1 + 1) x (n2 + 1) 的陣列 11 | // 並初始化為 0 12 | vector> DP(n1 + 1, vector (n2 + 1, 0)); 13 | // 依序填表 14 | for(int i = 1; i <= n1; i++){ 15 | for(int j = 1; j <= n2; j++){ 16 | // 最後一個字元相同 17 | if(S1[i - 1] == S2[j - 1]){ 18 | DP[i][j] = DP[i - 1][j - 1] + 1; 19 | } 20 | // 最後一個字元不相同 21 | else{ 22 | DP[i][j] = DP[i - 1][j] > DP[i][j - 1] ? DP[i - 1][j] : DP[i][j - 1]; 23 | } 24 | } 25 | } 26 | // 印出表格 27 | cout << "DP Table:" << endl; 28 | for(int i = 0; i <= n1; i++){ 29 | for(int j = 0; j <= n2; j++){ 30 | // 以寬度 3 輸出每一筆資料 31 | cout << setw(3) << DP[i][j]; 32 | } 33 | cout << endl; 34 | } 35 | // 答案在 DP[n1][n2] 36 | return DP[n1][n2]; 37 | } 38 | 39 | int main() 40 | { 41 | string s1 = "GRADUATE"; 42 | string s2 = "GUAVA"; 43 | cout << "s1 = " << s1 << endl; 44 | cout << "s2 = " << s2 << endl; 45 | cout<<"Length of LCS: "<< LCS(s1, s2); 46 | 47 | return 0; 48 | } 49 | -------------------------------------------------------------------------------- /Chapter08/08_13_LCS_輸出.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | using namespace std; 7 | 8 | int LCS(string& S1, string& S2){ 9 | int n1 = S1.length(); 10 | int n2 = S2.length(); 11 | // 宣告一個大小為 (n1 + 1) x (n2 + 1) 的陣列 12 | // 並初始化為 0 13 | vector> DP( 14 | n1 + 1, 15 | vector (n2 + 1, 0) 16 | ); 17 | // 記錄方向用 18 | vector> Direction( 19 | n1 + 1, 20 | vector (n2 + 1, 0) 21 | ); 22 | // 依序填表 23 | for(int i = 1; i <= n1; i++){ 24 | for(int j = 1; j <= n2; j++){ 25 | // 最後一個字元相同 26 | if(S1[i - 1] == S2[j - 1]){ 27 | DP[i][j] = DP[i - 1][j - 1] + 1; 28 | // 從左上來的 29 | Direction[i][j] = 1; 30 | } 31 | // 最後一個字元不相同 32 | else{ 33 | if(DP[i - 1][j] > DP[i][j - 1]){ 34 | // 從上方來 35 | DP[i][j] = DP[i - 1][j]; 36 | Direction[i][j] = 2; 37 | } 38 | else{ 39 | // 從左方來 40 | DP[i][j] = DP[i][j - 1]; 41 | Direction[i][j] = 3; 42 | } 43 | } 44 | } 45 | } 46 | // 印出表格 47 | cout << "DP Table:" << endl; 48 | for(int i = 0; i <= n1; i++){ 49 | for(int j = 0; j <= n2; j++){ 50 | // 以寬度 3 輸出每一筆資料 51 | cout << setw(3) << DP[i][j] << " "; 52 | // 印出方向 53 | if(Direction[i][j] == 1) 54 | cout << "↖"; 55 | else if(Direction[i][j] == 2) 56 | cout << "↑"; 57 | else 58 | cout << "←"; 59 | } 60 | cout << endl; 61 | } 62 | // 因為順序相反,最先新增的字元最後輸出 63 | // 故把路徑存在 stack 中便可以反向印出 64 | stack LCS; 65 | int x = n1, y = n2; 66 | while(DP[x][y]){ 67 | // 左上 68 | if(Direction[x][y] == 1){ 69 | LCS.push(S1[x - 1]); 70 | x--; 71 | y--; 72 | } 73 | // 上 74 | else if(Direction[x][y] == 2) 75 | x--; 76 | // 左 77 | else 78 | y--; 79 | } 80 | cout << endl; 81 | // 印出 stack 的資料 82 | cout << "LCS: "; 83 | while(!LCS.empty()){ 84 | cout << LCS.top(); 85 | LCS.pop(); 86 | } 87 | cout << endl; 88 | // 答案在 DP[n1][n2] 89 | return DP[n1][n2]; 90 | } 91 | int main(){ 92 | string s1 = "ACGACTGGT"; 93 | string s2 = "CAGTCAACT"; 94 | cout << "s1 = " << s1 << endl; 95 | cout << "s2 = " << s2 << endl; 96 | cout<<"Length of LCS: "<< LCS(s1, s2); 97 | return 0; 98 | } 99 | -------------------------------------------------------------------------------- /Chapter08/08_14_minDistance.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #583. 字串間的刪除次數 Delete Operation for Two Strings 4 | 5 | A. 題目 6 | 給定兩字串,請告訴我至少要刪除幾次字元,每次只能刪去一個字元,才可以讓這兩字串的內容完全一致? 7 | 8 | B. 出處 9 | https://leetcode.com/problems/delete-operation-for-two-strings/ 10 | 11 | */ 12 | 13 | class Solution { 14 | public: 15 | int minDistance(string word1, string word2) { 16 | int n1 = word1.length(); 17 | int n2 = word2.length(); 18 | // 宣告一個大小為 (n1 + 1) x (n2 + 1) 的陣列 19 | // 並初始化為 0 20 | vector> DP(n1 + 1, vector (n2 + 1, 0)); 21 | // DP[i][0]: 長度 i 的資料要刪幾次才會跟 0 一樣 22 | // Ans: i 次 23 | for(int i = 0; i <= n1; i++){ 24 | DP[i][0] = i; 25 | } 26 | // DP[0][i]: 長度 i 的資料要刪幾次才會跟 0 一樣 27 | // Ans: i 次 28 | for(int i = 0; i <= n2; i++){ 29 | DP[0][i] = i; 30 | } 31 | // 依序填表 32 | for(int i = 1; i <= n1; i++){ 33 | for(int j = 1; j <= n2; j++){ 34 | // 最後一個字元相同 35 | if(word1[i - 1] == word2[j - 1]){ 36 | DP[i][j] = DP[i - 1][j - 1]; 37 | } 38 | // 最後一個字元不相同 39 | else{ 40 | DP[i][j] = DP[i - 1][j] < DP[i][j - 1] ? DP[i - 1][j] : DP[i][j - 1]; 41 | DP[i][j]++; 42 | } 43 | } 44 | } 45 | return DP[n1][n2]; 46 | } 47 | }; 48 | -------------------------------------------------------------------------------- /Chapter08/08_15_置物櫃出租.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:置物櫃出租 (2018/10 P4) 4 | 5 | A. 題目 6 | 李老闆有 M 個置物櫃,目前已經租給 N 個客戶,已知第 i 個客戶租了 C(i) 個櫃子,但現在李老闆自己需要使用 T 個櫃子,如果剩下的空櫃不夠,他需要把部分櫃子從客戶那清空,而且每個客戶只能選擇全退或全不退,已知第 i 個客戶的利潤與客戶所租的櫃數 C(i) 相同,請計算李老闆的最小損失。 7 | 8 | B. 題目出處 9 | 2018/10 APCS實作題 #4 10 | 本題可於 一中電腦資訊研究社 Online Judge中測試,網址如下: 11 | https://judge.tcirc.tw/ShowProblem?problemid=d075 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | int main() 21 | { 22 | int M, N, T; 23 | cin >> N >> M >> T; 24 | vector space(N); 25 | for(int i = 0; i < N; i++) 26 | cin >> space[i]; 27 | 28 | // 宣告 (N + 1) * (M - T + 1) 的二維陣列 29 | vector> DP(N + 1, vector(M - T + 1, 0)); 30 | // 客戶的空間前插入 0 31 | space.insert(space.begin(), 0); 32 | 33 | for(int j = 1; j <= M - T; j++){ 34 | for(int i = 1; i <= N; i++){ 35 | if(j < space[i]) 36 | // 櫃子不足,不挑第 i 個客戶 37 | DP[i][j] = DP[i - 1][j]; 38 | else{ 39 | // 不挑第 i 個客戶 40 | int not_take = DP[i - 1][j]; 41 | // 挑第 i 項物品 42 | int take = DP[i - 1][j - space[i]] + space[i]; 43 | // 從挑與不挑中選價值大的 44 | DP[i][j] = not_take > take ? not_take : take; 45 | } 46 | } 47 | } 48 | 49 | int sum = 0; 50 | // 所有客戶的利潤加總 51 | for(int value : space) 52 | sum += value; 53 | 54 | cout << sum - DP[N][M - T]; 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /Chapter08/08_16_勇者修煉.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:勇者修煉 (2020/10 P3) 4 | 5 | A. 題目 6 | 一開始有 M×N 的二維陣列 E,陣列內的資料為 -100 ~ +100,代表勇者落腳在該處會得到的經驗值,一開始勇者可以從第一列的任意位置開始,且每一步可以向左、向右、向下走,但不能回頭走過去已經走過的路,請算出勇者走到最後一列後,最多可以得到多少經驗值。 7 | 8 | B. 題目出處 9 | 2020/10 APCS實作題 #3 10 | 本題可於 Zerojudge 中測試,網址如下: 11 | https://zerojudge.tw/ShowProblem?problemid=f314 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | 18 | using namespace std; 19 | 20 | int main () { 21 | ios::sync_with_stdio(false); 22 | cin.tie(0); 23 | 24 | int M, N; 25 | cin >> M >> N; 26 | 27 | // E[j]:(i,j) 的經驗值 28 | // L[j]:從左邊至 i 的最大值 29 | // R[j]:從右邊至 i 的最大值 30 | // DP[i][j]:至(i,j)的最大值 31 | vector E(N + 1, 0); 32 | vector L(N + 2, 0); 33 | vector R(N + 2, 0); 34 | // 宣告 (M + 1)*(N + 2) 的二維陣列 35 | vector> DP((M + 1), vector(N + 2, 0)); 36 | 37 | // 左右為 INT_MIN 38 | L[0] = L[N + 1] = -2147483648; 39 | R[0] = R[N + 1] = -2147483648; 40 | 41 | // 逐行輸入並計算 DP 42 | for (int i = 1; i <= M; i++) { 43 | // 輸入資料 44 | for (int j = 1; j <= N; j++) 45 | cin >> E[j]; 46 | 47 | // 從左到右計算 48 | for (int j = 1; j <= N; j++) 49 | L[j] = max(L[j - 1], DP[i - 1][j]) + E[j]; 50 | 51 | // 從右到左計算 52 | for (int j = N; j > 0; j--) 53 | R[j] = max(R[j + 1], DP[i - 1][j]) + E [j]; 54 | 55 | // 計算 DP[i][j] 56 | for (int j = 1; j <= N; j++) 57 | DP[i][j] = max(L[j], R[j]); 58 | } 59 | 60 | // 找出最大值 61 | long long int ans = 0; 62 | for (int j = 1; j <= N; j++) 63 | if (DP[M][j] > ans) 64 | ans = DP[M][j]; 65 | cout << ans << endl; 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /Chapter08/08_17_飛黃騰達.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:飛黃騰達 (2021/01/09 P4) 4 | 5 | A. 題目 6 | 二維平面上的第一象限裡有許多點,意即 x,y≥0。請試著任選一個點出發,並且試著任選一個點出發,並嘗試走訪越多的點越好,但規定走訪的過程中只能從往x正向與y正向的方向走,比方說如果你從 〖(x〗_1,y_i) 走到 〖(x〗_2,y_2) ,即代表 x_2-x_1≥0 而且 y_2-y_1≥0,請找出在這個規則之下,你最多能夠走訪幾個點。 7 | 8 | B. 題目出處 9 | 2021/01/09 APCS實作題 #4 10 | 本題可於 Zerojudge 中測試,網址如下: 11 | https://zerojudge.tw/ShowProblem?problemid=f608 12 | 13 | */ 14 | 15 | #include 16 | #include 17 | #include 18 | 19 | using namespace std; 20 | 21 | typedef struct{ 22 | int x, y; 23 | } Point; 24 | 25 | bool cmp(Point*& a, Point*& b){ 26 | // 根據 x 座標由小到大排 27 | // 如果 x 座標相同,則根據 y 座標由小到大排 28 | // 注意這裡傳入資料為指標 29 | if((a->x) != (b->x)) 30 | return (a->x) < (b->x); 31 | else 32 | return (a->y) < (b->y); 33 | } 34 | 35 | int main(){ 36 | int len; 37 | cin >> len; 38 | // 把 data 長度初始化成 len 39 | vector data(len); 40 | // 輸入資料 41 | for(int i = 0; i < len; i++){ 42 | int tmp_x, tmp_y; 43 | cin >> tmp_x >> tmp_y; 44 | data[i] = new Point{tmp_x, tmp_y}; 45 | } 46 | 47 | // 把資料按照題目要求加以排序 48 | // 排序後的資料才能進行二分搜尋 49 | sort(data.begin(), data.end(), cmp); 50 | // DP[i] 表示長度為 i 的路徑中最小的 y 為多少 51 | vector DP(len + 1, 2147483647); 52 | DP[0] = -2147483648; 53 | 54 | // 按照 x 由小到大取出所有資料 55 | // 取出過程中的 y 值只能遞增,並盡可能選越多點 56 | // 為最長遞增子數列的搜尋 57 | for(auto point : data){ 58 | // 利用二分搜尋法找出 y 的 upper bound 59 | // 不須嚴格遞增,所以找的是 upper bound 60 | auto iter= upper_bound(DP.begin(), DP.end(), point->y); 61 | *iter = point->y; 62 | } 63 | auto result = lower_bound(DP.begin(), DP.end(), 2147483647); 64 | // 該位置跟 vector 的起點距離再 - 1即是所求 65 | cout << result - DP.begin() - 1; 66 | return 0; 67 | } 68 | -------------------------------------------------------------------------------- /Chapter09/09_01_findJudge.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #997. 尋找小鎮法官 Find the Town Judge 4 | 5 | A. 題目 6 | 在一個小鎮裡,有 n 個居民分別被編號為 1 到 n。傳言其中一個居民其實是秘密的小鎮法官。 7 | 8 | 如果小鎮法官存在,那麼: 9 | a. 該法官不信任任何人 10 | b. 所有人(除了法官自己)都信任小鎮法官 11 | c. 只有剛好一個人滿足前兩項條件 12 | 13 | 給定一個向量 trust,trust[i] = [a,b] 代表 a 居民信任 b 居民。如果小鎮法官存在,且身份可以被判斷,回傳小鎮法官的編號,否則回傳 -1。 14 | 15 | B. 出處 16 | https://leetcode.com/problems/find-the-town-judge/ 17 | 18 | */ 19 | 20 | class Solution{ 21 | public: 22 | int findJudge(int n, vector>& trust){ 23 | // 用兩個向量分別記錄每個點的入度與出度 24 | // 入度與出度初始化為 0 25 | vector in_degree(n, 0); 26 | vector out_degree(n, 0); 27 | 28 | // 依序取出每個邊 29 | // 根據邊的資訊,統計每個頂點的入度與出度 30 | for (vector& edge: trust){ 31 | // 邊的方向為 edge[0] -> edge[1] 32 | // 居民編號為 1 到 n 33 | // 對應索引值編號 0 到 n - 1 34 | in_degree[edge[1] - 1]++; 35 | out_degree[edge[0] - 1]++; 36 | } 37 | 38 | // 檢查每個邊的入度與出度 39 | for (int i = 0; i < n; i++){ 40 | // 有符合小鎮法官定義的居民時,回傳編號 41 | if (in_degree[i] == n - 1 && out_degree[i] == 0){ 42 | return i + 1; 43 | } 44 | } 45 | // 沒有符合條件的頂點時 46 | return -1; 47 | } // end of findJudge 48 | }; // end of Solution 49 | -------------------------------------------------------------------------------- /Chapter09/09_02_findCenter.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1791. 找到星形圖的中心 Find Center of Star Graph 4 | 5 | A. 題目 6 | 有一個無向的星型圖,其 n 個節點分別被編號為 1 到 n。一個星型圖有一個中心節點,且該節點正好透過 n-1 個邊連接到其他所有的節點。 7 | 8 | 給定一個二維整數向量 edges,其中 edges[i] = [u_i,v_i] 代表節點 u_i 與 v_i 間有一條邊。回傳該星型圖的中心節點。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/find-center-of-star-graph/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | int findCenter(vector>& edges){ 18 | // 邊的數目 + 1 = 頂點數目 19 | int len = edges.size() + 1; 20 | // 本題為無向圖 21 | // 用向量記錄每個頂點的度數 22 | vector edge_connected(len, 0); 23 | 24 | // 依序取出每個邊 25 | // 邊連接到的兩個節點度數都要 + 1 26 | for (auto& edge:edges){ 27 | edge_connected[edge[0] - 1]++; 28 | edge_connected[edge[1] - 1]++; 29 | } 30 | 31 | // 檢查每個邊的度數 32 | // 中心節點的度數為 n-1 33 | for (int i = 0; i < len; i++){ 34 | if (edge_connected[i] == len - 1){ 35 | return i + 1; 36 | } 37 | } 38 | return -1; 39 | } // end of find Center; 40 | }; // end of Solution 41 | -------------------------------------------------------------------------------- /Chapter09/09_03_鄰接矩陣.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | // 圖的類別 8 | class Graph{ 9 | private: 10 | // 頂點的個數 11 | int vertex; 12 | // 用雙重指標來儲存邊 13 | int **matrix; 14 | // 也可以使用二維向量 15 | // vector> matrix; 16 | public: 17 | // 建構式 18 | Graph(int); 19 | // 印出所有的邊 20 | void Print_Matrix(); 21 | // 新增一條邊(起點,終點,權重), 成功回傳 true 22 | // 否則(邊已存在時)回傳 false 23 | bool Add_Edge(int, int, int=1); 24 | }; 25 | 26 | // 建構式 27 | Graph::Graph(int v){ 28 | vertex = v; 29 | // vertex 個指標構成的整數指標陣列 30 | matrix = (int**) malloc(sizeof(int*) *vertex); 31 | 32 | // 把每個整數指標長度設為 vertex 33 | // calloc 會把值初始化為 0 34 | for (int i=0 ; i(vertex, 0)); 40 | } 41 | 42 | // 印出所有的邊,等同於印出一個二維陣列 43 | void Graph::Print_Matrix(){ 44 | for(int i = 0; i < vertex; i++){ 45 | for(int j = 0; j < vertex; j++){ 46 | // 或者寫成 matrix[i][j] 47 | cout << *((*(matrix+i)+j)) << "\t"; 48 | } 49 | cout << endl; 50 | } 51 | } 52 | 53 | // 在圖上新增一條邊 54 | // 只傳入兩個參數時,邊的權重預設為 1 55 | bool Graph::Add_Edge(int from, int to, int weight){ 56 | // 邊已存在 57 | if (matrix[from][to] == 1){ 58 | cout << "Edge already exist." << endl; 59 | return false; 60 | } 61 | // 邊還未存在,進行新增 62 | else { 63 | matrix[from][to] = weight; 64 | // 若為無向邊需補上這一條 65 | matrix[to][from] = weight; 66 | } 67 | } 68 | 69 | int main(){ 70 | Graph g(10); 71 | g.Add_Edge(1,5,5); 72 | g.Add_Edge(2,6,3); 73 | g.Add_Edge(7,5,2); 74 | g.Print_Matrix(); 75 | 76 | return 0; 77 | } 78 | -------------------------------------------------------------------------------- /Chapter09/09_04_鄰接列表.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 代表邊的結構 8 | // 只需要紀錄指到的頂點即可 9 | typedef struct{ 10 | int to; 11 | int weight; 12 | }edge; 13 | 14 | class Graph{ 15 | private: 16 | int vertex; 17 | // 每個邊對應的 list 形成的向量 18 | vector> edges; 19 | public: 20 | Graph(int); 21 | void Print_Edges(); 22 | bool Add_Edge(int, int, int=1); 23 | }; 24 | 25 | Graph::Graph(int v){ 26 | // 設定頂點數 27 | vertex = v; 28 | // 把向量的長度設為 vertex 29 | edges.resize(vertex); 30 | } 31 | 32 | // 印出所有邊 33 | void Graph::Print_Edges(){ 34 | for (int i = 0; i < vertex; i++){ 35 | // 印出 from 36 | cout << i + 1 << "\t"; 37 | // 資料型別可以使用 auto 代替 38 | list::iterator iter = edges[i].begin(); 39 | // 處理從 from 出發的所有邊 40 | for(;iter != edges[i].end();iter++){ 41 | // 印出 to 和權重 42 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 43 | } 44 | cout << endl; 45 | } 46 | } 47 | 48 | bool Graph::Add_Edge(int from, int to, int weight){ 49 | // 在 from 對應的鏈結串列 50 | // 索引值是 from - 1 中加上新的邊 51 | edges[from - 1].push_back(new edge{to - 1, weight}); 52 | // 如果是無向邊,要加上反向 53 | // edges[to - 1].push_back(new edge{from - 1, weight}); 54 | } 55 | 56 | int main(){ 57 | Graph g(10); 58 | g.Add_Edge(1,5,5); 59 | g.Add_Edge(2,6,3); 60 | g.Add_Edge(2,8,4); 61 | g.Add_Edge(7,5,2); 62 | g.Print_Edges(); 63 | return 0; 64 | } 65 | -------------------------------------------------------------------------------- /Chapter09/09_05_cloneGraph.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #133. 複製圖 Clone Graph 4 | 5 | A. 題目 6 | 給定連通無向圖中某個節點的參考,回傳對該圖深複製的結果。 7 | 8 | 圖中的每個節點都含有一個值(整數)和一個含有所有其相鄰頂點的列表List[Node]: 9 | 10 | B. 出處 11 | https://leetcode.com/problems/clone-graph/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | private: 17 | // 記錄哪些頂點已經被複製過 18 | // 從原本圖上的 node 映射到複製出的頂點 clone 19 | // 只針對還沒複製過的頂點進行複製 20 | unordered_map Copied; 21 | public: 22 | Node* cloneGraph(Node* node){ 23 | // 例外處理:傳入的是空指標 24 | if (node == 0) 25 | return 0; 26 | 27 | // 一般情形 28 | // 在 Copied 內尋找 node 29 | // 如果 find 的結果在容器外時,代表找不到/還沒複製過 30 | if (Copied.find(node) == Copied.end()){ 31 | // Node 建構式要傳入 val 32 | Copied[node] = new Node(node->val); 33 | // 把 Node 的所有相鄰節點丟給複製出來的頂點 34 | // 且會先對該相鄰節點進行同樣過程 35 | //(相鄰節點先完成複製後才能被接上來) 36 | // 這裡用到深度優先搜尋的概念,可以之後再回頭來看 37 | for (Node* neighbor : node->neighbors){ 38 | (Copied[node] -> neighbors).push_back(cloneGraph(neighbor)); 39 | } 40 | } 41 | return Copied[node]; 42 | } // end of cloneGraph 43 | }; // end of Solution 44 | -------------------------------------------------------------------------------- /Chapter09/09_06_canFinish.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #207. 課程排序 Course Schedule 4 | 5 | A. 題目 6 | 你必須修習總共 numCourses 門課,編號分別為 0 到 numCourses-1。給定一個陣列 prerequisites,其中 prerequisites[i] = [a_i,b_i] 代表必須要先修過「b_i」這門課,才能往下修「a_i」這門課(注意 b_i 才是先修要求)。 7 | 8 | 舉例來說,如果有陣列中有 [0,1],就代表必須先修過課程 1,才可以修課程 0。 9 | 10 | 如果有任一種方法能夠把所有課程修完,回傳 true,否則回傳 false。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/course-schedule/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | public: 19 | bool canFinish(int numCourses, vector>& prerequisites){ 20 | // 鄰接列表外層是長度為 numCourses 的向量 21 | // 向量內每個元素都是另一個向量,記錄該課程的先修要求 22 | vector> edges(numCourses); 23 | 24 | // 計算每個頂點的入度,初始化為 0 25 | vector in_degree(numCourses, 0); 26 | 27 | // 依序處理每個先修數字對 28 | // prerequisite[1] -> prerequisite[0] 29 | for (auto& pre : prerequisites){ 30 | edges[pre[1]].push_back(pre[0]); 31 | // 調整頂點的入度 32 | in_degree[pre[0]]++; 33 | } 34 | 35 | // 進行拓樸排序 36 | // 儲存入度為 0 頂點的佇列 37 | queue topological_sort; 38 | // 已經加入過佇列的頂點數目 39 | int visited = 0; 40 | 41 | // 找到入度為 0 的點,先放入 Queue 中 42 | for (int i = 0; i < numCourses; i++){ 43 | if (in_degree[i] == 0){ 44 | // 放到佇列中 45 | topological_sort.push(i); 46 | visited++; 47 | } 48 | } 49 | 50 | // 當入度為 0 的頂點還未處理完時 51 | while (!topological_sort.empty()){ 52 | // 從 Queue 中拿出一個入度為 0 的頂點 53 | int current = topological_sort.front(); 54 | topological_sort.pop(); 55 | 56 | // 取出該頂點的所有出邊,調整相鄰頂點的入度 57 | for (int neighbor : edges[current]){ 58 | // 邊的方向:current -> neighbor 59 | in_degree[neighbor]--; 60 | 61 | // 如果 neighbor 調整後入度為 0 62 | // 把該頂點加到佇列中 63 | if (in_degree[neighbor]==0){ 64 | topological_sort.push(neighbor); 65 | // 加入拓樸排序的頂點數目加一 66 | visited++; 67 | } 68 | } // end of for 69 | } // end of while 70 | // 所有課程都被加入到拓樸排序中 71 | if (visited == numCourses){ 72 | return true; 73 | } 74 | // 否則代表無法產生拓樸排序,也就是無法修完所有課程 75 | return false; 76 | } // end of canFinish 77 | }; // end of Solution 78 | -------------------------------------------------------------------------------- /Chapter09/09_07_findOrder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #210. 課程排序II Course Schedule II 4 | 5 | A. 題目 6 | 你必須修習總共 numCourses 門課,編號分別為 0 到 numCourses-1。給定一個陣列 prerequisites,其中 prerequisites[i] = [a_i,b_i] 代表必須要先修過「b_i」這門課,才能往下修「a_i」這門課(注意順序不是反過來)。舉例來說,如果有陣列中有 [0,1],就代表必須先修過課程 1,才可以修課程 0。 7 | 8 | 回傳任一種能夠把所有課程修完的修課順序,如果沒有任何可行的順序,回傳一個空陣列。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/course-schedule-ii/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | vector findOrder(int numCourses, vector>& prerequisites){ 18 | // 鄰接列表外層是長度為 numCourses 的向量 19 | // 向量內每個元素都是另一個向量,記錄該課程的先修要求 20 | vector> edges(numCourses); 21 | // 儲存回傳結果的陣列 22 | vector result; 23 | 24 | // 計算每個頂點的入度,初始化為 0 25 | vector in_degree(numCourses, 0); 26 | 27 | // 依序處理每個先修數字對 28 | // prerequisite[1] -> prerequisite[0] 29 | for (auto& pre : prerequisites){ 30 | edges[pre[1]].push_back(pre[0]); 31 | // 調整頂點的入度 32 | in_degree[pre[0]]++; 33 | } 34 | 35 | // 進行拓樸排序 36 | // 儲存入度為 0 頂點的佇列 37 | queue topological_sort; 38 | 39 | // 找到入度為 0 的點,先放入 Queue 中 40 | for (int i = 0; i < numCourses; i++){ 41 | if (in_degree[i] == 0){ 42 | // 放到佇列中 43 | topological_sort.push(i); 44 | // 把新增的頂點放到回傳結果中 45 | result.push_back(i); 46 | } 47 | } 48 | 49 | // 當入度為 0 的頂點還未處理完時 50 | while (!topological_sort.empty()){ 51 | // 從 Queue 中拿出一個入度為 0 的頂點 52 | int current = topological_sort.front(); 53 | topological_sort.pop(); 54 | 55 | // 取出該頂點的所有出邊,調整相鄰頂點的入度 56 | for (int neighbor : edges[current]){ 57 | // 邊的方向:current -> neighbor 58 | in_degree[neighbor]--; 59 | 60 | // 如果 neighbor 調整後入度為 0 61 | // 把該頂點加到佇列中 62 | if (in_degree[neighbor] == 0){ 63 | topological_sort.push(neighbor); 64 | // 把新增的頂點放到回傳結果中 65 | result.push_back(neighbor); 66 | // 加入拓樸排序的頂點數目加一 67 | } 68 | } // end of for 69 | } // end of while 70 | // 能形成排序時,回傳結果向量 71 | if(result.size() == numCourses){ 72 | return result; 73 | } 74 | // 不能形成排序時,回傳空向量 75 | else { 76 | return vector(0); 77 | }; 78 | } // end of canFinish 79 | }; // end of Solution 80 | -------------------------------------------------------------------------------- /Chapter09/09_08_maximalNetworkRank.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1615. 最大網路排序 Maximal Network Rank 4 | 5 | A. 題目 6 | 共有 n 個城市,且有一些道路連接其間。每個 roads[i] = [a_i,b_i] 代表了 a_i 城市和 b_i 城市之間的一條雙向道路。 7 | 8 | 兩個不同城市間的「網路排序」被定義為連接到這兩個城市的道路數量總和,如果一條路連接的正好就是這兩個城市,只能計算一次。「最大網路排序」指的是這些城市當中,每一對(兩個城市)的網路排序中,其值最大者。 9 | 10 | 給定整數 n 和陣列 roads,回傳所有城市中能找到的最大網路排序。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/maximal-network-rank/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | public: 19 | int maximalNetworkRank(int n, vector>& roads){ 20 | // 記錄每個城市連接到幾條道路 21 | vector numbers(n, 0); 22 | // 記錄每對城市間是否有道路直接相連 23 | // 大小為 n x n 24 | vector> connected(n, vector(n, false)); 25 | 26 | // 依序針對每條道路處理 27 | for(auto& road : roads){ 28 | // road[0] <-> road[1] 29 | // 調整起點與終點城市的連接道路數量 30 | numbers[road[0]]++; 31 | numbers[road[1]]++; 32 | 33 | // 記錄城市間的相連情形 34 | connected[road[0]][road[1]] = true; 35 | connected[road[1]][road[0]] = true; 36 | } 37 | // 負無限大 38 | int max = -2147483648; 39 | int current; 40 | 41 | // 用雙重迴圈計算每對城市的網路排序 42 | for(int i = 0; i < n; i++){ 43 | for(int j = 0; j < n; j++){ 44 | // 不與自己城市計算 45 | if(i == j) 46 | continue; 47 | // 兩個城市間有道路直接連接 48 | else if (connected[i][j]) 49 | current = numbers[i] + numbers[j] - 1; 50 | // 兩個城市間沒有道路直接連接 51 | else 52 | current = numbers[i] + numbers[j]; 53 | // 取出最大值 54 | max = max > current ? max : current; 55 | } 56 | } 57 | // 回傳得到的網路排序中的最大值 58 | return max; 59 | } // end of maximalNetworkRank 60 | }; // end of Solution 61 | -------------------------------------------------------------------------------- /Chapter09/09_09_visit.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #841. 鑰匙與房間 Keys and Rooms 4 | 5 | A. 題目 6 | 總共有 N 個上鎖的房間,而你從編號 0 的房間 room_0 內出發(只有編號 0 沒有上鎖)。房間被依序編號為 0,1,2,...,N-1,且每個房間中有若干把鑰匙可以用來進入其他房間。 7 | 8 | 每間房間 i 會有一個 rooms[i],這是一個陣列,當中的每個元素 rooms[i][j] 值都是 [0,1,...,N-1] 中的某個整數,當 rooms[i][j] = v 時,代表該鑰匙可以把房間 v 打開。 9 | 10 | 打開其他房間並進入之後,隨時可以回到之前進入過的房間。只有在能夠進入全部 N 個房間時,回傳 true。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/keys-and-rooms/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | void visit( 19 | vector>& rooms, //每間房間裡的鑰匙 20 | vector& visited, //房間是否走過 21 | int current //現在進入的房間 22 | ){ 23 | // 進入 current 這間時把 visited 設為 true 24 | visited[current] = true; 25 | 26 | // 從這間房間進入其他房間後 27 | // 依序處理從這間房間可以進入的所有房間 28 | for (auto key : rooms[current]){ 29 | // 已經進入過這把鑰匙可以進入的房間了 30 | if (visited[key]){ 31 | continue; 32 | } 33 | // 進入這把鑰匙可以進入的房間 34 | else { 35 | visit(rooms, visited, key); 36 | } 37 | } 38 | } 39 | 40 | public: 41 | bool canVisitAllRooms(vector>& rooms){ 42 | int len = rooms.size(); 43 | // 用布林陣列記錄每個房間是否進去過 44 | // 初始化為 false,代表還未進去過 45 | vector visited(len, false); 46 | // 從房間 0 出發 47 | visit(rooms, visited, 0); 48 | 49 | // 因為上面 visit 函式傳入的是參考 50 | // visited 已經在 visit 函式執行中被改寫 51 | // 記錄了幾間房間進入過 52 | int counts = 0; 53 | for (int i = 0; i < len; i++){ 54 | // cout << visited[i] << endl; 55 | if (visited[i]){ 56 | counts++; 57 | } 58 | } 59 | 60 | // 如果全部的房間都進去過回傳 true 61 | // 否則回傳 false 62 | return counts == len; 63 | } // end of canVisitAllRooms 64 | }; //end of Solution 65 | -------------------------------------------------------------------------------- /Chapter10/10_01_BFS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | // 代表邊的結構 10 | // 只需要紀錄指到的頂點即可 11 | typedef struct{ 12 | int to; 13 | int weight; 14 | }edge; 15 | 16 | class Graph{ 17 | private: 18 | int vertex; 19 | // 每個邊對應的 list 形成的向量 20 | vector> edges; 21 | public: 22 | Graph(int); 23 | void Print_Edges(); 24 | bool Add_Edge(int, int, int=1); 25 | void BFS(int); 26 | }; 27 | 28 | Graph::Graph(int v){ 29 | // 設定頂點數 30 | vertex = v; 31 | // 把向量的長度設為 vertex 32 | edges.resize(vertex); 33 | } 34 | 35 | // 印出所有邊 36 | void Graph::Print_Edges(){ 37 | for (int i = 0; i < vertex; i++){ 38 | // 印出 from 39 | cout << i + 1 << "\t"; 40 | // 資料型別可以使用 auto 代替 41 | list::iterator iter = edges[i].begin(); 42 | // 處理從 from 出發的所有邊 43 | for(;iter != edges[i].end();iter++){ 44 | // 印出 to 和權重 45 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 46 | } 47 | cout << endl; 48 | } 49 | } 50 | 51 | bool Graph::Add_Edge(int from, int to, int weight){ 52 | // 在 from 對應的鏈結串列 53 | // 索引值是 from - 1 中加上新的邊 54 | edges[from - 1].push_back(new edge{to - 1, weight}); 55 | // 如果是無向邊,要加上反向 56 | // edges[to - 1].push_back(new edge{from - 1, weight}); 57 | } 58 | 59 | void Graph::BFS(int start){ 60 | // 起點 start 對應到索引值 start-1 61 | start--; 62 | 63 | // 顏色與 int 的對應 64 | // 0:白色 white 65 | // 1:灰色 gray 66 | // 2:黑色 black 67 | 68 | // 把所有頂點塗成白色 69 | vector color(vertex, 0); 70 | // BFS 使用的佇列 71 | queue BFS_Q; 72 | 73 | // 把起點放進 Queue 中並塗成灰色 74 | BFS_Q.push(start); 75 | color[start] = 1; 76 | 77 | // 印出頂點 78 | cout << start + 1 << "->"; 79 | 80 | // 當 Queue 中還有資料 81 | while(!BFS_Q.empty()){ 82 | // 取出 Queue 中最前面的資料再刪除 83 | int current = BFS_Q.front(); 84 | BFS_Q.pop(); 85 | 86 | // 針對 current 的相鄰節點處理 87 | for(auto iter = edges[current].begin(); 88 | iter != edges[current].end(); 89 | iter++ 90 | ){ 91 | // 只處理相鄰頂點中白色者 92 | if (color[(*iter)->to] == 0){ 93 | // 印出目前處理的節點 94 | cout << (*iter)->to + 1 << "->"; 95 | // 放入 Queue 中 96 | BFS_Q.push((*iter)->to); 97 | // 塗成灰色 98 | color[(*iter)->to] = 1; 99 | } 100 | } // end of for 101 | // current 處理完後把 current 塗成黑色 102 | color[current] = 2; 103 | } // end of while 104 | } // end of BFS 105 | 106 | int main(){ 107 | Graph g(7); 108 | g.Add_Edge(1,3); 109 | g.Add_Edge(1,2); 110 | g.Add_Edge(1,6); 111 | g.Add_Edge(3,4); 112 | g.Add_Edge(2,4); 113 | g.Add_Edge(2,7); 114 | g.Add_Edge(6,5); 115 | g.Add_Edge(4,5); 116 | g.Add_Edge(7,5); 117 | g.Add_Edge(5,3); 118 | g.BFS(1); 119 | return 0; 120 | } 121 | -------------------------------------------------------------------------------- /Chapter10/10_02_Connected_Component.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 代表邊的結構 8 | // 只需要紀錄指到的頂點即可 9 | typedef struct{ 10 | int to; 11 | int weight; 12 | }edge; 13 | 14 | class Graph{ 15 | private: 16 | int vertex; 17 | // 每個邊對應的 list 形成的向量 18 | vector> edges; 19 | public: 20 | Graph(int); 21 | void Print_Edges(); 22 | bool Add_Edge(int, int, int=1); 23 | void BFS(int); 24 | int Connected_Component(); 25 | }; 26 | 27 | Graph::Graph(int v){ 28 | // 設定頂點數 29 | vertex = v; 30 | // 把向量的長度設為 vertex 31 | edges.resize(vertex); 32 | } 33 | 34 | // 印出所有邊 35 | void Graph::Print_Edges(){ 36 | for (int i = 0; i < vertex; i++){ 37 | // 印出 from 38 | cout << i + 1 << "\t"; 39 | // 資料型別可以使用 auto 代替 40 | list::iterator iter = edges[i].begin(); 41 | // 處理從 from 出發的所有邊 42 | for(;iter != edges[i].end();iter++){ 43 | // 印出 to 和權重 44 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 45 | } 46 | cout << endl; 47 | } 48 | } 49 | 50 | bool Graph::Add_Edge(int from, int to, int weight){ 51 | // 在 from 對應的鏈結串列 52 | // 索引值是 from - 1 中加上新的邊 53 | edges[from - 1].push_back(new edge{to - 1, weight}); 54 | // 如果是無向邊,要加上反向 55 | edges[to - 1].push_back(new edge{from - 1, weight}); 56 | } 57 | 58 | void Graph::BFS(int start){ 59 | // 起點 start 對應到索引值 start-1 60 | start--; 61 | 62 | // 顏色與 int 的對應 63 | // 0:白色 white 64 | // 1:灰色 gray 65 | // 2:黑色 black 66 | 67 | // 把所有頂點塗成白色 68 | vector color(vertex, 0); 69 | // BFS 使用的佇列 70 | queue BFS_Q; 71 | 72 | // 把起點放進 Queue 中並塗成灰色 73 | BFS_Q.push(start); 74 | color[start] = 1; 75 | 76 | // 印出頂點 77 | cout << start + 1 << "->"; 78 | 79 | // 當 Queue 中還有資料 80 | while(!BFS_Q.empty()){ 81 | // 取出 Queue 中最前面的資料再刪除 82 | int current = BFS_Q.front(); 83 | BFS_Q.pop(); 84 | 85 | // 針對 current 的相鄰節點處理 86 | for(auto iter = edges[current].begin(); 87 | iter != edges[current].end(); 88 | iter++ 89 | ){ 90 | // 只處理相鄰頂點中白色者 91 | if(color[(*iter)->to] == 0){ 92 | // 印出目前處理的節點 93 | cout << (*iter)->to + 1 << "->"; 94 | // 放入 Queue 中 95 | BFS_Q.push((*iter)->to); 96 | // 塗成灰色 97 | color[(*iter)->to] = 1; 98 | } 99 | } // end of for 100 | // current 處理完後把 current 塗成黑色 101 | color[current] = 2; 102 | } // end of while 103 | } // end of BFS 104 | 105 | int Graph::Connected_Component(){ 106 | // 0:white; 1:gray; 2:black 107 | // 把所有頂點塗成白色 108 | vector color(vertex, 0); 109 | // 計算連通元件的數目 110 | int component = 0; 111 | // 任選一個起點開始 112 | for(int i = 0; i < vertex; i++){ 113 | // 如果 i 經過前面的處理還是白色 114 | // 就再從它開始進行 BFS 115 | if(color[i] == 0){ 116 | // 每做一輪 BFS 表示連通元件數目 + 1 117 | component++; 118 | queue BFS_Q; 119 | // 從 i 開始進行 BFS 120 | int start = i; 121 | BFS_Q.push(start); 122 | color[start] = 1; 123 | while(!BFS_Q.empty()){ 124 | // 取出 Queue 中第一筆資料 current 125 | int current = BFS_Q.front(); 126 | BFS_Q.pop(); 127 | // 找 current 的所有相鄰頂點 128 | for(auto iter = edges[current].begin(); 129 | iter!=edges[current].end(); 130 | iter++ 131 | ){ 132 | if(color[(*iter)->to]==0){ 133 | // current 的所有相鄰白點放入 Queue 134 | BFS_Q.push((*iter)->to); 135 | // 再把該點塗成灰色 136 | color[(*iter)->to] = 1; 137 | } 138 | } 139 | // 處理完 current 後把其塗黑 140 | color[current] = 2; 141 | } 142 | } 143 | } 144 | return component; 145 | } 146 | 147 | int main(){ 148 | Graph g(9); 149 | g.Add_Edge(1,2); 150 | g.Add_Edge(1,4); 151 | g.Add_Edge(3,4); 152 | g.Add_Edge(2,3); 153 | g.Add_Edge(5,6); 154 | g.Add_Edge(6,7); 155 | g.Add_Edge(8,9); 156 | 157 | cout << "Connected Components:" << g.Connected_Component(); 158 | return 0; 159 | } 160 | -------------------------------------------------------------------------------- /Chapter10/10_03_floodFill.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #733. 洪水填充 Flood Fill 4 | 5 | A. 題目 6 | 一個 m×n 的整數陣列 image 代表了一張圖片,其中 image[i][j] 是圖片中一個像素的像素值。 7 | 8 | 給定三個整數 sr, sc 和 newColor,請用「洪水填充」的方式從 image[sr][sc] 開始把整張圖片塗成新的顏色。 9 | 10 | 所謂「洪水填充」,是從起點開始,把該像素的上下左右四個像素中與起點顏色相同者都一起塗成新的顏色 newColor,接下來,把剛才的四個像素的上下左右四個像素中顏色相同者也都塗成 newColor,依此類推,直到用 newColor 填充完所有應被填充的像素。 11 | 12 | 完成洪水填充後,回傳修改過的 image。 13 | 14 | B. 出處 15 | https://leetcode.com/problems/flood-fill/ 16 | 17 | */ 18 | 19 | class Solution{ 20 | public: 21 | vector> floodFill(vector>& image, int sr, int sc, int newColor){ 22 | // 取得二維陣列 image 的大小 23 | int rows = image.size(); 24 | int cols = image[0].size(); 25 | 26 | // BFS 會用到的 Queue 27 | // 可以使用一維陣列來表示二維的資料 28 | queue Pixels; 29 | 30 | // image[x][y] = x*cols + y = P 31 | // (x,y) = (P/cols, P%cols) 32 | // 在 Queue 中加入起點 (sr, sc) 33 | Pixels.push(sr * cols + sc); 34 | // 起點原本的顏色 35 | int color_replaced = image[sr][sc]; 36 | 37 | // 例外處理:newColor 和起點原本的顏色相同 38 | // 不需做任何動作 39 | // 若不做此例外處理,可能產生無窮迴圈 40 | if (color_replaced == newColor){ 41 | return image; 42 | } 43 | 44 | // 先把起點換成 new_color 45 | image[sr][sc] = newColor; 46 | 47 | // Queue 中還有資料時繼續進行 48 | while(!Pixels.empty()){ 49 | // 取出 Queue 中最前面的資料 50 | int current = Pixels.front(); 51 | Pixels.pop(); 52 | 53 | // 把 Queue 中資料從位置 P 的形式回復為 (x, y) 形式 54 | int x = current / cols; 55 | int y = current % cols; 56 | 57 | // 需在邊界內,且與原點原本的顏色相同時才進行處理 58 | // 檢查下方像素 59 | if (x + 1 < rows && image[x + 1][y] == color_replaced){ 60 | // 顏色塗成 newColor 61 | image[x + 1][y] = newColor; 62 | // 從這個像素的座標 (x,y) 換算出這個點的位置 P 63 | int P = (x + 1) * cols + y; 64 | // 把位置 P 加到 Queue 中 65 | Pixels.push(P); 66 | } 67 | // 檢查上方像素 68 | if (x - 1 >= 0 && image[x - 1][y] == color_replaced){ 69 | image[x - 1][y]=newColor; 70 | int P = (x - 1) * cols + y; 71 | Pixels.push(P); 72 | } 73 | // 檢查左方像素 74 | if (y - 1 >= 0 && image[x][y - 1] == color_replaced){ 75 | image[x][y - 1]=newColor; 76 | int P = x * cols + (y - 1); 77 | Pixels.push(P); 78 | } 79 | // 檢查右方像素 80 | if (y + 1 >= 0 && image[x][y + 1] == color_replaced){ 81 | image[x][y + 1] = newColor; 82 | int P = x * cols + (y + 1); 83 | Pixels.push(P); 84 | } 85 | } // end of while 86 | return image; 87 | } // end of floodFill 88 | }; // end of Solution 89 | -------------------------------------------------------------------------------- /Chapter10/10_04_numIslands.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #200. 島嶼數目 Number of islands 4 | 5 | A. 題目 6 | 給定一個 n×n 的二維陣列 grid,代表一張地圖,其中 1 代表「陸地」,0 代表「水」,回傳「島嶼的數目」。 7 | 8 | 一個島嶼被水包圍,陸地間上下左右連接起來就成為一個島嶼,假設陣列的外面四周都是水(島嶼不會延伸超出邊界)。 9 | 10 | B. 出處 11 | http://leetcode.com/problems/number-of-islands/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | public: 17 | int numIslands(vector>& grid){ 18 | // 取得 grid 的大小 19 | int rows = grid.size(); 20 | int cols = grid[0].size(); 21 | // BFS 使用的 Queue 22 | queue Pixel; 23 | // 計算島嶼數目 24 | int islands = 0; 25 | 26 | // 對每筆資料進行處理S 27 | for (int i = 0; i < rows; i++){ 28 | for (int j = 0; j < cols; j++){ 29 | // 一旦發現一筆資料是未造訪過的陸地,就進行 BF 30 | // 把 '1' 定義為「未造訪過」 31 | // 把 '2' 定義為「已造訪過」 32 | if (grid[i][j] == '1'){ 33 | // 把 grid[i][j] 當作起點進行 BFS 34 | // 每做一輪 BFS,島嶼數量加 1 35 | islands++; 36 | // 先把該頂點標記為已造訪過 37 | grid[i][j] = '2'; 38 | // 把起點加入 Queue 中 39 | Pixel.push(i * cols + j); 40 | 41 | // BFS 42 | while(!Pixel.empty()){ 43 | // 每次從 Queue 中取出一筆資料 44 | int current = Pixel.front(); 45 | Pixel.pop(); 46 | int x = current / cols; 47 | int y = current % cols; 48 | 49 | // 處理下方 50 | if (x + 1 < rows && grid[x + 1][y] == '1'){ 51 | grid[x + 1][y] = '2'; 52 | Pixel.push((x + 1) * cols + y); 53 | } 54 | // 處理上方 55 | if (x - 1 >= 0 && grid[x - 1][y] == '1'){ 56 | grid[x - 1][y] = '2'; 57 | Pixel.push((x - 1) * cols + y); 58 | } 59 | // 處理右方 60 | if (y + 1 < cols && grid[x][y + 1] == '1'){ 61 | grid[x][y + 1] = '2'; 62 | Pixel.push(x * cols + (y + 1)); 63 | } 64 | // 處理左方 65 | if (y - 1 >= 0 && grid[x][y - 1] == '1'){ 66 | grid[x][y - 1] = '2'; 67 | Pixel.push(x * cols + (y - 1)); 68 | } 69 | } // end of while 70 | } // end of if 71 | } // end of inner for 72 | } // end of outer for 73 | return islands; 74 | } // end of numIslands 75 | }; // end of Solution 76 | -------------------------------------------------------------------------------- /Chapter10/10_05_permute.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #46. 排列 Permutations 4 | 5 | A. 題目 6 | 給定一個陣列 nums,當中有一些互不相同的整數。回傳這些整數所有可能的排列,回傳的順序不限。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/permutations/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | vector> permute(vector& nums){ 16 | // 取得 nums 的長度 17 | int len = nums.size(); 18 | // BFS 使用的 queue 19 | queue> permutation; 20 | // 要回傳的結果 21 | vector> final_result; 22 | 23 | // 第一個數字(第一層)可以是 nums 中的每個元素 24 | for (int i = 0; i < len; i++){ 25 | int number = nums[i]; 26 | // 建立一個長度為 1 的向量 [number] 27 | vector tmp(1, number); 28 | // 放到 queue 中 29 | permutation.push(tmp); 30 | } 31 | // 開始 BFS 32 | while(!permutation.empty()){ 33 | // 取出 Queue 中第一筆元素 34 | vector current = permutation.front(); 35 | permutation.pop(); 36 | 37 | // current 長度跟 nums 元素數目相同時為其中一組排列 38 | // Queue 中已經處理到樹中最下面一層的元素 [1,2,3] 39 | if (current.size() == len){ 40 | final_result.push_back(current); 41 | continue; 42 | } 43 | // 要再加入其他數字時 44 | for (int i = 0; i < len; i++){ 45 | // 繼續從 nums 中取出一個數字接在 current 後面 46 | int number = nums[i]; 47 | // 檢查 current 裡面是否已經包含 number 了 48 | // 比如 [1,2] 不可以再接一個 1 在後面 49 | auto iter = find(current.begin(), current.end(), number); 50 | // 已經包含了,不能加進該排列裡 51 | if (iter != current.end()){ 52 | continue; 53 | } 54 | else { 55 | // 另一向量複製 current,後面再加上新的數字 56 | vector current_expand( 57 | current.begin(),current.end() 58 | ); 59 | current_expand.push_back(number); 60 | permutation.push(current_expand); 61 | } 62 | } // end of for 63 | } // end of while 64 | // 回傳得到的所有排列 65 | return final_result; 66 | } // end of permute 67 | }; // end of Solution 68 | -------------------------------------------------------------------------------- /Chapter10/10_06_Shortest_Path.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 代表邊的結構 8 | // 只需要紀錄指到的頂點即可 9 | typedef struct{ 10 | int to; 11 | int weight; 12 | }edge; 13 | 14 | class Graph{ 15 | private: 16 | int vertex; 17 | // 每個邊對應的 list 形成的向量 18 | vector> edges; 19 | public: 20 | Graph(int); 21 | void Print_Edges(); 22 | bool Add_Edge(int, int, int=1); 23 | void BFS(int); 24 | int Connected_Component(); 25 | void Shortest_Path(int); 26 | }; 27 | 28 | Graph::Graph(int v){ 29 | // 設定頂點數 30 | vertex = v; 31 | // 把向量的長度設為 vertex 32 | edges.resize(vertex); 33 | } 34 | 35 | // 印出所有邊 36 | void Graph::Print_Edges(){ 37 | for (int i = 0; i < vertex; i++){ 38 | // 印出 from 39 | cout << i + 1 << "\t"; 40 | // 資料型別可以使用 auto 代替 41 | list::iterator iter = edges[i].begin(); 42 | // 處理從 from 出發的所有邊 43 | for(;iter != edges[i].end();iter++){ 44 | // 印出 to 和權重 45 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 46 | } 47 | cout << endl; 48 | } 49 | } 50 | 51 | bool Graph::Add_Edge(int from, int to, int weight){ 52 | // 在 from 對應的鏈結串列 53 | // 索引值是 from - 1 中加上新的邊 54 | edges[from - 1].push_back(new edge{to - 1, weight}); 55 | // 如果是無向邊,要加上反向 56 | edges[to - 1].push_back(new edge{from - 1, weight}); 57 | } 58 | 59 | void Graph::BFS(int start){ 60 | // 起點 start 對應到索引值 start-1 61 | start--; 62 | 63 | // 顏色與 int 的對應 64 | // 0:白色 white 65 | // 1:灰色 gray 66 | // 2:黑色 black 67 | 68 | // 把所有頂點塗成白色 69 | vector color(vertex, 0); 70 | // BFS 使用的佇列 71 | queue BFS_Q; 72 | 73 | // 把起點放進 Queue 中並塗成灰色 74 | BFS_Q.push(start); 75 | color[start] = 1; 76 | 77 | // 印出頂點 78 | cout << start + 1 << "->"; 79 | 80 | // 當 Queue 中還有資料 81 | while(!BFS_Q.empty()){ 82 | // 取出 Queue 中最前面的資料再刪除 83 | int current = BFS_Q.front(); 84 | BFS_Q.pop(); 85 | 86 | // 針對 current 的相鄰節點處理 87 | for(auto iter = edges[current].begin(); 88 | iter != edges[current].end(); 89 | iter++ 90 | ){ 91 | // 只處理相鄰頂點中白色者 92 | if(color[(*iter)->to] == 0){ 93 | // 印出目前處理的節點 94 | cout << (*iter)->to + 1 << "->"; 95 | // 放入 Queue 中 96 | BFS_Q.push((*iter)->to); 97 | // 塗成灰色 98 | color[(*iter)->to] = 1; 99 | } 100 | } // end of for 101 | // current 處理完後把 current 塗成黑色 102 | color[current] = 2; 103 | } // end of while 104 | } // end of BFS 105 | 106 | int Graph::Connected_Component(){ 107 | // 0:white; 1:gray; 2:black 108 | // 把所有頂點塗成白色 109 | vector color(vertex, 0); 110 | // 計算連通元件的數目 111 | int component = 0; 112 | // 任選一個起點開始 113 | for(int i = 0; i < vertex; i++){ 114 | // 如果 i 經過前面的處理還是白色 115 | // 就再從它開始進行 BFS 116 | if(color[i] == 0){ 117 | // 每做一輪 BFS 表示連通元件數目 + 1 118 | component++; 119 | queue BFS_Q; 120 | // 從 i 開始進行 BFS 121 | int start = i; 122 | BFS_Q.push(start); 123 | color[start] = 1; 124 | while(!BFS_Q.empty()){ 125 | // 取出 Queue 中第一筆資料 current 126 | int current = BFS_Q.front(); 127 | BFS_Q.pop(); 128 | // 找 current 的所有相鄰頂點 129 | for(auto iter = edges[current].begin(); 130 | iter!=edges[current].end(); 131 | iter++ 132 | ){ 133 | if(color[(*iter)->to]==0){ 134 | // current 的所有相鄰白點放入 Queue 135 | BFS_Q.push((*iter)->to); 136 | // 再把該點塗成灰色 137 | color[(*iter)->to] = 1; 138 | } 139 | } 140 | // 處理完 current 後把其塗黑 141 | color[current] = 2; 142 | } 143 | } 144 | } 145 | return component; 146 | } 147 | 148 | void Graph::Shortest_Path(int start){ 149 | // 0:white; 1:gray; 2:black 150 | // 顏色初始化為白色 151 | vector color(vertex, 0); 152 | // 距離初始化為 - 1 153 | vector distance(vertex, - 1); 154 | queue BFS_Q; 155 | // 把起點置入 Queue 中,接著初始化 156 | BFS_Q.push(start); 157 | color[start] = 1; 158 | distance[start] = 0; 159 | 160 | // 開始 BFS 161 | while(!BFS_Q.empty()){ 162 | // 取出第一筆資料 163 | int current = BFS_Q.front(); 164 | BFS_Q.pop(); 165 | // 找 current 的所有相鄰頂點 166 | for(auto iter = edges[current].begin(); 167 | iter != edges[current].end(); 168 | iter++ 169 | ){ 170 | if(color[(*iter)->to] == 0){ 171 | // current 的所有相鄰白點放入 Queue 172 | BFS_Q.push((*iter)->to); 173 | color[(*iter)->to] = 1; 174 | distance[(*iter)->to] = distance[current] + 1; 175 | cout << "Distance of " << (*iter)->to + 1 << ":" << distance[(*iter)->to] << endl; 176 | } 177 | } 178 | color[current] = 2; 179 | } 180 | } 181 | 182 | 183 | int main(){ 184 | Graph g(7); 185 | g.Add_Edge(1,3); 186 | g.Add_Edge(1,2); 187 | g.Add_Edge(1,6); 188 | g.Add_Edge(3,4); 189 | g.Add_Edge(2,4); 190 | g.Add_Edge(2,7); 191 | g.Add_Edge(6,5); 192 | g.Add_Edge(4,5); 193 | g.Add_Edge(7,5); 194 | g.Add_Edge(5,3); 195 | g.Shortest_Path(1); 196 | return 0; 197 | } 198 | -------------------------------------------------------------------------------- /Chapter10/10_07_shortestPathBinaryMatrix.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1091. 二元陣列中的最短路徑 Shortest Path in Binary Matrix 4 | 5 | A. 題目 6 | 給定一個二維、大小為 n×n 的二元陣列 grid,回傳最短「通關路徑」的長度,如果該陣列沒有通關路徑,回傳 -1。 7 | 8 | 「通關路徑」定義為從左上角(座標 (0,0) 的方格)走到右下角(座標 (n-1,n-1) 的方格)的路徑,且: 9 | 10 | 該路徑經過的所有方格值都是 0 11 | 該路徑經過的一個方格與下一個方格有邊相鄰,或者有角相鄰(往上下左右、左上、右上、左下或右下走) 12 | 13 | 通關路徑的長度指的是該路徑經過的方格數。 14 | 15 | B. 出處 16 | https://leetcode.com/problems/shortest-path-in-binary-matrix/ 17 | 18 | */ 19 | 20 | class Solution{ 21 | public: 22 | int shortestPathBinaryMatrix(vector>& grid){ 23 | // 取出 grid 的大小 24 | int rows = grid.size(); 25 | int cols = grid[0].size(); 26 | queue Position; 27 | 28 | // 例外處理:如果起點就是 1,沒有路徑,直接回傳 -1 29 | if (grid[0][0]){ 30 | return -1; 31 | } 32 | 33 | // 一般情況,從左上角開始 34 | Position.push(0); 35 | 36 | // 把原本陣列中 1 都改成 -1,避免與距離 1 混淆 37 | for (int i = 0; i < rows; i++){ 38 | for (int j = 0; j < cols; j++){ 39 | if (grid[i][j]){ 40 | grid[i][j] = -1; 41 | }else { 42 | // 把陣列中的 0 都改成 int_max 43 | grid[i][j] = 2147483647; 44 | } 45 | } 46 | } 47 | 48 | // 起點的距離是 1,因為此時路徑經過了一個方格 49 | grid[0][0] = 1; 50 | 51 | // 八個方向: 下、上、左、右、右下、左上、左下、右上 52 | // 如索引值 7 的 (x,y)=(-1,1) 代表往右上移動 53 | int direction_x[8] = {1,-1,0,0,1,-1,1,-1}; 54 | int direction_y[8] = {0,0,-1,1,1,-1,-1,1}; 55 | // 開始 BFS 56 | while(!Position.empty()){ 57 | // 取出 Queue 中最前面的資料 58 | int current = Position.front(); 59 | Position.pop(); 60 | 61 | // 把位置 P 轉換為 (x,y) 形式 62 | // P = x * cols + y; 63 | // (x, y) = (P / cols, P % cols) 64 | int x = current / cols; 65 | int y = current % cols; 66 | 67 | // 要造訪的方向有 8 個 68 | // 用設定好的八個方向的 x、y 偏移量來移動 69 | for (int i = 0; i < 8; i++){ 70 | // 計算出要造訪的下個方格的位置 71 | int new_x = x + direction_x[i]; 72 | int new_y = y + direction_y[i]; 73 | // 移動前的距離 74 | int distance = grid[x][y]; 75 | 76 | // 如果移動之後超出邊界或者為牆(-1),不處理 77 | // 直接嘗試其他方向 78 | if (new_x < 0 || new_y < 0) 79 | continue; 80 | if (new_x >= rows || new_y >=cols) 81 | continue; 82 | if (grid[new_x][new_y] == -1) 83 | continue; 84 | 85 | // 移動後方格要填上的距離為舊距離 + 1 86 | int distance_now = distance + 1; 87 | // 如果目前的距離比較短 88 | if (distance_now < grid[new_x][new_y]){ 89 | // 修改移動後方格的距離 90 | grid[new_x][new_y] = distance_now; 91 | // 把移動後的方格放入 Queue 中 92 | int P = new_x * cols + new_y; 93 | Position.push(P); 94 | } 95 | } 96 | } // end of while 97 | // 如果右下角方格不是無限大,代表可以走到 98 | if (grid[rows - 1][cols - 1] != 2147483647) 99 | return grid[rows - 1][cols - 1]; 100 | else 101 | return -1; 102 | } // end of shortestPathBinaryMatrix 103 | }; // end of Solution 104 | -------------------------------------------------------------------------------- /Chapter10/10_08_sumOfLeftLeaves.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #404. 左葉節點的和 Sum of Left Leaves 4 | 5 | A. 題目 6 | 給定一個二元樹的根節點,回傳該樹中所有的左葉節點。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/sum-of-left-leaves/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | public: 15 | int sumOfLeftLeaves(TreeNode* root){ 16 | // 例外處理:樹是空的 17 | if (root == 0) 18 | return 0; 19 | // 一般情形 20 | int sum = 0; 21 | queue BFS; 22 | 23 | // 從根節點開始做 BFS 24 | BFS.push(root); 25 | while(!BFS.empty()){ 26 | // 從 Queue 中取出最前面的元素 27 | TreeNode* current = BFS.front(); 28 | BFS.pop(); 29 | // 檢查目前的節點 current(比如範例中的 3)的左節點 30 | // 是否為葉節點(如範例中 9 的左右節點都是空的) 31 | // current->left!=0:current 有左節點 32 | // current->left->left == 0:左節點的左節點是空的 33 | // current->left->right == 0:左節點的右節點是空的 34 | if (current->left != 0){ 35 | if (current->left->left == 0 && current->left->right == 0){ 36 | // 把該左節點的值加入 sum 中 37 | sum += current->left->val; 38 | } 39 | } 40 | // 把 current 的子節點加入 Queue 中 41 | // 使樹中每一階層的節點都被依序加入 Queue 42 | if (current->left != 0){ 43 | BFS.push(current->left); 44 | } 45 | if (current->right != 0){ 46 | BFS.push(current->right); 47 | } 48 | } // end of while 49 | return sum; 50 | } // end of sumOfLeftLeaves 51 | }; // end of Solution 52 | -------------------------------------------------------------------------------- /Chapter10/10_09_numEnclaves.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | LeetCode #1020. 飛地數目 Number of Enclaves 3 | A. 題目 4 | 給定一個二維、大小為 m×n 的二元陣列 grid,每個值為 0 的方格代表海,值為 1 的方格代表陸地。 5 | 一次「移動」是從一個陸地方格走到相鄰(上下左右四個方向)的陸地方格,或者走到邊界之外。回傳所有「從這裡出發時,不論走多少步,都無法走到邊界外」的陸地方格數量。 6 | B. 出處 7 | https://leetcode.com/problems/number-of-enclaves/ 8 | */ 9 | 10 | class Solution{ 11 | // BFS 的函式 12 | void BFS(vector>& grid, int rows, int cols, int start){ 13 | queue Position; 14 | 15 | // 傳入的 start 是開始 BFS 的起點 16 | // 轉換成 (x,y) 形式 17 | int x = start / cols; 18 | int y = start % cols; 19 | 20 | // 透過兩個陣列設定上下左右四個方向的 x、y 偏移量 21 | int direction_x[4] = {-1,1,0,0}; 22 | int direction_y[4] = {0,0,-1,1}; 23 | 24 | Position.push(start); 25 | while(!Position.empty()){ 26 | // 取出 Queue 中最前面的元素,並轉換為 (x,y) 形式 27 | int P = Position.front(); 28 | Position.pop(); 29 | int x = P / cols; 30 | int y = P % cols; 31 | 32 | if(grid[x][y] == 0) 33 | continue; 34 | 35 | // 把取出的點改為 0 36 | grid[x][y] = 0; 37 | 38 | // 透過 x、y 偏移量,往上下左右四個方向移動 39 | for (int i = 0; i < 4 ; i++){ 40 | int new_x = x + direction_x[i]; 41 | int new_y = y + direction_y[i]; 42 | // 邊界處理 43 | if (new_x < 0 || new_y < 0) 44 | continue; 45 | if (new_x >= rows||new_y >= cols) 46 | continue; 47 | 48 | // 如果移動後是 0,不需加到 Queue 中 49 | if (grid[new_x][new_y] == 0){ 50 | continue; 51 | } 52 | // 移動後方格是 1 時需要加到 Queue 中處理 53 | Position.push(new_x * cols + new_y); 54 | } 55 | } // end of while 56 | } // end of BFS 57 | 58 | public: 59 | int numEnclaves(vector>& grid){ 60 | // 取得 grid 的大小 61 | int rows = grid.size(); 62 | int cols = grid[0].size(); 63 | // 記錄飛地數目 64 | int sum = 0; 65 | 66 | for (int i = 0; i < rows; i++){ 67 | // Position = x*cols+y 68 | // 檢查最左邊的直行 69 | if (grid[i][0]){ 70 | // 對應的 Position 是 i*cols 71 | BFS(grid, rows, cols, i * cols); 72 | } 73 | // 檢查最右邊的直行 74 | if (grid[i][cols - 1]){ 75 | // 對應的 Position 是 i * cols + (cols - 1) 76 | BFS(grid, rows, cols, i * cols + (cols - 1)); 77 | } 78 | } 79 | 80 | for (int j = 0; j < cols; j++){ 81 | // 檢查最上面的橫列 82 | if (grid[0][j]){ 83 | // 對應的 Position 是 j 84 | BFS(grid, rows, cols, j); 85 | } 86 | // 檢查最下面的橫列 87 | if (grid[rows - 1][j]){ 88 | // 對應的 Position 是 (rows-1)*cols+j 89 | BFS(grid, rows, cols, (rows - 1) * cols + j); 90 | } 91 | } 92 | // 用雙重迴圈檢查剩下幾個 1 93 | for (int i = 0; i < rows; i++){ 94 | for (int j = 0; j < cols; j++){ 95 | if (grid[i][j]) 96 | sum++; 97 | } 98 | } 99 | return sum; 100 | } // end of numEnclaves 101 | }; // end of Solution -------------------------------------------------------------------------------- /Chapter10/10_10_闖關遊戲.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:闖關路線 (2019/10 P3) 4 | 5 | A. 題目 6 | 有一個闖關遊戲,遊戲方式是在一條線不斷左右移動角色,直到移動到終點。已知線上有 N 個位置,由左至右以 0 ~ N - 1 表示,開始時,會接收到 D、L、R,分別代表:終點位置、每次按下左鍵時左移的距離、每次按下右鍵時右移的距離,且角色會從位置 0 開始移動。 7 | 8 | 另外,每個位置 i 都有一扇傳送門可以瞬間移動角色至 P(i),因此每次按下按鍵後,角色會先跟據按下的按鍵往左或往右移動到位置 i,接著角色就會被瞬間移動至 P(i)。 9 | 10 | 其中某些點的 P(i)=i,代表會在這些位置上停留不瞬間移動,這些點稱為停留點,並且起點與終點都一定是停留點,另外,瞬間移動後的位置也都一定是停留點,並不會持續瞬間移動。 11 | 12 | 現在的目標是以用最少的按鍵數來操作角色,讓角色可以從起點到終點,移動過程中也不可以超過範圍 [0, N - 1],且某些點的瞬間移動後的位置可能會超出範圍,所以移動到這些點同樣會導致闖關失敗。 13 | 14 | B. 題目出處 15 | 16 | 2019/10 APCS實作題 #3 17 | 本題可於 一中電腦資訊研究社 Online Judge 中測試,網址如下: https://judge.tcirc.tw/ShowProblem?problemid=d094 18 | 19 | */ 20 | 21 | #include 22 | #include 23 | #include 24 | 25 | using namespace std; 26 | 27 | int main() { 28 | ios_base::sync_with_stdio(false); 29 | cin.tie(0); 30 | 31 | int N, D, L, R; 32 | cin >> N >> D >> L >> R; 33 | 34 | vector P(N); 35 | for(int i = 0; i < N; i++){ 36 | cin >> P[i]; 37 | } 38 | bool found = false; 39 | vector visited(N, false); 40 | // first: 位置 41 | // second: 次數 42 | queue> BFS; 43 | BFS.push(make_pair(0, 0)); 44 | while (!BFS.empty()){ 45 | pair current = BFS.front(); 46 | BFS.pop(); 47 | int times = current.first; 48 | int position = current.second; 49 | visited[position] = true; 50 | 51 | // 走到終點了 52 | if(position == D){ 53 | found = true; 54 | cout << times << endl; 55 | break; 56 | } 57 | 58 | // 往左走,合法且未走過 59 | int left = position - L; 60 | if(left >= 0 && !visited[left]){ 61 | // 瞬間移動後仍合法 62 | if(P[left] >= 0 && P[left] < N){ 63 | visited[left] = true; 64 | BFS.push(make_pair(times + 1, P[left])); 65 | } 66 | } 67 | 68 | // 往右走,合法且未走過 69 | int right = position + R; 70 | if(right < N && !visited[right]){ 71 | // 瞬間移動後仍合法 72 | if(P[right] >= 0 && P[right] < N){ 73 | visited[right] = true; 74 | BFS.push(make_pair(times + 1, P[right])); 75 | } 76 | } 77 | } 78 | // 沒找到輸出 -1 79 | if(!found) cout << -1; 80 | 81 | return 0; 82 | } 83 | -------------------------------------------------------------------------------- /Chapter11/11_01_DFS.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | using namespace std; 6 | 7 | // 代表邊的結構 8 | // 只需要紀錄指到的頂點即可 9 | typedef struct{ 10 | int to; 11 | int weight; 12 | }edge; 13 | 14 | class Graph{ 15 | private: 16 | int vertex; 17 | // 每個邊對應的 list 形成的向量 18 | vector> edges; 19 | vector color; 20 | void DFS_Visit(int); 21 | public: 22 | Graph(int); 23 | void Print_Edges(); 24 | bool Add_Edge(int, int, int=1); 25 | void DFS(int); 26 | }; 27 | 28 | Graph::Graph(int v){ 29 | // 設定頂點數 30 | vertex = v; 31 | color.resize(vertex); 32 | // 把向量的長度設為 vertex 33 | edges.resize(vertex); 34 | } 35 | 36 | // 印出所有邊 37 | void Graph::Print_Edges(){ 38 | for (int i = 0; i < vertex; i++){ 39 | // 印出 from 40 | cout << i + 1 << "\t"; 41 | // 資料型別可以使用 auto 代替 42 | list::iterator iter = edges[i].begin(); 43 | // 處理從 from 出發的所有邊 44 | for(;iter != edges[i].end();iter++){ 45 | // 印出 to 和權重 46 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 47 | } 48 | cout << endl; 49 | } 50 | } 51 | 52 | bool Graph::Add_Edge(int from, int to, int weight){ 53 | // 在 from 對應的鏈結串列 54 | // 索引值是 from - 1 中加上新的邊 55 | edges[from - 1].push_back(new edge{to - 1, weight}); 56 | // 如果是無向邊,要加上反向 57 | // edges[to - 1].push_back(new edge{from - 1, weight}); 58 | } 59 | 60 | void Graph::DFS(int start){ 61 | start--; 62 | // 把所有顏色塗成白色,0: 白色 63 | fill(color.begin(), color.end(), 0); 64 | // 從起點開始做深度優先搜尋 65 | DFS_Visit(start); 66 | } 67 | 68 | void Graph::DFS_Visit(int current){ 69 | // 印出目前所在的位置 current 70 | cout << current + 1 << "->"; 71 | // 把目前所在位置塗成灰色,1: 灰色 72 | color[current] = 1; 73 | 74 | // 往 current 的相鄰頂點走 75 | for(auto iter = edges[current].begin(); 76 | iter != edges[current].end(); 77 | iter++) 78 | { 79 | int neighbor = (*iter)->to; 80 | // 但只能往相鄰的白點走 81 | if(color[neighbor] == 0){ 82 | DFS_Visit(neighbor); 83 | } 84 | } 85 | // 處理完 vertex 後把 vertex 變為黑色 86 | color[vertex] = 2; 87 | } 88 | 89 | int main(){ 90 | Graph g(7); 91 | g.Add_Edge(1,3); 92 | g.Add_Edge(1,2); 93 | g.Add_Edge(1,6); 94 | g.Add_Edge(3,4); 95 | g.Add_Edge(2,4); 96 | g.Add_Edge(2,7); 97 | g.Add_Edge(6,5); 98 | g.Add_Edge(4,5); 99 | g.Add_Edge(7,5); 100 | g.Add_Edge(5,3); 101 | g.DFS(1); 102 | return 0; 103 | } 104 | -------------------------------------------------------------------------------- /Chapter11/11_02_canFinish.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #207. 課程排序 Course Schedule 4 | 5 | A. 題目 6 | 你必須修習總共 numCourses 門課,編號分別為 0 到 numCourses-1。給定一個陣列 prerequisites,其中 prerequisites[i] = [a_i,b_i] 代表必須要先修過「b_i」這門課,才能往下修「a_i」這門課(注意 b_i 才是先修要求)。 7 | 8 | 舉例來說,如果有陣列中有 [0,1],就代表必須先修過課程 1,才可以修課程 0。 9 | 10 | 如果有任一種方法能夠把所有課程修完,回傳 true,否則回傳 false。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/course-schedule/ 14 | 15 | */ 16 | class Solution{ 17 | // 儲存頂點顏色的向量 18 | vector color; 19 | // 儲存邊的向量 20 | vector> edges; 21 | bool check(int vertex){ 22 | // 把目前頂點 vertex 塗成灰色 23 | color[vertex] = 1; 24 | // 檢查頂點 vertex 的每個相鄰頂點 25 | for (int i = 0; i < edges[vertex].size(); i++){ 26 | // 如果該相鄰頂點是白色 27 | if (color[edges[vertex][i]] == 0){ 28 | // 從該相鄰頂點出發,再往下檢查是否有環 29 | bool cycled = check(edges[vertex][i]); 30 | // 往下找有環時,回傳 true,代表圖中有環 31 | if (cycled) return true; 32 | } 33 | // 如果該相鄰頂點是灰色 34 | else if (color[edges[vertex][i]] == 1){ 35 | // 相鄰頂點是灰色,回傳 true,代表圖中有環 36 | return true; 37 | } 38 | } 39 | // 頂點 vertex 處理,塗黑 40 | color[vertex] = 2; 41 | // 從這個頂點向下都沒有遇到環時,回傳 false 42 | return false; 43 | } 44 | 45 | public: 46 | bool canFinish(int numCourses, vector>& prerequisites){ 47 | // 初始化 color 與 edges 向量 48 | color.resize(numCourses); 49 | edges.resize(numCourses); 50 | 51 | // 取出先修資訊建立每條邊 52 | for (auto edge:prerequisites){ 53 | // edge[1] -> edge[0] 54 | edges[edge[1]].push_back(edge[0]); 55 | } 56 | 57 | // 不一定是連通圖 58 | // 如果跑完一次 DFS 還有白色頂點,要再做另一次 DFS 59 | for (int i = 0; i < numCourses; i++){ 60 | // 確定從頂點 i 出發會不會有環 61 | if (color[i] == 0){ 62 | bool cycled = check(i); 63 | // 有環時回傳 false,代表無法把所有課修完 64 | if (cycled) 65 | return false; 66 | } 67 | } 68 | return true; 69 | } // end of canFinish 70 | }; // end of Solution 71 | -------------------------------------------------------------------------------- /Chapter11/11_03_isBipartite.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #785. 判別二分圖 Is Graph Bipartite? 4 | 5 | A. 題目 6 | 有一個共有 n 個節點的無向圖,每個節點分別被編號為 0 到 n-1。給定一個二維陣列 graph,其中 graph[u] 是含有節點 u 所有相鄰節點的陣列。也就是說,如果 graph[u] 中有節點 v,則節點 u 和節點 v 之間有一條無向邊。 7 | 8 | 如果一個圖是二分圖,該圖的所有節點可以被分到兩個獨立的集合 A 和 B 之一當中,使得該圖上的每一條邊都是連接一個「集合 A 中的點」與一個「集合 B 中的點」。 9 | 10 | 只有在該圖為二分圖時,回傳 true。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/is-graph-bipartite/ 14 | 15 | */ 16 | 17 | class Solution{ 18 | // 頂點的顏色,只有 0, -1 或 1 三種 19 | // -1, 1 代表顏色,0 代表還沒走過 20 | vector color; 21 | bool DFS(int vertex, vector>& graph){ 22 | // 依序檢查相鄰節點 23 | for (int neighbor : graph[vertex]){ 24 | // 如果相鄰節點的染色與 vertex 的染色衝突 25 | // 比如 vertex 的染色是「1」,相鄰節點也被染為「1」 26 | if (color[neighbor] == color[vertex]) 27 | return false; 28 | // 如果相鄰頂點未造訪過 29 | if (color[neighbor] == 0){ 30 | // 把相鄰頂點染上「相反」顏色 31 | // vertex 染色為 1 時,相鄰頂點染上 -1 32 | // vertex 染色為 -1 時,相鄰頂點染上 1 33 | color[neighbor] = -1 * color[vertex]; 34 | // 往相鄰頂點繼續進行 35 | bool flag = DFS(neighbor, graph); 36 | if(!flag){return false;} 37 | } 38 | } // end of for 39 | // 若從頂點 vertex 往下染色都不會發生衝突,回傳 true 40 | return true; 41 | } 42 | 43 | public: 44 | bool isBipartite(vector>& graph){ 45 | // 取出頂點個數 46 | int vertex = graph.size(); 47 | // 0 代表還未著色過(黑白染色) 48 | color.resize(vertex, 0); 49 | 50 | // 對所有白色頂點進行 DFS 51 | // 過程中用 color 另外染色(與原本的白、灰、黑獨立) 52 | // 染色順序為 -1 1 -1 1... 53 | for (int i = 0; i < vertex; i++){ 54 | if (color[i] == 0){ 55 | color[i] = 1; 56 | // 從起點開始,檢查能不能順利進行黑白染色 57 | // 起點的染色為「1」 58 | bool flag = DFS(i, graph); 59 | // 如果染色過程中產生衝突,回傳 false 60 | // 代表非二分圖 61 | if(!flag){return false;} 62 | } 63 | } // end of for 64 | // 沒有衝突時,代表為二分圖 65 | return true; 66 | } // end of isBipartite 67 | }; // end of Solution 68 | -------------------------------------------------------------------------------- /Chapter11/11_04_findOrder.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #210. 課程排序II Course Schedule II 4 | 5 | A. 題目 6 | 你必須修習總共 numCourses 門課,編號分別為 0 到 numCourses-1。給定一個陣列 prerequisites,其中 prerequisites[i] = [a_i,b_i] 代表必須要先修過「b_i」這門課,才能往下修「a_i」這門課(注意順序不是反過來)。舉例來說,如果有陣列中有 [0,1],就代表必須先修過課程 1,才可以修課程 0。 7 | 8 | 回傳任一種能夠把所有課程修完的修課順序,如果沒有任何可行的順序,回傳一個空陣列。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/course-schedule-ii/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | vector color; 17 | vector> edges; 18 | // 儲存拓樸排序的向量 19 | vector topological_sort; 20 | bool check(int vertex){ 21 | color[vertex] = 1; 22 | for (int i = 0; i < edges[vertex].size(); i++){ 23 | if (color[edges[vertex][i]] == 0){ 24 | // 從該相鄰頂點出發,再往下檢查是否有環 25 | bool cycled = check(edges[vertex][i]); 26 | // 回傳 true,代表圖中有環 27 | if (cycled){return true;} 28 | 29 | } 30 | // 如果該相鄰頂點是灰色 31 | else if (color[edges[vertex][i]]==1){ 32 | // 該頂點的相鄰頂點是灰色,回傳 true,圖中有環 33 | return true; 34 | } 35 | } 36 | // 頂點 vertex 處理完成 37 | color[vertex] = 2; 38 | // 這裡用的是向量,每次把資料插到向量「前端」 39 | topological_sort.insert(topological_sort.begin(),vertex); 40 | return false; 41 | } 42 | 43 | public: 44 | vector findOrder(int numCourses, vector>& prerequisites){ 45 | // 初始化 color 與 edges 向量 46 | color.resize(numCourses); 47 | edges.resize(numCourses); 48 | // 取出先修資訊建立每條邊 49 | for (auto edge : prerequisites){ 50 | // edge[1] -> edge[0] 51 | edges[edge[1]].push_back(edge[0]); 52 | } 53 | // 從每個白色頂點出發作 DFS 54 | for (int i = 0; i < numCourses; i++){ 55 | // 確定從頂點 i 出發會不會有環 56 | if (color[i] == 0){ 57 | bool cycled = check(i); 58 | // 有環時回傳 false,代表無法把所有課修完 59 | if (cycled) return vector (0); 60 | } 61 | } 62 | return topological_sort; 63 | } // end of findOrder 64 | }; // end of Solution 65 | -------------------------------------------------------------------------------- /Chapter11/11_05_SCC.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 代表邊的結構 9 | // 只需要紀錄指到的頂點即可 10 | typedef struct{ 11 | int to; 12 | int weight; 13 | }edge; 14 | 15 | class Graph{ 16 | private: 17 | int vertex; 18 | // 每個邊對應的 list 形成的向量 19 | vector> edges; 20 | // 顛倒圖,尋找強連通元件時會用到 21 | vector> edges_inverse; 22 | vector color; 23 | stack topological_sort; 24 | void DFS_Visit(int); 25 | void DFS_Visit_Topological(int); 26 | public: 27 | Graph(int); 28 | void Print_Edges(); 29 | bool Add_Edge(int, int, int=1); 30 | void DFS(int); 31 | void Get_Topological_Sort(int); 32 | void DFS_SCC(int); 33 | void Print_SCC(); 34 | }; 35 | 36 | Graph::Graph(int v){ 37 | // 設定頂點數 38 | vertex = v; 39 | // 把向量的長度設為 vertex 40 | color.resize(vertex); 41 | edges.resize(vertex); 42 | edges_inverse.resize(vertex); 43 | } 44 | 45 | // 印出所有邊 46 | void Graph::Print_Edges(){ 47 | for (int i = 0; i < vertex; i++){ 48 | // 印出 from 49 | cout << i + 1 << "\t"; 50 | // 資料型別可以使用 auto 代替 51 | list::iterator iter = edges[i].begin(); 52 | // 處理從 from 出發的所有邊 53 | for(;iter != edges[i].end();iter++){ 54 | // 印出 to 和權重 55 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 56 | } 57 | cout << endl; 58 | } 59 | } 60 | 61 | bool Graph::Add_Edge(int from, int to, int weight){ 62 | // 在 from 對應的鏈結串列 63 | // 索引值是 from - 1 中加上新的邊 64 | edges[from - 1].push_back(new edge{to - 1, weight}); 65 | edges_inverse[to - 1].push_back(new edge{from - 1, weight}); 66 | // 如果是無向邊,要加上反向 67 | // edges[to - 1].push_back(new edge{from - 1, weight}); 68 | } 69 | 70 | void Graph::DFS(int start){ 71 | start--; 72 | // 把所有顏色塗成白色,0: 白色 73 | fill(color.begin(), color.end(), 0); 74 | // 從起點開始做深度優先搜尋 75 | DFS_Visit(start); 76 | } 77 | 78 | void Graph::DFS_Visit(int current){ 79 | // 印出目前所在的位置 current 80 | cout << current + 1 << "->"; 81 | // 把目前所在位置塗成灰色,1: 灰色 82 | color[current] = 1; 83 | 84 | // 往 current 的相鄰頂點走 85 | for(auto iter = edges[current].begin(); 86 | iter != edges[current].end(); 87 | iter++) 88 | { 89 | int neighbor = (*iter)->to; 90 | // 但只能往相鄰的白點走 91 | if(color[neighbor] == 0){ 92 | DFS_Visit(neighbor); 93 | } 94 | } 95 | // 處理完 vertex 後把 vertex 變為黑色 96 | color[vertex] = 2; 97 | } 98 | 99 | void Graph::DFS_Visit_Topological(int current){ 100 | color[current] = 1; 101 | // 從 current 的鄰邊中找白點繼續往下做深度優先搜尋 102 | for(auto iter = edges[current].begin(); 103 | iter!=edges[current].end(); 104 | iter++) 105 | { 106 | int neighbor = (*iter)->to; 107 | if(color[neighbor] == 0){ 108 | DFS_Visit_Topological(neighbor); 109 | } 110 | } 111 | color[current] = 2; 112 | // 從離開 current 後就插入拓樸排序內 113 | 114 | topological_sort.push(current); 115 | } 116 | 117 | void Graph::Get_Topological_Sort(int start){ 118 | start--; 119 | // 把所有點塗成白色 120 | fill(color.begin(), color.end(), 0); 121 | // 從起點開始做拓墣排序 122 | DFS_Visit_Topological(start); 123 | for(int i = 0; i < vertex; i++){ 124 | if(color[i] == 0){ 125 | DFS_Visit_Topological(i); 126 | } 127 | } 128 | } 129 | 130 | void Graph::DFS_SCC(int current){ 131 | // 進入時塗成灰色 132 | color[current] = 1; 133 | // 印出目前的頂點 134 | cout << current + 1 << " "; 135 | // 在 GT 上搜尋所有 current 的相鄰節點 136 | for(auto iter = edges_inverse[current].begin(); 137 | iter != edges_inverse[current].end(); 138 | iter++) 139 | { 140 | int neighbor = (*iter)->to; 141 | // 如果相鄰節點是白色,就對該點繼續進行 DFS 142 | if(color[neighbor] == 0){ 143 | DFS_SCC(neighbor); 144 | } 145 | } 146 | // 處理完時塗成黑色 147 | color[current] = 2; 148 | } 149 | 150 | void Graph::Print_SCC(){ 151 | // 從頂點 1 開始取得拓樸排序 152 | Get_Topological_Sort(1); 153 | // 把所有頂點塗成白色 154 | fill(color.begin(), color.end(), 0); 155 | 156 | int SCC = 1; 157 | // 以拓樸排序,在顛倒圖上跑深度優先搜尋 158 | while(!topological_sort.empty()){ 159 | int current = topological_sort.top(); 160 | topological_sort.pop(); 161 | // 找到還沒探索過的點進行深度優先搜尋 162 | // 每一輪深度優先搜尋跑遍的頂點都屬於同個強連通元件 163 | if(color[current] == 0){ 164 | // 印出目前是第幾個強連通元件 165 | cout << "SCC #" << SCC << ":" << endl; 166 | DFS_SCC(current); 167 | SCC++; 168 | cout << endl; 169 | } 170 | } 171 | } 172 | 173 | int main(){ 174 | Graph g(7); 175 | g.Add_Edge(2,1); 176 | g.Add_Edge(1,2); 177 | g.Add_Edge(3,2); 178 | g.Add_Edge(1,3); 179 | g.Add_Edge(6,4); 180 | g.Add_Edge(3,5); 181 | g.Add_Edge(3,6); 182 | g.Add_Edge(4,6); 183 | g.Add_Edge(5,6); 184 | g.Add_Edge(7,6); 185 | g.Add_Edge(4,7); 186 | g.Print_SCC(); 187 | return 0; 188 | } 189 | -------------------------------------------------------------------------------- /Chapter11/11_06_NQueen.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using namespace std; 5 | 6 | void Print_Stack(stack& s){ 7 | // 邊界條件:s 是空的 8 | if (s.empty()){return;} 9 | // 取出 s 中最上面的元素 10 | int col = s.top(); 11 | s.pop(); 12 | // 遞迴取出下一個元素 13 | Print_Stack(s); 14 | // 印出當前元素 15 | cout << col + 1 << " "; 16 | // 將當前元素放回 stack 中 17 | s.push(col); 18 | } 19 | 20 | // 檢查新皇后在 (row, col) 時,是否會與 stack 裡的皇后衝突 21 | bool Available(stack s, int row, int col){ 22 | // s 為空的時候一定不會衝突 23 | if (s.empty()){return true;}; 24 | 25 | // 檢查新皇后是否會與 stack 最上面的皇后衝突 26 | // 取出前一個皇后的位置 (s.size(), col_prev_queen) 27 | int col_prev_queen = s.top(); 28 | s.pop(); 29 | 30 | // 先假設不會衝突,可以放該位置 31 | bool result = true; 32 | 33 | // 新皇后與先前的皇后位置比較 34 | // 相差 diff_r 個橫列、diff_c 個直行 35 | int diff_r = row - s.size(); 36 | int diff_c = col - col_prev_queen; 37 | 38 | // 直行相同時代表位置衝突 39 | if (col == col_prev_queen) 40 | result = false; 41 | // 新皇后在先前皇后右下角對角線上時 42 | if (diff_c == diff_r) 43 | result = false; 44 | // 新皇后在先前皇后左下角對角線上時 45 | if (diff_c == -1*diff_r) 46 | result = false; 47 | // 如果與現在比對的先前皇后沒有衝突 48 | if (result){ 49 | // 繼續跟上一個橫列的皇后比對 50 | // 注意上面執行過 s.pop() 51 | // 因此傳入的 s 中,最上面是「再上一個橫列」的皇后 52 | result = Available(s,row,col); 53 | } 54 | return result; 55 | } 56 | 57 | int KQueens(stack& s, int N){ 58 | // stack 已有 N 個元素,代表找到一組可行解 59 | if (s.size() == N){ 60 | // 印出目前這組可行解 61 | Print_Stack(s); 62 | cout << endl; 63 | return 1; 64 | } 65 | 66 | // stack 還未放滿時,往下執行 67 | // 計算在不更動目前的皇后下,還有幾組解 68 | int counts = 0; 69 | 70 | // 檢查下一個橫列的某種放法是否可行 71 | for (int i = 0; i < N; i++){ 72 | // s 是已放的前幾個皇后的位置 73 | // s.size() 和 i 是下一個皇后的「橫列」和「直行」 74 | if (Available(s, s.size(), i)){ 75 | // 擺放位置可行,把新的皇后放到該位置上 76 | s.push(i); 77 | // 放了新的皇后之後,往下繼續放下一橫列的皇后 78 | counts += KQueens(s,N); 79 | // 下一個橫列可能有好幾個可以放 80 | // 因此要 pop 掉,才能接續試下一個可行位置 81 | s.pop(); 82 | } 83 | } 84 | return counts; 85 | } 86 | 87 | int main(){ 88 | int N; 89 | cout << "Please enter N:"; 90 | cin >> N; 91 | 92 | stack position; 93 | cout << "Possibility: " << KQueens(position, N); 94 | return 0; 95 | } 96 | -------------------------------------------------------------------------------- /Chapter11/11_07_KQueens.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #51. N 皇后問題 N-Queens 4 | 5 | A. 題目 6 | 「N 皇后問題」指的是要在 n×n 的西洋棋盤上放置 N 個皇后,使得皇后間不會互相攻擊。給定一個整數 n,以任意順序回傳「N 皇后問題」所有可行的解。 7 | 8 | 回傳時,需以 'Q' 和 '.'(分別代表擺放皇后的位置和空位)來表示整個棋盤上的分佈。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/n-queens/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | // 儲存目前皇后位置的堆疊 17 | stack Positions; 18 | // 回傳的結果 19 | vector> result; 20 | // 檢查新皇后位置 (row, col) 是否合法 21 | bool Available(stack s, int row, int col){ 22 | if (s.empty()){return true;} 23 | int col_prev_queen = s.top(); 24 | s.pop(); 25 | bool result = true; 26 | 27 | int diff_c = col - col_prev_queen; 28 | int diff_r = row - s.size(); 29 | 30 | if (col==col_prev_queen) 31 | result = false; 32 | if (diff_c==diff_r) 33 | result = false; 34 | if (diff_c==-1*diff_r) 35 | result = false; 36 | if (result) 37 | result = Available(s,row,col); 38 | return result; 39 | } 40 | 41 | // 生成一個可行解用 'Q' 和 '.' 表示 42 | vector Add_Solution(stack& s, int n){ 43 | if (s.empty()){return {};} 44 | // 從可行解 s 中取出一個橫列的皇后位置 45 | int col = s.top(); 46 | s.pop(); 47 | // 遞迴得到前面所有橫列產生的向量 this_solution 48 | // 比如得到 ["..Q.","Q...","...Q"] 49 | vector this_solution = Add_Solution(s, n); 50 | // 產生目前橫列的表達方式 51 | string result_str; 52 | 53 | // 除了要放的位置 col 外,用 '.' 補滿 54 | // 比如得到 ".Q.." 55 | for (int i = 0; i < col; i++){ 56 | result_str += '.'; 57 | } 58 | result_str += 'Q'; 59 | for (int i = col + 1; i < n; i++){ 60 | result_str += '.'; 61 | } 62 | s.push(col); 63 | // 在 this_solution 中加上目前橫列的內容 64 | this_solution.push_back(result_str); 65 | return this_solution; 66 | } 67 | 68 | int KQueens(stack& s, int N){ 69 | // 找到一個可行解 70 | if (s.size() == N){ 71 | // Add_Solution 產生對應的向量後加到 result 中 72 | result.push_back(Add_Solution(s,N)); 73 | } 74 | int counts = 0; 75 | for (int i = 0; i < N; i++){ 76 | if (Available(s, s.size(), i)){ 77 | s.push(i); 78 | counts += KQueens(s,N); 79 | s.pop(); 80 | } 81 | } 82 | return counts; 83 | } 84 | public: 85 | vector> solveNQueens(int n){ 86 | KQueens(Positions, n); 87 | return result; 88 | } // end of solveNQueens 89 | }; // end of Solution 90 | -------------------------------------------------------------------------------- /Chapter11/11_08_maxAreaOfIsland.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #695. 最大島嶼的面積 Max Area of Island 4 | 5 | A. 題目 6 | 給定一個 m×n 的二元陣列 grid,一個「島嶼」是一些上下左右連接在一起的「1」,假設 grid 的邊界外全部都是代表水的「0」。 7 | 8 | 一個島嶼的面積是組成整個島嶼的「1」的個數,回傳整個 grid 中最大島嶼的面積,如果沒有任何島嶼,回傳 0。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/max-area-of-island/ 12 | 13 | */ 14 | 15 | class Solution{ 16 | // 從 (i, j) 開始深度優先搜尋,並同時計算面積 17 | int Area(vector>& grid, int i, int j, int row, int col){ 18 | // 超出邊界時,回傳面積 0 19 | if (i < 0 || j < 0) 20 | return 0; 21 | if (i >= row || j >= col) 22 | return 0; 23 | // 遇到海洋或已經走過的陸地時,回傳 0 24 | if (grid[i][j] == 0 || grid[i][j] == -1) 25 | return 0; 26 | 27 | // 把現在造訪的陸地值改為 -1 28 | grid[i][j] = -1; 29 | 30 | // 往四個方向移動 31 | int up = Area(grid, i - 1, j, row, col); 32 | int down = Area(grid, i + 1, j, row, col); 33 | int left = Area(grid, i, j - 1, row, col); 34 | int right = Area(grid, i, j + 1, row, col); 35 | 36 | // 回傳的面積是目前方格面積 1 37 | // 加上往四個方向移動後繼續處理所得面積 38 | return up + down + left + right + 1; 39 | } 40 | 41 | public: 42 | int maxAreaOfIsland(vector>& grid){ 43 | // 取出 grid 的大小 44 | int row = grid.size(); 45 | int col = grid[0].size(); 46 | // 最大島嶼面積 47 | int max_area = 0; 48 | 49 | for (int i = 0; i < row; i++){ 50 | for (int j = 0; j < col ; j++){ 51 | // 遇到陸地 1 時就進行深度優先搜尋 52 | if (grid[i][j] == 1){ 53 | int tmp = Area(grid,i,j,row,col); 54 | max_area = max_area > tmp ? 55 | max_area : tmp; 56 | } 57 | 58 | } 59 | } 60 | return max_area; 61 | } // end of maxAreaOfIsland 62 | }; // end of Solution 63 | -------------------------------------------------------------------------------- /Chapter11/11_09_countServers.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1267. 溝通伺服器的總數 Count Servers that Communicate 4 | 5 | A. 題目 6 | 給定一個 m×n 的整數陣列,代表了一個伺服器中心的地圖,陣列中每個 1 代表該處有伺服器,0 代表該處沒有伺服器。兩個伺服器只有位在相同直行或相同橫列時,被認為會互相「溝通」。回傳會和其他伺服器溝通的伺服器總數。 7 | 8 | B. 出處 9 | https://leetcode.com/problems/count-servers-that-communicate/ 10 | 11 | */ 12 | 13 | class Solution{ 14 | int Connected(vector>& grid, int i, int j, int row, int col){ 15 | // 不處理的情形 16 | if (grid[i][j] == -1) 17 | return 0; 18 | // 把目前伺服器標成 -1 代表已處理過 19 | grid[i][j] = -1; 20 | int row_sum = 0, col_sum = 0; 21 | 22 | // 檢查同橫列的伺服器 23 | for (int m = 0; m < col; m++){ 24 | if (grid[i][m] == 1){ 25 | row_sum += Connected(grid, i, m, row, col); 26 | } 27 | } 28 | // 檢查同直行的伺服器 29 | for (int n = 0; n < row; n++){ 30 | if (grid[n][j] == 1){ 31 | col_sum += Connected(grid, n, j, row, col); 32 | } 33 | } 34 | return row_sum + col_sum + 1; 35 | } 36 | 37 | public: 38 | int countServers(vector>& grid){ 39 | // 取得 grid 的大小 40 | int row = grid.size(); 41 | int col = grid[0].size(); 42 | // 記錄會與其他伺服器溝通的伺服器數量 43 | int counts = 0; 44 | int tmp; 45 | 46 | for (int i = 0; i < row; i++){ 47 | for (int j = 0; j < col; j++){ 48 | if (grid[i][j] == 1){ 49 | // tmp 從現在這台伺服器向外深度優先搜尋 50 | // 得到過程中共有幾台有在溝通的伺服器 51 | tmp = Connected(grid, i, j, row, col); 52 | // tmp = 1 時,目前的伺服器沒有向外溝通 53 | // 有溝通時,counts 加上 DFS 找到的 54 | if (tmp > 1) 55 | counts += tmp; 56 | } 57 | } // end of inner for 58 | } // end of outer for 59 | return counts; 60 | } // end of countServers 61 | }; // end of Solution 62 | -------------------------------------------------------------------------------- /Chapter11/11_10_小群體.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:小群體 (2017/03/04 P2) 4 | 5 | A. 題目 6 | Q 同學正在學習程式,P 老師出了以下的題目讓他練習。 7 | 8 | 一群人在一起時經常會形成一個一個的小群體。假設有 N 個人,編號由 0 到 N-1,每個人都寫下他最好朋友的編號(最好朋友有可能是他自己的編號,如果他自己沒有其他好友),在本題中,每個人的好友編號絕對不會重複,也就是說 0 到 N-1 每個數字都恰好出現一次。 9 | 10 | 這種好友的關係會形成一些小群體。例如 N=10,好友編號如下, 11 | 12 | 0 的好友是 4,4 的好友是 6,6 的好友是 8,8 的好友是 5,5 的好友是 0,所以 0、4、 6、8、和 5 就形成了一個小群體。另外,1 的好友是 7 而且 7 的好友是 1,所以 1 和 7 形成另一個小群體,同理,3 和 9 是一個小群體,而 2 的好友是自己,因此他自己 是一個小群體。總而言之,在這個例子裡有 4 個小群體:{0,4,6,8,5}、{1,7}、{3,9}、 {2}。本題的問題是:輸入每個人的好友編號,計算出總共有幾個小群體。 Q 同學想了想卻不知如何下手,和藹可親的 P 老師於是給了他以下的提示:如果你從 任何一人 x 開始,追蹤他的好友,好友的好友,….,這樣一直下去,一定會形成一 個圈回到 x,這就是一個小群體。如果我們追蹤的過程中把追蹤過的加以標記,很容 易知道哪些人已經追蹤過,因此,當一個小群體找到之後,我們再從任何一個還未追 蹤過的開始繼續找下一個小群體,直到所有的人都追蹤完畢。 Q 同學聽完之後很順利的完成了作業。 13 | 14 | 在本題中,你的任務與 Q 同學一樣:給定一群人的好友,請計算出小群體個數。 15 | 16 | B. 出處 17 | APCS 2017/03/04,實作題第2題 18 | 本題可以在zerojudge上測試與繳交,網址如下: 19 | https://zerojudge.tw/ShowProblem?problemid=c291 20 | 21 | */ 22 | 23 | #include 24 | #include 25 | 26 | using namespace std; 27 | 28 | int main(){ 29 | ios_base::sync_with_stdio(0); 30 | cin.tie(0); 31 | 32 | int N; 33 | cin >> N; 34 | vector relationship(N); 35 | // 輸入每個人的好朋友編號 36 | for(int i = 0; i < N; i++){ 37 | cin >> relationship[i]; 38 | } 39 | 40 | // 記錄每個點是否有被拜訪過 41 | // 一開始沒有拜訪過任何點,初始化成 false 42 | vector visited(N, false); 43 | 44 | int groups = 0; 45 | 46 | // 把每個還沒拜訪過的點進行深度優先搜尋 47 | for(int i = 0; i < N; i++){ 48 | // 拜訪過就跳過不處理 49 | if (visited[i]) 50 | continue; 51 | // 每一輪深度優先搜尋就代表新的小群體 52 | groups++; 53 | // 記錄現在走到哪個點 54 | int current = i; 55 | // 開始深度優先搜尋 56 | while(!visited[current]){ 57 | visited[current] = true; 58 | current = relationship[current]; 59 | } 60 | } 61 | cout << groups; 62 | return 0; 63 | } 64 | -------------------------------------------------------------------------------- /Chapter11/11_11_血緣關係.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | APCS:血緣關係 (2016/03/05 P4) 4 | 5 | A. 題目 6 | 小宇有一個大家族。有一天,他發現記錄整個家族成員和成員間血緣關係的家族族譜。小宇對於最遠的血緣關係 (我們稱之為"血緣距離") 有多遠感到很好奇。 7 | 8 | 上圖為家族的關係圖。0是7的孩子,1、2和3是0的孩子,4和5 是1的孩子,6是3的孩子。我們可以輕易的發現最遠的親戚關係為4(或 5)和6,他們的"血緣距離"是4 (4~1,1~0,0~3,3~6)。 9 | 10 | 給予任一家族的關係圖,請找出最遠的「血緣距離」。你可以假設只有一個人是整個家族成員的祖先,而且沒有兩個成員有同樣的小孩。 11 | 12 | B. 出處 13 | APCS 2016/03/05,實作題第4題 14 | 本題可以在 zerojudge 上繳交,注意需改成在同一程式內直接處理多筆測資。 15 | https://zerojudge.tw/ShowProblem?problemid=b967 16 | 17 | */ 18 | 19 | #include 20 | #include 21 | 22 | using namespace std; 23 | 24 | void DFS(vector& distance, vector>& relationship, int current){ 25 | // 跑遍 current 的所有相鄰頂點 26 | for(int neighbor : relationship[current]){ 27 | // distance[neighbor] = -1 代表還沒走過 28 | if(distance[neighbor] != -1) 29 | continue; 30 | distance[neighbor] = distance[current] + 1; 31 | DFS(distance, relationship, neighbor); 32 | } 33 | } 34 | 35 | int main(){ 36 | ios_base::sync_with_stdio(0); 37 | cin.tie(0); 38 | 39 | int N; 40 | while(cin >> N){ 41 | vector> relationship(N); 42 | vector distance(N, -1); 43 | // 輸入每個人的孩子編號 44 | for(int i = 0; i < N - 1; i++){ 45 | int parent, child; 46 | cin >> parent >> child; 47 | // 無向圖,A->B = B->A 48 | relationship[parent].push_back(child); 49 | relationship[child].push_back(parent); 50 | } 51 | 52 | // 從節點 0 出發作深度優先搜尋 53 | distance[0] = 0; 54 | DFS(distance, relationship, 0); 55 | int index_max = 0, max_distance = 0; 56 | // 找出最遠距離的頂點 57 | for(int i = 0; i < N; i++){ 58 | if(distance[i] > max_distance){ 59 | index_max = i; 60 | max_distance = distance[i]; 61 | } 62 | } 63 | // 重設距離 64 | fill(distance.begin(), distance.end(), -1); 65 | distance[index_max] = 0; 66 | // 從 index_max 出發作深度優先搜尋 67 | DFS(distance, relationship, index_max); 68 | max_distance = 0; 69 | // 找出最遠距離的頂點 70 | for(int i = 0; i < N; i++){ 71 | if(distance[i] > max_distance){ 72 | max_distance = distance[i]; 73 | } 74 | } 75 | cout << max_distance << endl; 76 | } 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /Chapter11/11_12_樹狀圖分析.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | APCS:樹狀圖分析 (2017/10 P3) 3 | A. 題目 4 | 5 | 本題是關於有根樹(rooted tree)。在一棵 n 個節點的有根樹中,每個節點都是以 1~n 的不同數字來編號,描述一棵有根樹必須定義節點與節點之間的親子關係。一棵有根樹恰有一個節點沒有父節點(parent),此節點被稱為根節點(root),除了根節點以外的每一個節點都恰有一個父節點,而每個節點被稱為是它父節點的子節點(child),有些節點沒有子節點,這些節點稱為葉節點(leaf)。在當有根樹只有一個節點時,這個節點既是根節點同時也是葉節點。 6 | 7 | 在圖形表示上,我們將父節點畫在子節點之上,中間畫一條邊(edge)連結。例如,上圖中表示的是一棵 9 個節點的有根樹,其中,節點 1 為節點 6 的父節點,而節點 6 為節點 1 的子節點;又 5、3 與 8 都是 2 的子節點。節點 4 沒有父節點,所以節點 4 是根節點;而 6、9、3 與 8 都是葉節點。 8 | 9 | 樹狀圖中的兩個節點 u 和 v 之間的距離 d(u,v)定義為兩節點之間邊的數量。如上圖中,d(7, 5) = 2,而 d(1, 2) = 3。對於樹狀圖中的節點 v,我們以 h(v)代表節點 v 的高度,其定義是節點 v 和節點 v 下面最遠的葉節點之間的距離,而葉節點的高度定義為 0。例如節點 6 的高度為 0,節點 2 的高度為 2,而節點 4 的高度為 4。 10 | 11 | 此外,我們定義 H(T)為 T 中所有節點的高度總和,也就是說 H(T)=∑_(v∈T)▒〖h(v)〗。給定一個樹狀圖 T,請找出 T 的根節點以及高度總和 H(T)。 12 | 13 | B. 題目出處 14 | 15 | 2017/10 APCS實作題 #3 16 | 本題可於 Zerojudge 中測試,網址如下: 17 | https://zerojudge.tw/ShowProblem?problemid=c463 18 | 19 | */ 20 | 21 | #include 22 | #include 23 | 24 | using namespace std; 25 | 26 | long long int ans = 0; 27 | 28 | // 深度優先搜尋 29 | long long int DFS ( 30 | vector>& adj_list, 31 | int idx) 32 | { 33 | long long int h, max = 0; 34 | // 計算子樹們的高 35 | for(int node : adj_list[idx]){ 36 | h = DFS(adj_list, node); 37 | if (h > max) 38 | max = h; 39 | } 40 | ans += max; 41 | max++; 42 | return max; 43 | } 44 | 45 | int main ( ) { 46 | ios::sync_with_stdio(false); 47 | cin.tie(0); 48 | 49 | int N, k, child, root; 50 | cin >> N; 51 | // 注意編號從 1 開始 52 | vector root_candidate(N + 1, true); 53 | vector> adj_list(N + 1); 54 | for(int i = 1; i < N + 1; i++){ 55 | cin >> k; 56 | for(int j = 1; j < k + 1; j++){ 57 | cin >> child; 58 | adj_list[i].push_back(child); 59 | // child 不可能為子節點 60 | root_candidate[child] = false; 61 | } 62 | } 63 | // 首先找出根節點 64 | for(int i = 1; i < N + 1; i++){ 65 | if(root_candidate[i]){ 66 | root = i; 67 | break; 68 | } 69 | } 70 | // 從根節點開始深度優先搜尋 71 | DFS(adj_list, root); 72 | cout << root << endl << ans; 73 | return 0; 74 | } 75 | -------------------------------------------------------------------------------- /Chapter12/12_01_Kruskal.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | using namespace std; 8 | 9 | typedef struct{ 10 | int from; 11 | int to; 12 | int weight; 13 | }edge; 14 | 15 | class Graph{ 16 | private: 17 | vector MST_Edges; 18 | vector MST_Set; 19 | int vertex; 20 | vector> edges; 21 | int Find_Set(int); 22 | void Merge_Set(int,int); 23 | public: 24 | Graph(int); 25 | void MST_Kruskal(); 26 | void Print_Edges(); 27 | void Add_Edge(int,int,int=1); 28 | }; 29 | 30 | // 自訂邊的排序函式 31 | class cmp{ 32 | public: 33 | bool operator()(edge &e1, edge &e2){ 34 | // 依照邊的權重排序 35 | // 權重較小的邊排在 Priority Queue 前 36 | return e1.weight > e2.weight; 37 | } 38 | }; 39 | 40 | void Graph::MST_Kruskal(){ 41 | int edges_completed = 0; 42 | MST_Edges.clear(); 43 | // 根據權重把邊排序 44 | priority_queue, cmp> sorted_edges; 45 | 46 | // 初始化 47 | for(int i = 0; i < vertex; i++){ 48 | MST_Set[i] = -1; 49 | // 把邊加入 priority_queue 50 | for (auto iter = edges[i].begin(); 51 | iter!=edges[i].end(); 52 | iter++) 53 | { 54 | // iter 本身的類型是 list::iterator 55 | // iter: iterator 56 | // *iter: edge* 57 | // **iter: edge 58 | // 把邊 push 進 queue 中 59 | sorted_edges.push(**iter); 60 | } 61 | } 62 | 63 | // 處理完所有邊,或者取的邊數已經到達頂點數-1時結束迴圈 64 | while (!sorted_edges.empty() && edges_completed < vertex - 1){ 65 | edge current = sorted_edges.top(); 66 | sorted_edges.pop(); 67 | // current 為 crossing edge 68 | if(Find_Set(current.from) != Find_Set(current.to)){ 69 | Merge_Set(current.from, current.to); 70 | MST_Edges.push_back(current); 71 | edges_completed++; 72 | } 73 | } 74 | 75 | // 算出最小生成樹權重 76 | int sum = 0; 77 | for(int i = 0; i < vertex - 1; i++){ 78 | // 輸出邊的資訊 79 | cout << MST_Edges[i].from + 1<< "->" << MST_Edges[i].to + 1 << ":" << MST_Edges[i].weight << endl; 80 | sum += MST_Edges[i].weight; 81 | } 82 | cout << "Weight of this MST = " << sum << endl; 83 | } 84 | 85 | int Graph::Find_Set(int u){ 86 | int root = u; 87 | while (MST_Set[root] >= 0){ 88 | root = MST_Set[root]; 89 | } 90 | while (u != root){ 91 | int predecessor = MST_Set[u]; 92 | MST_Set[u] = root; 93 | u = predecessor; 94 | } 95 | return root; 96 | } 97 | 98 | void Graph::Merge_Set(int u, int v){ 99 | int u_root = Find_Set(u); 100 | int v_root = Find_Set(v); 101 | if (MST_Set[u] <= MST_Set[v]){ 102 | // u 的頂點數比較多時 103 | MST_Set[u_root] += MST_Set[v_root]; 104 | MST_Set[v_root] = u_root; 105 | } else { 106 | // v 的頂點數比較多時 107 | MST_Set[v_root] += MST_Set[u_root]; 108 | MST_Set[u_root] = v_root; 109 | } 110 | } 111 | 112 | Graph::Graph(int v){ 113 | vertex = v; 114 | edges.resize(vertex); 115 | MST_Set.resize(vertex); 116 | } 117 | 118 | void Graph::Print_Edges(){ 119 | for(int i = 0; i < vertex; i++){ 120 | cout << i + 1 << "\t"; 121 | auto iter = edges[i].begin(); 122 | // list::iterator 123 | for(; iter != edges[i].end(); iter++){ 124 | cout << "->" << (*iter)->to + 1 << "," << (*iter)->weight; 125 | } 126 | cout << endl; 127 | } 128 | } 129 | 130 | void Graph::Add_Edge(int from,int to,int weight){ 131 | edges[from - 1].push_back(new edge{from - 1, to - 1, weight}); 132 | edges[to - 1].push_back(new edge{to - 1, from - 1, weight}); // Undirected graph 133 | } 134 | 135 | int main() { 136 | Graph g(7); 137 | g.Add_Edge(1,2,20); 138 | g.Add_Edge(1,3,10); 139 | g.Add_Edge(1,4,15); 140 | g.Add_Edge(2,5,10); 141 | g.Add_Edge(3,5,10); 142 | g.Add_Edge(3,4,10); 143 | g.Add_Edge(3,6,5); 144 | g.Add_Edge(4,7,25); 145 | g.Add_Edge(6,7,10); 146 | g.Add_Edge(5,7,5); 147 | g.MST_Kruskal(); 148 | return 0; 149 | } 150 | -------------------------------------------------------------------------------- /Chapter12/12_02_minCostConnectPoints_Kruskal.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#1584. Min Cost to Connect All Points 4 | 5 | A. 題目 6 | 給定一個陣列 points,代表了二維平面上一些頂點的整數座標,points[i] = [x_i,y_i]。 7 | 8 | 連接兩個頂點 [x_i,y_i] 和 [x_j,y_j] 的成本設定為 |x_i-x_j |+|y_i-y_j |,也就是 x 座標相減的絕對值與 y 座標相減的絕對值之和。 9 | 10 | 回傳要連接所有頂點的最小成本。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/min-cost-to-connect-all-points/ 14 | 15 | */ 16 | 17 | // 邊的結構 18 | struct edge{ 19 | int from; 20 | int to; 21 | int cost; 22 | }; 23 | 24 | // 邊的排序函式 25 | class compare{ 26 | public: 27 | bool operator()(edge &e1, edge &e2){ 28 | return e1.cost > e2.cost; 29 | } 30 | }; 31 | 32 | class Solution{ 33 | vector MST_set; 34 | // Find_Set 35 | int Find_Set(int current){ 36 | int root = current; 37 | while (MST_set[root]>=0){ 38 | root = MST_set[root]; 39 | } 40 | while (current!=root){ 41 | int predecessor = MST_set[current]; 42 | MST_set[current] = root; 43 | current = predecessor; 44 | } 45 | return root; 46 | } 47 | // Merge_Set 48 | void Merge_Set(int u, int v){ 49 | int u_root = Find_Set(u); 50 | int v_root = Find_Set(v); 51 | if (u_root == v_root){ return ; } 52 | if (MST_set[u_root] < MST_set[v_root]){ 53 | MST_set[u_root] += MST_set[v_root]; 54 | MST_set[v_root] = u_root; 55 | } else { 56 | MST_set[v_root] += MST_set[u_root]; 57 | MST_set[u_root] = v_root; 58 | } 59 | } 60 | public: 61 | int minCostConnectPoints(vector>& points){ 62 | // 已加入的邊數 63 | int completed = 0; 64 | int total_cost = 0; 65 | int vertex = points.size(); 66 | MST_set.resize(vertex); 67 | priority_queue, compare> 68 | sorted_edges; 69 | 70 | // 計算所有邊的成本,並生成該條邊 71 | for (int i = 0; i < vertex; i++){ 72 | MST_set[i] = -1; 73 | // 在任兩個點間都加上一條邊edge(i, j) 74 | for (int j = i + 1; j < vertex; j++){ 75 | // 計算 cost 76 | int current_cost = 77 | abs(points[i][0] - points[j][0]) + 78 | abs(points[i][1] - points[j][1]); 79 | edge current = {i, j, current_cost}; 80 | sorted_edges.push(current); 81 | } // end of inner for 82 | } // end of outer for 83 | // 依序取出所有邊作處理 84 | while (!sorted_edges.empty() && 85 | completed < vertex - 1) 86 | { 87 | edge current = sorted_edges.top(); 88 | sorted_edges.pop(); 89 | // 若 current 為 crossing edge 90 | if (Find_Set(current.from) != 91 | Find_Set(current.to)) 92 | { 93 | Merge_Set(current.from, current.to); 94 | total_cost += current.cost; 95 | completed++; 96 | } 97 | } 98 | // 回傳最小成本 99 | return total_cost; 100 | } // end of minCostConnectPoints 101 | }; // end of Solution 102 | -------------------------------------------------------------------------------- /Chapter12/12_04_minCostConnectPoints_Prim.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode#1584. Min Cost to Connect All Points 4 | 5 | A. 題目 6 | 給定一個陣列 points,代表了二維平面上一些頂點的整數座標,points[i] = [x_i,y_i]。 7 | 8 | 連接兩個頂點 [x_i,y_i] 和 [x_j,y_j] 的成本設定為 |x_i-x_j |+|y_i-y_j |,也就是 x 座標相減的絕對值與 y 座標相減的絕對值之和。 9 | 10 | 回傳要連接所有頂點的最小成本。 11 | 12 | B. 出處 13 | https://leetcode.com/problems/min-cost-to-connect-all-points/ 14 | 15 | */ 16 | 17 | // 邊的結構 18 | struct edge{ 19 | int from; 20 | int to; 21 | int cost; 22 | }; 23 | 24 | class Solution{ 25 | vector predecessor; 26 | vector distance; 27 | vector finished; 28 | vector> edges; 29 | int vertex; 30 | 31 | int Find_Minimal_Distance(){ 32 | int minimal = INT_MAX; 33 | int index = -1; 34 | for (int i=0 ; i>& points){ 44 | // 初始化 45 | int total_cost = 0; 46 | vertex = points.size(); 47 | predecessor.resize(vertex); 48 | distance.resize(vertex); 49 | finished.resize(vertex); 50 | edges.resize(vertex); 51 | 52 | // 加入邊與權重 53 | for (int i = 0; i < vertex; i++){ 54 | for (int j=i + 1; j < vertex; j++){ 55 | int current_cost = 56 | abs(points[i][0]-points[j][0]) + 57 | abs(points[i][1]-points[j][1]); 58 | edge current1{i, j, current_cost}; 59 | edge current2{j, i, current_cost}; 60 | edges[i].push_back(current1); 61 | edges[j].push_back(current2); 62 | } 63 | } 64 | 65 | for (int i = 0; i < vertex; i++){ 66 | predecessor[i] = -1; 67 | distance[i] = INT_MAX; 68 | finished[i] = false; 69 | } 70 | 71 | // 開始把頂點加入 MST 中 72 | distance[0] = 0; 73 | int index = 0; 74 | for (int i = 0; i < vertex; i++){ 75 | index = Find_Minimal_Distance(); 76 | finished[index] = true; 77 | total_cost += distance[index]; 78 | auto iter = edges[index].begin(); 79 | for (; iter != edges[index].end(); iter++){ 80 | int target = iter->to; 81 | int current_weight = distance[target]; 82 | int cross_weight = iter->cost; 83 | if (finished[target]) continue; 84 | if (cross_weight < current_weight){ 85 | distance[target] = cross_weight; 86 | predecessor[target] = index; 87 | } 88 | } 89 | } 90 | return total_cost; 91 | } // end of minCostConnectPoints 92 | }; // end of Solution 93 | -------------------------------------------------------------------------------- /Chapter13/13_01_Edmonds_Karp.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | using namespace std; 7 | 8 | // 邊的結構 9 | typedef struct{ 10 | int from; 11 | int to; 12 | // 目前流量 13 | int flow; 14 | // 容量 15 | int capacity; 16 | }edge; 17 | 18 | class Graph{ 19 | private: 20 | int vertex; 21 | // 每個邊對應的 list 形成的向量 22 | vector> edges; 23 | public: 24 | Graph(int); 25 | void Print_Edges(); 26 | bool Add_Edge(int, int, int=1); 27 | int Network_Flow(int, int); 28 | }; 29 | 30 | Graph::Graph(int v){ 31 | // 設定頂點數 32 | vertex = v; 33 | // 把向量的長度設為 vertex 34 | edges.resize(vertex); 35 | } 36 | 37 | bool Graph::Add_Edge(int from, int to, int capacity){ 38 | edges[from - 1].push_back(new edge{from - 1, to - 1, 0, capacity}); 39 | edges[to - 1].push_back(new edge{to - 1, from - 1, 0, capacity}); // Undirected graph 40 | } 41 | 42 | int Graph::Network_Flow(int s, int t){ 43 | s--; 44 | t--; 45 | // 記錄最大流流量 46 | int max_flow = 0; 47 | while(true){ 48 | // Step 1: 用廣度優先搜尋找到新的增廣路徑 49 | queue> data; 50 | // 記錄是否找到增廣路徑 51 | bool found = false; 52 | vector initial_path; 53 | vector current; 54 | vector visited(vertex); 55 | initial_path.push_back(s); 56 | // 沒走過:0、有走過:1 57 | visited[s] = 1; 58 | data.push(initial_path); 59 | while(!data.empty()){ 60 | // 取出 queue 中的第一個路徑 61 | current = data.front(); 62 | data.pop(); 63 | int now = current.back(); 64 | // 檢查目前頂點是否已經到達匯點 t 65 | // 若已經到達匯點 t 66 | if(now == t){ 67 | found = true; 68 | break; 69 | } 70 | // 還未到達匯點 t 71 | else{ 72 | for(auto iter=edges[now].begin(); iter!=edges[now].end(); iter++) { 73 | auto target = *iter; 74 | // 往有剩餘流量、還未造訪過的頂點方向移動 75 | if(target->capacity > target->flow && visited[target->to] == 0) { 76 | // 在原本的路徑後加上新造訪的頂點 77 | vector new_path = current; 78 | new_path.push_back((*iter)->to); 79 | // 記錄該頂點已經造訪過 80 | visited[(*iter)->to] = 1; 81 | data.push(new_path); 82 | } // end of inner if 83 | } // end of for 84 | } // end of outer if 85 | } // end of while 86 | 87 | // 沒有找到增廣路徑 88 | if(!found) 89 | break; 90 | // Step2: 找到增廣路徑中剩餘容量最小的邊 91 | int minimal = 2147483647; 92 | for(int i = 0; i < current.size() - 1; i++){ 93 | int u = current[i]; 94 | int v = current[i + 1]; 95 | // 要得到邊(u,v)的剩餘容量,只能從 edges 中尋找 96 | // 若改用矩陣方式儲存圖,會較方便 97 | for(auto iter=edges[u].begin(); iter!=edges[u].end(); iter++){ 98 | // 在 u 的出邊中找到指向 v 的那條 99 | // 比較 minimal 與邊的剩餘容量 100 | int residual = (*iter)->capacity - (*iter)->flow; 101 | if((*iter)->to == v && residual < minimal){ 102 | minimal = residual; 103 | } 104 | } // end of inner for 105 | } // end of outer for 106 | // Step3: 更新殘餘網路 107 | for(int i = 0; i < current.size() - 1; i++){ 108 | int u = current[i]; 109 | int v = current[i + 1]; 110 | for(auto iter=edges[u].begin(); iter!=edges[u].end(); iter++){ 111 | // 在 u 的出邊中找到指向 v 的那條 112 | if((*iter)->to == v){ 113 | (*iter)->flow += minimal; 114 | } 115 | } 116 | } 117 | max_flow += minimal; 118 | } // end of while 119 | return max_flow; 120 | } 121 | 122 | int main(){ 123 | Graph g(6); 124 | g.Add_Edge(1,2,5); 125 | g.Add_Edge(1,3,3); 126 | g.Add_Edge(2,3,2); 127 | g.Add_Edge(2,4,3); 128 | g.Add_Edge(3,5,6); 129 | g.Add_Edge(4,5,2); 130 | g.Add_Edge(4,6,5); 131 | g.Add_Edge(5,6,2); 132 | cout << "Max Flow: " << g.Network_Flow(1, 6); 133 | return 0; 134 | } 135 | -------------------------------------------------------------------------------- /Chapter13/13_02_質數伴侶.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | 質數伴侶問題 4 | 5 | 如果兩正整數的和為一質數,則可以稱這兩正整數為「質數伴侶」,比如說 3 + 4 = 7,這時候 3 與 4 互為質數伴侶。質數伴侶可以應用在密碼學上,現在的問題是要從多個整數中挑選出多對「質數伴侶」,比方如果現在有四個整數:3、4、6、7,這時候可以挑出兩對「質數伴侶」分別為3、4與6、7。 6 | 7 | 「質數伴侶」問題指的是若給你一堆數字,請問最多能從這些數字中找出幾對「質數伴侶」? 8 | 9 | */ 10 | 11 | #include 12 | #include 13 | 14 | using namespace std; 15 | 16 | // 判斷 input 是否為質數 17 | bool Is_Prime(int input){ 18 | for(int i = 2; i * i <= input; i++){ 19 | // 找到 input 的因數,回傳 false 20 | if(input % i == 0) 21 | return false; 22 | } 23 | return true; 24 | } 25 | 26 | // 從奇數 odd 找尋增廣路徑 27 | bool Find(int odd, vector& even, vector& used, vector& match){ 28 | // 試圖與每個偶數都配對看看 29 | for(int i = 0; i < even.size(); i++){ 30 | // 看與目前的偶數 even[i] 能否合成質數 31 | if(Is_Prime(odd + even[i]) && !used[i]){ 32 | used[i] = true; 33 | // 如果該偶數還沒配對過 34 | // 或與該偶數配對的奇數還有其他選擇 35 | if(match[i] == 0 || 36 | Find(match[i], even, used, match)) 37 | { 38 | match[i] = odd; 39 | return true; 40 | } 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | int main(){ 47 | int N; 48 | // 輸入有幾個整數 49 | cin >> N; 50 | 51 | // 存放所有數的集合 52 | vector data(N); 53 | // 存放奇數與偶數的集合 54 | vector odd; 55 | vector even; 56 | 57 | // 輸入所有數字 58 | for(int i = 0; i < N; i++){ 59 | cin >> data[i]; 60 | if(data[i] % 2) 61 | odd.push_back(data[i]); 62 | else 63 | even.push_back(data[i]); 64 | } 65 | // 質數伴侶的數目 66 | int counts = 0; 67 | 68 | // 紀錄每個偶數的配對狀況 69 | vector match(even.size(), 0); 70 | // 依序從每個奇數開始搜尋增廣路徑 71 | for(int i = 0; i < odd.size(); i++){ 72 | // 開始時,所有偶數都沒用過 73 | vector used(even.size(), false); 74 | // 是否能從第 i 個奇數出發找出增廣路徑 75 | if(Find(odd[i], even, used, match)) 76 | // 能找到增廣路徑,質數伴侶數目 +1 77 | counts++; 78 | } 79 | cout << counts << endl; 80 | return 0; 81 | } 82 | -------------------------------------------------------------------------------- /Chapter14/14_01_Bellman_Ford.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | // 代表邊的結構 11 | // 只需要紀錄指到的頂點即可 12 | typedef struct{ 13 | int to; 14 | int weight; 15 | }edge; 16 | 17 | class Graph{ 18 | private: 19 | vector distance; 20 | vector predecessor; 21 | int vertex; 22 | // 每個邊對應的 list 形成的向量 23 | vector> edges; 24 | bool Relax(int, int, float); 25 | void Initialize(int); 26 | public: 27 | Graph(int); 28 | bool Add_Edge(int, int, int=1); 29 | bool Bellman_Ford(int); 30 | void Print_Path(int); 31 | }; 32 | 33 | Graph::Graph(int v){ 34 | // 設定頂點數 35 | vertex = v; 36 | // 把向量的長度設為 vertex 37 | edges.resize(vertex); 38 | distance.resize(vertex); 39 | predecessor.resize(vertex); 40 | } 41 | 42 | bool Graph::Add_Edge(int from, int to, int weight){ 43 | edges[from - 1].push_back(new edge{to - 1, weight}); 44 | } 45 | 46 | bool Graph::Relax(int from, int to, float weight){ 47 | if(distance[to] > distance[from] + weight){ 48 | distance[to] = distance[from] + weight; 49 | predecessor[to] = from; 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | void Graph::Initialize(int s){ 56 | distance.clear(); 57 | predecessor.clear(); 58 | for(int i = 0; i < vertex; i++){ 59 | distance[i] = numeric_limits::max(); 60 | predecessor[i] = -1; 61 | } 62 | // 起點距離為 0 63 | distance[s] = 0; 64 | } 65 | 66 | bool Graph::Bellman_Ford(int s){ 67 | s--; 68 | // 初始化 69 | Initialize(s); 70 | list::iterator it; 71 | // 重複 v - 1 輪 72 | for(int i = 0; i < vertex - 1; i++){ 73 | // 每一輪對所有邊進行 Relax 74 | for(int j = 0; j < vertex; j++){ 75 | for(it = edges[j].begin(); it != edges[j].end();it++){ 76 | Relax(j, (*it)->to, (*it)->weight); 77 | } 78 | } 79 | } 80 | // 判斷負環是否存在 81 | for(int j = 0; j < vertex; j++){ 82 | for(it = edges[j].begin(); it != edges[j].end(); it++){ 83 | // 負環存在 84 | if(distance[(*it)->to] > distance[j] + (*it)->weight) 85 | return false; 86 | } 87 | } 88 | 89 | // 印出距離 90 | for(int j = 0; j < vertex; j++){ 91 | cout << s + 1 << "<->" << j + 1 << ":" << distance[j] << endl; 92 | } 93 | return true; 94 | } 95 | 96 | void Graph::Print_Path(int dest){ 97 | dest--; 98 | if(distance[dest] != numeric_limits::max()){ 99 | cout << "Distance:" << distance[dest] << endl; 100 | stack path; 101 | path.push(dest); 102 | while(predecessor[dest] != -1){ 103 | path.push(predecessor[dest]); 104 | dest = predecessor[dest]; 105 | } 106 | cout << "Path:"; 107 | while(!path.empty()){ 108 | int vertex_now = path.top(); 109 | path.pop(); 110 | cout << vertex_now + 1 << " "; 111 | } 112 | cout << endl; 113 | } 114 | } 115 | 116 | int main(){ 117 | Graph g(7); 118 | g.Add_Edge(1,2,20); 119 | g.Add_Edge(1,3,10); 120 | g.Add_Edge(1,4,15); 121 | g.Add_Edge(2,5,10); 122 | g.Add_Edge(3,5,10); 123 | g.Add_Edge(3,4,10); 124 | g.Add_Edge(3,6,5); 125 | g.Add_Edge(4,7,25); 126 | g.Add_Edge(6,7,10); 127 | g.Add_Edge(5,7,20); 128 | // 從 1 開始 129 | g.Bellman_Ford(1); 130 | // 找 1 到 7 的最短路徑 131 | g.Print_Path(7); 132 | return 0; 133 | } 134 | -------------------------------------------------------------------------------- /Chapter14/14_02_SPFA.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace std; 9 | 10 | // 代表邊的結構 11 | // 只需要紀錄指到的頂點即可 12 | typedef struct{ 13 | int to; 14 | int weight; 15 | }edge; 16 | 17 | class Graph{ 18 | private: 19 | vector distance; 20 | vector predecessor; 21 | int vertex; 22 | // 每個邊對應的 list 形成的向量 23 | vector> edges; 24 | bool Relax(int, int, float); 25 | void Initialize(int); 26 | public: 27 | Graph(int); 28 | bool Add_Edge(int, int, int=1); 29 | bool SPFA(int); 30 | void Print_Path(int); 31 | }; 32 | 33 | Graph::Graph(int v){ 34 | // 設定頂點數 35 | vertex = v; 36 | // 把向量的長度設為 vertex 37 | edges.resize(vertex); 38 | distance.resize(vertex); 39 | predecessor.resize(vertex); 40 | } 41 | 42 | bool Graph::Add_Edge(int from, int to, int weight){ 43 | edges[from - 1].push_back(new edge{to - 1, weight}); 44 | } 45 | 46 | bool Graph::Relax(int from, int to, float weight){ 47 | if(distance[to] > distance[from] + weight){ 48 | distance[to] = distance[from] + weight; 49 | predecessor[to] = from; 50 | return true; 51 | } 52 | return false; 53 | } 54 | 55 | void Graph::Initialize(int s){ 56 | distance.clear(); 57 | predecessor.clear(); 58 | for(int i = 0; i < vertex; i++){ 59 | distance[i] = numeric_limits::max(); 60 | predecessor[i] = -1; 61 | } 62 | // 起點距離為 0 63 | distance[s] = 0; 64 | } 65 | 66 | bool Graph::SPFA(int s){ 67 | s--; 68 | // 初始化 69 | Initialize(s); 70 | // 有被更新的頂點放入 Queue 71 | queue candidate; 72 | // 記錄更新次數 73 | vector update(vertex, 0); 74 | // 從起點開始 75 | candidate.push(s); 76 | while(!candidate.empty()){ 77 | int current = candidate.front(); 78 | candidate.pop(); 79 | auto it = edges[current].begin(); 80 | // 跑遍從 current 出發的所有邊 81 | for(; it != edges[current].end(); it++){ 82 | bool relaxed = Relax(current, (*it)->to, (*it)->weight); 83 | if(relaxed && (*it)->to != candidate.back()){ 84 | // 更新次數 + 1 85 | update[(*it)->to] += 1; 86 | // 有負環 87 | if(update[(*it)->to] == vertex) 88 | return false; 89 | candidate.push((*it)->to); 90 | } 91 | } 92 | } 93 | // 印出距離 94 | for(int j = 0; j < vertex; j++){ 95 | cout << s + 1 << "<->" << j + 1 << ":" << distance[j] << endl; 96 | } 97 | return true; 98 | } 99 | 100 | 101 | void Graph::Print_Path(int dest){ 102 | dest--; 103 | if(distance[dest] != numeric_limits::max()){ 104 | cout << "Distance:" << distance[dest] << endl; 105 | stack path; 106 | path.push(dest); 107 | while(predecessor[dest] != -1){ 108 | path.push(predecessor[dest]); 109 | dest = predecessor[dest]; 110 | } 111 | cout << "Path:"; 112 | while(!path.empty()){ 113 | int vertex_now = path.top(); 114 | path.pop(); 115 | cout << vertex_now + 1 << " "; 116 | } 117 | cout << endl; 118 | } 119 | } 120 | 121 | int main(){ 122 | Graph g(7); 123 | g.Add_Edge(1,2,20); 124 | g.Add_Edge(1,3,10); 125 | g.Add_Edge(1,4,15); 126 | g.Add_Edge(2,5,10); 127 | g.Add_Edge(3,5,10); 128 | g.Add_Edge(3,4,10); 129 | g.Add_Edge(3,6,5); 130 | g.Add_Edge(4,7,25); 131 | g.Add_Edge(6,7,10); 132 | g.Add_Edge(5,7,20); 133 | // 從 1 開始 134 | g.SPFA(1); 135 | // 找 1 到 7 的最短路徑 136 | g.Print_Path(7); 137 | return 0; 138 | } 139 | -------------------------------------------------------------------------------- /Chapter14/14_03_findCheapestPrice_Bellman_Ford.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #787. K次轉機內的最便宜航班 Cheapest Flights Within K Stops 4 | 5 | A. 題目 6 | 有 n 個城市中間由航班連接,給定一組資料 flights,其中 flights[i]=[〖from〗_i,〖to〗_i,〖price〗_i],分別代表該航班的起點、終點、價格。 7 | 8 | 今天另外給一個出發城市src與目的地城市dst,請找出一條航線最多經過 K 次轉機,且該條航線是所有可行航線中價格最便宜的,如果存在這麼一條航線就回傳該航線的價格,若否則回傳 -1。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/cheapest-flights-within-k-stops/ 12 | 13 | */ 14 | 15 | class Solution { 16 | public: 17 | int findCheapestPrice(int n, vector>& flights, int src, int dst, int K){ 18 | // 起點至每個點的最短距離 19 | vector distance(n, INT_MAX); 20 | // 把起點的最短距離設成 0 21 | distance[src] = 0; 22 | 23 | // 對所有邊進行 K+1 輪 relax 24 | for(int i = 0; i <= K; i++) 25 | { 26 | // 用上一輪的 distance 進行 relax 27 | vector current(distance); 28 | // 對所有邊進行 relax 29 | for(auto e : flights){ 30 | int s = e[0]; 31 | int f = e[1]; 32 | int p = e[2]; 33 | // 目前還沒有找到抵達 start 的方式 34 | if(distance[s] == INT_MAX) 35 | continue; 36 | if(current[f] > distance[s] + p) 37 | current[f] = distance[s] + p; 38 | } 39 | distance = current; 40 | } 41 | // 最後還是到不了 dst 42 | if (distance[dst] == INT_MAX) 43 | return -1; 44 | return distance[dst]; 45 | } 46 | }; 47 | -------------------------------------------------------------------------------- /Chapter14/14_03_findCheapestPrice_SPFA.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #787. K次轉機內的最便宜航班 Cheapest Flights Within K Stops 4 | 5 | A. 題目 6 | 有 n 個城市中間由航班連接,給定一組資料 flights,其中 flights[i]=[〖from〗_i,〖to〗_i,〖price〗_i],分別代表該航班的起點、終點、價格。 7 | 8 | 今天另外給一個出發城市src與目的地城市dst,請找出一條航線最多經過 K 次轉機,且該條航線是所有可行航線中價格最便宜的,如果存在這麼一條航線就回傳該航線的價格,若否則回傳 -1。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/cheapest-flights-within-k-stops/ 12 | 13 | */ 14 | 15 | class Solution { 16 | public: 17 | int findCheapestPrice(int n, vector>& flights, int src, int dst, int K){ 18 | // 用鄰接列表的方式儲存邊 19 | vector>> edges(n); 20 | 21 | // pair 中的 first 為航線的終點 22 | // pair 中的 second 為航線的費用 23 | for(auto e : flights){ 24 | edges[e[0]].push_back(make_pair(e[1], e[2])); 25 | } 26 | 27 | // 起點至每個點的最短距離 28 | vector distance(n, INT_MAX); 29 | // 把起點的最短距離設成 0 30 | distance[src] = 0; 31 | queue BFS; 32 | // 從起點開始 33 | BFS.push(src); 34 | 35 | // 往外 K + 1 輪 36 | for(int i = 0; i <= K; i++){ 37 | // 此輪開始前,Queue 中有幾個點 38 | int v = BFS.size(); 39 | // 上一輪的基礎 40 | vector tmp(distance); 41 | // 處理該輪的所有頂點 42 | for(int j = 0; j < v; j++){ 43 | int current = BFS.front(); 44 | BFS.pop(); 45 | for (auto e : edges[current]) { 46 | int new_dist = 47 | distance[current] + e.second; 48 | if (new_dist < distance[e.first] && 49 | new_dist < tmp[e.first]) 50 | { 51 | // 只有更新的點需要進行下一輪 relax 52 | tmp[e.first] = new_dist; 53 | BFS.push(e.first); 54 | } 55 | } 56 | } 57 | distance = tmp; 58 | } 59 | 60 | // 最後還是到不了 dst 61 | if (distance[dst] == INT_MAX) 62 | return -1; 63 | return distance[dst]; 64 | } 65 | }; 66 | -------------------------------------------------------------------------------- /Chapter14/14_04_DAG.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | // 代表邊的結構 10 | // 只需要紀錄指到的頂點即可 11 | typedef struct{ 12 | int to; 13 | int weight; 14 | }edge; 15 | 16 | class Graph{ 17 | private: 18 | vector distance; 19 | vector predecessor; 20 | int vertex; 21 | // 每個邊對應的 list 形成的向量 22 | vector> edges; 23 | bool Relax(int, int, float); 24 | vector color; 25 | stack topological_sort; 26 | void DFS_Visit_Topological(int); 27 | void Get_Topological_Sort(int); 28 | void Initialize(int); 29 | public: 30 | Graph(int); 31 | void Print_Edges(); 32 | bool Add_Edge(int, int, int=1); 33 | void DAG(int); 34 | void Print_Path(int); 35 | }; 36 | 37 | bool Graph::Relax(int from, int to, float weight){ 38 | if(distance[from] == numeric_limits::max()) 39 | return false; 40 | if(distance[to] > distance[from] + weight){ 41 | distance[to] = distance[from] + weight; 42 | predecessor[to] = from; 43 | return true; 44 | } 45 | return false; 46 | } 47 | 48 | Graph::Graph(int v){ 49 | // 設定頂點數 50 | vertex = v; 51 | // 把向量的長度設為 vertex 52 | color.resize(vertex); 53 | edges.resize(vertex); 54 | distance.resize(vertex); 55 | predecessor.resize(vertex); 56 | } 57 | 58 | void Graph::Initialize(int s){ 59 | distance.clear(); 60 | predecessor.clear(); 61 | for(int i = 0; i < vertex; i++){ 62 | distance[i] = numeric_limits::max(); 63 | predecessor[i] = -1; 64 | } 65 | // 起點距離為 0 66 | distance[s] = 0; 67 | } 68 | 69 | bool Graph::Add_Edge(int from, int to, int weight){ 70 | edges[from - 1].push_back(new edge{to - 1, weight}); 71 | } 72 | 73 | void Graph::DFS_Visit_Topological(int current){ 74 | color[current] = 1; 75 | // 從 current 的鄰邊中找白點繼續往下做深度優先搜尋 76 | for(auto iter = edges[current].begin(); 77 | iter!=edges[current].end(); 78 | iter++) 79 | { 80 | int neighbor = (*iter)->to; 81 | if(color[neighbor] == 0){ 82 | DFS_Visit_Topological(neighbor); 83 | } 84 | } 85 | color[current] = 2; 86 | // 從離開 current 後就插入拓樸排序內 87 | topological_sort.push(current); 88 | } 89 | 90 | void Graph::Get_Topological_Sort(int start){ 91 | // 把所有點塗成白色 92 | fill(color.begin(), color.end(), 0); 93 | // 從起點開始做拓墣排序 94 | DFS_Visit_Topological(start); 95 | for(int i = 0; i < vertex; i++){ 96 | if(color[i] == 0){ 97 | DFS_Visit_Topological(i); 98 | } 99 | } 100 | } 101 | 102 | void Graph::DAG(int s){ 103 | s--; 104 | // 初始化 105 | Initialize(s); 106 | // 利用深度優先搜尋生成拓樸排序 107 | Get_Topological_Sort(s); 108 | cout << "Topological sort:"; 109 | // 依照拓樸排序的順序進行 Relax 110 | while(!topological_sort.empty()){ 111 | int index = topological_sort.top(); 112 | cout << index + 1 << " "; 113 | topological_sort.pop(); 114 | // Relax 頂點 index 的 所有出邊 115 | auto it=edges[index].begin(); 116 | for(; it!=edges[index].end(); it++){ 117 | Relax(index,(*it)->to,(*it)->weight); 118 | } 119 | } 120 | cout << endl; 121 | // 印出距離 122 | for(int j = 0; j < vertex; j++){ 123 | cout << s + 1 << "<->" << j + 1 << ":" << distance[j] << endl; 124 | } 125 | cout << endl; 126 | } 127 | 128 | void Graph::Print_Path(int dest){ 129 | dest--; 130 | if(distance[dest] != numeric_limits::max()){ 131 | cout << "Distance:" << distance[dest] << endl; 132 | stack path; 133 | path.push(dest); 134 | while(predecessor[dest] != -1){ 135 | path.push(predecessor[dest]); 136 | dest = predecessor[dest]; 137 | } 138 | cout << "Path:"; 139 | while(!path.empty()){ 140 | int vertex_now = path.top(); 141 | path.pop(); 142 | cout << vertex_now + 1 << " "; 143 | } 144 | cout << endl; 145 | } 146 | } 147 | 148 | int main(){ 149 | Graph g(7); 150 | g.Add_Edge(1,2,20); 151 | g.Add_Edge(1,3,10); 152 | g.Add_Edge(1,4,15); 153 | g.Add_Edge(2,5,10); 154 | g.Add_Edge(3,5,10); 155 | g.Add_Edge(3,4,10); 156 | g.Add_Edge(3,6,5); 157 | g.Add_Edge(4,7,25); 158 | g.Add_Edge(6,7,10); 159 | g.Add_Edge(5,7,20); 160 | // 從 1 開始 161 | g.DAG(1); 162 | // 找 1 到 7 的最短路徑 163 | g.Print_Path(7); 164 | return 0; 165 | } 166 | -------------------------------------------------------------------------------- /Chapter14/14_05_Dijkstra.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | using namespace std; 8 | 9 | // 每個頂點 vertex 有編號、predecessor 和 distance 10 | typedef struct{ 11 | int index; 12 | int predecessor; 13 | float distance; 14 | }vertex; 15 | 16 | // 代表邊的結構 17 | // 只需要紀錄指到的頂點即可 18 | typedef struct{ 19 | int to; 20 | int weight; 21 | }edge; 22 | 23 | class Graph{ 24 | private: 25 | vector distance; 26 | vector predecessor; 27 | int n; 28 | // 每個邊對應的 list 形成的向量 29 | vector> edges; 30 | bool Relax(int, int, float); 31 | vector color; 32 | void Initialize(int); 33 | public: 34 | Graph(int); 35 | void Print_Edges(); 36 | bool Add_Edge(int, int, int=1); 37 | void Dijkstra(int); 38 | void Print_Path(int); 39 | }; 40 | 41 | bool Graph::Relax(int from, int to, float weight){ 42 | if(distance[from] == numeric_limits::max()) 43 | return false; 44 | if(distance[to] > distance[from] + weight){ 45 | distance[to] = distance[from] + weight; 46 | predecessor[to] = from; 47 | return true; 48 | } 49 | return false; 50 | } 51 | 52 | Graph::Graph(int v){ 53 | // 設定頂點數 54 | n = v; 55 | // 把向量的長度設為 n 56 | color.resize(n); 57 | edges.resize(n); 58 | distance.resize(n); 59 | predecessor.resize(n); 60 | } 61 | 62 | void Graph::Initialize(int s){ 63 | distance.clear(); 64 | predecessor.clear(); 65 | // n 為頂點個數 66 | for(int i = 0; i < n; i++){ 67 | distance[i] = numeric_limits::max(); 68 | predecessor[i] = -1; 69 | } 70 | // 起點距離為 0 71 | distance[s] = 0; 72 | } 73 | 74 | bool Graph::Add_Edge(int from, int to, int weight){ 75 | edges[from - 1].push_back(new edge{to - 1, weight}); 76 | } 77 | 78 | // 排序函式,供priority_queue 使用,注意使用 > 才能從距離最近者開始取 79 | class compare{ 80 | public: 81 | bool operator()(vertex* v1, vertex* v2){ 82 | return v1->distance > v2->distance; 83 | } 84 | }; 85 | 86 | void Graph::Dijkstra(int s){ 87 | s--; 88 | priority_queue, compare> candidates; 89 | vector already_shortest(n, false); 90 | vertex* vertex_now; 91 | // 初始化 92 | Initialize(s); 93 | // 從起點 s 開始 94 | candidates.push(new vertex{s, -1, 0}); 95 | // 不斷取出距離最近的頂點 96 | while(!candidates.empty()){ 97 | int index = candidates.top()->index; 98 | // index 已經在最短路徑組中 99 | candidates.pop(); 100 | if(already_shortest[index]){ 101 | continue; 102 | } 103 | // 把 index 加入最短路徑組 104 | already_shortest[index] = true; 105 | cout << index + 1 << "->"; 106 | auto it = edges[index].begin(); 107 | // Relax index 的所有出邊 108 | for(; it!=edges[index].end(); it++){ 109 | bool relaxed = Relax(index, (*it)->to, (*it)->weight); 110 | if(relaxed) 111 | candidates.push(new vertex{(*it)->to, predecessor[(*it)->to], distance[(*it)->to]} ); 112 | } 113 | } 114 | cout << endl; 115 | } 116 | 117 | void Graph::Print_Path(int dest){ 118 | dest--; 119 | if(distance[dest] != numeric_limits::max()){ 120 | cout << "Distance:" << distance[dest] << endl; 121 | stack path; 122 | path.push(dest); 123 | while(predecessor[dest] != -1){ 124 | path.push(predecessor[dest]); 125 | dest = predecessor[dest]; 126 | } 127 | cout << "Path:"; 128 | while(!path.empty()){ 129 | int vertex_now = path.top(); 130 | path.pop(); 131 | cout << vertex_now + 1 << " "; 132 | } 133 | cout << endl; 134 | } 135 | } 136 | 137 | int main(){ 138 | Graph g(7); 139 | g.Add_Edge(1,2,20); 140 | g.Add_Edge(1,3,10); 141 | g.Add_Edge(1,4,15); 142 | g.Add_Edge(2,5,10); 143 | g.Add_Edge(3,5,10); 144 | g.Add_Edge(3,4,10); 145 | g.Add_Edge(3,6,5); 146 | g.Add_Edge(4,7,25); 147 | g.Add_Edge(6,7,10); 148 | g.Add_Edge(5,7,20); 149 | // 從 1 開始 150 | g.Dijkstra(1); 151 | // 找 1 到 7 的最短路徑 152 | g.Print_Path(7); 153 | return 0; 154 | } 155 | -------------------------------------------------------------------------------- /Chapter14/14_06_networkDelayTime.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #743. 網路的延遲時間 Network Delay Time 4 | 5 | A. 題目 6 | 7 | 網路中有 n 個節點,編號從 1 到 n,另外 times 代表網路中節點跟節點之間的訊號延遲時間,其中 times[i] = (u_i,v_i,w_i),u_i 代表訊號起點、v_i 代表訊號終點、w_i 代表訊號的延遲時間,請問網路中所有節點收到訊號至少需要多少時間?如果無法讓全網路都收到訊號則回傳 -1。 8 | 9 | B. 出處 10 | https://leetcode.com/problems/network-delay-time/ 11 | 12 | */ 13 | 14 | // 每個頂點 vertex 有編號、predecessor 和 distance 15 | typedef struct{ 16 | int index; 17 | int distance; 18 | }vertex; 19 | 20 | // 排序函式,供priority_queue 使用 21 | class compare{ 22 | public: 23 | bool operator()(vertex* v1, vertex* v2){ 24 | return v1->distance > v2->distance; 25 | } 26 | }; 27 | 28 | class Solution { 29 | public: 30 | int networkDelayTime(vector>& times, int n, int k){ 31 | priority_queue, compare> candidates; 32 | vector already_shortest(n + 1, false); 33 | // 起點至每個點的最短距離 34 | vector distance(n + 1, INT_MAX); 35 | int result = 0; 36 | 37 | // 改用鄰接列表儲存資料 38 | vector>> edges(n + 1); 39 | // pair 中的 first 為連線的終點 40 | // pair 中的 second 為連線的所需時間 41 | for (auto e : times) 42 | edges[e[0]].push_back(make_pair(e[1], e[2])); 43 | 44 | // 從起點 k 開始 45 | candidates.push(new vertex{k, 0}); 46 | distance[k] = 0; 47 | // 不斷取出距離最近的頂點 48 | while(!candidates.empty()){ 49 | int index = candidates.top()->index; 50 | candidates.pop(); 51 | // index 已經在最短路徑組中 52 | if(already_shortest[index]){ 53 | continue; 54 | } 55 | // 把 index 加入最短路徑組 56 | already_shortest[index] = true; 57 | // 記錄下最後找到最短路徑時的距離 58 | result = distance[index]; 59 | // Relax index 的所有出邊 60 | for(int i = 0; i < edges[index].size(); i++){ 61 | int target = edges[index][i].first; 62 | int w = edges[index][i].second; 63 | if(distance[target] > distance[index] + w){ 64 | distance[target] = distance[index] + w; 65 | candidates.push(new vertex{target, distance[index] + w}); 66 | } 67 | } 68 | } 69 | for(int i = 1; i <= n; i++){ 70 | if(!already_shortest[i]){ 71 | return -1; 72 | } 73 | } 74 | // 全部的頂點都有找到最短路徑 75 | return result; 76 | } 77 | }; 78 | -------------------------------------------------------------------------------- /Chapter14/14_07_maxProbability.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1514. 機率最大的路徑 Path with Maximum Probability 4 | 5 | A. 題目 6 | 給定一無向圖有n個節點,編號從0開始,另外給一陣列edges紀錄邊,其中 edges[i]=[a,b],代表a、b之間的成功機率,且其成功機率為succProb[i]。 7 | 8 | 請計算出從start節點到end節點的最大成功機率,要是中間沒有任何路徑請回傳0。 9 | 10 | B. 出處 11 | https://leetcode.com/problems/path-with-maximum-probability/ 12 | 13 | */ 14 | 15 | // 每個頂點 vertex 有編號、predecessor 和 prob 16 | typedef struct{ 17 | int index; 18 | double prob; 19 | }vertex; 20 | 21 | // 排序函式,供priority_queue 使用,注意不等號與先前反向 22 | class compare{ 23 | public: 24 | bool operator()(vertex* v1, vertex* v2){ 25 | return v1->prob < v2->prob; 26 | } 27 | }; 28 | 29 | class Solution { 30 | public: 31 | double maxProbability(int n, vector>& edges, vector& succProb, int start, int end){ 32 | priority_queue, compare> candidates; 33 | vector already_shortest(n, false); 34 | // 起點至每個點的最大機率 35 | vector prob(n, 0); 36 | 37 | // 改用鄰接列表儲存資料 38 | vector>> graph(n); 39 | // pair 中的 first 為邊的終點 40 | // pair 中的 second 為邊的成功機率 41 | for (int i = 0; i < edges.size(); i++) { 42 | auto e = edges[i]; 43 | graph[e[0]].push_back(make_pair(e[1], succProb[i])); 44 | graph[e[1]].push_back(make_pair(e[0], succProb[i])); 45 | } 46 | 47 | // 從起點 start 開始 48 | candidates.push(new vertex{start, 1}); 49 | prob[start] = 1; 50 | // 不斷取出機率最大的頂點 51 | while(!candidates.empty()){ 52 | int index = candidates.top()->index; 53 | candidates.pop(); 54 | // index 已經在最短路徑組中 55 | if(already_shortest[index]){ 56 | continue; 57 | } 58 | // 把 index 加入最短路徑組 59 | already_shortest[index] = true; 60 | // 找到終點時的機率 61 | if(index == end) 62 | return prob[index]; 63 | // Relax index 的所有出邊 64 | for(int i = 0; i < graph[index].size(); i++){ 65 | int target = graph[index][i].first; 66 | double w = graph[index][i].second; 67 | // 保留機率最大者 68 | if(prob[target] < prob[index] * w){ 69 | prob[target] = prob[index] * w; 70 | candidates.push(new vertex{target, prob[index] * w}); 71 | } 72 | } 73 | } 74 | return 0; 75 | } 76 | }; 77 | -------------------------------------------------------------------------------- /Chapter14/14_08_Floyd_Warshall.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | using namespace std; 6 | 7 | class Graph{ 8 | private: 9 | int vertex; 10 | vector> distance; 11 | vector> predecessor; 12 | public: 13 | Graph(int); 14 | void Print_Distance(); 15 | void Print_Predecessor(); 16 | void Floyd_Warshall(); 17 | bool Add_Edge(int,int,int=1); 18 | }; 19 | 20 | void Graph::Floyd_Warshall(){ 21 | for(int k = 0; k < vertex; k++){ 22 | for(int i = 0; i < vertex; i++){ 23 | for(int j = 0; j < vertex; j++){ 24 | if(distance[i][k] == numeric_limits::max()) 25 | continue; 26 | else if(distance[k][j] == numeric_limits::max()) 27 | continue; 28 | else if(distance[i][j] > distance[i][k] + distance[k][j]){ 29 | distance[i][j] = distance[i][k] + distance[k][j]; 30 | predecessor[i][j] = k; 31 | } 32 | } 33 | } 34 | } 35 | } 36 | 37 | Graph::Graph(int v){ 38 | // 設定頂點數 39 | vertex = v; 40 | // 把向量的長度設為 vertex x vertex 41 | distance.resize(vertex, 42 | vector(vertex, numeric_limits::max())); 43 | predecessor.resize(vertex, vector(vertex, -1)); 44 | for(int i = 0; i < vertex; i++){ 45 | distance[i][i] = 0; 46 | } 47 | } 48 | 49 | bool Graph::Add_Edge(int from, int to, int weight){ 50 | from--; 51 | to--; 52 | if(distance[from][to] == numeric_limits::max()){ 53 | distance[from][to] = weight; 54 | predecessor[from][to] = from; 55 | } 56 | if(distance[to][from] == numeric_limits::max()){ 57 | distance[to][from] = weight; 58 | predecessor[to][from] = to; 59 | } 60 | } 61 | 62 | void Graph::Print_Distance(){ 63 | for(int i = 0; i < vertex; i++){ 64 | for(int j = 0; j < vertex; j++){ 65 | if(distance[i][j] == numeric_limits::max()) 66 | cout << "Inf\t"; 67 | else 68 | cout << distance[i][j] << "\t"; 69 | } 70 | cout << endl; 71 | } 72 | } 73 | 74 | void Graph::Print_Predecessor(){ 75 | for(int i = 0; i < vertex; i++){ 76 | for(int j = 0; j < vertex; j++){ 77 | cout << predecessor[i][j] << "\t"; 78 | } 79 | cout << endl; 80 | } 81 | } 82 | int main() 83 | { 84 | Graph g(7); 85 | g.Add_Edge(1,2,20); 86 | g.Add_Edge(1,3,10); 87 | g.Add_Edge(1,4,15); 88 | g.Add_Edge(2,5,10); 89 | g.Add_Edge(3,5,10); 90 | g.Add_Edge(3,4,10); 91 | g.Add_Edge(3,6,5); 92 | g.Add_Edge(4,7,25); 93 | g.Add_Edge(6,7,10); 94 | g.Add_Edge(5,7,20); 95 | 96 | cout << "Distance Matrix......" << endl; 97 | g.Print_Distance(); 98 | cout << "Predecessor Matrix......" << endl; 99 | g.Print_Predecessor(); 100 | cout << "Floyd Warshall Algorithm......" << endl; 101 | g.Floyd_Warshall(); 102 | cout << "Distance Matrix......" << endl; 103 | g.Print_Distance(); 104 | cout << "Predecessor Matrix......" << endl; 105 | g.Print_Predecessor(); 106 | 107 | return 0; 108 | } 109 | -------------------------------------------------------------------------------- /Chapter14/14_09_findTheCity.cpp: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | LeetCode #1334. 閾值距離內鄰居最少的城市 Find the City With the Smallest Number of Neighbors at a Threshold Distance 4 | 5 | A. 題目 6 | 圖上有有 n 個城市,城市的編號從0到n - 1,另外給一個edges陣列,其中 edges[i] = [from_i,to_i,weight_i],分別代表 from_i 城市與 to_i 城市間的最短 7 | 距離 weight_i。 8 | 9 | 若給定另外一整數distanceThreshold,定義若兩城市間的最短距離落在distanceThreshold內(包含distanceThreshold),則代表這兩個城市互為「鄰居」,現在請試著找出有最少的「鄰居」的城市,如果有超過一個城市符合,則回傳其中編號最大者。 10 | 11 | B. 出處 12 | https://leetcode.com/problems/find-the-city-with-the-smallest-number-of-neighbors-at-a-threshold-distance/ 13 | 14 | */ 15 | 16 | class Solution { 17 | public: 18 | int findTheCity(int n, vector>& edges, int distanceThreshold){ 19 | // n x n 的最短距離陣列 20 | vector> dist(n, vector(n, INT_MAX)); 21 | for(auto e: edges){ 22 | dist[e[0]][e[1]] = e[2]; 23 | dist[e[1]][e[0]] = e[2]; 24 | } 25 | // 自身到自身的距離為 0 26 | for(int i = 0; i < n; i++) 27 | dist[i][i] = 0; 28 | 29 | // Floyd Warshall 30 | // 採納第 k 個頂點為中繼頂點 31 | for(int k = 0; k < n; k++){ 32 | for(int i = 0; i < n; i++){ 33 | for(int j = 0; j < n; j++){ 34 | if(i == j) 35 | continue; 36 | if(dist[i][k] == INT_MAX) 37 | continue; 38 | if(dist[k][j] == INT_MAX) 39 | continue; 40 | int new_dist = dist[i][k] + dist[k][j]; 41 | if(dist[i][j] > new_dist) 42 | dist[i][j] = new_dist; 43 | } 44 | } 45 | } 46 | 47 | int min_neighbor = INT_MAX; 48 | int min_city = -1; 49 | // 計算每個城市的鄰居個數 50 | for(int i = 0; i < n; i++){ 51 | int neighbors = 0; 52 | for(int j = 0; j < n; j++){ 53 | if(i == j) 54 | continue; 55 | if(dist[i][j] <= distanceThreshold) 56 | neighbors++; 57 | } 58 | // 記錄下最少的鄰居 59 | if(min_neighbor >= neighbors){ 60 | min_neighbor = neighbors; 61 | min_city = i; 62 | } 63 | } 64 | 65 | return min_city; 66 | } 67 | }; 68 | -------------------------------------------------------------------------------- /勘誤表: -------------------------------------------------------------------------------- 1 | 2-12 2 | Q2 的程式碼中 j < N 應改為 j ≦ N,後續亦同: 3 | 「只有 1 + 2(n − 1) ≦ N 時,才會滿足迴圈的條件而繼續執行。 4 | 移項後可以得到 n ≦ (N+1)/2」 5 | 6 | 2-13 7 | Q3 的程式碼中 i > 1 應改為 i ≧ 1,後續亦同: 8 | 「迴圈從 i = M 時開始執行,i 必須大於等於 1 才會繼續執行,且每次執行完 9 | i 會除以 2。」 10 | 「因 M / 2^(n-1) 要滿足大於等於 1 的要求,所以: 11 | M / 2^(n-1) ≧ 1 12 | M ≧ 2^(n-1) 13 | n ≦ log2(M) + 1」 14 | 15 | 2-14 16 | Q5 的程式碼中 j < N 應改為 j ≦ N,後續亦同: 17 | 「j 從 1 開始執行,j 須小於等於 N 才會繼續執行,且 j 每次執行都會乘以 2」 18 | 「由於 2^(n-1) ≦ N 時迴圈才會執行,兩邊取 log_2」 19 | 「得到 n ≦ log2(N) + 1」 20 | 21 | Q6 的程式碼中內層迴圈的 j < N 應改為 j ≦ N 22 | 23 | 2-31 24 | Q3-A:內層迴圈的 j < N/2 應改為 j ≦ N/2 25 | Q3-B:外層迴圈的 i > 5 應改為 i ≧ 5 26 | Q3-B:內層迴圈的 j < N 應改為 j ≦ N 27 | 28 | 3-16 29 | Q2-A:決策問題中,可以在多項式的時間複雜度內驗證的問題稱為? 30 | 31 | 3-17 32 | Q4:改為單選題 33 | 34 | 4-73 35 | i-C:沒有Best Case、Worst Case的差別,或有其他因素影響 36 | 37 | 5-47 38 | Q4-A:選項應改為 1、2、3、4、5、6、7 39 | 40 | 9-6: 41 | 入度的符號應為:deg-(A) 42 | 出度的符號應為:deg+(A) 43 | 44 | 10-46: 45 | Chapter10/10_09_numEnclaves.cpp 更新,須在BFS內的while loop中檢查grid[x][y]是否為零,否則按原本寫法會TLE --------------------------------------------------------------------------------