Approach 1: Sliding windows¶
Intuition¶
When we look for the longest subsequence each number can form, we're essentially looking for a window of consecutive numbers. Instead of repeatedly searching for where each window ends, we can be more efficient by using a technique called the sliding window approach.
This involves maintaining a range with a left and a right boundary that dynamically adjusts as we move through the sorted array. Starting with both boundaries at the beginning of the array, we extend the right boundary to include as many numbers as possible while ensuring the condition holds — specifically, that the difference between the largest and smallest numbers in the range does not exceed 2⋅k. If the condition is violated, we adjust the left boundary to restore the range. The maximum length of this range across all positions gives us the desired result.
Algorithm¶
- Sort the input array
numsin ascending order. - Initialize a variable
ansto 0 to track the maximum beauty possible. - Initialize a variable
leftto 0 to serve as theleftpointer of our window. - For each index
rightfrom 0 to length of nums: Whileleftis less thanrightand the difference between elements atrightandleftindices is less than or equal to2 * k. - Increment
leftpointer by 1. - Update
ansto be the maximum of currentansand(right - left + 1). Returnansas our answer.
Complexity¶
- Time complexity: $O(NLogN)$ with
Nis the length of input arraynumswhere: - $O(NLogN)$ is for sorting the input array
nums. - $O(N)$ is for finding the maximum beauty possible with sliding windows technique.
- Space complexity: $O(N)$ with space of the input array
numsand without some constants space forleft,right, andans.
Code¶
Go¶
import "sort"
func maximumBeauty(nums []int, k int) int {
sort.Ints(nums)
left, n, ans := 0, len(nums), 0
for right := 0; right < n; right++ {
for nums[right] - nums[left] > 2 * k {
left++
}
ans = max(ans, right - left + 1)
}
return ans
}
Approach 2: Binary search¶
Intuition¶
- The intuition for the binary search approach is not that we can sort the input array as we do not apply binary search onto the
numsvector itself, but it is that the possible results of the algorithm is a monotonic increasing solution range. In Layman term it means that if beauty of rangexis valid, then rangey < xmust be valid, so we memo thexand check if there is any range bigger thanx:[x+1...n]using binary search. Result <= x is valid, so we check whether there might be another result > x (monotonic increase)
Algorithm¶
- Similar to the sliding window approach, we need to sort first
- We will do a binary search on the possible valid range instead of the input
numsitself. For each possible beauty range, we apply binary search insde thehelper()function to see if it is valid.
Complexity¶
- Time complexity: $O(NLogN)$ as we need to sort the
numsarray - Space complexity: $O(1)$ for extra memory and $O(N)$ if we factor in the input array
Code¶
Cpp¶
class Solution {
public:
int maximumBeauty(vector<int>& nums, int k) {
int n = nums.size(), res = 0;
sort(nums.begin(), nums.end());
int left = 0, right = n;
while(left < right) {
int mid = left + (right-left)/2;
if(!helper(nums, k, n, mid)) {
res = max(res, mid);
left = mid+1;
} else right = mid;
}
res++;
return res;
}
bool helper(vector<int>& nums, int k, int n, int mid) {
int start = 0, l = 0;
for(int i = 0; i < n; i++) {
while(start < n && nums[i]-nums[start] > 2*k) {
start++;
}
l = max(l, i-start+1);
}
return l <= mid;
}
};
Approach 3: Prefix sum¶
Intuition¶
- This is the optimized solution of the problem as it does not require sorting the input array
nums - If we think of each element
nums[i]as a range of[i-k, i+k], then the result is the point in which it has the most overlapped range.
Algorithm¶
- First we create a
prefvector with a size of the biggest element increased by 1 insidenums:vector<int> pref(*max_element(nums.begin(), nums.end())+1, 0); - Then for each element
nums[i], we update the range[i-k, i+k], after that we just need to return the largest elementpref[i]as our result. - Because repeatedly updating
pref[i]as we iteratenumswill be costly and likely resulted with TLE, we can use mutable prefix sum to lazily update ourprefix, then do a final update before getting our result.
Complexity¶
- Time complexity: $O(N + M)$ with M is the value of the max element inside
numsarray, as we dont need to sort but need to populate theprefarray of sizemax_element+1 - Space complexity: $O(M)$ it is actually $O(M+1)$ asymptote to $O(M)$
Code¶
class Solution {
public:
int maximumBeauty(vector<int>& nums, int k) {
int n = nums.size(), res = 0, size = *max_element(nums.begin(), nums.end())+1;
if(n == 1) return 1;
vector<int> pref(size, 0);
for(int i : nums) {
if(i-k < 0) pref[0]++;
else pref[i-k]++;
if(i+k+1 >= size) pref[size-1]--;
else pref[i+k+1]--;
}
res = pref[0];
for(int i = 1; i < size; i++) {
pref[i] += pref[i-1];
res = max(res, pref[i]);
}
return res;
}
};