├── .gitignore ├── README.md ├── SUMMARY.md ├── array ├── README.md ├── find_minimum_in_rotated_sorted_array.md ├── find_peak_element.md ├── largest_rectangle_in_histogram.md ├── maximal_rectangle.md ├── merge_sorted_array.md ├── palindrome_number.md ├── pascals_triangle.md ├── plus_one.md ├── remove_duplicates_from_sorted_array.md ├── remove_element.md ├── search_a_2d_matrix.md ├── search_for_a_range.md ├── search_insert_position.md └── sum.md ├── backtracking ├── README.md ├── combination.md ├── permutation.md └── subsets.md ├── bit_manipulation ├── README.md ├── missing_number.md ├── number_of_1_bits.md └── power_of_two.md ├── dynamic_programming ├── README.md ├── best_time_to_buy_and_sell_stock.md ├── climbing_stairs.md ├── maximum_subarray.md ├── perfect_squares.md ├── triangle.md ├── unique_binary_search_trees.md └── unique_paths.md ├── greedy ├── README.md ├── candy.md ├── gas_station.md ├── jump_game.md └── word_break.md ├── linked_list ├── README.md ├── add_two_numbers.md ├── copy_list_with_random_pointer.md ├── linked_list_cycle.md ├── merge_sorted_lists.md ├── partition_list.md ├── remove_duplicates_from_sorted_list.md ├── reorder_list.md ├── reverse_linked_list.md ├── rotate_list.md ├── sort_list.md └── swap_nodes_in_pairs.md ├── math ├── README.md └── reverse_integer.md ├── string ├── README.md ├── add_binary.md └── basic_calculator_2.md └── tree ├── README.md ├── balanced_binary_tree.md ├── binary_search_tree_iterator.md ├── binary_tree_depth_order_traversal.md ├── binary_tree_level_order_traversal.md ├── binary_tree_path.md ├── construct_binary_tree.md ├── convert_sorted_listarray_to_binary_search_tree.md ├── count_complete_tree_nodes.md ├── depth_of_binary_tree.md ├── flatten_binary_tree_to_linked_list.md ├── path_sum.md ├── path_sum_ii.md ├── populating_next_right_pointers_in_each_node.md ├── recover_binary_search_tree.md ├── same_tree.md ├── sum_root_to_leaf_numbers.md ├── symmetric_tree.md └── validate_binary_search_tree.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 | 首先声明,我和张晓翀都不是算法牛人,确切的说应该是算法的门外汉,小白一个。所以我们为了撬开算法的大门,各自刷完了一遍LeetCode的题目,这其中碰到了很多困难,当然也少不了用了Google以及参考了别人的代码。 4 | 5 | 做完一遍下来,陡然发现,很多题目还是忘记了,再次碰到又不知道如何下手,其实这就是典型的没有理解,掌握透。所以我们决定,应该好好的将自己做题的思路记录下来,一个知识点,自己能弄懂,写出来让大家都明白,同时能做到举一反三,触类旁通,那么在一定程度上面才算是真的掌握了。 6 | 7 | 于是就有了现在准备开始的这本书:《LeetCode题解》,用来记录我们刷LeetCode题目时候的心酸历史。我们保证,书中的代码一定通过了当时LeetCode的测试,虽然后续可能因为LeetCode测试条件的改变导致某些解题无法通过,但我们会实时的跟进。 8 | 9 | 编程语言使用C++,代码风格上面并没有强制的采用什么编码规范,毕竟是算法解题,只需要代码清晰易懂就可以了。 10 | 11 | 我们准备按照LeetCode的题型分类来组织章节,譬如Array,Hash Table等,而对每个章节里面的题目,通常采用由易到难的方式进行说明。采用这种方式,能让我们在短时间内快速学习掌握某一类知识,同时也便于讲解说明。 12 | 13 | 当然,除了LeetCode现有的题目,我们也希望在每个章节加入相关的扩展知识,这需要我们参考大量现有的算法书籍,鉴于个人精力时间有限,可能并不会完全实施。 14 | 15 | 最后,我们非常欢迎大家的反馈(前提是有人看我们的东西)。如果你有任何的意见建议,欢迎在Github的issue里面提出,或者直接与我们联系。 16 | 17 | # Thanks Contributor 18 | 19 | + 陈心宇 [collectchen@gmail.com](collectchen@gmail.com) 20 | + 张晓翀 [xczhang07@gmail.com](xczhang07@gmail.com) 21 | 22 | # Maintainer 23 | + SiddonTang [siddontang@gmail.com](siddontang@gmail.com) 24 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | 3 | * [Introduction](README.md) 4 | * [Array](array/README.md) 5 | * [Remove Element](array/remove_element.md) 6 | * [Remove Duplicates from Sorted Array](array/remove_duplicates_from_sorted_array.md) 7 | * [Plus One](array/plus_one.md) 8 | * [Pascal's Triangle](array/pascals_triangle.md) 9 | * [Merge Sorted Array](array/merge_sorted_array.md) 10 | * [Sum](array/sum.md) 11 | * [Find Minimum in Rotated Sorted Array ](array/find_minimum_in_rotated_sorted_array.md) 12 | * [Largest Rectangle in Histogram ](array/largest_rectangle_in_histogram.md) 13 | * [Maximal Rectangle](array/maximal_rectangle.md) 14 | * [Palindrome Number](array/palindrome_number.md) 15 | * [Search a 2D Matrix](array/search_a_2d_matrix.md) 16 | * [Search for a Range](array/search_for_a_range.md) 17 | * [Search Insert Position](array/search_insert_position.md) 18 | * [Find Peak Element ](array/find_peak_element.md) 19 | * [Bit Manipulation](bit_manipulation/README.md) 20 | * [Missing Number](bit_manipulation/missing_number.md) 21 | * [Power of Two](bit_manipulation/power_of_two.md) 22 | * [Number of 1 Bits](bit_manipulation/number_of_1_bits.md) 23 | * [Tree](tree/README.md) 24 | * [Depth of Binary Tree ](tree/depth_of_binary_tree.md) 25 | * [Construct Binary Tree](tree/construct_binary_tree.md) 26 | * [Binary Tree Level Order Traversal](tree/binary_tree_level_order_traversal.md) 27 | * [Symmetric Tree](tree/symmetric_tree.md) 28 | * [Same Tree](tree/same_tree.md) 29 | * [Balanced Binary Tree](tree/balanced_binary_tree.md) 30 | * [Path Sum](tree/path_sum.md) 31 | * [Binary Tree Depth Order Traversal ](tree/binary_tree_depth_order_traversal.md) 32 | * [Populating Next Right Pointers in Each Node ](tree/populating_next_right_pointers_in_each_node.md) 33 | * [Convert Sorted List/Array to Binary Search Tree ](tree/convert_sorted_listarray_to_binary_search_tree.md) 34 | * [Path Sum II](tree/path_sum_ii.md) 35 | * [Flatten Binary Tree to Linked List](tree/flatten_binary_tree_to_linked_list.md) 36 | * [Validate Binary Search Tree](tree/validate_binary_search_tree.md) 37 | * [Recover Binary Search Tree](tree/recover_binary_search_tree.md) 38 | * [Binary Tree Path](tree/binary_tree_path.md) 39 | * [Sum Root to Leaf Numbers](tree/sum_root_to_leaf_numbers.md) 40 | * [Dynamic Programming](dynamic_programming/README.md) 41 | * [Best Time To Buy And Sell Stock](dynamic_programming/best_time_to_buy_and_sell_stock.md) 42 | * [Unique Paths ](dynamic_programming/unique_paths.md) 43 | * [Maximum Subarray](dynamic_programming/maximum_subarray.md) 44 | * [Climbing Stairs ](dynamic_programming/climbing_stairs.md) 45 | * [Triangle](dynamic_programming/triangle.md) 46 | * [Unique Binary Search Trees](dynamic_programming/unique_binary_search_trees.md) 47 | * [Perfect Squares](dynamic_programming/perfect_squares.md) 48 | * [Backtracking](backtracking/README.md) 49 | * [Combination](backtracking/combination.md) 50 | * [Subsets](backtracking/subsets.md) 51 | * [Permutation](backtracking/permutation.md) 52 | * [Greedy](greedy/README.md) 53 | * [Jump Game](greedy/jump_game.md) 54 | * [Gas Station](greedy/gas_station.md) 55 | * [Candy](greedy/candy.md) 56 | * [Word Break](greedy/word_break.md) 57 | * [Linked List](linked_list/README.md) 58 | * [Linked List Cycle](linked_list/linked_list_cycle.md) 59 | * [Remove Duplicates from Sorted List ](linked_list/remove_duplicates_from_sorted_list.md) 60 | * [Merge Sorted Lists](linked_list/merge_sorted_lists.md) 61 | * [Reverse Linked List](linked_list/reverse_linked_list.md) 62 | * [Swap Nodes in Pairs](linked_list/swap_nodes_in_pairs.md) 63 | * [Sort List](linked_list/sort_list.md) 64 | * [Rotate List](linked_list/rotate_list.md) 65 | * [Reorder List ](linked_list/reorder_list.md) 66 | * [Partition List ](linked_list/partition_list.md) 67 | * [Add Two Numbers](linked_list/add_two_numbers.md) 68 | * [Copy List with Random Pointer](linked_list/copy_list_with_random_pointer.md) 69 | * [Math](math/README.md) 70 | * [Reverse Integer](math/reverse_integer.md) 71 | * [String](string/README.md) 72 | * [Add Binary](string/add_binary.md) 73 | * [Basic Calculator II](string/basic_calculator_2.md) 74 | 75 | -------------------------------------------------------------------------------- /array/README.md: -------------------------------------------------------------------------------- 1 | # Array 2 | -------------------------------------------------------------------------------- /array/find_minimum_in_rotated_sorted_array.md: -------------------------------------------------------------------------------- 1 | # Find Minimum in Rotated Sorted Array 2 | 3 | > Suppose a sorted array is rotated at some pivot unknown to you beforehand. 4 | 5 | > (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2). 6 | 7 | > Find the minimum element. 8 | 9 | > You may assume no duplicate exists in the array. 10 | 11 | 这题要求在一个轮转了的排序数组里面找到最小值,我们可以用二分法来做。 12 | 13 | 首先我们需要知道,对于一个区间A,如果A[start] < A[stop],那么该区间一定是有序的了。 14 | 15 | 假设在一个轮转的排序数组A,我们首先获取中间元素的值,A[mid],mid = (start + stop) / 2。因为数组没有重复元素,那么就有两种情况: 16 | 17 | + A[mid] > A[start],那么最小值一定在右半区间,譬如[4,5,6,7,0,1,2],中间元素为7,7 > 4,最小元素一定在[7,0,1,2]这边,于是我们继续在这个区间查找。 18 | + A[mid] < A[start],那么最小值一定在左半区间,譬如[7,0,1,2,4,5,6],这件元素为2,2 < 7,我们继续在[7,0,1,2]这个区间查找。 19 | 20 | 代码如下: 21 | 22 | ```c++ 23 | class Solution { 24 | public: 25 | int findMin(vector &num) { 26 | int size = num.size(); 27 | 28 | if(size == 0) { 29 | return 0; 30 | } else if(size == 1) { 31 | return num[0]; 32 | } else if(size == 2) { 33 | return min(num[0], num[1]); 34 | } 35 | 36 | int start = 0; 37 | int stop = size - 1; 38 | 39 | while(start < stop - 1) { 40 | if(num[start] < num[stop]) { 41 | return num[start]; 42 | } 43 | 44 | int mid = start + (stop - start) / 2; 45 | if(num[mid] > num[start]) { 46 | start = mid; 47 | } else if(num[mid] < num[start]) { 48 | stop = mid; 49 | } 50 | } 51 | 52 | return min(num[start], num[stop]); 53 | } 54 | }; 55 | ``` 56 | 57 | # Find Minimum in Rotated Sorted Array 58 | 59 | > Suppose a sorted array is rotated at some pivot unknown to you beforehand. 60 | 61 | > (i.e., 0 1 2 4 5 6 7 might become 4 5 6 7 0 1 2). 62 | 63 | > Find the minimum element. 64 | 65 | > The array may contain duplicates. 66 | 67 | 这题跟上题唯一的区别在于元素可能有重复,我们仍然采用上面的方法,只是需要处理mid与start相等这种额外情况。 68 | 69 | + A[mid] > A[start],右半区间查找。 70 | + A[mid] < A[start],左半区间查找。 71 | + A[mid] = A[start],出现这种情况,我们跳过start,重新查找,譬如[2,2,2,1],A[mid] = A[start]都为2,这时候我们跳过start,使用[2,2,1]继续查找。 72 | 73 | 代码如下: 74 | 75 | ```c++ 76 | class Solution { 77 | public: 78 | int findMin(vector &num) { 79 | int size = num.size(); 80 | 81 | if(size == 0) { 82 | return 0; 83 | } else if(size == 1) { 84 | return num[0]; 85 | } else if(size == 2) { 86 | return min(num[0], num[1]); 87 | } 88 | 89 | int start = 0; 90 | int stop = size - 1; 91 | 92 | while(start < stop - 1) { 93 | if(num[start] < num[stop]) { 94 | return num[start]; 95 | } 96 | 97 | int mid = start + (stop - start) / 2; 98 | if(num[mid] > num[start]) { 99 | start = mid; 100 | } else if(num[mid] < num[start]) { 101 | stop = mid; 102 | } else { 103 | start++; 104 | } 105 | } 106 | 107 | return min(num[start], num[stop]); 108 | } 109 | }; 110 | ``` 111 | 112 | 这题需要注意,如果重复元素很多,那么最终会退化到遍历整个数组,而不是二分查找了。 113 | -------------------------------------------------------------------------------- /array/find_peak_element.md: -------------------------------------------------------------------------------- 1 | # Find Peak Element 2 | 3 | > A peak element is an element that is greater than its neighbors. 4 | 5 | > Given an input array where num[i] ≠ num[i+1], find a peak element and return its index. 6 | 7 | > The array may contain multiple peaks, in that case return the index to any one of the peaks is fine. 8 | 9 | > You may imagine that num[-1] = num[n] = -∞. 10 | 11 | > For example, in array [1, 2, 3, 1], 3 is a peak element and your function should return the index number 2. 12 | 13 | 这题要求我们在一个无序的数组里面找到一个peak元素,所谓peak,就是值比两边邻居大就行了。 14 | 15 | 对于这题,最简单地解法就是遍历数组,只要找到第一个元素,大于两边就可以了,复杂度为O(N)。但这题还可以通过二分来做。 16 | 17 | 首先我们找到中间节点mid,如果大于两边返回当前index就可以了,如果左边的节点比mid大,那么我们可以继续在左半区间查找,这里面一定存在一个peak,为什么这么说呢?假设此时的区间范围为[0, mid - 1], 因为num[mid - 1]一定大于num[mid]了,如果num[mid - 2] <= num[mid - 1],那么num[mid - 1]就是一个peak。如果num[mid - 2] > num[mid - 1],那么我们就继续在[0, mid - 2]区间查找,因为num[-1]为负无穷,所以最终我们绝对能在左半区间找到一个peak。同理右半区间一样。 18 | 19 | 代码如下: 20 | 21 | ```c++ 22 | class Solution { 23 | public: 24 | int findPeakElement(const vector &num) { 25 | int n = num.size(); 26 | if(n == 1) { 27 | return 0; 28 | } 29 | 30 | int start = 0; 31 | int end = n - 1; 32 | int mid = 0; 33 | 34 | while(start <= end) { 35 | mid = start + (end - start) / 2; 36 | if((mid == 0 || num[mid] >= num[mid - 1]) && 37 | (mid == n - 1 || num[mid] >= num[mid + 1])) { 38 | return mid; 39 | }else if(mid > 0 && num[mid-1] > num[mid]) { 40 | end = mid - 1; 41 | } else { 42 | start = mid + 1; 43 | } 44 | } 45 | return mid; 46 | } 47 | }; 48 | ``` 49 | -------------------------------------------------------------------------------- /array/largest_rectangle_in_histogram.md: -------------------------------------------------------------------------------- 1 | # Largest Rectangle in Histogram 2 | 3 | > Given n non-negative integers representing the histogram's bar height where the width of each bar is 1, find the area of largest rectangle in the histogram. 4 | 5 | > ![histogram](http://www.leetcode.com/wp-content/uploads/2012/04/histogram.png) 6 | 7 | > Above is a histogram where width of each bar is 1, given height = [2,1,5,6,2,3]. 8 | 9 | > ![histogram_area](http://www.leetcode.com/wp-content/uploads/2012/04/histogram_area.png) 10 | 11 | > The largest rectangle is shown in the shaded area, which has area = 10 unit. 12 | 13 | > For example, 14 | > Given height = [2,1,5,6,2,3], 15 | > return 10. 16 | 17 | 这道题目算是比较难得一道题目了,首先最简单的做法就是对于任意一个bar,向左向右遍历,直到高度小于该bar,这时候计算该区域的矩形区域面积。对于每一个bar,我们都做如上处理,最后就可以得到最大值了。当然这种做法是O(n2),铁定过不了大数据集合测试的。 18 | 19 | 从上面我们直到,对于任意一个bar n,我们得到的包含该bar n的矩形区域里面bar n是最小的。我们使用ln和rn来表示bar n向左以及向右第一个小于bar n的bar的索引位置。 20 | 21 | 譬如题目中的bar 2的高度为5,它的ln为1,rn为4。包含bar 2的矩形区域面积为(4 - 1 - 1) * 5 = 10。 22 | 23 | 我们可以从左到右遍历所有bar,并将其push到一个stack中,如果当前bar的高度小于栈顶bar,我们pop出栈顶的bar,同时以该bar计算矩形面积。那么我们如何知道该bar的ln和rn呢?rn铁定就是当前遍历到的bar的索引,而ln则是当前的栈顶bar的索引,因为此时栈顶bar的高度一定小于pop出来的bar的高度。 24 | 25 | 为了更好的处理最后一个bar的情况,我们在实际中会插入一个高度为0的bar,这样就能pop出最后一个bar并计算了。 26 | 27 | 代码如下: 28 | 29 | ```c++ 30 | class Solution { 31 | public: 32 | int largestRectangleArea(vector &height) { 33 | vector s; 34 | //插入高度为0的bar 35 | height.push_back(0); 36 | 37 | int sum = 0; 38 | int i = 0; 39 | while(i < height.size()) { 40 | if(s.empty() || height[i] > height[s.back()]) { 41 | s.push_back(i); 42 | i++; 43 | } else { 44 | int t = s.back(); 45 | s.pop_back(); 46 | //这里还需要考虑stack为空的情况 47 | sum = max(sum, height[t] * (s.empty() ? i : i - s.back() - 1)); 48 | } 49 | } 50 | 51 | return sum; 52 | } 53 | }; 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /array/maximal_rectangle.md: -------------------------------------------------------------------------------- 1 | # Maximal Rectangle 2 | 3 | > Given a 2D binary matrix filled with 0's and 1's, find the largest rectangle containing all ones and return its area. 4 | 5 | 这题是一道难度很大的题目,至少我刚开始的时候完全不知道怎么做,也是google了才知道的。 6 | 7 | 这题要求在一个矩阵里面求出全部包含1的最大矩形面积,譬如这个: 8 | 9 | ``` 10 | 0 0 0 0 11 | 1 1 1 1 12 | 1 1 1 0 13 | 0 1 0 0 14 | ``` 15 | 16 | 我们可以知道,最大的矩形面积为6。也就是下图中虚线包围的区域。那么我们如何得到这个区域呢? 17 | 18 | ``` 19 | 0 0 0 0 20 | |--------| 21 | |1 1 1 |1 22 | |1 1 1 |0 23 | |--------| 24 | 0 1 0 0 25 | ``` 26 | 27 | 对于上面哪一题,我们先去掉最下面的一行,然后就可以发现,它可以转化成一个直方图,数据为[2, 2, 2, 0],我们认为1就是高度,如果碰到0,譬如上面最右列,则高度为0,而计算这个直方图最大矩形面积就很容易了,我们已经在[Largest Rectangle in Histogram](largest_rectangle_in_histogram.md)处理了。 28 | 29 | 所以我们可以首先得到每一行的直方图,分别求出改直方图的最大区域,最后就能得到结果了。 30 | 31 | 代码如下: 32 | 33 | ```c++ 34 | class Solution { 35 | public: 36 | int maximalRectangle(vector > &matrix) { 37 | if(matrix.empty() || matrix[0].empty()) { 38 | return 0; 39 | } 40 | 41 | int m = matrix.size(); 42 | int n = matrix[0].size(); 43 | 44 | vector > height(m, vector(n, 0)); 45 | for(int i = 0; i < m; i++) { 46 | for(int j = 0; j < n; j++) { 47 | if(matrix[i][j] == '0') { 48 | height[i][j] = 0; 49 | } else { 50 | height[i][j] = (i == 0) ? 1 : height[i - 1][j] + 1; 51 | } 52 | } 53 | } 54 | 55 | int maxArea = 0; 56 | for(int i = 0; i < m; i++) { 57 | maxArea = max(maxArea, largestRectangleArea(height[i])); 58 | } 59 | return maxArea; 60 | } 61 | 62 | int largestRectangleArea(vector &height) { 63 | vector s; 64 | height.push_back(0); 65 | 66 | int sum = 0; 67 | int i = 0; 68 | while(i < height.size()) { 69 | if(s.empty() || height[i] > height[s.back()]) { 70 | s.push_back(i); 71 | i++; 72 | } else { 73 | int t = s.back(); 74 | s.pop_back(); 75 | sum = max(sum, height[t] * (s.empty() ? i : i - s.back() - 1)); 76 | } 77 | } 78 | 79 | return sum; 80 | } 81 | }; 82 | ``` 83 | 84 | 85 | -------------------------------------------------------------------------------- /array/merge_sorted_array.md: -------------------------------------------------------------------------------- 1 | # Merge Sorted Array 2 | 3 | > Given two sorted integer arrays A and B, merge B into A as one sorted array. 4 | 5 | > Note: 6 | > You may assume that A has enough space (size that is greater or equal to m + n) to hold additional elements from B. The number of elements initialized in A and B are m and n respectively. 7 | 8 | A和B都已经是排好序的数组,我们只需要从后往前比较就可以了。 9 | 10 | 因为A有足够的空间容纳A + B,我们使用游标i指向m + n - 1,也就是最大数值存放的地方,从后往前遍历A,B,谁大就放到i这里,同时递减i。 11 | 12 | 代码如下: 13 | 14 | ```c++ 15 | class Solution { 16 | public: 17 | void merge(int A[], int m, int B[], int n) { 18 | int i = m + n - 1; 19 | int j = m - 1; 20 | int k = n - 1; 21 | 22 | while(i >= 0) { 23 | if(j >= 0 && k >= 0) { 24 | if(A[j] > B[k]) { 25 | A[i] = A[j]; 26 | j--; 27 | } else { 28 | A[i] = B[k]; 29 | k--; 30 | } 31 | } else if(j >= 0) { 32 | A[i] = A[j]; 33 | j--; 34 | } else if(k >= 0) { 35 | A[i] = B[k]; 36 | k--; 37 | } 38 | i--; 39 | } 40 | } 41 | }; 42 | ``` 43 | -------------------------------------------------------------------------------- /array/palindrome_number.md: -------------------------------------------------------------------------------- 1 | # Palindrome Number 2 | 3 | > Determine whether an integer is a palindrome. Do this without extra space. 4 | 5 | 题目翻译: 6 | 给定一个数字,要求判断这个数字是否为回文数字. 比如121就是回文数字,122就不是回文数字. 7 | 8 | 解题思路: 9 | 这道题很明显是一道数学题,计算一个数字是否是回文数字,我们其实就是将这个数字除以10,保留他的余数,下次将余数乘以10,加上这个数字再除以10的余数. 10 | 11 | 需要注意的点: 12 | 1. 负数不是回文数字. 13 | 2. 0是回文数字. 14 | 15 | 时间复杂度: 16 | logN 17 | 18 | 代码如下: 19 | 20 | ```c++ 21 | class Solution { 22 | public: 23 | bool isPalindrome(int x) { 24 | if(x < 0) 25 | return false; 26 | else if(x == 0) 27 | return true; 28 | else 29 | { 30 | int tmp = x; 31 | int y = 0; 32 | while(x != 0) 33 | { 34 | y = y*10 + x%10; 35 | x = x/10; 36 | } 37 | if(y == tmp) 38 | return true; 39 | else 40 | return false; 41 | } 42 | } 43 | }; 44 | ``` 45 | 46 | 47 | -------------------------------------------------------------------------------- /array/pascals_triangle.md: -------------------------------------------------------------------------------- 1 | # Pascal's Triangle 2 | 3 | > Given numRows, generate the first numRows of Pascal's triangle. 4 | 5 | > For example, given numRows = 5, 6 | > Return 7 | 8 | > ``` 9 | [ 10 | [1], 11 | [1,1], 12 | [1,2,1], 13 | [1,3,3,1], 14 | [1,4,6,4,1] 15 | ] 16 | > ``` 17 | 18 | 要得到一个帕斯卡三角,我们只需要找到规律即可。 19 | 20 | + 第k层有k个元素 21 | + 每层第一个以及最后一个元素值为1 22 | + 对于第k(k > 2)层第n(n > 1 && n < k)个元素A[k][n],A[k][n] = A[k-1][n-1] + A[k-1][n] 23 | 24 | 知道了上面的规律,就很好做了,我们使用一个二维数组来存储整个三角,代码如下: 25 | 26 | ```c++ 27 | class Solution { 28 | public: 29 | vector > generate(int numRows) { 30 | vector > vals; 31 | vals.resize(numRows); 32 | 33 | for(int i = 0; i < numRows; i++) { 34 | vals[i].resize(i + 1); 35 | vals[i][0] = 1; 36 | vals[i][vals[i].size() - 1] = 1; 37 | for(int j = 1; j < vals[i].size() - 1; j++) { 38 | vals[i][j] = vals[i - 1][j - 1] + vals[i - 1][j]; 39 | } 40 | } 41 | 42 | return vals; 43 | } 44 | }; 45 | ``` 46 | 47 | # Pascal's Triangle II 48 | 49 | > Given an index k, return the kth row of the Pascal's triangle. 50 | 51 | > For example, given k = 3, 52 | > Return [1,3,3,1]. 53 | 54 | 不同于上一题,这里我们仅仅需要得到的第k层的集合,但只能使用O(k)的空间。所以不能用前面二维数组的方式,只能使用一位数组滚动计算。 55 | 56 | 在第一题里面,我们知道,帕斯卡三角的计算公式是这样的,A[k][n] = A[k-1][n-1] + A[k-1][n]。 57 | 58 | 假设现在数组存放的第3层的数据,[1, 3, 3, 1],如果我们需要计算第4层的数据,如果我们从前往后计算,譬如A[4][2]= A[3][1] + A[3][2],也就是4,但是因为只有一个数组,所以需要将4这个值覆盖到2这个位置,那么我们计算A[4][3]的时候就会出现问题了,因为这时候A[3][2]不是3,而是4了。 59 | 60 | 为了解决这个问题,我们只能从后往前计算,仍然是上面那个例子,我们实现计算A[4][3] = A[3][2] + A[3][3],也就是6,我们将6直接覆盖到3这个位置,但不会影响我们计算A[4][2],因为A[4][2] = A[3][1] + A[3][2],已经不会涉及到3这个位置了。 61 | 62 | 理解了如何计算,代码就很简单了: 63 | 64 | ```c++ 65 | class Solution { 66 | public: 67 | vector getRow(int rowIndex) { 68 | vector vals; 69 | 70 | vals.resize(rowIndex + 1, 1); 71 | 72 | for(int i = 0; i < rowIndex + 1; ++i) { 73 | for(int j = i - 1; j >= 1; --j) { 74 | vals[j] = vals[j] + vals[j - 1]; 75 | } 76 | 77 | } 78 | 79 | return vals; 80 | } 81 | }; 82 | ``` 83 | -------------------------------------------------------------------------------- /array/plus_one.md: -------------------------------------------------------------------------------- 1 | # Plus One 2 | 3 | > Given a non-negative number represented as an array of digits, plus one to the number. 4 | 5 | > The digits are stored such that the most significant digit is at the head of the list. 6 | 7 | 这道题目很简单,就是考的加法进位问题。直接上代码: 8 | 9 | ```c++ 10 | class Solution { 11 | public: 12 | vector plusOne(vector &digits) { 13 | vector res(digits.size(), 0); 14 | int sum = 0; 15 | int one = 1; 16 | for(int i = digits.size() - 1; i >= 0; i--) { 17 | sum = one + digits[i]; 18 | one = sum / 10; 19 | res[i] = sum % 10; 20 | } 21 | 22 | if(one > 0) { 23 | res.insert(res.begin(), one); 24 | } 25 | return res; 26 | } 27 | }; 28 | ``` 29 | 30 | -------------------------------------------------------------------------------- /array/remove_duplicates_from_sorted_array.md: -------------------------------------------------------------------------------- 1 | # Remove Duplicates from Sorted Array 2 | 3 | > Given a sorted array, remove the duplicates in place such that > each element appear only once and return the new length. 4 | 5 | > Do not allocate extra space for another array, you must do this in place with constant memory. 6 | 7 | > For example, 8 | > Given input array A = [1,1,2], 9 | 10 | > Your function should return length = 2, and A is now [1,2]. 11 | 12 | 这道题目与前一题Remove Element比较类似。但是在一个排序好的数组里面删除重复的元素。 13 | 14 | 首先我们需要知道,对于一个排好序的数组来说,`A[N + 1] >= A[N]`,我们仍然使用两个游标i和j来处理,假设现在i = j + 1,如果A[i] == A[j],那么我们递增i,直到A[i] != A[j],这时候我们再设置A[j + 1] = A[i],同时递增i和j,重复上述过程直到遍历结束。 15 | 16 | 代码如下: 17 | 18 | ```c++ 19 | class Solution { 20 | public: 21 | int removeDuplicates(int A[], int n) { 22 | if(n == 0) { 23 | return 0; 24 | } 25 | 26 | int j = 0; 27 | for(int i = 1; i < n; i++) { 28 | if(A[j] != A[i]) { 29 | A[++j] = A[i]; 30 | } 31 | } 32 | return j + 1; 33 | } 34 | }; 35 | ``` 36 | 37 | 譬如一个数组为1,1,2,3,首先i = 1,j = 0,这时候A[i] = A[j],于是递增i,碰到2,不等于1,此时设置A[j + 1] = A[i],也就是A[1] = A[2],递增i和j为3和1,这时候A[3] != A[1],设置A[j + 1] = A[i],也就是A[2] = A[3],再次递增,遍历结束。这时候新的数组长度就为2 + 1,也就是3。 38 | 39 | # Remove Duplicates from Sorted Array II 40 | 41 | > Follow up for "Remove Duplicates": 42 | > What if duplicates are allowed at most twice? 43 | 44 | > For example, 45 | > Given sorted array A = [1,1,1,2,2,3], 46 | 47 | > Your function should return length = 5, and A is now [1,1,2,2,3]. 48 | 49 | 紧接着上一题,同样是移除重复的元素,但是可以允许最多两次重复元素存在。 50 | 51 | 仍然是第一题的思路,但是我们需要用一个计数器来记录重复的次数,如果重复次数大于等于2,我们会按照第一题的方式处理,如果不是重复元素了,我们将计数器清零。 52 | 53 | 代码如下: 54 | 55 | ```c++ 56 | class Solution { 57 | public: 58 | int removeDuplicates(int A[], int n) { 59 | if(n == 0) { 60 | return 0; 61 | } 62 | 63 | int j = 0; 64 | int num = 0; 65 | for(int i = 1; i < n; i++) { 66 | if(A[j] == A[i]) { 67 | num++; 68 | if(num < 2) { 69 | A[++j] = A[i]; 70 | } 71 | } else { 72 | A[++j] = A[i]; 73 | num = 0; 74 | } 75 | } 76 | return j + 1; 77 | } 78 | }; 79 | ``` 80 | -------------------------------------------------------------------------------- /array/remove_element.md: -------------------------------------------------------------------------------- 1 | # Remove Element 2 | 3 | > Given an array and a value, remove all instances of that > value in place and return the new length. 4 | 5 | > The order of elements can be changed. It doesn't matter what you leave beyond the new length. 6 | 7 | 作为开胃菜,我当然选取了最容易的一道题目,在一个数组里面移除指定value,并且返回新的数组长度。这题唯一需要注意的地方在于`in place`,不能新建另一个数组。 8 | 9 | 方法很简单,使用两个游标i,j,遍历数组,如果碰到了value,使用j记录位置,同时递增i,直到下一个非value出现,将此时i对应的值复制到j的位置上,增加j,重复上述过程直到遍历结束。这时候j就是新的数组长度。 10 | 11 | 代码如下: 12 | 13 | ```c++ 14 | class Solution { 15 | public: 16 | int removeElement(int A[], int n, int elem) { 17 | int i = 0; 18 | int j = 0; 19 | for(i = 0; i < n; i++) { 20 | if(A[i] == elem) { 21 | continue; 22 | } 23 | 24 | A[j] = A[i]; 25 | j++; 26 | } 27 | 28 | return j; 29 | } 30 | }; 31 | ``` 32 | 33 | 举一个最简单的例子,譬如数组为1,2,2,3,2,4,我们需要删除2,首先初始化i和j为0,指向第一个位置,因为第一个元素为1,所以A[0] = A[0],i和j都加1,而第二个元素为2,我们递增i,直到碰到3,此时A[1] = A[3],也就是3,递增i和j,这时候下一个元素又是2,递增i,直到碰到4,此时A[2] = A[5],也就是4,再次递增i和j,这时候数组已经遍历完毕,结束。这时候j的值为3,刚好就是新的数组的长度。 34 | -------------------------------------------------------------------------------- /array/search_a_2d_matrix.md: -------------------------------------------------------------------------------- 1 | # Search a 2D Matrix 2 | 3 | > Write an efficient algorithm that searches for a value in an m x n matrix. This matrix has the following properties: 4 | 5 | > Integers in each row are sorted from left to right. 6 | The first integer of each row is greater than the last integer of the previous row. 7 | For example, 8 | 9 | > Consider the following matrix: 10 | 11 | ``` 12 | [ 13 | [1, 3, 5, 7], 14 | [10, 11, 16, 20], 15 | [23, 30, 34, 50] 16 | ] 17 | ``` 18 | 题目翻译: 19 | 给定一个矩阵和一个特定值,要求写出一个高效的算法在这个矩阵中快速的找出是否这个给定的值存在. 20 | 但是这个矩阵有以下特征. 21 | 22 | 1. 对于每一行,数值是从左到右从小到大排列的. 23 | 2. 对于每一列,数值是从上到下从小到大排列的. 24 | 25 | 26 | 题目解析: 27 | 对于这个给定的矩阵,我们如果用brute force解法,用两个嵌套循环,O(n2)便可以得到答案.但是我们需要注意的是这道题已经给定了这个矩阵的两个特性,这两个特性对于提高我们算法的时间复杂度有很大帮助,首先我们给出一个O(n)的解法,也就是说我们可以固定住右上角的元素,根据递增或者递减的规律,我们可以判断这个给定的数值是否存在于这个矩阵当中. 28 | 29 | ```c++ 30 | class Solution { 31 | public: 32 | bool searchMatrix(vector > &matrix, int target) { 33 | /* we set the corner case as below: 34 | 1, if the row number of input matrix is 0, we set it false 35 | 2, if the colomun number of input matrix is 0, we set it false*/ 36 | if(matrix.size() == 0) 37 | return false; 38 | if(matrix[0].size() == 0) 39 | return false; 40 | int rowNumber = 0; 41 | int colNumber = matrix[0].size()-1; 42 | while(rowNumber < matrix.size() && colNumber >= 0) 43 | { 44 | if(target < matrix[rowNumber][colNumber]) 45 | --colNumber; 46 | else if(target > matrix[rowNumber][colNumber]) 47 | ++rowNumber; 48 | else 49 | return true; 50 | } 51 | return false; 52 | } 53 | }; 54 | 55 | ``` 56 | -------------------------------------------------------------------------------- /array/search_for_a_range.md: -------------------------------------------------------------------------------- 1 | # Search for a Range 2 | 3 | > Given a sorted array of integers, find the starting and ending position of a given target value. 4 | 5 | > Your algorithm's runtime complexity must be in the order of O(log n). 6 | 7 | > If the target is not found in the array, return [-1, -1]. 8 | 9 | > For example, 10 | 11 | > Given [5, 7, 7, 8, 8, 10] and target value 8, 12 | 13 | > return [3, 4]. 14 | 15 | 这题要求在一个排好序可能有重复元素的数组里面找到包含某个值的区间范围。要求使用O(log n)的时间,所以我们采用两次二分查找。首先二分找到第一个该值出现的位置,譬如m,然后在[m, n)区间内第二次二分找到最后一个该值出现的位置。代码如下: 16 | 17 | ```c++ 18 | class Solution { 19 | public: 20 | vector searchRange(int A[], int n, int target) { 21 | if(n == 0) { 22 | return vector({-1, -1}); 23 | } 24 | 25 | vector v; 26 | int low = 0; 27 | int high = n - 1; 28 | //第一次二分找第一个位置 29 | while(low <= high) { 30 | int mid = low + (high - low) / 2; 31 | if(A[mid] >= target) { 32 | high = mid - 1; 33 | } else { 34 | low = mid + 1; 35 | } 36 | } 37 | 38 | if(low < n && A[low] == target) { 39 | v.push_back(low); 40 | } else { 41 | return vector({-1, -1}); 42 | } 43 | 44 | low = low; 45 | high = n - 1; 46 | //从第一个位置开始进行第二次二分,找最后一个位置 47 | while(low <= high) { 48 | int mid = low + (high - low) / 2; 49 | if(A[mid] <= target) { 50 | low = mid + 1; 51 | } else { 52 | high = mid - 1; 53 | } 54 | } 55 | 56 | v.push_back(high); 57 | return v; 58 | } 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /array/search_insert_position.md: -------------------------------------------------------------------------------- 1 | # Search Insert Position 2 | 3 | > Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order. 4 | 5 | > You may assume no duplicates in the array. 6 | 7 | > Here are few examples. 8 | 9 | > [1,3,5,6], 5 → 2 10 | 11 | > [1,3,5,6], 2 → 1 12 | 13 | > [1,3,5,6], 7 → 4 14 | 15 | > [1,3,5,6], 0 → 0 16 | 17 | 这题要求在一个排好序的数组查找某值value,如果存在则返回对应index,不存在则返回能插入到数组中的index(保证数组有序)。 18 | 19 | 对于不存在的情况,我们只需要在数组里面找到最小的一个值大于value的index,这个index就是我们可以插入的位置。譬如[1, 3, 5, 6],查找2,我们知道3是最小的一个大于2的数值,而3的index为1,所以我们需要在1这个位置插入2。如果数组里面没有值大于value,则插入到数组末尾。 20 | 21 | 我们采用二分法解决: 22 | 23 | ```c++ 24 | class Solution { 25 | public: 26 | int searchInsert(int A[], int n, int target) { 27 | int low = 0; 28 | int high = n - 1; 29 | 30 | while(low <= high) { 31 | int mid = low + (high - low) / 2; 32 | if(A[mid] == target) { 33 | return mid; 34 | } else if(A[mid] < target) { 35 | low = mid + 1; 36 | } else { 37 | high = mid - 1; 38 | } 39 | } 40 | 41 | return low; 42 | } 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /array/sum.md: -------------------------------------------------------------------------------- 1 | # 2Sum 2 | > Given an array of intergers, find two numbers such that they add up to a specific target number. The function twoSum should return indices of the two numbers such that they add up to the target, where index1 must be less than index2 Please note that your returned answers (both index1 and index2) are not zero-based. 3 | 4 | > You may assume that each input would have exactly one solution. 5 | 6 | > Input: numbers={2, 7, 11, 15}, target=9 7 | Output: index1=1, index2=2 8 | 9 | 题目翻译: 10 | 这道题目的意思是给定一个数组和一个值,让求出这个数组中两个值的和等于这个给定值的坐标。输出是有要求的,1, 坐标较小的放在前面,较大的放在后面。2, 这俩坐标不能为零。 11 | 12 | 题目分析: 13 | 第一步: 我们要分析题意,其中有三个关键点: 14 | 15 | 1. 求出来的坐标值要按序排列。 16 | 2. 这两个坐标不能从零开始。 17 | 3. 这道题目假设是只有一组答案符合要求,这样降低了我们解题的难度。 18 | 19 | 根据题目我们可以得到以下信息: 20 | 21 | 1. 我们得到坐标的时候,要根据大小的顺序放入数组。 22 | 2. 因为坐标值不能为零,所以我们得到的坐标都要+1。 23 | 3. 因为有且只有一组答案符合要求,所以这大大的降低了这道题目的难度,也就是说,我们只要找到符合条件的两个数,存入结果,直接终止程序,返回答案即可。 24 | 25 | 解题思路: 26 | 这道题不是很难,是leetcode最开始的题目,要求很明确,很直接,如果我们用两个for循环,O(n2)的时间复杂度去求解的话,很容易计算出来,但这明显不是面试官需要的答案。brute force只有在你不知道如何优化题目的时候,将就的给出的一个解法。。那么我们能不能用O(n)的时间复杂度去解这道题呢?很显然是可以的,不过,天下没有掉馅饼的事情啦,既然优化了时间复杂度,我们就要牺牲空间复杂度啦。在这里用什么呢?stack?queue?vector?还是hash_map? 27 | 28 | 对于stack和queue,除了pop外,查找的时间复杂度也是O(n)。明显不是我们所需要的,那么什么数据结构的查找时间复杂度小呢?很显然是 hash_map, 查找的时间复杂度理想情况下是O(1)。所以我们先来考虑hash_map,看看hash_map怎么求解这个问题。 29 | 30 | 我们可以先把这个数组的所有元素存到hashmap中,一次循环就行, 时间复杂度为O(n),之后对所给数组在进行遍历,针对其中的元素我们只要用another_number = target-numbers[i],之后用hashmap的find function来查找这个值,如果存在的话,在进行后续比较(详见代码),如果不存在的话,继续查找,好啦,思路已经摆在这里了,详见代码吧。 31 | 32 | ```c++ 33 | 34 | class Solution { 35 | public: 36 | vector twoSum(vector &numbers, int target) { 37 | //边角问题,我们要考虑边角问题的处理 38 | vector ret; 39 | if(numbers.size() <= 1) 40 | return ret; 41 | //新建一个map 模式的来存储numbers里面的元素和index, 42 | //这里的unordered_map相当于hash_map 43 | unordered_map myMap; 44 | for(int i = 0; i < numbers.size(); ++i) 45 | myMap[numbers[i]] = i; 46 | for(int i = 0; i < numbers.size(); ++i) 47 | { 48 | int rest_val = target - numbers[i]; 49 | if(myMap.find(rest_val)!=myMap.end()) 50 | { 51 | int index = myMap[rest_val]; 52 | if(index == i) 53 | continue; //如果是同一个数字,我们就pass,是不会取这个值的 54 | if(index < i) 55 | { 56 | ret.push_back(index+1); //这里+1是因为题目说明白了要non-zero based index 57 | ret.push_back(i+1); 58 | return ret; 59 | } 60 | else 61 | { 62 | ret.push_back(i+1); 63 | ret.push_back(index+1); 64 | return ret; 65 | } 66 | } 67 | } 68 | } 69 | }; 70 | ``` 71 | 以上解法的要注意的是记得要检查这两个坐标是不是相同的,因为我们并不需要同样的数字。 72 | 73 | 74 | #3Sum 75 | > Given an array S of n integers, are there elements a, b, c in S such that a + b + c = 0? Find all unique triplets in the array which gives the sum of zero. 76 | 77 | > Note: 78 | Elements in a triplet (a,b,c) must be in non-descending order. (ie, a ≤ b ≤ c) 79 | The solution set must not contain duplicate triplets. 80 | 81 | 题目翻译: 82 | 83 | 给定一个整型数组num,找出这个数组中满足这个条件的所有数字: 84 | num[i]+num[j]+num[k] = 0. 并且所有的答案是要和其他不同的,也就是说两个相同的答案是不被接受的。 85 | 86 | 题目的两点要求: 87 | 88 | 1. 每个答案组里面的三个数字是要从大到小排列起来的。 89 | 2. 每个答案不可以和其他的答案相同。 90 | 91 | 题目分析: 92 | 93 | 1. 每一个答案数组triplet中的元素是要求升序排列的。 94 | 2. 不能包含重复的答案数组。 95 | 96 | 解题思路: 97 | 98 | 1. 根据第一点要求: 因为要求每个答案数组中的元素是升序排列的,所以在开头我们要对数组进行排序。 99 | 100 | 2. 根据第二点要求: 101 | 因为不能包含重复的答案数组,所以我们要在代码里面做一切去掉重复的操作,对于数组,这样的操作是相同的。最开始我做leetcode的时候是把所有满足条件的答案数组存起来,之后再用map进行处理,感觉那样太麻烦了,所以这次给出的答案是不需要额外空间的。 102 | 103 | 时间复杂度分析: 104 | 105 | 对于这道题,因为是要找三个元素,所以怎样都要O(n2)的时间复杂度,目前我没有想出来O(n)时间复杂度的解法。 106 | 107 | 归根结底,其实这是two pointers的想法,定位其中两个指针,根据和的大小来移动另外一个。解题中所要注意的就是一些细节问题。好了,上代码吧。 108 | 109 | 110 | ```c++ 111 | 112 | class Solution { 113 | public: 114 | //constant space version 115 | vector > threeSum(vector &num) { 116 | vector> ret; 117 | //corner case invalid check 118 | if(num.size() <= 2) 119 | return ret; 120 | 121 | //first we need to sort the array because we need the non-descending order 122 | sort(num.begin(), num.end()); 123 | 124 | for(int i = 0; i < num.size()-2; ++i) 125 | { 126 | int j = i+1; 127 | int k = num.size()-1; 128 | while(j < k) 129 | { 130 | vector curr; //create a tmp vector to store each triplet which satisfy the solution. 131 | if(num[i]+num[j]+num[k] == 0) 132 | { 133 | curr.push_back(num[i]); 134 | curr.push_back(num[j]); 135 | curr.push_back(num[k]); 136 | ret.push_back(curr); 137 | ++j; 138 | --k; 139 | //this two while loop is used to skip the duplication solution 140 | while(j < k&&num[j-1] == num[j]) 141 | ++j; 142 | while(j < k&&num[k] == num[k+1]) 143 | --k; 144 | } 145 | else if(num[i]+num[j]+num[k] < 0) //if the sum is less than the target value, we need to move j to forward 146 | ++j; 147 | else 148 | --k; 149 | } 150 | //this while loop also is used to skip the duplication solution 151 | while(i < num.size()-1&&num[i] == num[i+1]) 152 | ++i; 153 | } 154 | return ret; 155 | } 156 | }; 157 | ``` 158 | 根据以上代码,我们要注意的就是用于去除重复的那三个while loop。一些细节问题比如for loop中的i < num.size()-2;因为j和k都在i后面,所以减掉两位。当然如果写成i< num.size(); 也是可以通过测试的,但感觉思路不是很清晰。另外一点,就是不要忘记了corner case check呀。 159 | 160 | # 3Sum Closest 161 | 162 | > Given an array S of n integers, find three integers in S such that the sum is closest to a given number, target. Return the sum of the three integers. You man assume that each input would have exactly one solution. 163 | 164 | 题目翻译: 165 | 166 | 给定一个整形数组S和一个具体的值,要求找出在这数组中三个元素的和和这个给定的值最小。input只有一个有效答案。 167 | 168 | 题目要求: 169 | 170 | 这道题比较直接,也没有什么具体的要求。 171 | 172 | 题目分析: 173 | 174 | 1. 最短距离:两个整数的最短距离是0.这点对于这道题比较重要,别忽略。 175 | 2. 这道题和3Sum几乎同出一辙,所以方便于解题,我们还是在开头要对数组进行排序,要么没法定位指针移动。 176 | 177 | 3. 另外,这道题中用到了INT_MAX这个值,这个值和 INT_MIN是相对应的,在很多比较求最大值最小值的情况,经常用这两个变量。 178 | 179 | 解题思路: 180 | 181 | 这道题的解题方法和3Sum几乎相同,设定三个指针,固定两个,根据和的大小移动另外一个。属于这道题目自己的东西就是distance比较这块儿,建立一个tmp distance和min distance比较。 182 | 183 | 时间复杂度分析: 184 | 185 | 这道题目和3Sum几乎是一个思路,所以时间复杂度为O(n2)。 186 | 187 | 代码如下: 188 | ```c++ 189 | 190 | class Solution { 191 | public: 192 | 193 | int threeSumClosest(vector &num, int target) { 194 | //invalid corner case check 195 | if(num.size() <= 2) 196 | return -1; 197 | 198 | int ret = 0; 199 | //first we suspect the distance between the sum and the target is the largest number in int 200 | int distance = INT_MAX; 201 | sort(num.begin(),num.end()); //sort is needed 202 | for(int i = 0; i < num.size()-2; ++i) 203 | { 204 | int j = i+1; 205 | int k = num.size()-1; 206 | while(j < k) 207 | { 208 | int tmp_val = num[i]+num[j]+num[k]; 209 | int tmp_distance; 210 | if(tmp_val < target) 211 | { 212 | tmp_distance = target - tmp_val; 213 | if(tmp_distance < distance) 214 | { 215 | distance = tmp_distance; 216 | ret = num[i]+num[j]+num[k]; 217 | } 218 | ++j; 219 | } 220 | else if(tmp_val > target) 221 | { 222 | tmp_distance = tmp_val-target; 223 | if(tmp_distance < distance) 224 | { 225 | distance = tmp_distance; 226 | ret= num[i]+num[j]+num[k]; 227 | } 228 | --k; 229 | } 230 | else //note: in this case, the sum is 0, 0 means the shortest distance from the sum to the target 231 | { 232 | ret = num[i]+num[j]+num[k]; 233 | return ret; 234 | } 235 | } 236 | } 237 | return ret; 238 | } 239 | }; 240 | ``` 241 | 总结: 242 | 这道题的解决方法主要要注意以下几点: 243 | 1. 首先要对数组进行排序。 244 | 2. 0是两个数组间最小的距离。 245 | 246 | 247 | 248 | # 4Sum 249 | 250 | > Given an array S of n integers, are there elements a, b, c and d in S such that a+b+c+d = target? Find all unique quadruplets in the array which gives the sume of target. 251 | 252 | > Note: 253 | 1. Elements in quadruplets (a, b, c, d) must be in non-descending order. (ie, a<=b<=c<=d) 254 | 2. The solution must not contain duplicates quadruplets. 255 | 256 | 257 | 题目翻译: 258 | 259 | 给定一个整型数字数组num和一个目标值target,求出数组中所有的组合满足条件: num[a]+num[b]+num[c]+num[d] = target. 260 | 261 | 并且要满足的条件是: 262 | 1. num[a] <= num[b] <= num[c] <= num[d] 263 | 2. 答案中的组合没有重复的. 264 | 265 | 题目分析: 266 | 267 | 这道题和3Sum几乎同出一辙,只不过是要求四个数字的和,在时间复杂度上要比3Sum高一个数量级。对于两点要求的处理: 268 | 1. 首先要对整个数组进行排序,这样得到的答案自然是排序好的. 269 | 2. 对于重复答案的处理和3Sum是一摸一样的。 270 | 271 | 解题思路: 272 | 同3Sum. 273 | 274 | 时间复杂度分析: 275 | 276 | 这道题的解法,我选择的是空间复杂度为1, 时间复杂度为O(n3).对于这样的问题,如果到了KSum(K>=5), 我觉得可以用hash_map来牺牲空间复杂度换取好一些的时间复杂度. 277 | 278 | 代码如下: 279 | 280 | ```c++ 281 | class Solution { 282 | public: 283 | vector > fourSum(vector &num, int target) { 284 | vector> ret; 285 | if(num.size() <= 3) //invalid corner case check 286 | return ret; 287 | sort(num.begin(), num.end()); //cause we need the result in quadruplets should be non-descending 288 | for(int i = 0; i < num.size()-3; ++i) 289 | { 290 | if(i > 0 && num[i] == num[i-1]) 291 | continue; 292 | for(int j = i+1; j < num.size()-2; ++j) 293 | { 294 | if(j > i+1 && num[j] == num[j-1]) 295 | continue; 296 | int k = j+1; 297 | int l = num.size()-1; 298 | while(k < l) 299 | { 300 | int sum = num[i]+num[j]+num[k]+num[l]; 301 | if(sum == target) 302 | { 303 | vector curr; //create a temporary vector to store the each quadruplets 304 | curr.push_back(num[i]); 305 | curr.push_back(num[j]); 306 | curr.push_back(num[k]); 307 | curr.push_back(num[l]); 308 | ret.push_back(curr); 309 | //the two while loops are used to skip the duplication solutions 310 | do{++k;} 311 | while(k 根据以上的2Sum, 3Sum, 3Sum Cloest, 还有4Sum,我相信只要认真看完每道题的解法的童鞋,都会发现一定的规律,相信这时候会有人想,如果变成KSum问题,我们应该如何求解?这是个很好的想法,下面,我们来看看问题扩展. 339 | 340 | 首先,对于2Sum,我们用的解法是以空间复杂度来换取时间复杂度,那么,2Sum我们可不可以in place来解?时间复杂度又是多少? 341 | 答案是当然可以,我们可以先sort一遍,之后再扫一变,sort的时间复杂度是O(nlogn),扫一遍是O(n),因此,这种解法的时间复杂度是O(nlogn), 当然,如果对于要找index,leetcode上的题不能用这个方法,因为我们sort一遍之后,index会发生一些变化。但是我们可以用以下这个function来作为一个Helper function对于K Sum(考虑到如果K > 2, sort一遍数组的时间开销不算是主要的时间开销了): 342 | 343 | ```c++ 344 | void twoSum(vector &numbers, int begin, int first, int second, int target, vector>& ret) { 345 | if(begin >= numbers.size()-1) 346 | return; 347 | int b = begin; 348 | int e = numbers.size()-1; 349 | while(b < e) 350 | { 351 | int rest = numbers[b]+numbers[e]; 352 | if(rest == target) 353 | { 354 | vector tmp_ret; 355 | tmp_ret.push_back(first); 356 | tmp_ret.push_back(second); 357 | tmp_ret.push_back(numbers[b]); 358 | tmp_ret.push_back(numbers[e]); 359 | ret.push_back(tmp_ret); 360 | do{b++;} 361 | while(b &numbers, int begin, int first, int second, int target, vector>& ret) { 380 | if(begin >= numbers.size()-1) 381 | return; 382 | int b = begin; 383 | int e = numbers.size()-1; 384 | while(b < e) 385 | { 386 | int rest = numbers[b]+numbers[e]; 387 | if(rest == target) 388 | { 389 | vector tmp_ret; 390 | tmp_ret.push_back(first); 391 | tmp_ret.push_back(second); 392 | tmp_ret.push_back(numbers[b]); 393 | tmp_ret.push_back(numbers[e]); 394 | ret.push_back(tmp_ret); 395 | do{b++;} 396 | while(b > fourSum(vector &num, int target) { 407 | vector> ret; 408 | if(num.size() <= 3) //invalid corner case check 409 | return ret; 410 | sort(num.begin(), num.end()); //cause we need the result in quadruplets should be non-descending 411 | for(int i = 0; i < num.size()-3; ++i) 412 | { 413 | if(i > 0 && num[i] == num[i-1]) 414 | continue; 415 | for(int j = i+1; j < num.size()-2; ++j) 416 | { 417 | if(j > i+1 && num[j] == num[j-1]) 418 | continue; 419 | twoSum(num, j+1, num[i], num[j], target-(num[i]+num[j]), ret); 420 | } 421 | } 422 | return ret; 423 | } 424 | }; 425 | ``` 426 | 427 | 以上解法可以延伸到KSum.不过是相当于对于n-2个数做嵌套循环。这么写出来使得思路清晰,以后遇到了类似问题可以解决。 428 | 429 | 430 | -------------------------------------------------------------------------------- /backtracking/README.md: -------------------------------------------------------------------------------- 1 | # Backtracking 2 | -------------------------------------------------------------------------------- /backtracking/combination.md: -------------------------------------------------------------------------------- 1 | # Combination 2 | 3 | 在这个section里面,我们主要来过一下像leetcode里面类似combination这一系列的题,这类题应该归结为DFS+Backtracking。掌握了大体思想,注意一下边角处理就好,比如剪枝。 4 | 5 | 先来讨论一下第一题Combination. 6 | 7 | #Combination 8 | 9 | > Given two integers n and k, return all possible combinations of k numbers out of 1,2,...,n. 10 | 11 | 题目翻译: 12 | 给定两个整型数组n和k,要求返由k个数组成的combination,其实应该叫做组合. 这个combination应该是高中里面的组合。这k个数是在n中任选k个数,由题意可得,这里的k应该小于或等于n(这个条件不要忘了做validation check哦). 13 | 14 | 题目分析: 15 | 我觉得应该还有不少读者困惑什么是combination,这里我们先给一个例子比如n=3, k=2的条件下, 所有可能的combination如下: 16 | [1,2], [1,3], [2,3]. 注意:[2,3]和[3,2]是相同的,我们只要求有其中一个就可以了. 17 | 所以解题的时候,我们要避免相同的组合出现. 18 | 19 | 解题思路: 20 | 看到这道题,首先第一想法应该是用递归来求解.如果要是用循环来求解,这个时间复杂度应该是比较恐怖了.并且,这个递归是一层一层往深处去走的,打个比方,我们一个循环,首先求得以1开始的看个数的combination,之后再求以2开始的,以此类推,所以开始是对n个数做DFS, n-1个数做DFS...所以应该是对n*(n-1)*...*1做DFS. 在程序中,我们可以加一些剪枝条件来减少程序时间. 21 | 22 | 23 | 时间复杂度: 24 | 在题目分析中,我们提到了对于对n,n-1,...,1做DFS,所以时间复杂度是O(n!) 25 | 26 | 代码如下: 27 | 28 | ```c++ 29 | 30 | class Solution { 31 | public: 32 | vector > combine(int n, int k) { 33 | vector> ret; 34 | if(n <= 0) //corner case invalid check 35 | return ret; 36 | 37 | vector curr; 38 | DFS(ret,curr, n, k, 1); //we pass ret as reference at here 39 | return ret; 40 | } 41 | 42 | void DFS(vector>& ret, vector curr, int n, int k, int level) 43 | { 44 | if(curr.size() == k) 45 | { 46 | ret.push_back(curr); 47 | return; 48 | } 49 | if(curr.size() > k) // consider this check to save run time 50 | return; 51 | 52 | for(int i = level; i <= n; ++i) 53 | { 54 | curr.push_back(i); 55 | DFS(ret,curr,n,k,i+1); 56 | curr.pop_back(); 57 | } 58 | } 59 | 60 | 61 | }; 62 | ``` 63 | 64 | # Combination Sum 65 | 66 | > Given a set of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. 67 | 68 | > The same repeated number may be chosen from C unlimited number of times. 69 | 70 | Note: 71 | 1. All numbers (including target) will be positive integers. 72 | 2. Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak). 73 | 3. The solution set must not contain duplicate combinations. 74 | 75 | 题目翻译: 76 | 给一个数组C和一个目标值T, 找出所有的满足条件的组合:使得组合里面的数字之和等于T,并且一些数字可以从C中午先选择。 77 | 78 | 注意: 79 | 1. 所有给定的数字均为正整数.(这意味着我们加corner case invalid check的时候要加一条,如果给定T不是正整数,我们就没必要在往下进行了) 80 | 81 | 2. 所有的答案组中要满足升序排列. 82 | 83 | 3. 最后的答案数组不能包含重复答案. 84 | 85 | 题目分析: 86 | 这道题的大体思路和combination是相同的,不同的地方在于一个数字可以使用多次,这也造成了我们进行实现function的时候要注意的问题,也就是说,传入递归的参数不同于combination. 87 | 88 | 时间复杂度: 89 | 没什么好说的,和combination的时间复杂度是相同的.O(n!) 90 | 91 | 代码如下: 92 | 93 | ```c++ 94 | class Solution { 95 | public: 96 | vector > combinationSum(vector &candidates, int target) { 97 | vector> ret; 98 | //corner case invalid check 99 | if(candidates.size() == 0 || target < 0) 100 | return ret; 101 | vector curr; 102 | sort(candidates.begin(),candidates.end()); //because the requirments need the elements should be in non-descending order 103 | BackTracking(ret,curr,candidates,target,0); 104 | return ret; 105 | } 106 | 107 | /* we use reference at here because the function return type is 0, make the code understand easily */ 108 | void BackTracking(vector>& ret, vector curr, vector candidates, int target, int level) 109 | { 110 | if(target == 0) 111 | { 112 | ret.push_back(curr); 113 | return; 114 | } 115 | else if(target < 0) //save time 116 | return; 117 | 118 | for(int i = level; i < candidates.size(); ++i) 119 | { 120 | target -= candidates[i]; 121 | curr.push_back(candidates[i]); 122 | BackTracking(ret,curr,candidates,target,i); //unlike combination, we do not use i+1 because we can use the same number multiple times. 123 | curr.pop_back(); 124 | target += candidates[i]; 125 | } 126 | } 127 | 128 | }; 129 | ``` 130 | 131 | # Combination Sum II 132 | 133 | > Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T. 134 | 135 | > Each number in C may only be used once in the combination. 136 | 137 | Note: 138 | 139 | 1. All numbers (including target) will be positive integers. 140 | 141 | 2. Elements in a combination (a1, a2, … , ak) must be in non-descending order. (ie, a1 ≤ a2 ≤ … ≤ ak). 142 | 143 | 3. The solution set must not contain duplicate combinations. 144 | 145 | 146 | 题目翻译: 147 | 给定一个数组C和一个特定值T,要求找出这里面满足以下条件的所有答案:数组中数字的值加起来等于特定和的答案. 148 | 149 | 数组中每个数字只能用一次.(同three sum和four sum的解法) 150 | 151 | 注意条件: 152 | 1. 给定数组的所有值必须是正整数.(意味着我们加corner case invalid check的时候要检查T) 153 | 2. 答案数组中的值必须为升序排列.(我们要对数组进行排序) 154 | 3. 最终答案不能包含重复数组. 155 | 156 | 代码如下: 157 | ```c++ 158 | class Solution { 159 | public: 160 | vector > combinationSum2(vector &num, int target) { 161 | vector> ret; 162 | if(num.size() == 0 || target < 0) //invalid corner case check 163 | return ret; 164 | vector curr; 165 | sort(num.begin(), num.end()); 166 | BackTracking(ret,curr,num,target,0); 167 | return ret; 168 | } 169 | 170 | void BackTracking(vector>& ret, vector curr, vector num, int target, int level) 171 | { 172 | if(target == 0) 173 | { 174 | ret.push_back(curr); 175 | return; 176 | } 177 | else if(target < 0) 178 | return; 179 | for(int i = level; i < num.size(); ++i) 180 | { 181 | target -= num[i]; 182 | curr.push_back(num[i]); 183 | BackTracking(ret,curr,num,target,i+1); 184 | curr.pop_back(); 185 | target += num[i]; 186 | while(i < num.size()-1 && num[i] == num[i+1]) //we add this while loop is to skip the duplication result 187 | ++i; 188 | } 189 | } 190 | 191 | }; 192 | ``` 193 | 194 | # Letter Combinations of a Phone Number 195 | 196 | > Given a digit string, return all possible letter combinations that the number could represent. A mapping of digit to letters (just like on the telephone buttons) is given as below: 197 | ![](http://upload.wikimedia.org/wikipedia/commons/thumb/7/73/Telephone-keypad2.svg/200px-Telephone-keypad2.svg.png) 198 | 199 | ``` 200 | Input:Digit string "23" 201 | Output: ["ad", "ae", "af", "bd", "be", "bf", "cd", "ce", "cf"]. 202 | ``` 203 | 题目翻译: 204 | 给定一个字符串数字,返回这个字符串数字在电话表上可能的combination,一个map的电话键盘在上图已经给出. 205 | 206 | 题目分析: 207 | 这道题目的给出,具体的解题思路是和combination是相同的,不同的地方是我们要先建一个dictionary,以方便查找.之后用combination的相同方法,对于每一个数字,在dictionary中查找它所对应的说有的数字. 208 | 209 | 解题思路: 210 | 我是用字符串数组来构建这个dictionary的,用于下标代表数字,例如,下标为2,我的字典就会有这种对应的关系:dic[2] = "abc".只要把给定数字字符串的每一个数字转换为int类型,就可以根据字典查找出这个数字所对应的所有字母.当然,再构建字典的时候,我们需要注意dic[0] = "", dic[1] = "".这两个特殊的case,因为电话键盘并没有这两个数字相对应的字符串. 211 | 212 | 时间复杂度: 213 | O(3^n) 214 | 215 | 216 | 代码如下: 217 | 218 | ```c++ 219 | 220 | class Solution { 221 | public: 222 | vector letterCombinations(string digits) { 223 | vector ret; 224 | /* for this question, we need to create a look-up dictionary */ 225 | vector dic; 226 | string tmp; 227 | dic.push_back(" "); 228 | dic.push_back(" "); 229 | dic.push_back("abc"); 230 | dic.push_back("def"); 231 | dic.push_back("ghi"); 232 | dic.push_back("jkl"); 233 | dic.push_back("mno"); 234 | dic.push_back("pqrs"); 235 | dic.push_back("tuv"); 236 | dic.push_back("wxyz"); 237 | combinations(ret,tmp,digits,dic,0); 238 | return ret; 239 | } 240 | 241 | void combinations(vector& ret, string tmp, string digits, vector dic, int level) 242 | { 243 | if(level == digits.size()) 244 | { 245 | ret.push_back(tmp); 246 | return; 247 | } 248 | 249 | int index = digits[level]-'0'; 250 | for(int i = 0; i < dic[index].size(); ++i) 251 | { 252 | tmp.push_back(dic[index][i]); 253 | combinations(ret,tmp,digits,dic,level+1); 254 | tmp.pop_back(); 255 | } 256 | } 257 | 258 | 259 | }; 260 | ``` 261 | 262 | 263 | -------------------------------------------------------------------------------- /backtracking/permutation.md: -------------------------------------------------------------------------------- 1 | # Permutation 2 | 3 | # Permutation这个分支是在backtracking下的一个子分支,其具体的解题方法和Combination几乎是同出一辙,一个思路,对于给定数组用DFS方法一层一层遍历,在这个section当中,我们将对于leetcode上出现的permutation问题进行逐个分析与解答. 4 | 5 | # Permutations 6 | 7 | > given a collection of numbers, return all posibile permutations. 8 | 9 | > For example, [1,2,3] have the following permutations: 10 | [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], and [3,2,1]. 11 | 12 | 题目翻译: 13 | 给定一个整形数组,要求求出这个数组的所有变形体,具体例子看上文中给出的例子就可以. 14 | 15 | 题目分析: 16 | 这道题很直接,几乎算是没有坑,相信大家都可以理解题目的要求. Permutation的解题方法和Combination几乎是相同的,唯一需要注意的是,Permutation需要加一个bool类型的数组来进行记录哪个元素访问了,哪个没有,这样才不会导致重复出现,并且不同于Combination的一点是,Permutation不需要排序. 17 | 18 | 解题思路: 19 | 遇到这种问题,很显然,第一个想法我们首先回去想到DFS,递归求解,对于数组中的每一个元素,找到以他为首节点的Permutations,这就要求在递归中,每次都要从数组的第一个元素开始遍历,这样,,就引入了另外一个问题,我们会对于同一元素访问多次,这就不是我们想要的答案了,所以我们引入了一个bool类型的数组,用来记录哪个元素被遍历了(通过下标找出对应).在对于每一个Permutation进行求解中,如果访问了这个元素,我们将它对应下表的bool数组中的值置为true,访问结束后,我们再置为false. 20 | 21 | 时间复杂度分析: 22 | 这道题同Combination,所以对于这道题的解答,时间复杂度同样是O(n!) 23 | 24 | 代码如下: 25 | ```c++ 26 | 27 | class Solution { 28 | public: 29 | vector > permute(vector &num) { 30 | vector> permutations; 31 | if(num.size() == 0) //invalid corner case check 32 | return permutations; 33 | vector curr; 34 | vector isVisited(num.size(), false); //using this bool type array to check whether or not the elments has been visited 35 | backTracking(permutations,curr,isVisited,num); 36 | return permutations; 37 | } 38 | 39 | void backTracking(vector>& ret, vector curr, vector isVisited, vector num) 40 | { 41 | if(curr.size() == num.size()) 42 | { 43 | ret.push_back(curr); 44 | return; 45 | } 46 | 47 | for(int i = 0; i < num.size(); ++i) 48 | { 49 | if(isVisited[i] == false) 50 | { 51 | isVisited[i] = true; 52 | curr.push_back(num[i]); 53 | backTracking(ret,curr,isVisited,num); 54 | isVisited[i] = false; 55 | curr.pop_back(); 56 | } 57 | } 58 | } 59 | 60 | 61 | }; 62 | ``` 63 | 64 | # Permutations II 65 | 66 | > Given a collection of numbers that might contain duplicates, return all possible unique permutations. 67 | 68 | > For example, 69 | [1,1,2] have the following unique permutations: 70 | [1,1,2], [1,2,1], and [2,1,1]. 71 | 72 | 题目翻译: 73 | 给定一个整形数组,这个数组中可能会包含重复的数字,要求我们返回的是这个数组不同的Permutations,也就是说每一种可能的permutation在最后的答案中只能出现一次.上文的例子能清晰的告诉读者不同的地方. 74 | 75 | 题目分析: 76 | 对于这道题。也是要求permutation,大体上的解题思路和Permutations是相同的,但是不同点在哪里呢? 77 | 不同点为: 78 | 1. 这个给定的数组中可能会含有相同的数字. 79 | 2. 最后答案不接受重复相同的答案组. 80 | 81 | 对于这两点要求,Permutations的解法是无法解决的,所以我们就要考虑怎样满足以上两个要求. 我们可以对整个input数组进行排序,在求解答案的时候,只要一个元素的permutation求出来了,在这个元素后面和这个元素相同的元素,我们完全都可以pass掉,其实这个方法在sum和combination里面已经是屡试不爽了. 82 | 83 | 解题思路: 84 | 除了加上对于重复答案的处理外,剩下思路同Permutation一模一样。 85 | 86 | 时间复杂度: 87 | O(n!) 88 | 89 | 代码如下: 90 | ```c++ 91 | 92 | class Solution { 93 | public: 94 | vector > permuteUnique(vector &num) { 95 | vector> permutations; 96 | if(num.size() == 0) 97 | return permutations; 98 | vector curr; 99 | vector isVisited(num.size(), false); 100 | /* we need to sort the input array here because of this array 101 | contains the duplication value, then we need to skip the duplication 102 | value for the final result */ 103 | sort(num.begin(),num.end()); 104 | DFS(permutations,curr,num,isVisited); 105 | return permutations; 106 | } 107 | 108 | void DFS(vector>& ret, vector curr, vector num, vector isVisited) 109 | { 110 | if(curr.size() == num.size()) 111 | { 112 | ret.push_back(curr); 113 | return; 114 | } 115 | 116 | for(int i = 0; i < num.size(); ++i) 117 | { 118 | if(isVisited[i] == false) 119 | { 120 | isVisited[i] = true; 121 | curr.push_back(num[i]); 122 | DFS(ret,curr,num,isVisited); 123 | isVisited[i] = false; 124 | curr.pop_back(); 125 | while(i < num.size()-1 && num[i] == num[i+1]) //we use this while loop to skip the duplication value in the input array. 126 | ++i; 127 | } 128 | } 129 | } 130 | }; 131 | 132 | ``` 133 | 134 | -------------------------------------------------------------------------------- /backtracking/subsets.md: -------------------------------------------------------------------------------- 1 | # Subsets 2 | 3 | > Given a set of distinct integers, S, return all possible subsets. 4 | 5 | > Note: 6 | > Elements in a subset must be in non-descending order. 7 | The solution set must not contain duplicate subsets. 8 | For example, 9 | If S = [1,2,3], a solution is: 10 | 11 | > 12 | ``` 13 | [ 14 | [3], 15 | [1], 16 | [2], 17 | [1,2,3], 18 | [1,3], 19 | [2,3], 20 | [1,2], 21 | [] 22 | ] 23 | ``` 24 | > 25 | 26 | 这题其实就是列出集合里面的所有子集合,同时要求子集合元素需要升序排列。 27 | 28 | 首先我们需要对集合排序,对于一个n元素的集合,首先我们取第一个元素,加入子集合中,后面的n - 1个元素可以认为是第一个元素的子节点,我们依次遍历,譬如遍历到第二个元素的时候,后续的n - 2个元素又是第二个元素的子节点,再依次遍历处理,直到最后一个元素,然后回溯,继续处理。处理完第一个元素之后,我们按照同样的方式处理第二个元素。 29 | 30 | 譬如上面的[1, 2, 3],首先取出1,加入子集合,后面的2和3就是1的子节点,先取出2,把[1, 2]加入子集合,后面的3就是2的子节点,取出3,把[1, 2, 3]加入子集合。然后回溯,取出3,将[1, 3]加入子集合。 31 | 32 | 1处理完成之后,我们可以同样方式处理2,以及3。 33 | 34 | 代码如下: 35 | 36 | ```c++ 37 | class Solution { 38 | public: 39 | vector > res; 40 | vector > subsets(vector &S) { 41 | if(S.empty()) { 42 | return res; 43 | } 44 | 45 | sort(S.begin(), S.end()); 46 | 47 | //别忘了空集合 48 | res.push_back(vector()); 49 | 50 | vector v; 51 | 52 | generate(0, v, S); 53 | 54 | return res; 55 | } 56 | 57 | void generate(int start, vector& v, vector &S) { 58 | if(start == S.size()) { 59 | return; 60 | } 61 | 62 | for(int i = start; i < S.size(); i++) { 63 | v.push_back(S[i]); 64 | 65 | res.push_back(v); 66 | 67 | generate(i + 1, v, S); 68 | 69 | v.pop_back(); 70 | } 71 | } 72 | }; 73 | ``` 74 | 75 | # Subsets II 76 | 77 | > Given a collection of integers that might contain duplicates, S, return all possible subsets. 78 | 79 | > Note: 80 | > Elements in a subset must be in non-descending order. 81 | The solution set must not contain duplicate subsets. 82 | For example, 83 | If S = [1,2,2], a solution is: 84 | 85 | > 86 | ``` 87 | [ 88 | [2], 89 | [1], 90 | [1,2,2], 91 | [2,2], 92 | [1,2], 93 | [] 94 | ] 95 | ``` 96 | > 97 | 98 | 这题跟上题唯一的区别在于有重复元素,但是我们得到的子集合又不能有相同的,其实做法很简单,仍然按照上面的处理,只是遍历子节点的时候如果发现有相等的,只遍历一个,后续跳过。代码如下: 99 | 100 | ```c++ 101 | class Solution { 102 | public: 103 | vector > res; 104 | 105 | vector > subsetsWithDup(vector &S) { 106 | if(S.empty()) { 107 | return res; 108 | } 109 | 110 | sort(S.begin(), S.end()); 111 | 112 | res.push_back(vector()); 113 | 114 | vector v; 115 | 116 | generate(0, v, S); 117 | 118 | return res; 119 | } 120 | 121 | void generate(int start, vector& v, vector &S) { 122 | if(start == S.size()) { 123 | return; 124 | } 125 | 126 | for(int i = start; i < S.size(); i++) { 127 | v.push_back(S[i]); 128 | 129 | res.push_back(v); 130 | 131 | generate(i + 1, v, S); 132 | 133 | v.pop_back(); 134 | 135 | //这里跳过相同的 136 | while(i < S.size() - 1 && S[i] == S[i + 1]) { 137 | i++; 138 | } 139 | } 140 | } 141 | }; 142 | ``` 143 | -------------------------------------------------------------------------------- /bit_manipulation/README.md: -------------------------------------------------------------------------------- 1 | # Bit Manipulation 2 | -------------------------------------------------------------------------------- /bit_manipulation/missing_number.md: -------------------------------------------------------------------------------- 1 | # Missing Number 2 | 3 | > Given an array containing n distinct numbers taken from 0, 1, 2, ..., n, find the one that is missing from the array. 4 | 5 | > For example, 6 | > Given nums = ```[0, 1, 3]``` return ```2```. 7 | 8 | > Note: 9 | > Your algorithm should run in linear runtime complexity. Could you implement it using only constant extra space complexity? 10 | 11 | 题目翻译: 12 | 从0到n之间取出n个不同的数,找出漏掉的那个。 13 | 注意:你的算法应当具有线性的时间复杂度。你能实现只占用常数额外空间复杂度的算法吗? 14 | 15 | 题目分析: 16 | 最直观的思路是对数据进行排序,然后依次扫描,便能找出漏掉的数字,但是基于比较的排序算法的时间复杂度至少是```nlog(n)```,不满足题目要求。 17 | 18 | 一种可行的具有线性时间复杂度的算法是求和。对0到n求和,然后对给出的数组求和,二者之差即为漏掉的数字。但是这种方法不适用于0是漏掉的数字的情况,因为此时两个和是相同的。(或者也能由此得出漏掉的数字是0) 19 | 20 | 从CPU指令所耗费的时钟周期来看,比加法更高效率的运算是异或(XOR)运算。本题的标签里有位运算,暗示本题可以用位运算的方法解决。 21 | 22 | 异或运算的一个重要性质是,相同的数异或得0,不同的数异或不为0,且此性质可以推广到多个数异或的情形。本题的解法如下,首先将0到n这些数进行异或运算,然后对输入的数组进行异或运算,最后将两个结果进行异或运算,结果便是漏掉的数字,因为其他数字在两个数组中都是成对出现的,异或运算会得到0。 23 | 24 | 时间复杂度:O(n) 25 | 空间复杂度:O(1) 26 | 27 | 代码如下: 28 | 29 | ```c++ 30 | class Solution { 31 | public: 32 | int missingNumber(vector& nums) { 33 | int x = 0; 34 | for (int i = 0; i <= nums.size(); i++) x ^= i; 35 | for (auto n : nums) x ^= n; 36 | return x; 37 | } 38 | }; 39 | ``` 40 | 41 | 42 | -------------------------------------------------------------------------------- /bit_manipulation/number_of_1_bits.md: -------------------------------------------------------------------------------- 1 | # Number of 1 Bits 2 | 3 | > Write a function that takes an unsigned integer and returns the number of ’1' bits it has (also known as the Hamming weight). 4 | > For example, the 32-bit integer `11` has binary representation `00000000000000000000000000001011`, so the function should return 3. 5 | 6 | 7 | 题目翻译: 8 | 给出一个整数,求它包含二进制1的位数。例如,32位整数`11`的二进制表达形式是`00000000000000000000000000001011`,那么函数应该返回3。 9 | 10 | 题目分析: 11 | 设输入的数为n,把n与1做二进制的与(AND)运算,即可判断它的最低位是否为1。如果是的话,把计数变量加一。然后把n向右移动一位,重复上述操作。当n变为0时,终止算法,输出结果。 12 | 13 | 时间复杂度:O(n) 14 | 空间复杂度:O(1) 15 | 16 | 代码如下: 17 | 18 | ```c++ 19 | class Solution { 20 | public: 21 | int hammingWeight(uint32_t n) { 22 | int count = 0; 23 | while (n > 0) { 24 | count += n & 1; 25 | n >>= 1; 26 | } 27 | return count; 28 | } 29 | }; 30 | ``` 31 | 32 | 33 | -------------------------------------------------------------------------------- /bit_manipulation/power_of_two.md: -------------------------------------------------------------------------------- 1 | # Power of Two 2 | 3 | > Given an integer, write a function to determine if it is a power of two. 4 | 5 | 6 | 题目翻译: 7 | 给出一个整数,判断它是否是2的幂。 8 | 9 | 题目分析: 10 | 2的整数次幂对应的二进制数只含有0个或者1个1,所以我们要做的就是判断输入的数的二进制表达形式里是否符合这一条件。 11 | 有一种corner case需要注意,当输入的数为负数的时候,一定不是2的幂。 12 | 13 | 时间复杂度:O(n) 14 | 空间复杂度:O(1) 15 | 16 | 代码如下: 17 | 18 | ```c++ 19 | class Solution { 20 | public: 21 | bool isPowerOfTwo(int n) { 22 | if (n < 0) return false; 23 | bool hasOne = false; 24 | while (n > 0) { 25 | if (n & 1) { 26 | if (hasOne) { 27 | return false; 28 | } 29 | else { 30 | hasOne = true; 31 | } 32 | } 33 | n >>= 1; 34 | } 35 | return hasOne; 36 | } 37 | }; 38 | ``` 39 | 40 | 41 | -------------------------------------------------------------------------------- /dynamic_programming/README.md: -------------------------------------------------------------------------------- 1 | # Dynamic Programming 2 | -------------------------------------------------------------------------------- /dynamic_programming/best_time_to_buy_and_sell_stock.md: -------------------------------------------------------------------------------- 1 | # Best Time to Buy and Sell Stock 2 | 3 | > Say you have an array for which the ith element is the price of a given stock on day i. 4 | 5 | > If you were only permitted to complete at most one transaction (ie, buy one and sell one share of the stock), design an algorithm to find the maximum profit. 6 | 7 | 这是卖股票的第一个题目,根据题意我们知道只能进行一次交易,但需要获得最大的利润,所以我们需要在最低价买入,最高价卖出,当然买入一定要在卖出之前。 8 | 9 | 对于这一题,还是比较简单的,我们只需要遍历一次数组,通过一个变量记录当前最低价格,同时算出此次交易利润,并与当前最大值比较就可以了。 10 | 11 | 代码如下: 12 | 13 | ```c++ 14 | class Solution { 15 | public: 16 | int maxProfit(vector &prices) { 17 | if(prices.size() <= 1) { 18 | return 0; 19 | } 20 | 21 | int minP = prices[0]; 22 | 23 | int profit = prices[1] - prices[0]; 24 | 25 | for(int i = 2; i < prices.size(); i++) { 26 | minP = min(prices[i - 1], minP); 27 | profit = max(profit, prices[i] - minP); 28 | } 29 | 30 | if(profit < 0) { 31 | return 0; 32 | } 33 | 34 | return profit; 35 | } 36 | }; 37 | ``` 38 | 39 | # Best Time to Buy and Sell Stock II 40 | 41 | > Say you have an array for which the ith element is the price of a given stock on day i. 42 | 43 | > Design an algorithm to find the maximum profit. You may complete as many transactions as you like (ie, buy one and sell one share of the stock multiple times). However, you may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). 44 | 45 | 这题相对于上一题来说更加容易(这不知道为啥是II),因为不限制交易次数,我们在第i天买入,如果发现i + 1天比i高,那么就可以累加到利润里面。 46 | 47 | 代码如下: 48 | 49 | ```c++ 50 | class Solution { 51 | public: 52 | int maxProfit(vector &prices) { 53 | int len = (int)prices.size(); 54 | if(len <= 1) { 55 | return 0; 56 | } 57 | 58 | int sum = 0; 59 | for(int i = 1; i < len; i++) { 60 | if(prices[i] - prices[i - 1] > 0) { 61 | sum += prices[i] - prices[i - 1]; 62 | } 63 | } 64 | 65 | return sum; 66 | } 67 | }; 68 | ``` 69 | 70 | # Best Time to Buy and Sell Stock III 71 | 72 | > Say you have an array for which the ith element is the price of a given stock on day i. 73 | 74 | > Design an algorithm to find the maximum profit. You may complete at most two transactions. 75 | 76 | > Note: 77 | > You may not engage in multiple transactions at the same time (ie, you must sell the stock before you buy again). 78 | 79 | 这题是三道题目中最难的一题,只允许两次股票交易,如果只允许一次,那么题目就退化到第一题了,根据第一题的算法,我们可以得到[0,1,...,i]区间的最大利润,同时在从后往前扫描得到[i,i+1,...,n-1]的最大利润,两个相加就可以得到该题的解了。 80 | 81 | 代码如下: 82 | 83 | ```c++ 84 | class Solution { 85 | public: 86 | int maxProfit(vector &prices) { 87 | int len = (int)prices.size(); 88 | if(len <= 1) { 89 | return 0; 90 | } 91 | 92 | vector profits; 93 | profits.resize(len); 94 | 95 | //首先我们正向遍历得到每天一次交易的最大收益 96 | //并保存到profits里面 97 | int minP = prices[0]; 98 | int sum = numeric_limits::min(); 99 | for(int i = 1; i < len; i++) { 100 | minP = min(minP, prices[i - 1]); 101 | profits[i] = max(sum, prices[i] - minP); 102 | 103 | sum = profits[i]; 104 | } 105 | 106 | int maxP = prices[len - 1]; 107 | int sum2 = numeric_limits::min(); 108 | 109 | //逆向遍历 110 | for(int i = len - 2; i >= 0; i--) { 111 | maxP = max(maxP, prices[i + 1]); 112 | sum2 = max(sum2, maxP - prices[i]); 113 | 114 | if(sum2 > 0) { 115 | //这里我们直接将其加入profits里面, 116 | //不需要额外保存 117 | profits[i] = profits[i] + sum2; 118 | sum = max(sum, profits[i]); 119 | } 120 | } 121 | 122 | return sum > 0 ? sum : 0; 123 | } 124 | }; 125 | ``` 126 | 127 | 卖股票的1和3已经涉及到了动态规划,但笔者这方面还未有太多知识积累,所以不能很好的列举出动态规划方程,这是笔者后续需要努力提升的,后续补上。 128 | -------------------------------------------------------------------------------- /dynamic_programming/climbing_stairs.md: -------------------------------------------------------------------------------- 1 | # Climbing Stairs 2 | 3 | > You are climbing a stair case. It takes n steps to reach to the top. 4 | 5 | > Each time you can either climb 1 or 2 steps. In how many distinct ways can you climb to the top? 6 | 7 | 这道题目其实就是斐波那契数列问题,题目比较简单,我们很容易就能列出dp方程 8 | 9 | `dp[n] = dp[n - 1] + dp[n - 2]` 10 | 11 | 初始条件dp[1] = 1, dp[2] = 2。 12 | 13 | 代码如下: 14 | 15 | ```c++ 16 | class Solution { 17 | public: 18 | int climbStairs(int n) { 19 | int f1 = 2; 20 | int f2 = 1; 21 | if(n == 1) { 22 | return f2; 23 | } else if(n == 2) { 24 | return f1; 25 | } 26 | 27 | int fn; 28 | for(int i = 3; i <= n; i++) { 29 | fn = f1 + f2; 30 | f2 = f1; 31 | f1 = fn; 32 | } 33 | return fn; 34 | } 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /dynamic_programming/maximum_subarray.md: -------------------------------------------------------------------------------- 1 | # Maximum Subarray 2 | 3 | > Find the contiguous subarray within an array (containing at least one number) which has the largest sum. 4 | 5 | > For example, given the array [−2,1,−3,4,−1,2,1,−5,4], 6 | the contiguous subarray [4,−1,2,1] has the largest sum = 6. 7 | 8 | 这题是一道经典的dp问题,我们可以很容易的得到其dp方程,假设dp[i]是数组a [0, i]区间最大的值,那么 9 | 10 | `dp[i + 1] = max(dp[i], dp[i] + a[i + 1])` 11 | 12 | 代码如下: 13 | 14 | ```c++ 15 | class Solution { 16 | public: 17 | int maxSubArray(int A[], int n) { 18 | int sum = 0; 19 | int m = INT_MIN; 20 | 21 | for(int i = 0; i < n; i++) { 22 | sum += A[i]; 23 | m = max(m, sum); 24 | //如果sum小于0了,将sum重置为0,从i + 1再次开始计算 25 | if(sum < 0) { 26 | sum = 0; 27 | } 28 | } 29 | 30 | return m; 31 | } 32 | }; 33 | ``` 34 | 35 | 虽然这道题目用dp解起来很简单,但是题目说了,问我们能不能采用divide and conquer的方法解答,也就是二分法。 36 | 37 | 假设数组A[left, right]存在最大区间,mid = (left + right) / 2,那么无非就是三中情况: 38 | 39 | 1. 最大值在A[left, mid - 1]里面 40 | 2. 最大值在A[mid + 1, right]里面 41 | 3. 最大值跨过了mid,也就是我们需要计算[left, mid - 1]区间的最大值,以及[mid + 1, right]的最大值,然后加上mid,三者之和就是总的最大值 42 | 43 | 我们可以看到,对于1和2,我们通过递归可以很方便的求解,然后在同第3的结果比较,就是得到的最大值。 44 | 45 | 代码如下: 46 | 47 | ```c++ 48 | class Solution { 49 | public: 50 | int maxSubArray(int A[], int n) { 51 | return divide(A, 0, n - 1, INT_MIN); 52 | } 53 | 54 | int divide(int A[], int left, int right, int tmax) { 55 | if(left > right) { 56 | return INT_MIN; 57 | } 58 | 59 | int mid = left + (right - left) / 2; 60 | //得到子区间[left, mid - 1]最大值 61 | int lmax = divide(A, left, mid - 1, tmax); 62 | //得到子区间[mid + 1, right]最大值 63 | int rmax = divide(A, mid + 1, right, tmax); 64 | 65 | tmax = max(tmax, lmax); 66 | tmax = max(tmax, rmax); 67 | 68 | int sum = 0; 69 | int mlmax = 0; 70 | //得到[left, mid - 1]最大值 71 | for(int i = mid - 1; i >= left; i--) { 72 | sum += A[i]; 73 | mlmax = max(mlmax, sum); 74 | } 75 | 76 | sum = 0; 77 | int mrmax = 0; 78 | //得到[mid + 1, right]最大值 79 | for(int i = mid + 1; i <= right; i++) { 80 | sum += A[i]; 81 | mrmax = max(mrmax, sum); 82 | } 83 | 84 | tmax = max(tmax, A[mid] + mlmax + mrmax); 85 | return tmax; 86 | } 87 | }; 88 | ``` 89 | 90 | # Maxmimum Product Subarray 91 | 92 | > Find the contiguous subarray within an array (containing at least one number) which has the largest product. 93 | 94 | > For example, given the array [2,3,-2,4], 95 | the contiguous subarray [2,3] has the largest product = 6. 96 | 97 | 这题是求数组中子区间的最大乘积,对于乘法,我们需要注意,负数乘以负数,会变成正数,所以解这题的时候我们需要维护两个变量,当前的最大值,以及最小值,最小值可能为负数,但没准下一步乘以一个负数,当前的最大值就变成最小值,而最小值则变成最大值了。 98 | 99 | 我们的动态方程可能这样: 100 | 101 | ``` 102 | maxDP[i + 1] = max(maxDP[i] * A[i + 1], A[i + 1], minDP[i] * A[i + 1]) 103 | minDP[i + 1] = min(minDP[i] * A[i + 1], A[i + 1], maxDP[i] * A[i + 1] 104 | dp[i + 1] = max(dp[i], maxDP[i + 1]) 105 | ``` 106 | 107 | 这里,我们还需要注意元素为0的情况,如果A[i]为0,那么maxDP和minDP都为0,我们需要从A[i + 1]重新开始。 108 | 109 | 代码如下: 110 | 111 | ```c++ 112 | class Solution { 113 | public: 114 | int maxProduct(int A[], int n) { 115 | if(n == 0){ 116 | return 0; 117 | } else if(n == 1) { 118 | return A[0]; 119 | } 120 | 121 | int p = A[0]; 122 | int maxP = A[0]; 123 | int minP = A[0]; 124 | for(int i = 1; i < n; i++) { 125 | int t = maxP; 126 | maxP = max(max(maxP * A[i], A[i]), minP * A[i]); 127 | minP = min(min(t * A[i], A[i]), minP * A[i]); 128 | p = max(maxP, p); 129 | } 130 | 131 | return p; 132 | } 133 | }; 134 | ``` 135 | -------------------------------------------------------------------------------- /dynamic_programming/perfect_squares.md: -------------------------------------------------------------------------------- 1 | # Perfect Squares 2 | 3 | > Given a positive integer n, find the least number of perfect square numbers (for example, 1, 4, 9, 16, ...) which sum to n. 4 | > For example, given ```n = 12```, return 3 because ```12 = 4 + 4 + 4```; given ```n = 13```, return 2 because ```13 = 4 + 9```. 5 | 6 | 题目翻译: 7 | 给出一个正整数`n`,求至少需要多少个完全平方数(例如1,4,9,16……)相加能得到n。 8 | 例如,给出`n = 12`,返回3,因为`12 = 4 + 4 + 4`。给出`n = 13`,返回2,因为`13 = 4 + 9`。 9 | 10 | 题目分析: 11 | 乍一看题目,比较天真的想法是,先从不大于n的最大的完全平方数开始组合,如果和超过了n,就换小一点的完全平方数。但问题是,最后如果凑不齐的话,只能添加很多1,总量上就不是最少的了。例如12,题目中给的例子是4+4+4,需要3个完全平方数。如果从最大的开始组合,那么是9+1+1+1,需要4个完全平方数。 12 | 13 | 从另一个角度来想,用穷举法来求解就是把不大于n的所有可能的完全平方数的组合都算出来,然后找出和为n的组合中数量最少的那种组合。如果不大于n的完全平方数有m个的话,这个方法的时间复杂度是O(m^m)。显然穷举法时间复杂度过大,不是可行的方法。观察到,在枚举的过程中,有一些组合显然不是最优的,比如把12拆成12个1相加。另外,如果我们能够记录已经找到的最小组合,那么稍大一些的数只需要在此基础上添加若干个完全平方数即可。这里面就包含了动态规划的思想。 14 | 15 | 具体来说,我们用一个数组来记录已有的结果,初始化为正无穷(`INT_MAX`)。外层循环变量`i`从`0`到`n`,内层循环变量`j`在`i`的基础上依次加上每个整数的完全平方,超过`n`的不算。那么`i + j*j`这个数需要的最少的完全平方数的数量,就是数组中当前的数值,和`i`位置的数值加上一,这两者之间较小的数字。如果当前的值较小,说明我们已经找到过它需要的完全平方数的个数(最初都是正无穷)。否则的话,说明在`i`的基础上加上`j`的平方符合条件,所需的完全平方数的个数就是`i`需要的个数加上一。 16 | 17 | 代码如下 18 | 19 | ```c++ 20 | class Solution { 21 | public: 22 | int numSquares(int n) { 23 | vector dp(n + 1, INT_MAX); 24 | dp[0] = 0; 25 | for (int i = 0; i <= n; i++) { 26 | for (int j = 1; i + j * j <= n; j++) { 27 | dp[i + j * j] = min(dp[i + j * j], dp[i] + 1); 28 | } 29 | } 30 | return dp[n]; 31 | } 32 | }; 33 | ``` 34 | -------------------------------------------------------------------------------- /dynamic_programming/triangle.md: -------------------------------------------------------------------------------- 1 | # Triangle 2 | 3 | > Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below. 4 | 5 | > For example, given the following triangle 6 | 7 | > ``` 8 | [ 9 | [2], 10 | [3,4], 11 | [6,5,7], 12 | [4,1,8,3] 13 | ] 14 | ``` 15 | > The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11). 16 | 17 | 这题要求我们求出一个三角形中从顶到底最小路径和,并且要求只能使用O(n)的空间。 18 | 19 | 这题有两种解法,自顶向下以及自底向上。 20 | 21 | 首先来看自顶向下,根据题目我们知道,每向下一层,我们只能选择邻接数字进行累加,譬如上面第1行的数字3,它的下一行邻接数字就是6和5。 22 | 23 | 我们假设dp[m][n]保存了第m行第n个节点的最小路径和,我们有如下dp方程 24 | 25 | + `dp[m + 1][n] = min(dp[m][n], dp[m][n - 1]) + triangle[m + 1][n] if n > 0` 26 | + `dp[m + 1][0] = dp[m][0] + triangle[m + 1][0]` 27 | 28 | 因为只能使用O(n)的空间,所以我们需要滚动计算,使用一个一位数组保存每层的最小路径和,参考Pascal's Triangle,我们知道,为了防止计算的时候不覆盖以前的值,所以我们需要从后往前计算。 29 | 30 | 代码如下 31 | 32 | ```c++ 33 | class Solution { 34 | public: 35 | int minimumTotal(vector > &triangle) { 36 | int row = triangle.size(); 37 | if(row == 0) { 38 | return 0; 39 | } 40 | 41 | //初始化为最大值 42 | vector total(row, INT_MAX); 43 | total[0] = triangle[0][0]; 44 | int minTotal = INT_MAX; 45 | for(int i = 1; i < row; i++) { 46 | for(int j = i; j >= 0; j--) { 47 | if(j == 0) { 48 | total[j] = total[j] + triangle[i][j]; 49 | } else { 50 | //上一层total[i]为INT_MAX,不会影响最小值 51 | total[j] = min(total[j - 1], total[j]) + triangle[i][j]; 52 | } 53 | } 54 | } 55 | for(int i = 0; i < row; i++) { 56 | minTotal = min(minTotal, total[i]); 57 | } 58 | return minTotal; 59 | } 60 | }; 61 | ``` 62 | 63 | 区别于自顶向下,另一种更简单的做法就是自底向上了。dp方程为 64 | 65 | `dp[m][n] = min(dp[m + 1][n], dp[m + 1][n + 1]) + triangle[m][n]` 66 | 67 | 我们仍然可以使用一位数组滚动计算。 68 | 69 | 代码如下 70 | 71 | ```c++ 72 | class Solution { 73 | public: 74 | int minimumTotal(vector > &triangle) { 75 | if(triangle.empty()) { 76 | return 0; 77 | } 78 | int row = triangle.size(); 79 | vector dp; 80 | dp.resize(row); 81 | //用最底层的数据初始化 82 | for(int i = 0; i < dp.size(); i++) { 83 | dp[i] = triangle[row-1][i]; 84 | } 85 | 86 | for(int i = row - 2; i >= 0; i--) { 87 | for(int j = 0; j <= i; j++) { 88 | dp[j] = triangle[i][j] + min(dp[j], dp[j + 1]); 89 | } 90 | } 91 | return dp[0]; 92 | } 93 | }; 94 | ``` 95 | -------------------------------------------------------------------------------- /dynamic_programming/unique_binary_search_trees.md: -------------------------------------------------------------------------------- 1 | # Unique Binary Search Trees 2 | 3 | > Given n, how many structurally unique BST's (binary search trees) that store values 1...n? 4 | 5 | > For example, 6 | > Given n = 3, there are a total of 5 unique BST's. 7 | 8 | >``` 9 | 1 3 3 2 1 10 | \ / / / \ \ 11 | 3 2 1 1 3 2 12 | / / \ \ 13 | 2 1 2 3 14 | ``` 15 | > 16 | 17 | 这道题目要求给定一个数n,有多少种二叉树排列方式,用来存储1到n。 18 | 19 | 刚开始拿到这题的时候,完全不知道如何下手,但考虑到二叉树的性质,对于任意以i为根节点的二叉树,它的左子树的值一定小于i,也就是[0, i - 1]区间,而右子树的值一定大于i,也就是[i + 1, n]。假设左子树有m种排列方式,而右子树有n种,则对于i为根节点的二叉树总的排列方式就是m x n。 20 | 21 | 我们使用dp[i]表示i个节点下面二叉树的排列个数,那么dp方程为: 22 | 23 | `dp[i] = sum(dp[k] * dp[i - k -1]) 0 <= k < i` 24 | 25 | 代码如下: 26 | 27 | ```c++ 28 | class Solution { 29 | public: 30 | int numTrees(int n) { 31 | vector dp(n + 1, 0); 32 | 33 | //dp初始化 34 | dp[0] = 1; 35 | dp[1] = 1; 36 | 37 | for(int i = 2; i <= n; i++) { 38 | for(int j = 0; j < i; j++) { 39 | //如果左子树的个数为j,那么右子树为i - j - 1 40 | dp[i] += dp[j] * dp[i - j - 1]; 41 | } 42 | } 43 | 44 | return dp[n]; 45 | } 46 | }; 47 | ``` 48 | 49 | # Unique Binary Search Trees II 50 | 51 | > Given n, generate all structurally unique BST's (binary search trees) that store values 1...n. 52 | 53 | > For example, 54 | Given n = 3, your program should return all 5 unique BST's shown below. 55 | 56 | >``` 57 | 1 3 3 2 1 58 | \ / / / \ \ 59 | 3 2 1 1 3 2 60 | / / \ \ 61 | 2 1 2 3 62 | ``` 63 | > 64 | 65 | 这题跟前面一题不同,需要得到所有排列的解。 66 | 67 | 根据前面我们知道,对于在n里面的任意i,它的排列数为左子树[0, i - 1]的排列数 x 右子树[i + 1, n]的排列数,所以我们只需要得到i的左子树和右子树的所有排列,就能得到i的所有排列了。而这个使用递归就能搞定,代码如下: 68 | 69 | ```c++ 70 | class Solution { 71 | public: 72 | vector generateTrees(int n) { 73 | return generate(1, n); 74 | } 75 | 76 | vector generate(int start, int stop){ 77 | vector vs; 78 | if(start > stop) { 79 | //没有子树了,返回null 80 | vs.push_back(NULL); 81 | return vs; 82 | } 83 | 84 | for(int i = start; i <= stop; i++) { 85 | auto l = generate(start, i - 1); 86 | auto r = generate(i + 1, stop); 87 | 88 | //获取左子树和右子树所有排列之后,放到root为i的节点的下面 89 | for(int j = 0; j < l.size(); j++) { 90 | for(int k = 0; k < r.size(); k++) { 91 | TreeNode* n = new TreeNode(i); 92 | n->left = l[j]; 93 | n->right = r[k]; 94 | vs.push_back(n); 95 | } 96 | } 97 | } 98 | 99 | return vs; 100 | } 101 | }; 102 | ``` 103 | -------------------------------------------------------------------------------- /dynamic_programming/unique_paths.md: -------------------------------------------------------------------------------- 1 | # Unique Paths 2 | 3 | > A robot is located at the top-left corner of a m x n grid (marked 'Start' in the diagram below). 4 | 5 | > The robot can only move either down or right at any point in time. The robot is trying to reach the bottom-right corner of the grid (marked 'Finish' in the diagram below). 6 | 7 | > How many possible unique paths are there? 8 | 9 | 这题是一道典型的dp问题,如果机器人要到(i, j)这个点,他可以选择先到(i - 1, j)或者,(i, j - 1),也就是说,到(i, j)的唯一路径数等于(i - 1, j)加上(i, j - 1)的个数,所以我们很容易得出dp方程: 10 | 11 | `dp[i][j] = dp[i - 1][j] + dp[i][j - 1]` 12 | 13 | dp[i][j]表示从点(0, 0)到(i, j)唯一路径数量。 14 | 15 | 代码如下: 16 | 17 | ```c++ 18 | class Solution { 19 | public: 20 | int uniquePaths(int m, int n) { 21 | int dp[m][n]; 22 | //初始化dp,m x 1情况全为1 23 | for(int i = 0; i < m; i++) { 24 | dp[i][0] = 1; 25 | } 26 | 27 | //初始化dp,1 x n情况全为1 28 | for(int j = 0; j < n; j++) { 29 | dp[0][j] = 1; 30 | } 31 | 32 | for(int i = 1; i < m; i++) { 33 | for(int j = 1; j < n; j++) { 34 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 35 | } 36 | } 37 | 38 | return dp[m - 1][n - 1]; 39 | } 40 | }; 41 | ``` 42 | 43 | # Unique Paths II 44 | 45 | > Now consider if some obstacles are added to the grids. How many unique paths would there be? 46 | 47 | > An obstacle and empty space is marked as 1 and 0 respectively in the grid. 48 | 49 | 这题跟上一题唯一的区别在于多了障碍物,如果某一个点有障碍,那么机器人无法通过。 50 | 51 | 代码如下: 52 | 53 | ```c++ 54 | class Solution { 55 | public: 56 | int uniquePathsWithObstacles(vector > &obstacleGrid) { 57 | if(obstacleGrid.empty() || obstacleGrid[0].empty()) { 58 | return 0; 59 | } 60 | 61 | int m = obstacleGrid.size(); 62 | int n = obstacleGrid[0].size(); 63 | 64 | int dp[m][n]; 65 | 66 | //下面初始dp的时候需要根据obstacleGrid的值来确定 67 | dp[0][0] = (obstacleGrid[0][0] == 0 ? 1 : 0); 68 | 69 | //我们需要注意m x 1以及1 x n的初始化 70 | for(int i = 1; i < m; i++) { 71 | dp[i][0] = ((dp[i - 1][0] == 1 && obstacleGrid[i][0] == 0) ? 1 : 0); 72 | } 73 | 74 | for(int j = 1; j < n; j++) { 75 | dp[0][j] = ((dp[0][j - 1] == 1 && obstacleGrid[0][j] == 0) ? 1 : 0); 76 | } 77 | 78 | 79 | for(int i = 1; i < m; i++) { 80 | for(int j = 1; j < n; j++) { 81 | if(obstacleGrid[i][j] == 1) { 82 | dp[i][j] = 0; 83 | } else { 84 | dp[i][j] = dp[i - 1][j] + dp[i][j - 1]; 85 | } 86 | } 87 | } 88 | 89 | return dp[m - 1][n - 1]; 90 | } 91 | }; 92 | ``` 93 | 94 | # Minimum Path Sum 95 | 96 | > Given a m x n grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path. 97 | 98 | > Note: You can only move either down or right at any point in time. 99 | 100 | 这题跟前面两题差不多,所以放到这里说明了。我们使用dp[i][j]表明从(0, 0)到(i, j)最小的路径和,那么dp方程为: 101 | 102 | `dp[i][j] = min(dp[i][j-1], dp[i - 1][j]) + grid[i][j]` 103 | 104 | 代码如下: 105 | 106 | ```c++ 107 | class Solution { 108 | public: 109 | int minPathSum(vector > &grid) { 110 | if(grid.empty() || grid[0].empty()) { 111 | return 0; 112 | } 113 | 114 | int row = grid.size(); 115 | int col = grid[0].size(); 116 | 117 | int dp[row][col]; 118 | 119 | dp[0][0] = grid[0][0]; 120 | for(int i = 1; i < row; i++) { 121 | dp[i][0] = dp[i - 1][0] + grid[i][0]; 122 | } 123 | 124 | for(int j = 1; j < col; j++) { 125 | dp[0][j] = dp[0][j - 1] + grid[0][j]; 126 | } 127 | 128 | for(int i = 1; i < row; i++) { 129 | for(int j = 1; j < col; j++) { 130 | dp[i][j] = min(dp[i - 1][j], dp[i][j - 1]) + grid[i][j]; 131 | } 132 | } 133 | 134 | return dp[row - 1][col - 1]; 135 | } 136 | }; 137 | ``` 138 | -------------------------------------------------------------------------------- /greedy/README.md: -------------------------------------------------------------------------------- 1 | # Greedy 2 | -------------------------------------------------------------------------------- /greedy/candy.md: -------------------------------------------------------------------------------- 1 | # Candy 2 | 3 | > There are N children standing in a line. Each child is assigned a rating value. 4 | 5 | > You are giving candies to these children subjected to the following requirements: 6 | 7 | > Each child must have at least one candy. 8 | > Children with a higher rating get more candies than their neighbors. 9 | What is the minimum candies you must give? 10 | 11 | 好了,终于到了小盆友,排队领糖果的时候了,我们可是坏叔叔。 12 | 13 | 这题要求每个小孩至少要领到一颗糖果,但是高级别的小孩要比它旁边的孩子得到的糖果多(小孩的世界也有不平等了),问最少需要发多少糖果? 14 | 15 | 首先我们会给每个小朋友一颗糖果,然后从左到右,假设第i个小孩的等级比第i - 1个小孩高,那么第i的小孩的糖果数量就是第i - 1个小孩糖果数量在加一。再我们从右到左,如果第i个小孩的等级大于第i + 1个小孩的,同时第i个小孩此时的糖果数量小于第i + 1的小孩,那么第i个小孩的糖果数量就是第i + 1个小孩的糖果数量加一。 16 | 17 | 代码如下: 18 | 19 | ```c++ 20 | class Solution { 21 | public: 22 | int candy(vector &ratings) { 23 | vector candys; 24 | //首先每人发一颗糖 25 | candys.resize(ratings.size(), 1); 26 | //这个循环保证了右边高级别的孩子一定比左边的孩子糖果数量多 27 | for(int i = 1; i < (int)ratings.size(); i++) { 28 | if(ratings[i] > ratings[i - 1]) { 29 | candys[i] = candys[i - 1] + 1; 30 | } 31 | } 32 | 33 | //这个循环保证了左边高级别的孩子一定比右边的孩子糖果数量多 34 | for(int i = (int)ratings.size() - 2; i >= 0; i--) { 35 | if(ratings[i] > ratings[i + 1] && candys[i] <= candys[i + 1]) { 36 | candys[i] = candys[i + 1] + 1; 37 | } 38 | } 39 | 40 | int n = 0; 41 | for(int i = 0; i < (int)candys.size(); i++) { 42 | n += candys[i]; 43 | } 44 | return n; 45 | } 46 | }; 47 | ``` 48 | -------------------------------------------------------------------------------- /greedy/gas_station.md: -------------------------------------------------------------------------------- 1 | # Gas Station 2 | 3 | > There are N gas stations along a circular route, where the amount of gas at station i is gas[i]. 4 | 5 | > You have a car with an unlimited gas tank and it costs cost[i] of gas to travel from station i to its next station (i+1). You begin the journey with an empty tank at one of the gas stations. 6 | 7 | > Return the starting gas station's index if you can travel around the circuit once, otherwise return -1. 8 | 9 | > Note: 10 | > The solution is guaranteed to be unique. 11 | 12 | 这题的意思就是求出从哪一个油站开始,能走完整个里程,并且这个结果是唯一的。 13 | 14 | 首先我们可以得到所有油站的油量totalGas,以及总里程需要消耗的油量totalCost,如果totalCost大于totalGas,那么铁定不能够走完整个里程。 15 | 16 | 如果totalGas大于totalCost了,那么就能走完整个里程了,假设现在我们到达了第i个油站,这时候还剩余的油量为sum,如果 sum + gas[i] - cost[i]小于0,我们无法走到下一个油站,所以起点一定不在第i个以及之前的油站里面(都铁定走不到第i + 1号油站),起点只能在i + 1后者后面。 17 | 18 | 代码如下: 19 | 20 | ```c++ 21 | class Solution { 22 | public: 23 | int canCompleteCircuit(vector &gas, vector &cost) { 24 | int sum = 0; 25 | int total = 0; 26 | int k = 0; 27 | for(int i = 0; i < (int)gas.size(); i++) { 28 | sum += gas[i] - cost[i]; 29 | //小于0就只可能在i + 1或者之后了 30 | if(sum < 0) { 31 | k = i + 1; 32 | sum = 0; 33 | } 34 | total += gas[i] - cost[i]; 35 | } 36 | 37 | if(total < 0) { 38 | return -1; 39 | } else { 40 | return k; 41 | } 42 | } 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /greedy/jump_game.md: -------------------------------------------------------------------------------- 1 | # Jump Game 2 | 3 | > Given an array of non-negative integers, you are initially positioned at the first index of the array. 4 | 5 | > Each element in the array represents your maximum jump length at that position. 6 | 7 | > Determine if you are able to reach the last index. 8 | 9 | > For example: 10 | > A = [2,3,1,1,4], return true. 11 | 12 | > A = [3,2,1,0,4], return false. 13 | 14 | 这题比较简单,给你一个数组,里面每个元素表示你可以向后跳跃的步数,我们需要知道能不能移动到最后一个元素位置。 15 | 16 | 采用贪心法即可,譬如上面的[2, 3, 1, 1, 4],因为初始第一个位置为2,我们先跳1步,剩下1步了,到第二个元素位置,也就是3这个地方,因为3比1大,所以我们可以向后面跳跃3步,直接就到4了。 17 | 18 | 根据上面的规则,每次跳跃1步,我们可跳跃步数减1,如果新的位置步数大于剩余步数,使用新的步数继续移动,如果可跳跃次数小于0并且还没到最后一个元素,那么失败。 19 | 20 | 代码如下: 21 | 22 | ```c++ 23 | class Solution { 24 | public: 25 | bool canJump(int A[], int n) { 26 | if(n == 0) { 27 | return true; 28 | } 29 | 30 | int v = A[0]; 31 | 32 | for(int i = 1; i < n; i++) { 33 | v--; 34 | if(v < 0) { 35 | return false; 36 | } 37 | 38 | if(v < A[i]) { 39 | v = A[i]; 40 | } 41 | } 42 | return true; 43 | } 44 | }; 45 | ``` 46 | 47 | # Jump Game II 48 | 49 | > Given an array of non-negative integers, you are initially positioned at the first index of the array. 50 | 51 | > Each element in the array represents your maximum jump length at that position. 52 | 53 | > Your goal is to reach the last index in the minimum number of jumps. 54 | 55 | > For example: 56 | > Given array A = [2,3,1,1,4] 57 | 58 | > The minimum number of jumps to reach the last index is 2. (Jump 1 step from index 0 to 1, then 3 steps to the last index.) 59 | 60 | 这题不同于上一题,只要求我们得到最少的跳跃次数,所以铁定能走到终点的,我们仍然使用贪心法, 61 | 62 | 我们维护两个变量,当前能达到的最远点p以及下一次能达到的最远点q,在p的范围内迭代计算q,然后更新步数,并将最大的q设置为p。重复这个过程知道p达到终点。 63 | 64 | 代码如下: 65 | 66 | ```c++ 67 | class Solution { 68 | public: 69 | int jump(int A[], int n) { 70 | int step = 0; 71 | int cur = 0; 72 | int next = 0; 73 | 74 | int i = 0; 75 | while(i < n){ 76 | if(cur >= n - 1) { 77 | break; 78 | } 79 | 80 | while(i <= cur) { 81 | //更新最远达到点 82 | next = max(next, A[i] + i); 83 | i++; 84 | } 85 | step++; 86 | cur = next; 87 | } 88 | 89 | return step; 90 | } 91 | }; 92 | ``` 93 | 94 | -------------------------------------------------------------------------------- /greedy/word_break.md: -------------------------------------------------------------------------------- 1 | # Word Break 2 | 3 | > Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words. 4 | 5 | > For example, given 6 | s = "leetcode", 7 | dict = ["leet", "code"]. 8 | 9 | > Return true because "leetcode" can be segmented as "leet code". 10 | 11 | 这题的意思是给出一本词典以及一个字符串,能否切分这个字符串使得每个字串都在字典里面存在。 12 | 13 | 假设dp(i)表示长度为i的字串能否别切分,dp方程如下: 14 | 15 | `dp(i) = dp(j) && s[j, i) in dict, 0 <= j < i` 16 | 17 | 代码如下 18 | 19 | ```c++ 20 | class Solution { 21 | public: 22 | bool wordBreak(string s, unordered_set &dict) { 23 | int len = (int)s.size(); 24 | vector dp(len + 1, false); 25 | dp[0] = true; 26 | 27 | for(int i = 1; i <= len; i++) { 28 | for(int j = i - 1; j >= 0; j--) { 29 | if(dp[j] && dict.find(s.substr(j, i - j)) != dict.end()) { 30 | dp[i] = true; 31 | break; 32 | } 33 | } 34 | } 35 | return dp[len]; 36 | } 37 | }; 38 | ``` 39 | 40 | # World Break II 41 | 42 | > Given a string s and a dictionary of words dict, add spaces in s to construct a sentence where each word is a valid dictionary word. 43 | 44 | > Return all such possible sentences. 45 | 46 | > For example, given 47 | s = "catsanddog", 48 | dict = ["cat", "cats", "and", "sand", "dog"]. 49 | 50 | > A solution is ["cats and dog", "cat sand dog"]. 51 | 52 | 这道题不同于上一题,需要我们得到所有能切分的解。这道题难度很大,我们需要采用dp + dfs的方式求解,首先根据dp得到该字符串能否被切分,同时在dp的过程中记录属于字典的子串信息,供后续dfs使用。 53 | 54 | 首先我们使用dp[i][j]表示起始索引为i,长度为j的子串能否被切分,它有三种状态: 55 | 56 | 1. dp[i][j] = true && dp[i][j] in dict,这种情况是这个子串直接在字典中 57 | 2. dp[i][j] = true && dp[i][j] not in dict,这种情况是这个子串不在字典中,但是它能切分成更小的子串,而这些子串在字典中 58 | 3. dp[i][j] = false,子串不能被切分 59 | 60 | 根据题意,我们需要求出所有切分的解,所以在进行dp的时候需要处理1和2这两种情况,因为对于2来说,dp[i][j]是要继续被切分的,也就是说我们只需要关注第1种情况的子串。 61 | 62 | 当dp完成之后,我们就需要使用dfs来得到整个的解。在dp[i][j] = 1的情况下面,我们只需要dfs递归处理后面i + j开始的子串就可以了。 63 | 64 | 代码如下: 65 | 66 | ```c++ 67 | 68 | class Solution { 69 | public: 70 | vector >dp; 71 | vector vals; 72 | string val; 73 | 74 | vector wordBreak(string s, unordered_set &dict) { 75 | int len = (int)s.size(); 76 | dp.resize(len); 77 | for(int i = 0; i < len; i++) { 78 | dp[i].resize(len + 1, 0); 79 | } 80 | 81 | for(int i = 1; i <= len; i++) { 82 | for(int j = 0; j < len -i + 1; j++) { 83 | //直接存在于字典中,是第1种情况 84 | if(dict.find(s.substr(j, i)) != dict.end()) { 85 | dp[j][i] = 1; 86 | continue; 87 | } 88 | //如果不存在,则看子串是不是能被切分,这是第2中情况 89 | for(int k = 1; k < i && k < len -j; k++) { 90 | if(dp[j][k] && dp[j + k][i - k]) { 91 | dp[j][i] = 2; 92 | break; 93 | } 94 | } 95 | } 96 | } 97 | 98 | //不能切分,不用dfs了 99 | if(dp[0][len] == 0) { 100 | return vals; 101 | } 102 | 103 | dfs(s, 0); 104 | return vals; 105 | } 106 | 107 | void dfs(const string& s, int start) { 108 | int len = (int)s.size(); 109 | if(start == len) { 110 | vals.push_back(val); 111 | return; 112 | } 113 | 114 | for(int i = 1; i <= len - start;i++) { 115 | if(dp[start][i] == 1) { 116 | int oldLen = (int)val.size(); 117 | if(oldLen != 0) { 118 | val.append(" "); 119 | } 120 | val.append(s.substr(start, i)); 121 | 122 | //我们从start + i开始继续dfs 123 | dfs(s, start + i); 124 | val.erase(oldLen, string::npos); 125 | } 126 | } 127 | } 128 | }; 129 | ``` -------------------------------------------------------------------------------- /linked_list/README.md: -------------------------------------------------------------------------------- 1 | # Linked List 2 | 3 | 链表是重要的线性数据结构,链表的插入和删除操作具有O(1)的时间复杂度。但是链表不具有随机访问的能力,这一点给链表类问题带来了不少麻烦。另外,单向链表无法直接访问前驱节点,这也是链表的一大难点。 4 | 解决链表类问题首先需要熟悉链表的基本操作,包括创建、插入、删除、查找等。在此基础上实现链表的逆序,合并等操作。 5 | 6 | ### 双指针方法 7 | 链表问题中的一个重要的方法叫双指针法。定义两个指针,一个叫慢指针,另一个叫快指针。通常慢指针每次向前移动一个节点,而快指针每次向前移动若干个节点。这个方法通常用于寻找链表中特定的位置。比如找到链表的中点,可以让快指针每次移动两个节点。这样当快指针到达链表末尾时,慢指针刚好在链表中间的位置。 8 | -------------------------------------------------------------------------------- /linked_list/add_two_numbers.md: -------------------------------------------------------------------------------- 1 | # Add Two Numbers 2 | 3 | > You are given two linked lists representing two non-negative numbers. The digits are stored in reverse order and each of their nodes contain a single digit. Add the two numbers and return it as a linked list. 4 | 5 | > Input: (2 -> 4 -> 3) + (5 -> 6 -> 4) 6 | 7 | > Output: 7 -> 0 -> 8 8 | 9 | 两个链表相加的问题,需要处理好进位就成了,比较简单,直接上代码: 10 | 11 | ```c++ 12 | class Solution { 13 | public: 14 | ListNode *addTwoNumbers(ListNode *l1, ListNode *l2) { 15 | ListNode dummy(0); 16 | ListNode* p = &dummy; 17 | 18 | int cn = 0; 19 | 20 | while(l1 || l2) { 21 | int val = cn + (l1 ? l1->val : 0) + (l2 ? l2->val : 0); 22 | cn = val / 10; 23 | val = val % 10; 24 | p->next = new ListNode(val); 25 | p = p->next; 26 | if(l1) { 27 | l1 = l1->next; 28 | } 29 | if(l2) { 30 | l2 = l2->next; 31 | } 32 | } 33 | 34 | if(cn != 0) { 35 | p->next = new ListNode(cn); 36 | p = p->next; 37 | } 38 | 39 | return dummy.next; 40 | } 41 | }; 42 | ``` 43 | -------------------------------------------------------------------------------- /linked_list/copy_list_with_random_pointer.md: -------------------------------------------------------------------------------- 1 | # Copy List with Random Pointer 2 | 3 | > A linked list is given such that each node contains an additional random pointer which could point to any node in the list or null. 4 | 5 | > Return a deep copy of the list. 6 | 7 | 这题要求深拷贝一个带有random指针的链表random可能指向空,也可能指向链表中的任意一个节点。 8 | 9 | 对于通常的链表,我们递归依次拷贝就可以了,同时用一个hash表记录新旧节点的映射关系用以处理random问题。 10 | 11 | 代码如下: 12 | 13 | ```c++ 14 | class Solution { 15 | public: 16 | RandomListNode *copyRandomList(RandomListNode *head) { 17 | if(head == NULL) { 18 | return NULL; 19 | } 20 | 21 | RandomListNode dummy(0); 22 | RandomListNode* n = &dummy; 23 | RandomListNode* h = head; 24 | map m; 25 | while(h) { 26 | RandomListNode* node = new RandomListNode(h->label); 27 | n->next = node; 28 | n = node; 29 | 30 | node->random = h->random; 31 | 32 | m[h] = node; 33 | 34 | h = h->next; 35 | } 36 | 37 | h = dummy.next; 38 | while(h) { 39 | if(h->random != NULL) { 40 | h->random = m[h->random]; 41 | } 42 | h = h->next; 43 | } 44 | 45 | return dummy.next; 46 | } 47 | }; 48 | ``` 49 | 50 | 但这题其实还有更巧妙的作法。假设有如下链表: 51 | 52 | ``` 53 | |------------| 54 | | v 55 | 1 --> 2 --> 3 --> 4 56 | 57 | ``` 58 | 59 | 节点1的random指向了3。首先我们可以通过next遍历链表,依次拷贝节点,并将其添加到原节点后面,如下: 60 | 61 | ``` 62 | |--------------------------| 63 | | v 64 | 1 --> 1' --> 2 --> 2' --> 3 --> 3' --> 4 --> 4' 65 | | ^ 66 | |-------------------| 67 | ``` 68 | 69 | 因为我们只是简单的复制了random指针,所以新的节点的random指向的仍然是老的节点,譬如上面的1和1'都是指向的3。 70 | 71 | 调整新的节点的random指针,对于上面例子来说,我们需要将1'的random指向3',其实也就是原先random指针的next节点。 72 | 73 | ``` 74 | |--------------------------| 75 | | v 76 | 1 --> 1' --> 2 --> 2' --> 3 --> 3' --> 4 --> 4' 77 | | ^ 78 | |-------------------------| 79 | ``` 80 | 81 | 最后,拆分链表,就可以得到深拷贝的链表了。 82 | 83 | 代码如下: 84 | 85 | ```c++ 86 | class Solution { 87 | public: 88 | RandomListNode *copyRandomList(RandomListNode *head) { 89 | if(head == NULL) { 90 | return NULL; 91 | } 92 | 93 | //遍历并插入新的节点 94 | RandomListNode* n = NULL; 95 | RandomListNode* h = head; 96 | while(h) { 97 | RandomListNode* node = new RandomListNode(h->label); 98 | node->random = h->random; 99 | 100 | n = h->next; 101 | 102 | h->next = node; 103 | node->next = n; 104 | h = n; 105 | } 106 | 107 | //调整random 108 | h = head->next; 109 | while(h) { 110 | if(h->random != NULL) { 111 | h->random = h->random->next; 112 | } 113 | if(!h->next) { 114 | break; 115 | } 116 | h = h->next->next; 117 | } 118 | 119 | //断开链表 120 | h = head; 121 | RandomListNode dummy(0); 122 | RandomListNode* p = &dummy; 123 | while(h) { 124 | n = h->next; 125 | p->next = n; 126 | p = n; 127 | RandomListNode* nn = n->next; 128 | h->next = n->next; 129 | h = n->next; 130 | } 131 | 132 | return dummy.next; 133 | } 134 | }; 135 | ``` 136 | -------------------------------------------------------------------------------- /linked_list/linked_list_cycle.md: -------------------------------------------------------------------------------- 1 | # Linked List Cycle 2 | 3 | > Given a linked list, determine if it has a cycle in it. 4 | 5 | > Follow up: 6 | > Can you solve it without using extra space? 7 | 8 | 这道题就是判断一个链表是否存在环,非常简单的一道题目,我们使用两个指针,一个每次走两步,一个每次走一步,如果一段时间之后这两个指针能重合,那么铁定存在环了。 9 | 10 | 代码如下: 11 | 12 | ```c++ 13 | class Solution { 14 | public: 15 | bool hasCycle(ListNode *head) { 16 | if(head == NULL || head->next == NULL) { 17 | return false; 18 | } 19 | 20 | ListNode* fast = head; 21 | ListNode* slow = head; 22 | 23 | while(fast->next != NULL && fast->next->next != NULL) { 24 | fast = fast->next->next; 25 | slow = slow->next; 26 | if(slow == fast) { 27 | return true; 28 | } 29 | } 30 | 31 | return false; 32 | } 33 | }; 34 | ``` 35 | 36 | # Linked List Cycle II 37 | 38 | > Given a linked list, return the node where the cycle begins. If there is no cycle, return null. 39 | 40 | > Follow up: 41 | > Can you solve it without using extra space? 42 | 43 | 紧跟着第一题,这题不光要求出是否有环,而且还需要得到这个环开始的节点。譬如下面这个,起点就是n2。 44 | 45 | ``` 46 | n6-----------n5 47 | | | 48 | n1--- n2---n3--- n4| 49 | 50 | ``` 51 | 52 | 我们仍然可以使用两个指针fast和slow,fast走两步,slow走一步,判断是否有环,当有环重合之后,譬如上面在n5重合了,那么如何得到n2呢? 53 | 54 | 首先我们知道,fast每次比slow多走一步,所以重合的时候,fast移动的距离是slot的两倍,我们假设n1到n2距离为a,n2到n5距离为b,n5到n2距离为c,fast走动距离为`a + b + c + b`,而slow为`a + b`,有方程`a + b + c + b = 2 x (a + b)`,可以知道`a = c`,所以我们只需要在重合之后,一个指针从n1,而另一个指针从n5,都每次走一步,那么就可以在n2重合了。 55 | 56 | 代码如下: 57 | 58 | ```c++ 59 | class Solution { 60 | public: 61 | ListNode *detectCycle(ListNode *head) { 62 | if(head == NULL || head->next == NULL) { 63 | return NULL; 64 | } 65 | 66 | ListNode* fast = head; 67 | ListNode* slow = head; 68 | 69 | while(fast->next != NULL && fast->next->next != NULL) { 70 | fast = fast->next->next; 71 | slow = slow->next; 72 | if(fast == slow) { 73 | slow = head; 74 | while(slow != fast) { 75 | fast = fast->next; 76 | slow = slow->next; 77 | } 78 | return slow; 79 | } 80 | } 81 | 82 | return NULL; 83 | } 84 | }; 85 | ``` 86 | 87 | # Intersection of Two Linked Lists 88 | 89 | > Write a program to find the node at which the intersection of two singly linked lists begins. 90 | 91 | 92 | > For example, the following two linked lists: 93 | 94 | >``` 95 | A: a1 → a2 96 | ↘ 97 | c1 → c2 → c3 98 | ↗ 99 | B: b1 → b2 → b3 100 | ``` 101 | begin to intersect at node c1. 102 | 103 | 104 | > Notes: 105 | 106 | > + If the two linked lists have no intersection at all, return null. 107 | > + The linked lists must retain their original structure after the function returns. 108 | > + You may assume there are no cycles anywhere in the entire linked structure. 109 | > + Your code should preferably run in O(n) time and use only O(1) memory. 110 | 111 | 这题需要得到两个链表的交接点,也就是c1,这一题也很简单。 112 | 113 | + 遍历A,到结尾之后,将A最后的节点连接到B的开头,也就是`c3 -> b1` 114 | + 使用两个指针fast和slow,从a1开始,判断是否有环 115 | + 如果没环,在返回之前记得将`c3 -> b1`给断开 116 | + 如果有环,则按照第二题的解法得到c1,然后断开`c3 -> b1` 117 | 118 | 代码如下: 119 | 120 | ```c++ 121 | class Solution { 122 | public: 123 | ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { 124 | if(!headA) { 125 | return NULL; 126 | } else if (!headB) { 127 | return NULL; 128 | } 129 | 130 | ListNode* p = headA; 131 | while(p->next) { 132 | p = p->next; 133 | } 134 | 135 | //将两个链表串起来 136 | p->next = headB; 137 | 138 | ListNode* tail = p; 139 | p = headA; 140 | 141 | //fast和slow,判断是否有环 142 | headB = headA; 143 | while(headB->next && headB->next->next) { 144 | headA = headA->next; 145 | headB = headB->next->next; 146 | if(headA == headB) { 147 | break; 148 | } 149 | } 150 | 151 | if(!headA->next || !headB->next || !headB->next->next) { 152 | //没环,断开两个链表 153 | tail->next = NULL; 154 | return NULL; 155 | } 156 | 157 | //有环,得到环的起点 158 | headA = p; 159 | while(headA != headB) { 160 | headA = headA->next; 161 | headB = headB->next; 162 | } 163 | 164 | //断开两个链表 165 | tail->next = NULL; 166 | return headA; 167 | } 168 | }; 169 | ``` 170 | -------------------------------------------------------------------------------- /linked_list/merge_sorted_lists.md: -------------------------------------------------------------------------------- 1 | # Merge Two Sorted Lists 2 | 3 | > Merge two sorted linked lists and return it as a new list. The new list should be made by splicing together the nodes of the first two lists. 4 | 5 | 这题要求合并两个已经排好序的链表,很简单的题目,直接上代码: 6 | 7 | ```c++ 8 | class Solution { 9 | public: 10 | ListNode *mergeTwoLists(ListNode *l1, ListNode *l2) { 11 | ListNode dummy(0); 12 | ListNode* p = &dummy; 13 | 14 | while(l1 && l2) { 15 | int val1 = l1->val; 16 | int val2 = l2->val; 17 | //哪个节点小,就挂载,同时移动到下一个节点 18 | if(val1 < val2) { 19 | p->next = l1; 20 | p = l1; 21 | l1 = l1->next; 22 | } else { 23 | p->next = l2; 24 | p = l2; 25 | l2 = l2->next; 26 | } 27 | } 28 | 29 | //这里处理还未挂载的节点 30 | if(l1) { 31 | p->next = l1; 32 | } else if(l2) { 33 | p->next = l2; 34 | } 35 | 36 | return dummy.next; 37 | } 38 | }; 39 | ``` 40 | 41 | # Merge k Sorted Lists 42 | 43 | > Merge k sorted linked lists and return it as one sorted list. Analyze and describe its complexity. 44 | 45 | 这题需要合并k个排好序的链表,我们采用`divide and conquer`的方法,首先两两合并,然后再将先前合并的继续两两合并。时间复杂度为O(NlgN)。 46 | 47 | 代码如下: 48 | 49 | ```c++ 50 | class Solution { 51 | public: 52 | ListNode *mergeKLists(vector &lists) { 53 | if(lists.empty()) { 54 | return NULL; 55 | } 56 | 57 | int n = lists.size(); 58 | while(n > 1) { 59 | int k = (n + 1) / 2; 60 | for(int i = 0; i < n / 2; i++) { 61 | //合并i和i + k的链表,并放到i位置 62 | lists[i] = merge2List(lists[i], lists[i + k]); 63 | } 64 | //下个循环只需要处理前k个链表了 65 | n = k; 66 | } 67 | return lists[0]; 68 | } 69 | 70 | ListNode* merge2List(ListNode* n1, ListNode* n2) { 71 | ListNode dummy(0); 72 | ListNode* p = &dummy; 73 | while(n1 && n2) { 74 | if(n1->val < n2->val) { 75 | p->next = n1; 76 | n1 = n1->next; 77 | } else { 78 | p->next = n2; 79 | n2 = n2->next; 80 | } 81 | p = p->next; 82 | } 83 | 84 | if(n1) { 85 | p->next = n1; 86 | } else if(n2) { 87 | p->next = n2; 88 | } 89 | 90 | return dummy.next; 91 | } 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /linked_list/partition_list.md: -------------------------------------------------------------------------------- 1 | # Partition List 2 | 3 | > Given a linked list and a value x, partition it such that all nodes less than x come before nodes greater than or equal to x. 4 | 5 | > You should preserve the original relative order of the nodes in each of the two partitions. 6 | 7 | > For example, 8 | 9 | > Given 1->4->3->2->5->2 and x = 3, 10 | 11 | > return 1->2->2->4->3->5. 12 | 13 | 这题要求我们対链表进行切分,使得左半部分所有节点的值小于x,而右半部分大于等于x。 14 | 15 | 我们可以使用两个链表,p1和p2,以此遍历原链表,如果节点的值小于x,就挂载到p1下面,反之则放到p2下面,最后将p2挂载到p1下面就成了。 16 | 17 | 代码如下: 18 | 19 | ```c++ 20 | class Solution { 21 | public: 22 | ListNode *partition(ListNode *head, int x) { 23 | ListNode dummy1(0), dummy2(0); 24 | ListNode* p1 = &dummy1; 25 | ListNode* p2 = &dummy2; 26 | 27 | ListNode* p = head; 28 | while(p) { 29 | if(p->val < x) { 30 | p1->next = p; 31 | p1 = p1->next; 32 | } else { 33 | p2->next = p; 34 | p2 = p2->next; 35 | } 36 | p = p->next; 37 | } 38 | 39 | p2->next = NULL; 40 | p1->next = dummy2.next; 41 | return dummy1.next; 42 | } 43 | }; 44 | ``` 45 | -------------------------------------------------------------------------------- /linked_list/remove_duplicates_from_sorted_list.md: -------------------------------------------------------------------------------- 1 | # Remove Duplicates from Sorted List 2 | 3 | > Given a sorted linked list, delete all duplicates such that each element appear only once. 4 | 5 | > For example, 6 | 7 | > Given 1->1->2, return 1->2. 8 | 9 | > Given 1->1->2->3->3, return 1->2->3. 10 | 11 | 这题要求在一个有序的链表里面删除重复的元素,只保留一个,也是比较简单的一个题目,我们只需要判断当前指针以及下一个指针时候重复,如果是,则删除下一个指针就可以了。 12 | 13 | 代码如下: 14 | 15 | ```c++ 16 | class Solution { 17 | public: 18 | ListNode *deleteDuplicates(ListNode *head) { 19 | if(!head) { 20 | return head; 21 | } 22 | 23 | int val = head->val; 24 | ListNode* p = head; 25 | while(p && p->next) { 26 | if(p->next->val != val) { 27 | val = p->next->val; 28 | p = p->next; 29 | } else { 30 | //删除next 31 | ListNode* n = p->next->next; 32 | p->next = n; 33 | } 34 | } 35 | 36 | return head; 37 | } 38 | }; 39 | ``` 40 | 41 | # Remove Duplicates from Sorted List II 42 | 43 | > Given a sorted linked list, delete all nodes that have duplicate numbers, leaving only distinct numbers from the original list. 44 | 45 | > For example, 46 | 47 | > Given 1->2->3->3->4->4->5, return 1->2->5. 48 | 49 | > Given 1->1->1->2->3, return 2->3. 50 | 51 | 这题需要在一个有序的链表里面删除所有的重复元素的节点。因为不同于上题可以保留一个,这次需要全部删除,所以我们遍历的时候需要记录一个prev节点,用来处理删除节点之后节点重新连接的问题。 52 | 53 | 代码如下: 54 | 55 | ```c++ 56 | class Solution { 57 | public: 58 | ListNode *deleteDuplicates(ListNode *head) { 59 | if(!head) { 60 | return head; 61 | } 62 | 63 | //用一个dummy节点来当做head的prev 64 | ListNode dummy(0); 65 | dummy.next = head; 66 | ListNode* prev = &dummy; 67 | ListNode* p = head; 68 | while(p && p->next) { 69 | //如果没有重复,则prev为p,next为p->next 70 | if(p->val != p->next->val) { 71 | prev = p; 72 | p = p->next; 73 | } else { 74 | //如果有重复,则继续遍历,直到不重复的节点 75 | int val = p->val; 76 | ListNode* n = p->next->next; 77 | while(n) { 78 | if(n->val != val) { 79 | break; 80 | } 81 | n = n->next; 82 | } 83 | 84 | //删除重复节点 85 | prev->next = n; 86 | p = n; 87 | } 88 | } 89 | return dummy.next; 90 | } 91 | }; 92 | ``` 93 | -------------------------------------------------------------------------------- /linked_list/reorder_list.md: -------------------------------------------------------------------------------- 1 | # Reorder List 2 | 3 | > Given a singly linked list L: L0→L1→…→Ln-1→Ln, 4 | 5 | > reorder it to: L0→Ln→L1→Ln-1→L2→Ln-2→… 6 | 7 | > You must do this in-place without altering the nodes' values. 8 | 9 | > For example, 10 | 11 | > Given {1,2,3,4}, reorder it to {1,4,2,3}. 12 | 13 | 这题比较简单,其实就是将链表的左右两边合并,只是合并的时候右半部分需要翻转一下。 14 | 15 | 主要有三步: 16 | 17 | + 快慢指针找到切分链表 18 | + 翻转右半部分 19 | + 依次合并 20 | 21 | 代码如下: 22 | 23 | ```c++ 24 | class Solution { 25 | public: 26 | void reorderList(ListNode *head) { 27 | if(head == NULL || head->next == NULL) { 28 | return; 29 | } 30 | 31 | ListNode* fast = head; 32 | ListNode* slow = head; 33 | 34 | 35 | //快慢指针切分链表 36 | while(fast->next != NULL && fast->next->next != NULL){ 37 | fast = fast->next->next; 38 | slow = slow->next; 39 | } 40 | 41 | fast = slow->next; 42 | slow->next = NULL; 43 | 44 | //翻转右半部分 45 | ListNode dummy(0); 46 | while(fast) { 47 | ListNode* n = dummy.next; 48 | dummy.next = fast; 49 | ListNode* nn = fast->next; 50 | fast->next = n; 51 | fast = nn; 52 | } 53 | 54 | slow = head; 55 | fast = dummy.next; 56 | 57 | //依次合并 58 | while(slow) { 59 | if(fast != NULL) { 60 | ListNode* n = slow->next; 61 | slow->next = fast; 62 | ListNode* nn = fast->next; 63 | fast->next = n; 64 | fast = nn; 65 | slow = n; 66 | } else { 67 | break; 68 | } 69 | } 70 | } 71 | }; 72 | ``` 73 | -------------------------------------------------------------------------------- /linked_list/reverse_linked_list.md: -------------------------------------------------------------------------------- 1 | # Reverse Linked List II 2 | 3 | > Reverse a linked list from position m to n. Do it in-place and in one-pass. 4 | 5 | > For example: 6 | > Given 1->2->3->4->5->NULL, m = 2 and n = 4, 7 | 8 | > return 1->4->3->2->5->NULL. 9 | 10 | > Note: 11 | Given m, n satisfy the following condition: 12 | 1 ≤ m ≤ n ≤ length of list. 13 | 14 | 这题要求我们翻转[m, n]区间之间的链表。对于链表翻转来说,几乎都是通用的做法,譬如`p1 -> p2 -> p3 -> p4`,如果我们要翻转p2和p3,其实就是将p3挂载到p1的后面,所以我们需要知道p2的前驱节点p1。伪代码如下: 15 | 16 | ```c++ 17 | //保存p3 18 | n = p2->next; 19 | //将p3的next挂载到p2后面 20 | p2->next = p3->next; 21 | //将p3挂载到p1的后面 22 | p1->next = p3; 23 | //将p2挂载到p3得后面 24 | p3->next = p2; 25 | ``` 26 | 27 | 对于上题,我们首先遍历得到第m - 1个node,也就是pm的前驱节点。然后依次遍历,处理挂载问题就可以了。 28 | 29 | 代码如下: 30 | 31 | ```c++ 32 | class Solution { 33 | public: 34 | ListNode *reverseBetween(ListNode *head, int m, int n) { 35 | if(!head) { 36 | return head; 37 | } 38 | 39 | ListNode dummy(0); 40 | dummy.next = head; 41 | 42 | ListNode* p = &dummy; 43 | for(int i = 1; i < m; i++) { 44 | p = p->next; 45 | } 46 | 47 | //p此时就是pm的前驱节点 48 | ListNode* pm = p->next; 49 | 50 | for(int i = m; i < n; i++) { 51 | ListNode* n = pm->next; 52 | pm->next = n->next; 53 | n->next = p->next; 54 | p->next = n; 55 | } 56 | 57 | return dummy.next; 58 | } 59 | }; 60 | ``` 61 | 62 | # Reverse Nodes in k-Group 63 | 64 | > Given a linked list, reverse the nodes of a linked list k at a time and return its modified list. 65 | 66 | > If the number of nodes is not a multiple of k then left-out nodes in the end should remain as it is. 67 | 68 | > You may not alter the values in the nodes, only nodes itself may be changed. 69 | 70 | > Only constant memory is allowed. 71 | 72 | > For example, 73 | > Given this linked list: 1->2->3->4->5 74 | 75 | > For k = 2, you should return: 2->1->4->3->5 76 | 77 | > For k = 3, you should return: 3->2->1->4->5 78 | 79 | 这题要求我们按照每k个节点对其进行翻转,理解了链表如何翻转之后很容易处理,唯一需要注意的就是每次k个翻转之后,一定要知道最后一个节点,因为这个节点就是下组的前驱节点了。 80 | 81 | ```c++ 82 | ListNode *reverseKGroup(ListNode *head, int k) { 83 | if(k <= 1 || !head) { 84 | return head; 85 | } 86 | 87 | ListNode dummy(0); 88 | dummy.next = head; 89 | ListNode* p = &dummy; 90 | ListNode* prev = &dummy; 91 | 92 | while(p) { 93 | prev = p; 94 | for(int i = 0; i < k; i++){ 95 | p = p->next; 96 | if(!p) { 97 | //到这里已经不够k个没法翻转了 98 | return dummy.next; 99 | } 100 | } 101 | 102 | p = reverse(prev, p->next); 103 | } 104 | 105 | return dummy.next; 106 | } 107 | 108 | ListNode* reverse(ListNode* prev, ListNode* end) { 109 | ListNode* p = prev->next; 110 | 111 | while(p->next != end) { 112 | ListNode* n = p->next; 113 | p->next = n->next; 114 | n->next = prev->next; 115 | prev->next = n; 116 | } 117 | 118 | //这里我们会返回最后一个节点,作为下一组的前驱节点 119 | return p; 120 | } 121 | ``` 122 | -------------------------------------------------------------------------------- /linked_list/rotate_list.md: -------------------------------------------------------------------------------- 1 | # Rotate List 2 | 3 | > Given a list, rotate the list to the right by k places, where k is non-negative. 4 | 5 | > For example: 6 | 7 | > Given 1->2->3->4->5->NULL and k = 2, 8 | 9 | > return 4->5->1->2->3->NULL. 10 | 11 | 这题要求把链表后面k个节点轮转到链表前面。 12 | 13 | 对于这题我们首先需要遍历链表,得到链表长度n,因为k可能大于n,所以我们需要取余处理,然后将链表串起来形成一个换,在遍历`n - k % n`个节点,断开,就成了。譬如上面这个例子,k等于2,我们遍历到链表结尾之后,连接1,然后遍历 `5 - 2 % 5`个字节,断开环,下一个节点就是新的链表头了。 14 | 15 | 代码如下: 16 | 17 | ```c++ 18 | class Solution { 19 | public: 20 | ListNode *rotateRight(ListNode *head, int k) { 21 | if(!head || k == 0) { 22 | return head; 23 | } 24 | 25 | int n = 1; 26 | ListNode* p = head; 27 | //得到链表长度 28 | while(p->next) { 29 | p = p->next; 30 | n++; 31 | } 32 | 33 | k = n - k % n; 34 | 35 | //连接成环 36 | p->next = head; 37 | 38 | for(int i = 0; i < k; i++) { 39 | p = p->next; 40 | } 41 | 42 | //得到新的链表头并断开环 43 | head = p->next; 44 | p->next = NULL; 45 | return head; 46 | } 47 | }; 48 | ``` 49 | -------------------------------------------------------------------------------- /linked_list/sort_list.md: -------------------------------------------------------------------------------- 1 | # Sort List 2 | 3 | > Sort a linked list in O(n log n) time using constant space complexity. 4 | 5 | 这题要求我们对链表进行排序,我们可以使用divide and conquer的方式,依次递归的对链表左右两半进行排序就可以了。代码如下: 6 | 7 | ```c++ 8 | class Solution { 9 | public: 10 | ListNode *sortList(ListNode *head) { 11 | if(head == NULL || head->next == NULL) { 12 | return head; 13 | } 14 | 15 | ListNode* fast = head; 16 | ListNode* slow = head; 17 | 18 | //快慢指针得到中间点 19 | while(fast->next && fast->next->next) { 20 | fast = fast->next->next; 21 | slow = slow->next; 22 | } 23 | 24 | //将链表拆成两半 25 | fast = slow->next; 26 | slow->next = NULL; 27 | 28 | //左右两半分别排序 29 | ListNode* p1 = sortList(head); 30 | ListNode* p2 = sortList(fast); 31 | 32 | //合并 33 | return merge(p1, p2); 34 | } 35 | 36 | ListNode *merge(ListNode* l1, ListNode* l2) { 37 | if(!l1) { 38 | return l2; 39 | } else if (!l2) { 40 | return l1; 41 | } else if (!l1 && !l2) { 42 | return NULL; 43 | } 44 | 45 | ListNode dummy(0); 46 | ListNode* p = &dummy; 47 | 48 | while(l1 && l2) { 49 | if(l1->val < l2->val) { 50 | p->next = l1; 51 | l1 = l1->next; 52 | } else { 53 | p->next = l2; 54 | l2 = l2->next; 55 | } 56 | 57 | p = p->next; 58 | } 59 | 60 | if(l1) { 61 | p->next = l1; 62 | } else if(l2){ 63 | p->next = l2; 64 | } 65 | 66 | return dummy.next; 67 | } 68 | }; 69 | ``` 70 | 71 | # Insertion Sort List 72 | 73 | > Sort a linked list using insertion sort. 74 | 75 | 这题要求我们使用插入排序的方式对链表进行排序,假设一个链表前n个节点是有序,第n + 1的节点需要遍历前n个,插入到合适位置就可以了。 76 | 77 | 代码如下: 78 | 79 | ```c++ 80 | class Solution { 81 | public: 82 | ListNode *insertionSortList(ListNode *head) { 83 | if(head == NULL || head->next == NULL) { 84 | return head; 85 | } 86 | 87 | ListNode dummy(0); 88 | 89 | ListNode* p = &dummy; 90 | 91 | ListNode* cur = head; 92 | while(cur) { 93 | p = &dummy; 94 | 95 | while(p->next && p->next->val <= cur->val) { 96 | p = p->next; 97 | } 98 | 99 | ListNode* n = p->next; 100 | p->next = cur; 101 | 102 | cur = cur->next; 103 | p->next->next = n; 104 | } 105 | 106 | return dummy.next; 107 | } 108 | }; 109 | ``` 110 | -------------------------------------------------------------------------------- /linked_list/swap_nodes_in_pairs.md: -------------------------------------------------------------------------------- 1 | # Swap Nodes in Pairs 2 | 3 | > Given a linked list, swap every two adjacent nodes and return its head. 4 | 5 | > For example, 6 | > Given 1->2->3->4, you should return the list as 2->1->4->3. 7 | 8 | > Your algorithm should use only constant space. You may not modify the values in the list, only nodes itself can be changed. 9 | 10 | 这题要求遍历链表,两两交换,也算是一道比较简单的题目,我们只需要拿到需要交换的前驱节点就可以了。直接上代码: 11 | 12 | ```c++ 13 | class Solution { 14 | public: 15 | ListNode *swapPairs(ListNode *head) { 16 | if(!head || !head->next) { 17 | return head; 18 | } 19 | 20 | ListNode dummy(0); 21 | ListNode* p = &dummy; 22 | dummy.next = head; 23 | 24 | while(p && p->next && p->next->next) { 25 | ListNode* n = p->next; 26 | ListNode* nn = p->next->next; 27 | p->next = nn; 28 | n->next = nn->next; 29 | nn->next = n; 30 | p = p->next->next; 31 | } 32 | 33 | return dummy.next; 34 | } 35 | }; 36 | ``` 37 | -------------------------------------------------------------------------------- /math/README.md: -------------------------------------------------------------------------------- 1 | # Math 2 | 3 | > 在这一章,我们主要针对一些leetcode中出现的数学问题给出解析.这种问题一般都比较直接,但要求的是有一些数学功底. 4 | -------------------------------------------------------------------------------- /math/reverse_integer.md: -------------------------------------------------------------------------------- 1 | # Reverse Integer 2 | 3 | > Reverse Integer: Reverse digits of an integer. 4 | 5 | > Example1: x = 123, return 321. 6 | > Example: x = -123, return -321. 7 | 8 | 9 | > 题目翻译: 反转一个数字,比如123要反转为321,-123反转为-321. 10 | 11 | > 题目解析: 这是一个纯数学问题,我们要考虑到corner case的条件,也就是说如果这个数字是0的话,我们直接就返回这个数字就可以了.很简单的问题,直接上代码吧: 12 | 13 | 14 | ```c++ 15 | class Solution { 16 | public: 17 | int reverse(int x) { 18 | if(x == 0) 19 | return x; 20 | int ret = 0; 21 | while(x!=0) 22 | { 23 | if(ret > 2147483647/10 || ret < -2147483647/10) 24 | return 0; 25 | ret = ret*10 + x%10; 26 | x = x/10; 27 | } 28 | return ret; 29 | } 30 | }; 31 | 32 | ``` 33 | -------------------------------------------------------------------------------- /string/README.md: -------------------------------------------------------------------------------- 1 | # String 2 | > 在这一章,我们将会覆盖leetcode上跟string有关联的题目. 3 | -------------------------------------------------------------------------------- /string/add_binary.md: -------------------------------------------------------------------------------- 1 | # Add Binary 2 | 3 | > Given two binary strings, return their sum (also a binary string). 4 | 5 | > For example, 6 | a = "11" 7 | b = "1" 8 | Return "100". 9 | 10 | > 题目翻译: 对于给定的两个二进制数字所表达的字符串,我们求其相加所得到的结果, 根据上例便可得到答案. 11 | 12 | > 题目分析: 我认为这道题所要注意的地方涵盖以下几个方面: 13 | 14 | 1. 对字符串的操作. 15 | 2. 对于加法,我们应该建立一个进位单位,保存进位数值. 16 | 3. 我们还要考虑两个字符串如果不同长度会怎样. 17 | 4. int 类型和char类型的相互转换. 18 | 19 | > 时间复杂度:其实这就是针对两个字符串加起来跑一遍,O(n) n代表长的那串字符串长度. 20 | 21 | 代码如下: 22 | 23 | ```c++ 24 | class Solution { 25 | public: 26 | string addBinary(string a, string b) { 27 | int len1 = a.size(); 28 | int len2 = b.size(); 29 | if(len1 == 0) 30 | return b; 31 | if(len2 == 0) 32 | return a; 33 | 34 | string ret; 35 | int carry = 0; 36 | int index1 = len1-1; 37 | int index2 = len2-1; 38 | 39 | while(index1 >=0 && index2 >= 0) 40 | { 41 | int num = (a[index1]-'0')+(b[index2]-'0')+carry; 42 | carry = num/2; 43 | num = num%2; 44 | index1--; 45 | index2--; 46 | ret.insert(ret.begin(),num+'0'); 47 | } 48 | 49 | if(index1 < 0&& index2 < 0) 50 | { 51 | if(carry == 1) 52 | { 53 | ret.insert(ret.begin(),carry+'0'); 54 | return ret; 55 | } 56 | } 57 | 58 | while(index1 >= 0) 59 | { 60 | int num = (a[index1]-'0')+carry; 61 | carry = num/2; 62 | num = num%2; 63 | index1--; 64 | ret.insert(ret.begin(),num+'0'); 65 | } 66 | while(index2 >= 0) 67 | { 68 | int num = (b[index2]-'0')+carry; 69 | carry = num/2; 70 | num = num%2; 71 | index2--; 72 | ret.insert(ret.begin(),num+'0'); 73 | } 74 | if(carry == 1) 75 | ret.insert(ret.begin(),carry+'0'); 76 | return ret; 77 | } 78 | }; 79 | 80 | ``` 81 | 82 | 83 | -------------------------------------------------------------------------------- /string/basic_calculator_2.md: -------------------------------------------------------------------------------- 1 | # Basic Calculator II 2 | 3 | > Implement a basic calculator to evaluate a simple expression string. 4 | > The expression string contains only non-negative integers, ```+, -, *, /``` operators and empty spaces ``` ``` . The integer division should truncate toward zero. 5 | > You may assume that the given expression is always valid. 6 | > Some examples: 7 | ``` 8 | "3+2*2" = 7 9 | " 3/2 " = 1 10 | " 3+5 / 2 " = 5 11 | ``` 12 | > Note: Do not use the ```eval``` built-in library function. 13 | 14 | 题目翻译: 15 | 实现一个简易的计算器来对简单的字符串表达式求值。 16 | 字符串表达式只包含非负整数,+,-,*,/四种运算符,以及空格。整数除法向零取整。 17 | 给出的表达式都是有效的。 18 | 不要使用内置的eval函数。 19 | 20 | 题目分析: 21 | 通常对算术表达式求值都是用栈来实现的,但是鉴于本题的情形比较简单,所以可以不用栈来实现。 22 | 总体思路是,依次读入字符串里的字符,遇到符号的时候就进行运算。如果是乘除法,就把结果存入中间变量,如果是加减法就把结果存入最终结果。 23 | 24 | 用C++实现的时候,可以在循环中使用```string```类的```find_first_not_of```方法来跳过空格。 25 | 读到数字时,继续向后读,直到不是数字的字符,或者超出字符串长度为止。 26 | 27 | 代码如下: 28 | ```c++ 29 | class Solution { 30 | public: 31 | int calculate(string s) { 32 | int result = 0, inter_res = 0, num = 0; 33 | char op = '+'; 34 | char ch; 35 | for (int pos = s.find_first_not_of(' '); pos < s.size(); pos = s.find_first_not_of(' ', pos)) { 36 | ch = s[pos]; 37 | if (ch >= '0' && ch <= '9') { 38 | int num = ch - '0'; 39 | while (++pos < s.size() && s[pos] >= '0' && s[pos] <= '9') 40 | num = num * 10 + s[pos] - '0'; 41 | switch (op) { 42 | case '+': 43 | inter_res += num; 44 | break; 45 | case '-': 46 | inter_res -= num; 47 | break; 48 | case '*': 49 | inter_res *= num; 50 | break; 51 | case '/': 52 | inter_res /= num; 53 | break; 54 | } 55 | } 56 | else { 57 | if (ch == '+' || ch == '-') { 58 | result += inter_res; 59 | inter_res = 0; 60 | } 61 | op = s[pos++]; 62 | } 63 | } 64 | return result + inter_res; 65 | } 66 | }; 67 | ``` -------------------------------------------------------------------------------- /tree/README.md: -------------------------------------------------------------------------------- 1 | # Tree 2 | 3 | 树是一种重要的非线性数据结构,广泛地应用于计算机技术的各个领域。采用树可以实现一些高效地查找算法,例如数据库系统中用到的红黑树等。 4 | 5 | 树本身的定义是递归的,因此很多涉及到树的算法通常都可以用递归的方式来实现。然而递归算法在数据量较大的时候效率很低,所以通常会将递归改写成迭代算法。 6 | 7 | 涉及到树的题目主要包括树的遍历,平衡二叉树,查找二叉树等。 -------------------------------------------------------------------------------- /tree/balanced_binary_tree.md: -------------------------------------------------------------------------------- 1 | # Balanced Binary Tree 2 | 3 | > Given a binary tree, determine if it is height-balanced. 4 | 5 | > For this problem, a height-balanced binary tree is defined as a binary tree in which the depth of the two subtrees of every node never differ by more than 1. 6 | 7 | 题目翻译: 8 | 给定一颗二叉树, 写一个函数来检测这棵树是否是平衡二叉树. 对于这个问题, 一颗平衡树的定义是其中任意节点的左右子树的高度差不大于1. 9 | 10 | 解题思路: 11 | 这道题其实就是应用DFS,对于一颗二叉树边计算树的高度边计算差值,针对树里面的每一个节点计算它的左右子树的高度差,如果差值大于1,那么就返回-1,如果不大于1,从下往上再次检测. 12 | 13 | 时间复杂度: 14 | 由于是运用DFS,所以时间复杂度为O(n). 15 | 16 | 代码如下: 17 | ```c++ 18 | 19 | /** 20 | * Definition for binary tree 21 | * struct TreeNode { 22 | * int val; 23 | * TreeNode *left; 24 | * TreeNode *right; 25 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 26 | * }; 27 | */ 28 | class Solution { 29 | public: 30 | bool isBalanced(TreeNode *root) { 31 | //corner case check 32 | if(root == NULL) 33 | return true; 34 | int isBalanced = getHeight(root); 35 | if(isBalanced != -1) 36 | return true; 37 | else 38 | return false; 39 | } 40 | 41 | int getHeight(TreeNode* root) 42 | { 43 | if(root == NULL) 44 | return 0; 45 | int leftHeight = getHeight(root->left); 46 | if(leftHeight == -1) 47 | return -1; 48 | int rightHeight = getHeight(root->right); 49 | if(rightHeight == -1) 50 | return -1; 51 | int diffHeight = rightHeight > leftHeight? rightHeight-leftHeight:leftHeight-rightHeight; 52 | if(diffHeight > 1) 53 | return -1; 54 | else 55 | return diffHeight = (rightHeight>leftHeight?rightHeight:leftHeight)+1; 56 | } 57 | }; 58 | ``` 59 | -------------------------------------------------------------------------------- /tree/binary_search_tree_iterator.md: -------------------------------------------------------------------------------- 1 | # Binary Search Tree Iterator 2 | 3 | > Implement an iterator over a binary search tree (BST). Your iterator will be initialized with the root node of a BST. 4 | 5 | > Calling next() will return the next smallest number in the BST. 6 | 7 | > Note: next() and hasNext() should run in average O(1) time and uses O(h) memory, where h is the height of the tree. 8 | 9 | 题目翻译: 10 | 实现二叉查找树的迭代器。你实现的迭代器会使用二叉查找树的根节点来初始化。 11 | 12 | 调用next()方法会返回二叉搜索树里下一个最小的数。 13 | 14 | 注意:next()和hasNext()方法的时间和空间复杂度要求分别是O(1)和O(h),其中h是树的高度。 15 | 16 | 解题思路: 17 | 二叉查找树的性质是,每个节点的左节点小于它,而右节点大于它。最小的节点总是存在于树的最左侧,直接遍历获取的时间复杂度是O(h),而空间复杂度是O(1)。 18 | 19 | 题目中要求实现O(1)的时间复杂度和O(h)的空间复杂度。注意到每次获取最左侧的节点时,我们总会重复访问从根节点开始的所有左节点。因此我们可以用栈来存储遍历时经过的节点。 20 | 21 | 初始化的时候,把从根节点开始的所有左节点压入栈中,此时栈顶元素是最左下方的节点,亦即当前的最小值。next()函数弹出并返回栈顶元素。然后将弹出的节点的右子树的左侧节点依次压入栈中。这个操作和初始化时的压栈操作是相同的,故将其单独提取为一个函数。使用了栈以后,hasNext()函数只需返回栈是否为空即可。若栈不为空,那么尚有最小的元素未被访问,否则整棵树都已经被访问过。 22 | 23 | 时间和空间复杂度: 24 | 如题目要求,时间复杂度是O(1), 空间复杂度是O(h)。 25 | 26 | 代码如下: 27 | ```c++ 28 | /** 29 | * Definition for binary tree 30 | * struct TreeNode { 31 | * int val; 32 | * TreeNode *left; 33 | * TreeNode *right; 34 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 35 | * }; 36 | */ 37 | class BSTIterator { 38 | public: 39 | BSTIterator(TreeNode *root) { 40 | pushStack(root); 41 | } 42 | 43 | /** @return whether we have a next smallest number */ 44 | bool hasNext() { 45 | return !st.empty(); 46 | } 47 | 48 | /** @return the next smallest number */ 49 | int next() { 50 | TreeNode* node = st.top(); 51 | st.pop(); 52 | pushStack(node->right); 53 | return node->val; 54 | } 55 | private: 56 | stack st; 57 | void pushStack(TreeNode *node) { 58 | while (node != nullptr) { 59 | st.push(node); 60 | node = node->left; 61 | } 62 | } 63 | }; 64 | 65 | /** 66 | * Your BSTIterator will be called like this: 67 | * BSTIterator i = BSTIterator(root); 68 | * while (i.hasNext()) cout << i.next(); 69 | */ 70 | ``` 71 | -------------------------------------------------------------------------------- /tree/binary_tree_depth_order_traversal.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Depth Order Traversal 2 | 3 | 前面我们解决了tree的level order遍历问题,这里我们需要来处理tree的depth order,也就是前序,中序和后序遍历。 4 | 5 | # Binary Tree Preorder Traversal 6 | 7 | > Given a binary tree, return the preorder traversal of its nodes' values. 8 | 9 | > For example: 10 | > Given binary tree {1,#,2,3}, 11 | 12 | >``` 13 | 1 14 | \ 15 | 2 16 | / 17 | 3 18 | ``` 19 | 20 | > return [1,2,3]. 21 | 22 | > Note: Recursive solution is trivial, could you do it iteratively? 23 | 24 | 给定一颗二叉树,使用迭代的方式进行前序遍历。 25 | 26 | 因为不能递归,所以我们只能使用stack来保存迭代状态。 27 | 28 | 对于前序遍历,根节点是最先访问的,然后是左子树,最后才是右子树。当访问到根节点的时候,我们需要将右子树压栈,这样访问左子树之后,才能正确地找到对应的右子树。 29 | 30 | 代码如下: 31 | 32 | ```c++ 33 | class Solution { 34 | public: 35 | vector preorderTraversal(TreeNode *root) { 36 | vector vals; 37 | if(root == NULL) { 38 | return vals; 39 | } 40 | 41 | vector nodes; 42 | 43 | //首先将root压栈 44 | nodes.push_back(root); 45 | 46 | while(!nodes.empty()) { 47 | TreeNode* n = nodes.back(); 48 | vals.push_back(n->val); 49 | 50 | //访问了该节点,出栈 51 | nodes.pop_back(); 52 | 53 | //如果有右子树,压栈保存 54 | if(n->right) { 55 | nodes.push_back(n->right); 56 | } 57 | 58 | //如果有左子树,压栈保存 59 | if(n->left) { 60 | nodes.push_back(n->left); 61 | } 62 | } 63 | 64 | return vals; 65 | } 66 | }; 67 | ``` 68 | 69 | # Binary Tree Inorder Traversal 70 | 71 | 给定一颗二叉树,使用迭代的方式进行中序遍历。 72 | 73 | 对于中序遍历,首先遍历左子树,然后是根节点,最后才是右子树,所以我们需要用stack记录每次遍历的根节点,当左子树遍历完成之后,从stack弹出根节点,得到其右子树,开始新的遍历。 74 | 75 | 代码如下: 76 | 77 | ```c++ 78 | class Solution { 79 | public: 80 | vector inorderTraversal(TreeNode *root) { 81 | vector vals; 82 | if(root == NULL) { 83 | return vals; 84 | } 85 | 86 | vector nodes; 87 | TreeNode* p = root; 88 | while(p || !nodes.empty()) { 89 | //这里一直遍历左子树,将根节点压栈 90 | while(p) { 91 | nodes.push_back(p); 92 | p = p->left; 93 | } 94 | 95 | if(!nodes.empty()) { 96 | p = nodes.back(); 97 | vals.push_back(p->val); 98 | 99 | //将根节点弹出,获取右子树 100 | nodes.pop_back(); 101 | p = p->right; 102 | } 103 | } 104 | 105 | return vals; 106 | } 107 | }; 108 | ``` 109 | 110 | # Binary Tree Postorder Traversal 111 | 112 | 给定一颗二叉树,使用迭代的方式进行后序遍历。 113 | 114 | 对于后序遍历,首先遍历左子树,然后是右子树,最后才是根节点。当遍历到一个节点的时候,首先我们将右子树压栈,然后将左子树压栈。这里需要注意一下出栈的规则,对于叶子节点来说,直接可以出栈,但是对于根节点来说,我们需要一个变量记录上一次出栈的节点,如果上一次出栈的节点是该根节点的左子树或者右子树,那么该根节点可以出栈,否则这个根节点是新访问的节点,将右和左子树分别压栈。 115 | 116 | 代码如下: 117 | 118 | ```c++ 119 | class Solution { 120 | public: 121 | vector postorderTraversal(TreeNode *root) { 122 | vector vals; 123 | if(root == NULL) { 124 | return vals; 125 | } 126 | 127 | vector nodes; 128 | TreeNode* pre = NULL; 129 | 130 | nodes.push_back(root); 131 | 132 | while(!nodes.empty()) { 133 | TreeNode* p = nodes.back(); 134 | //如果不判断pre,我们就没法正确地出栈了 135 | if((p->left == NULL && p->right == NULL) || 136 | (pre != NULL && (pre == p->left || pre == p->right))) { 137 | vals.push_back(p->val); 138 | nodes.pop_back(); 139 | pre = p; 140 | } else { 141 | //右子树压栈 142 | if(p->right != NULL) { 143 | nodes.push_back(p->right); 144 | } 145 | 146 | //左子树压栈 147 | if(p->left != NULL) { 148 | nodes.push_back(p->left); 149 | } 150 | } 151 | } 152 | 153 | return vals; 154 | } 155 | }; 156 | ``` 157 | 158 | ## 总结 159 | 160 | 可以看到,树的遍历通过递归或者堆栈的方式都是比较容易的,网上还有更牛的不用栈的方法,只是我没理解,就不做过多说明了。 161 | -------------------------------------------------------------------------------- /tree/binary_tree_level_order_traversal.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Level Order Traversal 2 | 3 | > Given a binary tree, return the level order traversal of its nodes' values. (ie, from left to right, level by level). 4 | 5 | > For example: 6 | Given binary tree {3,9,20,#,#,15,7}, 7 | 8 | ``` 9 | 3 10 | / \ 11 | 9 20 12 | / \ 13 | 15 7 14 | ``` 15 | > return its level order traversal as: 16 | 17 | ``` 18 | [ 19 | [3], 20 | [9,20], 21 | [15,7] 22 | ] 23 | ``` 24 | 25 | 题目翻译: 26 | 给定一颗二叉树,返回一个二维数组,使这个二维数组的每一个元素代表着二叉树的一层的元素.例子已经明确给出. 27 | 28 | 题目分析: 29 | 对于二叉树的问题,我们第一想到的就是DFS或者BFS, DFS更易于理解代码,如果处理数据量不是很大的话.对于这样的面试题,我建议用DFS来求解. 30 | 31 | 需要注意的点为: 32 | 1. 对于一棵树,如果我们要求每一层的节点,并且存在一个二维数组里,首先我们要建一个二维数组,但是这个二维数组建多大的合适呢?我们就要求出这颗树的深度,根据深度来建立二维数组. 33 | 2. 题目要求为从左往右添加,所以我们也就是要先放左边的节点,再放右边的节点. 34 | 3. 对于这道题,我们首先就是要用DFS来求出这颗树的高度,之后再用DFS对于每一层遍历,这样节省了空间复杂度. 35 | 36 | 时间复杂度分析: 37 | 由于两次DFS是并列的,并没有嵌套,所以我们的时间复杂度为O(n). 38 | 39 | 代码如下: 40 | ```c++ 41 | /** 42 | * Definition for binary tree 43 | * struct TreeNode { 44 | * int val; 45 | * TreeNode *left; 46 | * TreeNode *right; 47 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 48 | * }; 49 | */ 50 | class Solution { 51 | public: 52 | /* for this question, we need to construct the ret vector first 53 | thus, we need to know the depth of this tree, we write a simple 54 | function to calculate the height of this tree */ 55 | vector > levelOrder(TreeNode *root) { 56 | int depth = getHeight(root); 57 | vector> ret(depth); 58 | if(depth == 0) //invalid check 59 | return ret; 60 | getSolution(ret,root,0); 61 | return ret; 62 | } 63 | 64 | void getSolution(vector>& ret, TreeNode* root, int level) 65 | { 66 | if(root == NULL) 67 | return; 68 | ret[level].push_back(root->val); 69 | getSolution(ret,root->left,level+1); 70 | getSolution(ret,root->right,level+1); 71 | } 72 | 73 | int getHeight(TreeNode* root) 74 | { 75 | if(root == NULL) 76 | return 0; 77 | int left = getHeight(root->left); 78 | int right = getHeight(root->right); 79 | int height = (left > right?left:right)+1; 80 | return height; 81 | } 82 | }; 83 | ``` 84 | 85 | # Binary Tree Level Order Traversal II 86 | 87 | > Given a binary tree, return the bottom-up level order traversal of its nodes' values. (from left to right, level by level from leaf to root) 88 | 89 | 90 | > For example: 91 | Given binary tree {3,9,20,#,#,15,7}, 92 | 93 | ``` 94 | 3 95 | / \ 96 | 9 20 97 | / \ 98 | 15 7 99 | ``` 100 | > return its level order traversal as: 101 | 102 | ``` 103 | [ 104 | [15,7], 105 | [9,20], 106 | [3] 107 | ] 108 | 109 | ``` 110 | 题目翻译: 111 | 给定一颗二叉树, 返回一个二维数组,这个二维数组要满足这个条件,二维数组的第一个一维数组要是这可二叉树的最下面一层,之后以此类推,根据以上例子应该知道要求的条件。 112 | 113 | 题目分析 && 解题思路: 114 | 这道题和Binary Tree Level Order Traversal 几乎是一摸一样的,唯一不同的就是二维数组的存储顺序,详见以下代码. 115 | 116 | 时间复杂度: 117 | O(n)-树的dfs均为O(n) 118 | 119 | 代码如下: 120 | ```c++ 121 | 122 | /** 123 | * Definition for binary tree 124 | * struct TreeNode { 125 | * int val; 126 | * TreeNode *left; 127 | * TreeNode *right; 128 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 129 | * }; 130 | */ 131 | class Solution { 132 | public: 133 | vector > levelOrderBottom(TreeNode *root) { 134 | int depth = height(root); 135 | vector> ret(depth); 136 | if(depth == 0) 137 | return ret; 138 | DFS(ret,ret.size()-1, root); 139 | return ret; 140 | } 141 | 142 | void DFS(vector>& ret, int level, TreeNode* root) 143 | { 144 | if(root == NULL) 145 | return; 146 | ret[level].push_back(root->val); 147 | DFS(ret,level-1,root->left); 148 | DFS(ret,level-1,root->right); 149 | } 150 | 151 | /* get the height first of all */ 152 | int height(TreeNode* root) 153 | { 154 | if(root == NULL) 155 | return 0; 156 | int left_side = height(root->left); 157 | int right_side = height(root->right); 158 | int height = (left_side > right_side?left_side:right_side)+1; 159 | return height; 160 | } 161 | }; 162 | 163 | ``` 164 | 165 | # Binary Tree Zigzag Level Order Traversal 166 | 167 | > Given a binary tree, return the zigzag level order traversal of its nodes' values. (ie, from left to right, then right to left for the next level and alternate between). 168 | 169 | > For example: 170 | > Given binary tree {3,9,20,#,#,15,7}, 171 | 172 | > ``` 173 | 3 174 | / \ 175 | 9 20 176 | / \ 177 | 15 7 178 | ``` 179 | 180 | > return its zigzag level order traversal as: 181 | ``` 182 | [ 183 | [3], 184 | [20,9], 185 | [15,7] 186 | ] 187 | ``` 188 | 189 | 如果完成了上面两题,这题应该是很简单的,我们只需要将得到的数据按照zigzag的方式翻转一下,代码如下: 190 | 191 | ```c++ 192 | class Solution { 193 | public: 194 | vector > vals; 195 | vector > zigzagLevelOrder(TreeNode *root) { 196 | build(root, 1); 197 | 198 | //翻转 199 | for(int i = 1; i < vals.size(); i+=2) { 200 | reverse(vals[i].begin(), vals[i].end()); 201 | } 202 | 203 | return vals; 204 | } 205 | 206 | void build(TreeNode* node, int level) { 207 | if(!node) { 208 | return; 209 | } 210 | 211 | if(vals.size() <= level - 1) { 212 | vals.push_back(vector()); 213 | } 214 | 215 | vals[level - 1].push_back(node->val); 216 | 217 | if(node->left) { 218 | build(node->left, level + 1); 219 | } 220 | 221 | if(node->right) { 222 | build(node->right, level + 1); 223 | } 224 | } 225 | }; 226 | ``` 227 | -------------------------------------------------------------------------------- /tree/binary_tree_path.md: -------------------------------------------------------------------------------- 1 | # Binary Tree Path 2 | 3 | > Given a binary tree, return all root-to-leaf paths. 4 | 5 | > For example, given the following binary tree: 6 | ``` 7 | 1 8 | / \ 9 | 2 3 10 | \ 11 | 5 12 | ``` 13 | > All root-to-leaf paths are: 14 | ``` 15 | ["1->2->5", "1->3"] 16 | ``` 17 | 18 | 题目翻译: 19 | 给定一棵二叉树,返回所有从根节点到叶节点的路径。 20 | 21 | 题目分析: 22 | 本题属于二叉树的遍历问题,可以用深度优先搜索来解决。 23 | 24 | 使用栈来记录遍历过程中访问过的节点。递归地访问每个节点的子节点,如果遇到叶节点,则输出记录的路径。返回上一层之前弹出栈顶元素。 25 | C++的vector容器也能做到后进先出,所以下面的代码并没有使用std::stack类来实现。 26 | 27 | 生成输出的字符串时,可以使用std::stringstream类来完成,类似于Java和C#中的StringBuilder。 28 | 29 | ```c++ 30 | /** 31 | * Definition for a binary tree node. 32 | * struct TreeNode { 33 | * int val; 34 | * TreeNode *left; 35 | * TreeNode *right; 36 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 37 | * }; 38 | */ 39 | class Solution { 40 | public: 41 | vector binaryTreePaths(TreeNode* root) { 42 | vector result; 43 | if (root == nullptr) return result; 44 | vector path; 45 | bfs(root, path, result); 46 | return result; 47 | } 48 | 49 | private: 50 | // 递归函数,深度优先搜索 51 | void bfs(TreeNode* node, vector& path, vector& result) { 52 | if (node == nullptr) return; 53 | path.push_back(node->val); 54 | if (node->left == nullptr && node->right == nullptr) 55 | result.push_back(generatePath(path)); 56 | else { 57 | if (node->left != nullptr) { 58 | bfs(node->left, path, result); 59 | path.pop_back(); 60 | } 61 | if (node->right != nullptr) { 62 | bfs(node->right, path, result); 63 | path.pop_back(); 64 | } 65 | } 66 | } 67 | 68 | // 辅助函数,用于生成路径字符串 69 | string generatePath(vector path) { 70 | stringstream ss; 71 | int i; 72 | for (i = 0; i < path.size() - 1; i++) ss << path[i] << "->"; 73 | ss << path[i]; 74 | return ss.str(); 75 | } 76 | }; 77 | ``` 78 | -------------------------------------------------------------------------------- /tree/construct_binary_tree.md: -------------------------------------------------------------------------------- 1 | # Construct Binary Tree from Inorder and Postorder Traversal 2 | 3 | > Given inorder and postorder traversal of a tree, construct the binary tree. 4 | 5 | 要知道如何构建二叉树,首先我们需要知道二叉树的几种遍历方式,譬如有如下的二叉树: 6 | 7 | ``` 8 | 1 9 | --------|------- 10 | 2 3 11 | ----|---- ----|---- 12 | 4 5 6 7 13 | ``` 14 | 15 | + 前序遍历 1245367 16 | + 中序遍历 4251637 17 | + 后续遍历 4526731 18 | 19 | 具体到上面这一题,我们知道了一个二叉树的中序遍历以及后序遍历的结果,那么如何构建这颗二叉树呢? 20 | 21 | 仍然以上面那棵二叉树为例,我们可以发现,对于后序遍历来说,最后一个元素一定是根节点,也就是1。然后我们在中序遍历的结果里面找到1所在的位置,那么它的左半部分就是其左子树,有半部分就是其右子树。 22 | 23 | 我们将中序遍历左半部分425取出,同时发现后序遍历的结果也在相应的位置上面,只是顺序稍微不一样,也就是452。我们可以发现,后序遍历中的2就是该子树的根节点。 24 | 25 | 上面说到了左子树,对于右子树,我们取出637,同时发现后序遍历中对应的数据偏移了一格,并且顺序也不一样,为673。而3就是这颗右子树的根节点。 26 | 27 | 重复上述过程,通过后续遍历找到根节点,然后在中序遍历数据中根据根节点拆分成两个部分,同时将对应的后序遍历的数据也拆分成两个部分,重复递归,就可以得到整个二叉树了。 28 | 29 | 代码如下: 30 | 31 | ```c++ 32 | class Solution { 33 | public: 34 | unordered_map m; 35 | TreeNode *buildTree(vector &inorder, vector &postorder) { 36 | if(postorder.empty()) { 37 | return NULL; 38 | } 39 | 40 | for(int i = 0; i < inorder.size(); i++) { 41 | m[inorder[i]] = i; 42 | } 43 | 44 | return build(inorder, 0, inorder.size() - 1, 45 | postorder, 0, postorder.size() - 1); 46 | } 47 | 48 | TreeNode* build(vector& inorder, int s0, int e0, 49 | vector& postorder, int s1, int e1) { 50 | if(s0 > e0 || s1 > e1) { 51 | return NULL; 52 | } 53 | 54 | TreeNode* root = new TreeNode(postorder[e1]); 55 | 56 | int mid = m[postorder[e1]]; 57 | int num = mid - s0; 58 | 59 | root->left = build(inorder, s0, mid - 1, postorder, s1, s1 + num - 1); 60 | root->right = build(inorder, mid + 1, e0, postorder, s1 + num, e1 - 1); 61 | 62 | return root; 63 | } 64 | }; 65 | ``` 66 | 67 | 这里我们需要注意,为了保证快速的在中序遍历结果里面找到根节点,我们使用了hash map。 68 | 69 | # Construct Binary Tree from Preorder and Inorder Traversal 70 | 71 | > Given preorder and inorder traversal of a tree, construct the binary tree. 72 | 73 | 这题跟上面那题类似,通过前序遍历和中序遍历的结果构造二叉树,我们只需要知道前序遍历的第一个值就是根节点,那么仍然可以采用上面提到的方式处理: 74 | 75 | + 通过前序遍历找到根节点 76 | + 通过根节点将中序遍历数据拆分成两部分 77 | + 对于各个部分重复上述操作 78 | 79 | 代码如下: 80 | 81 | ```c++ 82 | class Solution { 83 | public: 84 | unordered_map m; 85 | TreeNode *buildTree(vector &preorder, vector &inorder) { 86 | if(preorder.empty()) { 87 | return NULL; 88 | } 89 | 90 | for(int i = 0; i < inorder.size(); i++) { 91 | m[inorder[i]] = i; 92 | } 93 | 94 | return build(preorder, 0, preorder.size() - 1, inorder, 0, inorder.size() - 1); 95 | } 96 | 97 | TreeNode* build(vector& preorder, int s0, int e0, vector &inorder, int s1, int e1) { 98 | if(s0 > e0 || s1 > e1) { 99 | return NULL; 100 | } 101 | 102 | int mid = m[preorder[s0]]; 103 | 104 | TreeNode* root = new TreeNode(preorder[s0]); 105 | 106 | int num = mid - s1; 107 | 108 | root->left = build(preorder, s0 + 1, s0 + num, inorder, s1, mid - 1); 109 | root->right = build(preorder, s0 + num + 1, e0, inorder, mid + 1, e1); 110 | 111 | return root; 112 | } 113 | }; 114 | ``` 115 | 116 | 可以看到,这两道题目,只要能清楚了树的几种遍历方式,以及找到如何找到根节点,并通过中序遍历拆分成两个子树,就能很容易的搞定了,唯一需要注意的是写代码的时候拆分索引位置要弄对。 117 | -------------------------------------------------------------------------------- /tree/convert_sorted_listarray_to_binary_search_tree.md: -------------------------------------------------------------------------------- 1 | # Convert Sorted List to Binary Search Tree 2 | 3 | > Given a singly linked list where elements are sorted in ascending order, convert it to a height balanced BST. 4 | 5 | 这题需要将一个排好序的链表转成一个平衡二叉树,我们知道,对于一个二叉树来说,左子树一定小于根节点,而右子树大于根节点。所以我们需要找到链表的中间节点,这个就是根节点,链表的左半部分就是左子树,而右半部分则是右子树,我们继续递归处理相应的左右部分,就能够构造出对应的二叉树了。 6 | 7 | 这题的难点在于如何找到链表的中间节点,我们可以通过fast,slow指针来解决,fast每次走两步,slow每次走一步,fast走到结尾,那么slow就是中间节点了。 8 | 9 | 代码如下: 10 | 11 | ```c++ 12 | class Solution { 13 | public: 14 | 15 | TreeNode *sortedListToBST(ListNode *head) { 16 | return build(head, NULL); 17 | } 18 | 19 | TreeNode* build(ListNode* start, ListNode* end) { 20 | if(start == end) { 21 | return NULL; 22 | } 23 | 24 | ListNode* fast = start; 25 | ListNode* slow = start; 26 | 27 | while(fast != end && fast->next != end) { 28 | slow = slow->next; 29 | fast = fast->next->next; 30 | } 31 | 32 | TreeNode* node = new TreeNode(slow->val); 33 | node->left = build(start, slow); 34 | node->right = build(slow->next, end); 35 | 36 | return node; 37 | } 38 | }; 39 | ``` 40 | 41 | # Convert Sorted Array to Binary Search Tree 42 | 43 | > Given an array where elements are sorted in ascending order, convert it to a height balanced BST. 44 | 45 | 这题类似上面那题,同样地解题方式,对于数组来说,能更方便的得到中间节点,代码如下: 46 | 47 | ```c++ 48 | class Solution { 49 | public: 50 | TreeNode *sortedArrayToBST(vector &num) { 51 | return build(num, 0, num.size()); 52 | } 53 | 54 | TreeNode* build(vector& num, int start, int end) { 55 | if(start >= end) { 56 | return NULL; 57 | } 58 | 59 | int mid = start + (end - start) / 2; 60 | 61 | TreeNode* node = new TreeNode(num[mid]); 62 | node->left = build(num, start, mid); 63 | node->right = build(num, mid + 1, end); 64 | 65 | return node; 66 | } 67 | }; 68 | ``` 69 | -------------------------------------------------------------------------------- /tree/count_complete_tree_nodes.md: -------------------------------------------------------------------------------- 1 | # Count Complete Tree Nodes 2 | 3 | > Given a complete binary tree, count the number of nodes. 4 | 5 | > Definition of a complete binary tree from Wikipedia: 6 | > In a complete binary tree every level, except possibly the last, is completely filled, and all nodes in the last level are as far left as possible. It can have between 1 and 2h nodes inclusive at the last level h. 7 | 8 | 9 | 题目翻译: 10 | 给定一棵完全二叉树,统计树中节点的个数。 11 | 12 | 完全二叉树在维基百科上的定义是:除了最后一层,完全二叉树的每一层的节点都是满的。最后一层的节点全都靠近左侧。最后一层的深度为h时,这一层可以有1到2h个节点。 13 | 14 | 15 | 解题思路: 16 | 其实很多文献上对完全二叉树的定义都是从满二叉树延伸开来的。满二叉树就是深度为k并且有2^k-1个节点的二叉树。而完全二叉树可以直观地理解为从右下方水平方向上连续地移除一部分叶节点所产生的二叉树。满二叉树本身也是完全二叉树。 17 | 18 | 对于一棵满二叉树,其最左侧的叶节点的深度必然与最右侧的叶节点的深度相同。若不同,那么我们就递归地检查当前结点的左右子树,分别判断其最左和最右叶节点的深度是否相同。不难发现,对于完全二叉树,总能找到从某个节点开始下面的子树都是满二叉树。从而我们可以利用满二叉树的性质计算其节点个数。当递归函数返回时,左右子树的节点个数之和再加上1即为以当前结点作为根节点的总的节点个数。 19 | 20 | 注:计算2的n次方用移位运算效率最高。n为正整数则左移,n为负整数则右移。 21 | 22 | 代码如下: 23 | ```c++ 24 | /** 25 | * Definition for a binary tree node. 26 | * struct TreeNode { 27 | * int val; 28 | * TreeNode *left; 29 | * TreeNode *right; 30 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 31 | * }; 32 | */ 33 | class Solution { 34 | public: 35 | int countNodes(TreeNode* root) { 36 | int depthLeft = leftDepth(root); 37 | int depthRight = rightDepth(root); 38 | if (depthLeft == depthRight) return (1 << depthLeft) - 1; 39 | return countNodes(root->left) + countNodes(root->right) + 1; 40 | } 41 | private: 42 | int leftDepth(TreeNode* node) { 43 | int depth = 0; 44 | while (node != nullptr) { 45 | depth++; 46 | node = node->left; 47 | } 48 | return depth; 49 | } 50 | 51 | int rightDepth(TreeNode* node) { 52 | int depth = 0; 53 | while (node != nullptr) { 54 | depth++; 55 | node = node->right; 56 | } 57 | return depth; 58 | } 59 | }; 60 | 61 | ``` 62 | -------------------------------------------------------------------------------- /tree/depth_of_binary_tree.md: -------------------------------------------------------------------------------- 1 | # Maximum Depth of Binary Tree 2 | 3 | > Given a binary tree, find its maximum depth. 4 | 5 | > The maximum depth is the number of nodes along the longest path from the root node down to the farthest leaf node. 6 | 7 | 这题要求我们求出一个二叉树最大深度,也就是从根节点到最远的叶子节点的距离。 8 | 9 | 对于这题,我们只需要递归遍历二叉树,达到一个叶子节点的时候,记录深度,我们就能得到最深的深度了。 10 | 11 | 代码如下: 12 | 13 | ```c++ 14 | class Solution { 15 | public: 16 | int num; 17 | int maxDepth(TreeNode *root) { 18 | if(!root) { 19 | return 0; 20 | } 21 | 22 | //首先初始化num为最小值 23 | num = numeric_limits::min(); 24 | travel(root, 1); 25 | return num; 26 | } 27 | 28 | void travel(TreeNode* node, int level) { 29 | //如果没有左子树以及右子树了,就到了叶子节点 30 | if(!node->left && !node->right) { 31 | num = max(num, level); 32 | return; 33 | } 34 | 35 | if(node->left) { 36 | travel(node->left, level + 1); 37 | } 38 | 39 | if(node->right) { 40 | travel(node->right, level + 1); 41 | } 42 | } 43 | }; 44 | ``` 45 | 46 | # Minimum Depth of Binary Tree 47 | 48 | > Given a binary tree, find its minimum depth. 49 | 50 | > The minimum depth is the number of nodes along the shortest path from the root node down to the nearest leaf node. 51 | 52 | 这题跟上题几乎一样,区别在于需要求出根节点到最近的叶子节点的深度,我们仍然使用遍历的方式。 53 | 54 | 代码如下: 55 | 56 | ```c++ 57 | class Solution { 58 | public: 59 | int n; 60 | int minDepth(TreeNode *root) { 61 | if(!root) { 62 | return 0; 63 | } 64 | 65 | //初始化成最大值 66 | n = numeric_limits::max(); 67 | int d = 1; 68 | 69 | depth(root, d); 70 | return n; 71 | } 72 | 73 | void depth(TreeNode* node, int& d) { 74 | //叶子节点,比较 75 | if(!node->left && !node->right) { 76 | n = min(n, d); 77 | return; 78 | } 79 | 80 | if(node->left) { 81 | d++; 82 | depth(node->left, d); 83 | d--; 84 | } 85 | 86 | if(node->right) { 87 | d++; 88 | depth(node->right, d); 89 | d--; 90 | } 91 | } 92 | }; 93 | ``` 94 | -------------------------------------------------------------------------------- /tree/flatten_binary_tree_to_linked_list.md: -------------------------------------------------------------------------------- 1 | # Flatten Binary Tree to Linked List 2 | 3 | > Given a binary tree, flatten it to a linked list in-place. 4 | 5 | > For example, 6 | Given 7 | 8 | >``` 9 | 1 10 | / \ 11 | 2 5 12 | / \ \ 13 | 3 4 6 14 | ``` 15 | 16 | > The flattened tree should look like: 17 | >``` 18 | 1 19 | \ 20 | 2 21 | \ 22 | 3 23 | \ 24 | 4 25 | \ 26 | 5 27 | \ 28 | 6 29 | ``` 30 | 31 | 给定一颗二叉树,将其扁平化处理,我们可以看到处理之后的节点顺序其实跟前序遍历原二叉树的一致,所以我们只需要前序遍历二叉树同时处理就可以了。代码如下: 32 | 33 | ``` 34 | class Solution { 35 | public: 36 | void flatten(TreeNode *root) { 37 | if(!root) { 38 | return; 39 | } 40 | 41 | vector ns; 42 | TreeNode dummy(0); 43 | 44 | TreeNode* n = &dummy; 45 | 46 | ns.push_back(root); 47 | 48 | while(!ns.empty()) { 49 | TreeNode* p = ns.back(); 50 | ns.pop_back(); 51 | 52 | //挂载到右子树 53 | n->right = p; 54 | n = p; 55 | 56 | 57 | //右子树压栈 58 | if(p->right) { 59 | ns.push_back(p->right); 60 | p->right = NULL; 61 | } 62 | 63 | //左子树压栈 64 | if(p->left) { 65 | ns.push_back(p->left); 66 | p->left = NULL; 67 | } 68 | } 69 | } 70 | }; 71 | ``` 72 | -------------------------------------------------------------------------------- /tree/path_sum.md: -------------------------------------------------------------------------------- 1 | # Path Sum 2 | 3 | > Given a binary tree and a sum, determine if the tree has a root-to-leaf path such that adding up all the values along the path equals the given sum. 4 | 5 | > For example: 6 | Given the below binary tree and sum = 22, 7 | 8 | ``` 9 | 5 10 | / \ 11 | 4 8 12 | / / \ 13 | 11 13 4 14 | / \ \ 15 | 7 2 1 16 | 17 | ``` 18 | > return true, as there exist a root-to-leaf path 5->4->11->2 which sum is 22. 19 | 20 | 题目翻译: 21 | 给定一颗二叉树和一个特定值,写一个方法来判定这棵树是否存在这样一种条件,使得从root到其中一个叶子节点的路径的和等于给定的sum值. 22 | 23 | 解题思路: 24 | 这道题很常规,直接用DFS就可以求解. 25 | 26 | 时间复杂度: 27 | O(n) 28 | 29 | 代码如下: 30 | ```c++ 31 | /** 32 | * Definition for binary tree 33 | * struct TreeNode { 34 | * int val; 35 | * TreeNode *left; 36 | * TreeNode *right; 37 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 38 | * }; 39 | */ 40 | class Solution { 41 | public: 42 | bool hasPathSum(TreeNode *root, int sum) { 43 | if(root == NULL) 44 | return false; 45 | return DFS(sum, 0, root); 46 | } 47 | 48 | bool DFS(int target, int sum, TreeNode* root) 49 | { 50 | if(root == NULL) 51 | return false; 52 | sum += root->val; 53 | if(root->left == NULL && root->right == NULL) 54 | { 55 | if(sum == target) 56 | return true; 57 | else 58 | return false; 59 | } 60 | bool leftPart = DFS(target, sum, root->left); 61 | bool rightPart = DFS(target, sum, root->right); 62 | return leftPart||rightPart; 63 | } 64 | 65 | }; 66 | 67 | ``` 68 | -------------------------------------------------------------------------------- /tree/path_sum_ii.md: -------------------------------------------------------------------------------- 1 | # Path Sum II 2 | 3 | > Given a binary tree and a sum, find all root-to-leaf paths where each path's sum equals the given sum. 4 | 5 | > For example: 6 | Given the below binary tree and sum = 22. 7 | 8 | 9 | ``` 10 | 5 11 | / \ 12 | 4 8 13 | / / \ 14 | 11 13 4 15 | / \ \ 16 | 7 2 1 17 | 18 | ``` 19 | 20 | > return 21 | 22 | ``` 23 | [ 24 | [5,4,11,2], 25 | [5,8,4,5] 26 | ] 27 | ``` 28 | 题目翻译: 29 | 给定一个二叉树,并且给定一个值,找出所有从根节点到叶子节点和等于这个给定值的路径.上面的例子可以很好地让读者理解这个题目的目的. 30 | 31 | 解题思路: 32 | 这个题目和Path Sum的解法几乎是一模一样,都是用dfs来进行求解,不过就是在传参数的时候有些不同了,因为题目的要求也不同. 33 | 34 | 时间复杂度: 35 | O(n) 36 | 37 | 代码如下: 38 | 39 | ```c++ 40 | 41 | /** 42 | * Definition for binary tree 43 | * struct TreeNode { 44 | * int val; 45 | * TreeNode *left; 46 | * TreeNode *right; 47 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 48 | * }; 49 | */ 50 | class Solution { 51 | public: 52 | vector > pathSum(TreeNode *root, int sum) { 53 | vector> ret; 54 | if(root == NULL) 55 | return ret; 56 | vector curr; 57 | DFS(ret,curr,sum,0,root); 58 | return ret; 59 | } 60 | 61 | void DFS(vector>& ret, vector curr, int sum, int tmpsum, TreeNode* root) 62 | { 63 | if(root == NULL) 64 | return; 65 | tmpsum+=root->val; 66 | curr.push_back(root->val); 67 | if(tmpsum == sum) 68 | { 69 | if(root->left == NULL&&root->right == NULL) 70 | { 71 | ret.push_back(curr); 72 | return; 73 | } 74 | } 75 | DFS(ret,curr,sum,tmpsum,root->left); 76 | DFS(ret,curr,sum,tmpsum,root->right); 77 | } 78 | }; 79 | ``` 80 | 81 | 82 | -------------------------------------------------------------------------------- /tree/populating_next_right_pointers_in_each_node.md: -------------------------------------------------------------------------------- 1 | # Populating Next Right Pointers in Each Node 2 | 3 | > Given a binary tree 4 | 5 | >``` 6 | struct TreeLinkNode { 7 | TreeLinkNode *left; 8 | TreeLinkNode *right; 9 | TreeLinkNode *next; 10 | } 11 | ``` 12 | > Populate each next pointer to point to its next right node. If there is no next right node, the next pointer should be set to NULL. 13 | 14 | > Initially, all next pointers are set to NULL. 15 | 16 | > Note: 17 | 18 | > You may only use constant extra space. 19 | You may assume that it is a perfect binary tree (ie, all leaves are at the same level, and every parent has two children). 20 | For example, 21 | Given the following perfect binary tree, 22 | ``` 23 | 1 24 | / \ 25 | 2 3 26 | / \ / \ 27 | 4 5 6 7 28 | ``` 29 | After calling your function, the tree should look like: 30 | ``` 31 | 1 -> NULL 32 | / \ 33 | 2 -> 3 -> NULL 34 | / \ / \ 35 | 4->5->6->7 -> NULL 36 | ``` 37 | 38 | 这题需要在一棵完全二叉树中使用next指针连接旁边的节点,我们可以发现一些规律。 39 | 40 | + 如果一个子节点是根节点的左子树,那么它的next就是该根节点的右子树,譬如上面例子中的4,它的next就是2的右子树5。 41 | + 如果一个子节点是根节点的右子树,那么它的next就是该根节点next节点的左子树。譬如上面的5,它的next就是2的next(也就是3)的左子树。 42 | 43 | 所以代码如下: 44 | 45 | ```c++ 46 | class Solution { 47 | public: 48 | void connect(TreeLinkNode *root) { 49 | if(!root) { 50 | return; 51 | } 52 | 53 | TreeLinkNode* p = root; 54 | TreeLinkNode* first = NULL; 55 | while(p) { 56 | //记录下层第一个左子树 57 | if(!first) { 58 | first = p->left; 59 | } 60 | //如果有左子树,那么next就是父节点 61 | if(p->left) { 62 | p->left->next = p->right; 63 | } else { 64 | //叶子节点了,遍历结束 65 | break; 66 | } 67 | 68 | //如果有next,那么设置右子树的next 69 | if(p->next) { 70 | p->right->next = p->next->left; 71 | p = p->next; 72 | continue; 73 | } else { 74 | //转到下一层 75 | p = first; 76 | first = NULL; 77 | } 78 | } 79 | } 80 | }; 81 | ``` 82 | 83 | # Populating Next Right Pointers in Each Node II 84 | 85 | > What if the given tree could be any binary tree? Would your previous solution still work? 86 | 87 | > Note: 88 | 89 | > You may only use constant extra space. 90 | For example, 91 | Given the following binary tree, 92 | ``` 93 | 1 94 | / \ 95 | 2 3 96 | / \ \ 97 | 4 5 7 98 | ``` 99 | After calling your function, the tree should look like: 100 | ``` 101 | 1 -> NULL 102 | / \ 103 | 2 -> 3 -> NULL 104 | / \ \ 105 | 4-> 5 -> 7 -> NULL 106 | ``` 107 | 108 | 不同于上一题,这题的二叉树并不是完全二叉树,我们不光需要提供first指针用来表示一层的第一个元素,同时也需要使用另一个lst指针表示该层上一次遍历的元素。那么我们只需要处理好如何设置last的next指针就可以了。 109 | 110 | 代码如下: 111 | 112 | ```c++ 113 | class Solution { 114 | public: 115 | void connect(TreeLinkNode *root) { 116 | if(!root) { 117 | return; 118 | } 119 | 120 | TreeLinkNode* p = root; 121 | TreeLinkNode* first = NULL; 122 | TreeLinkNode* last = NULL; 123 | 124 | while(p) { 125 | //设置下层第一个元素 126 | if(!first) { 127 | if(p->left) { 128 | first = p->left; 129 | } else if(p->right) { 130 | first = p->right; 131 | } 132 | } 133 | 134 | if(p->left) { 135 | //如果有last,则设置last的next 136 | if(last) { 137 | last->next = p->left; 138 | } 139 | //last为left 140 | last = p->left; 141 | } 142 | 143 | if(p->right) { 144 | //如果有last,则设置last的next 145 | if(last) { 146 | last->next = p->right; 147 | } 148 | //last为right 149 | last = p->right; 150 | } 151 | 152 | //如果有next,则转到next 153 | if(p->next) { 154 | p = p->next; 155 | } else { 156 | //转到下一层 157 | p = first; 158 | last = NULL; 159 | first = NULL; 160 | } 161 | } 162 | } 163 | }; 164 | ``` 165 | 166 | 其实我们可以看到,第一题只是第二题的特例,所以第二题的解法也同样适用于第一题。 167 | -------------------------------------------------------------------------------- /tree/recover_binary_search_tree.md: -------------------------------------------------------------------------------- 1 | # Recover Binary Search Tree 2 | 3 | > Two elements of a binary search tree (BST) are swapped by mistake. 4 | 5 | > Recover the tree without changing its structure. 6 | 7 | > Note: 8 | 9 | > A solution using O(n) space is pretty straight forward. Could you devise a constant space solution? 10 | 11 | 这题需要修复一颗二叉搜索树的两个交换节点数据,我们知道对于一颗二叉搜索树来说,如果按照中序遍历,那么它输出的值是递增有序的,所以我们只需要按照中序遍历输出,在输出结果里面找到两个异常数据(比它后面输出结果大),交换这两个节点的数据就可以了。 12 | 13 | 但是这题要求使用O(1)的空间,如果采用通常的中序遍历(递归或者栈)的方式,都需要O(N)的空间,所以这里我们用Morris Traversal的方式来进行树的中序遍历。 14 | 15 | Morris Traversal中序遍历的原理比较简单: 16 | 17 | + 如果当前节点的左孩子为空,则输出当前节点并将其有孩子作为当前节点。 18 | + 如果当前节点的左孩子不为空,在当前节点的左子树中找到当前节点在中序遍历下的前驱节点,也就是当前节点左子树的最右边的那个节点。 19 | + 如果前驱节点的右孩子为空,则将它的右孩子设置为当前节点,当前节点更新为其左孩子。 20 | + 如果前驱节点的右孩子为当前节点,则将前驱节点的右孩子设为空,输出当前节点,当前节点更新为其右孩子。 21 | 22 | 重复上述过程,直到当前节点为空,递归的时候我们同时需要记录错误的节点。那么我们如何知道一个节点的数据是不是有问题呢?对于中序遍历来说,假设当前节点为cur,它的前驱节点为pre,如果cur的值小于pre的值,那么cur和pre里面的数据就是交换的了。 23 | 24 | 代码如下: 25 | 26 | ```c++ 27 | class Solution { 28 | public: 29 | void recoverTree(TreeNode *root) { 30 | TreeNode* cur = 0; 31 | TreeNode* pre = 0; 32 | TreeNode* p1 = 0; 33 | TreeNode* p2 = 0; 34 | TreeNode* preCur = 0; 35 | 36 | bool found = false; 37 | 38 | if(!root) { 39 | return; 40 | } 41 | 42 | cur = root; 43 | while(cur) { 44 | if(!cur->left) { 45 | //记录p1和p2 46 | if(preCur && preCur->val > cur->val) { 47 | if(!found) { 48 | p1 = preCur; 49 | found = true; 50 | } 51 | p2 = cur; 52 | } 53 | 54 | preCur = cur; 55 | cur = cur->right; 56 | } else { 57 | pre = cur->left; 58 | while(pre->right && pre->right != cur) { 59 | pre = pre->right; 60 | } 61 | 62 | if(!pre->right) { 63 | pre->right = cur; 64 | cur = cur->left; 65 | } else { 66 | //记录p1和p2 67 | if(preCur->val > cur->val) { 68 | if(!found) { 69 | p1 = preCur; 70 | found = true; 71 | } 72 | p2 = cur; 73 | } 74 | preCur = cur; 75 | pre->right = NULL; 76 | cur = cur->right; 77 | } 78 | } 79 | } 80 | 81 | if(p1 && p2) { 82 | int t = p1->val; 83 | p1->val = p2->val; 84 | p2->val = t; 85 | } 86 | } 87 | }; 88 | ``` 89 | -------------------------------------------------------------------------------- /tree/same_tree.md: -------------------------------------------------------------------------------- 1 | # Same Tree 2 | 3 | > Given two binary trees, write a function to check if they are equal or not. 4 | Two binary trees are considered equal if they are structurally identical and the nodes have the same values. 5 | 6 | 7 | 题目翻译: 8 | 给两棵树,写一个函数来判断这两棵树是否相同. 我们判定一棵树是否相同的条件为这两棵树的结构相同,并且每个节点的值相同. 9 | 10 | 11 | 解题思路: 12 | 这道题中规中矩,很简单,我们直接用DFS前序遍历这两棵树就可以了. 13 | 14 | 时间复杂度分析: 15 | 因为是DFS, 所以时间复杂度为O(n) 16 | 17 | 18 | 代码如下: 19 | ```c++ 20 | /** 21 | * Definition for binary tree 22 | * struct TreeNode { 23 | * int val; 24 | * TreeNode *left; 25 | * TreeNode *right; 26 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 27 | * }; 28 | */ 29 | class Solution { 30 | public: 31 | bool isSameTree(TreeNode *p, TreeNode *q) { 32 | if(p == NULL && q == NULL) 33 | return true; 34 | else if(p == NULL || q == NULL) 35 | return false; 36 | if(p->val == q->val) 37 | { 38 | bool left = isSameTree(p->left, q->left); 39 | bool right = isSameTree(p->right,q->right); 40 | return left&&right; 41 | } 42 | return false; 43 | } 44 | }; 45 | 46 | ``` 47 | -------------------------------------------------------------------------------- /tree/sum_root_to_leaf_numbers.md: -------------------------------------------------------------------------------- 1 | # Sum Root to Leaf Numbers 2 | 3 | > Given a binary tree containing digits from 0-9 only, each root-to-leaf path could represent a number. 4 | > An example is the root-to-leaf path `1->2->3` which represents the number `123`. 5 | > Find the total sum of all root-to-leaf numbers. 6 | > For example, 7 | 8 | ``` 9 | 1 10 | / \ 11 | 2 3 12 | ``` 13 | > The root-to-leaf path `1->2` represents the number `12`. 14 | > The root-to-leaf path `1->3` represents the number `13`. 15 | > Return the sum = 12 + 13 = 25. 16 | 17 | 题目翻译: 18 | 给定一棵二叉树,仅包含0到9这些数字,每一条从根节点到叶节点的路径表示一个数。例如,路径`1->2->3`表示数值123。求出所有路径表示的数值的和。 19 | 上述例子中,路径`1->2`表示数值12,路径`1->3`表示数值13。它们的和是25。 20 | 21 | 题目分析: 22 | 从根节点到叶节点的遍历方法是深度优先搜索(DFS)。解决本题只需在遍历过程中记录路径中的数字,在到达叶节点的时候把记录下来的数字转换成数值,加到和里面即可。 23 | 24 | 时间复杂度: 25 | O(n) 26 | 27 | 代码如下: 28 | ```c++ 29 | /** 30 | * Definition for a binary tree node. 31 | * struct TreeNode { 32 | * int val; 33 | * TreeNode *left; 34 | * TreeNode *right; 35 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 36 | * }; 37 | */ 38 | class Solution { 39 | public: 40 | int sumNumbers(TreeNode* root) { 41 | vector arr; 42 | int sum = 0; 43 | dfs(root, arr, sum); 44 | return sum; 45 | } 46 | 47 | int vec2num(vector& vec) { 48 | int num = 0; 49 | for (auto n : vec) { 50 | num = num * 10 + n; 51 | } 52 | return num; 53 | } 54 | 55 | void dfs(TreeNode* node, vector& arr, int& sum) { 56 | if (node == nullptr) return; 57 | arr.push_back(node->val); 58 | if (node->left == nullptr && node->right == nullptr) { 59 | sum += vec2num(arr); 60 | } else { 61 | if (node->left != nullptr) dfs(node->left, arr, sum); 62 | if (node->right != nullptr) dfs(node->right, arr, sum); 63 | } 64 | arr.pop_back(); 65 | } 66 | 67 | }; 68 | ``` 69 | 70 | 71 | -------------------------------------------------------------------------------- /tree/symmetric_tree.md: -------------------------------------------------------------------------------- 1 | # Symmetric Tree 2 | 3 | > Given a binary tree, check whether it is a mirror of itself(ie, symmetric around its center) 4 | 5 | > For example, this tree is symmetric: 6 | 7 | 8 | ``` 9 | 1 10 | / \ 11 | 2 2 12 | / \ / \ 13 | 3 4 4 3 14 | ``` 15 | > But the following tree is not. 16 | 17 | ``` 18 | 1 19 | / \ 20 | 2 2 21 | \ \ 22 | 3 3 23 | ``` 24 | 25 | 题目翻译: 26 | 判断一棵树是不是自己的镜像, 根据以上正反两个例子,我想大家都明白这道题的题目要求了,简单明了. 27 | 28 | 解题思路: 29 | 递归: 30 | 这道题没什么特别的地方,现在这里简单的分析一下解题思路,从根节点往下,我们要判断三个条件. 31 | 1. 左右两个节点的大小是否相同. 32 | 2. 左节点的左孩子是否和右节点的右孩子相同. 33 | 3. 左节点的右孩子是否和右节点的左孩子相同. 34 | 35 | 36 | 循环: 37 | 这道题的难点在于循环解法, 如果是循环解法,我们必须要用到额外的存储空间用于回溯,关键是对于这道题目, 我们要用多少?要怎么用?要用什么样的存储空间? 38 | 39 | 40 | 41 | 递归求解,如果以上三个条件对于每一层都满足,我们就可以认为这棵树是镜像树. 42 | 43 | 时间复杂度: 44 | 递归:本质其实就是DFS,时间复杂度为O(n),空间复杂度O(1) 45 | 递归:时间复杂度O(n),空间复杂度O(n) 46 | 47 | 48 | 递归代码如下: 49 | 50 | ```c++ 51 | /** 52 | * Definition for binary tree 53 | * struct TreeNode { 54 | * int val; 55 | * TreeNode *left; 56 | * TreeNode *right; 57 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 58 | * }; 59 | */ 60 | class Solution { 61 | public: 62 | bool isSymmetric(TreeNode *root) { 63 | if(root == NULL) 64 | return true; 65 | return Helper(root->left,root->right); 66 | } 67 | 68 | bool Helper(TreeNode* left, TreeNode* right) 69 | { 70 | if(left == NULL&&right == NULL) 71 | return true; 72 | else if(left == NULL||right == NULL) 73 | return false; 74 | bool cond1 = left->val == right->val; 75 | bool cond2 = Helper(left->left,right->right); 76 | bool cond3 = Helper(left->right, right->left); 77 | return cond1&&cond2&&cond3; 78 | } 79 | 80 | }; 81 | ``` 82 | 83 | 循环解法: 84 | 我们主要想介绍一下这道题的循环解法,对于循环,我们要满足对于每一层进行check,代替了递归,一般树的循环遍历,我们都是用FIFO的queue来作为临时空间存储变量的,所以这道题我们也选取了queue,但是我们用两个queue,因为我们要对于左右同时进行检查,很显然一个queue是不够的,具体实现细节,咱们还是看代码吧,,我相信代码更能解释方法. 85 | 86 | 循环代码如下: 87 | ```c++ 88 | 89 | /** 90 | * Definition for binary tree 91 | * struct TreeNode { 92 | * int val; 93 | * TreeNode *left; 94 | * TreeNode *right; 95 | * TreeNode(int x) : val(x), left(NULL), right(NULL) {} 96 | * }; 97 | */ 98 | class Solution { 99 | public: 100 | bool isSymmetric(TreeNode *root) { 101 | if(root == NULL) 102 | return true; 103 | TreeNode* n1 = root->left; 104 | TreeNode* n2 = root->right; 105 | if(!n1&&!n2) 106 | return true; 107 | if((!n1&&n2)||(n1&&!n2)) 108 | return false; 109 | queue Q1; 110 | queue Q2; 111 | Q1.push(n1); 112 | Q2.push(n2); 113 | while(!Q1.empty() && !Q2.empty()) 114 | { 115 | TreeNode* tmp1 = Q1.front(); 116 | TreeNode* tmp2 = Q2.front(); 117 | Q1.pop(); 118 | Q2.pop(); 119 | if((!tmp1&&tmp2) || (tmp1&&!tmp2)) 120 | return false; 121 | if(tmp1&&tmp2) 122 | { 123 | if(tmp1->val != tmp2->val) 124 | return false; 125 | Q1.push(tmp1->left); 126 | Q1.push(tmp1->right); //note: this line we should put the mirror sequence in two queues 127 | Q2.push(tmp2->right); 128 | Q2.push(tmp2->left); 129 | } 130 | } 131 | return true; 132 | } 133 | }; 134 | ``` 135 | -------------------------------------------------------------------------------- /tree/validate_binary_search_tree.md: -------------------------------------------------------------------------------- 1 | # Validate Binary Search Tree 2 | 3 | > Given a binary tree, determine if it is a valid binary search tree (BST). 4 | 5 | > Assume a BST is defined as follows: 6 | 7 | > + The left subtree of a node contains only nodes with keys less than the node's key. 8 | + The right subtree of a node contains only nodes with keys greater than the node's key. 9 | + Both the left and right subtrees must also be binary search trees. 10 | 11 | 这题需要判断是不是一个正确的二叉搜索树,比较简单地一道题。 12 | 13 | 我们通过递归整棵树来解决,代码如下: 14 | 15 | ```c++ 16 | class Solution { 17 | public: 18 | bool isValidBST(TreeNode *root) { 19 | return valid(root, numeric_limits::min(), numeric_limits::max()); 20 | } 21 | 22 | bool valid(TreeNode* node, int minVal, int maxVal) { 23 | if(!node) { 24 | return true; 25 | } 26 | 27 | if(node->val <= minVal || node->val >= maxVal) { 28 | return false; 29 | } 30 | 31 | return valid(node->left, minVal, node->val) && 32 | valid(node->right, node->val, maxVal); 33 | } 34 | }; 35 | ``` 36 | --------------------------------------------------------------------------------