@@ -27,3 +27,116 @@ k = 2
2727Output:
2828[8, 9]
2929```
30+
31+ ## Constraints
32+
33+ - 1 <= target <= nums.length
34+ - 1 <= nums.length <= 104
35+ - nums is sorted in ascending order.
36+ - -10^4 <= nums[ i] , x <= 10^4
37+
38+ ## Topics
39+
40+ - Array
41+ - Two Pointers
42+ - Binary Search
43+ - Sliding Window
44+ - Sorting
45+ - Heap (Priority Queue)
46+
47+ ## Solution
48+
49+ ### Using Optimized Binary Search
50+
51+ The key idea behind this algorithm is to efficiently locate the k numbers in a sorted array closest to a given target
52+ value by minimizing unnecessary comparisons. First, the algorithm uses binary search to find the element nearest to the
53+ target. Once this element is identified, the algorithm employs a two-pointer approach to establish a sliding window of
54+ size k around this element. Starting with this element’s previous and next elements of this element, keep expanding
55+ toward the left and right boundaries, choosing the closest element to the target value at each step.
56+
57+ Now, let’s look at the workflow of the implementation:
58+
59+ Before we proceed to the optimized approach, a few points need some consideration:
60+
61+ - If the length of nums is the same as the value of k, return all the elements.
62+ - If target is less than or equal to the first element in nums, the first k elements in nums are the closest integers to
63+ target. For example, if nums= [ 1, 2, 3] , target= 0, and k = 2, then the two closest integers to target are [ 1, 2] .
64+ - If target is greater than or equal to the last element in nums, the last k elements in nums are the closest integers to
65+ target. For example, if nums= [ 1, 2, 3] , target= 4, and k = 2, then the two closest integers to target are [ 2, 3] .
66+ - Otherwise, we search for the k closest elements in the whole array.
67+
68+ When we have to find k elements in the complete array, instead of traversing the whole array, we can use binary search
69+ to limit our search to the relevant parts. The optimized approach can be divided into two parts:
70+
71+ - Use binary search to find the index of the first closest integer to target in nums.
72+ - Use two pointers, window_left and window_right, to maintain a sliding window. We move the pointers conditionally,
73+ either towards the left or right, to expand the window until its size gets equal to k. The k elements in the window are
74+ the k closest integers to target.
75+
76+ Here’s how we’ll implement this algorithm:
77+
78+ - If the length of nums is the same as k, return nums.
79+ - If target ≤ nums[ 0] , return the first k elements in nums.
80+ - If target ≥ nums[ len(nums) - 1] , return the last k elements in nums.
81+ - Use binary search to find the index, first_closest, of the closest integer to target.
82+ - Initialize two pointers, left and right, to 0 and len(nums)-1, respectively.
83+ - Calculate the index of the middle pointer, mid, and check:
84+ - If the value pointed to by mid is equal to target, i.e., nums[ mid] = target, return mid.
85+ - If nums[ mid] < target, move left toward the right.
86+ - If nums[ mid] > target, move right toward the left.
87+ - Once we have found the closest element to target, return the index, first_closest, which points to it.
88+ - Create two pointers, window_left and window_right. The window_left pointer initially points to the index of the element
89+ that is to the left of nums[ first_closest] , and window_right points to the element that is to the right of window_left.
90+ This means window_left = nums[ first_closest] - 1, and window_right = window_left + 1.
91+ - Traverse nums while the sliding window size is less than k. In each loop, adjust the window size by moving the pointers
92+ as follows:
93+ - If nums[ window_left] is closer to target than nums[ window_right] , or if both are at equal distance, that is,
94+ |nums[ window_left] - target| ≤ |nums[ window_right] - target|, then window_left = window_left - 1.
95+ - If nums[ window_right] is closer to target than nums[ window_left] , that is, |nums[ window_right] - target| <
96+ |nums[ window_left] - target|, then window_right = window_right + 1.
97+ - Once we have k elements in the window, return them as the output.
98+
99+ ![ Solution 1] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_1.png )
100+ ![ Solution 2] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_2.png )
101+ ![ Solution 3] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_3.png )
102+ ![ Solution 4] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_4.png )
103+ ![ Solution 5] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_5.png )
104+ ![ Solution 6] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_6.png )
105+ ![ Solution 7] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_7.png )
106+ ![ Solution 8] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_8.png )
107+ ![ Solution 9] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_9.png )
108+ ![ Solution 10] ( ./images/solutions/k_closest_elements_with_modified_binary_search_solution_10.png )
109+
110+ To summarize, we use binary search to locate the first closest element to target, then create a sliding window using two
111+ pointers to select the k closest elements. The window adjusts its size by moving the pointers based on which adjacent
112+ element is closer to the target. Eventually, the window will have the required k elements, which are then returned.
113+
114+ #### Time Complexity
115+
116+ The time complexity of the binary search is O(logn), where n is the length of the input array nums. The sliding window
117+ step involves traversing the array once while adjusting the window size, and it takes O(k) time. The overall time
118+ complexity becomes O(logn+k).
119+
120+ #### Space Complexity
121+
122+ The space complexity of this solution is O(1).
123+
124+ ### Alternative Solution
125+
126+ Now, let’s see another way to solve this problem with slightly better time complexity. In this approach, we focus on
127+ choosing the left bound for binary search such that the search space reduces to n - k.
128+
129+ We initialize the left pointer to 0 and the right pointer to len(nums) - k. These values are assigned based on the
130+ observation that the left bound can’t exceed len(nums) - k to ensure we have enough elements for the window.
131+
132+ Next, while left < right, we perform a binary search to find the optimal position for the left bound of the sliding
133+ window. We calculate mid and compare the distances between target and the elements at nums[ mid] and nums[ mid + k] . If
134+ |nums[ mid] - target| is greater than |nums[ mid + k] - target|, it means the element at nums[ mid] is farther from target
135+ compared to the element at nums[ mid + k] . In this case, it updates left to mid + 1, shifting the left bound to the right.
136+ Otherwise, it updates the right to mid, moving the right bound closer to the left.
137+
138+ Once the while loop completes, return the elements of nums starting from left and including the next k elements. These
139+ elements represent the k closest elements to target. The creation of this list will take O(k) time.
140+
141+ Since the initial search space has a size of n - k, the binary search takes O(log(n−k)). Therefore, the time complexity
142+ of this solution is O(log(n−k)+k). The space complexity remains O(1).
0 commit comments