Array & String
1. Tools: string functions in C++
These functions are frequently used in algorithm solution, for more details and functions, refer to this.
// Searches the string for the first occurrence of the str, returns index
size_t find (const string& str, size_t pos = 0) const;
// Returns a newly constructed string object with its value initialized to a copy of a substring starting at pos with length len.
string substr (size_t pos = 0, size_t len = npos) const;
// erase characters from pos with length len
string &erase (size_t pos = 0, size_t len = npos);
// Returns the length of the string, in terms of bytes
size_t length();
2. Problem Type:
2.1. Elements Occurrences
When a problem need us to count the occurrences of elements, we could use Hash Table(in C++, it could be unordered_map or map) to solve the problem. Key is the elements in the string, while the value is the occurrence of that. Sometimes, we are only required to determine whether elements is exist in string. In this kind of problem, we can also use bitset.
2.1.1. Examples:
-
Determine if all characters of a string are unique.
-
Given two strings, determine if they are permutations of each other
- 1 method: use hash table to determine whether count the occurrences in two strings are the same
- 2nd method: sort two strings, then compare if two strings are the same.
-
Given a newspaper and message as two strings, check if the message can be composed using letters in the newspaper.
- use hash table to determine whether the letter occurrences in newspaper is greater than that in message.
- Time complex: O(n + m)
- Space complex: O( c ), which is the the number of letters
-
Anagram: Write a method anagram(s,t) to decide if two strings are anagrams or not.
- To solve in O(n) time and O(1) extra space, we could hash the string s as dictionary. Then, iterate the string t and find out whether dictionary can be the anagram of t.
2.2. Using hash table to store the previous result
The main idea is similar to the dynamic programming, as hash takes O(1) time to attain the previous result.
2.2.1 Examples:
-
Find a pair of two elements in an array, whose sum is a given target number.
- Let the pair as a + remains = target; We could use (key: a) as the elements in array, while (value: remain) is the a need to sum to target. If we find that the remain exist in the hash table, than return the result.
- Time complex: O(n)
-
Get the length of the longest consecutive elements sequence in an array. For example, given [31, 6, 32, 1, 3, 2],the longest consecutive elements sequence is [1, 2, 3]. Return its length: 3.
- In this problem, we can using tuple<min, max> as value. For each elements n, we will find if n-1 or n+1 exist in hash table. If not, the key-value would be:{n: <n, n>}. If so, we can update previous tuple as new result.
2.3. Merge Array
2.3.1 Examples:
vector<int> mergeSortedArray(vector<int> &A, vector<int> &B) {
// write your code here
vector<int> result;
if(A.empty() )
return B;
if (B.empty() )
return A;
int i = 0, j = 0;
int lenA = A.size(), lenB = B.size();
while(i < lenA && j < lenB) {
if(A[i] < B[j]) {
result.push_back(A[i]);
i++;
} else {
result.push_back(B[j]);
j++;
}
}
while(i < lenA ) {
result.push_back(A[i]);
i++;
}
while(j < lenB) {
result.push_back(B[j]);
j++;
}
return result;
}
2.4. Mean/Median/#n in Arrays
When the problem gives two or more arrays, and want you to find out the median/mean of the arrays. Due to quantity of the numbers, it’s can be hard to sort the array. Thus, we will use two priority queue to maintain the data. Take mean as example, the smaller priority queue’s size is size/2, then the larger priority queue’s size is size - size/2. Note here, the smaller queue uses default less< T>, while the larger queue uses larger< T>. Finally, if the size is odd, then get the top of large queue, else take the top of the smaller queue.
Similar problem can also be get the first N large/small numbers in a data stream, all we need to do is change the size of priority_queue that need to maintain.
2.4.1 Examples:
void mergeSortedArray(int A[], int m, int B[], int n) {
// write your code here
int i = m-1, j = n-1, k = m+n-1;
while(k >=0){
if(i < 0){
A[k--] = B[j--];
} else if (j < 0){
A[k--] = A[i--];
} else if (A[i] >= B[j]){
A[k--] = A[i--];
} else {
A[k--] = B[j--];
}
}
}
547. Intersection of Two Arrays
vector<int> intersection(vector<int> &nums1, vector<int> &nums2) {
// write your code here
vector<int> res;
int lenA = nums1.size(), lenB = nums2.size();
int i =0, j = 0;
sort(nums1.begin(), nums1.end());
sort(nums2.begin(), nums2.end());
while(i < lenA && j < lenB) {
if(nums1[i] < nums2[j])
i++;
if(nums1[i] > nums2[j])
j++;
if(nums1[i] == nums2[j]) {
if(res.empty() || res[res.size() -1] < nums1[i])
res.push_back(nums1[i]);
i++;
j++;
}
}
return res;
}
65. Median of two Sorted Arrays
Use two priority queues to maintain the median.
double findMedianSortedArrays(vector<int> &A, vector<int> &B) {
// write your code here
double result = 0.0;
int m = A.size(), n = B.size();
priority_queue<int> smaller, larger;
int size = (m + n) / 2;
A.insert(A.end(), B.begin(), B.end());
if(A.size() == 1) {
result += A[0];
return result;
}
for(int i = 0; i < A.size(); i++) {
if(i < size) {
smaller.push(A[i]);
continue;
}
int tmp = smaller.top();
if(A[i] < tmp ) {
smaller.pop();
smaller.push(A[i]);
tmp = -tmp;
larger.push(tmp);
} else {
larger.push(-A[i]);
}
}
int tmp = larger.top();
if ( (m + n) % 2) {
result -= tmp;
} else {
result += smaller.top();
result -= tmp;
result /= 2;
}
return result;
}
2.4. Subarray: PrefixSum
Maintain an array that
PrefixSum[i] = A[0] + A[1] + … A[i - 1], PrefixSum[0] = 0,
which will take O(n) space and O(n) time.
What is the point to maintain such an array? When we need the sum from index i, to index j, then we have Sum(i~j) = PrefixSum[j + 1] - PrefixSum[i].
2.4.1 Examples:
int maxSubArray(vector<int> &nums) {
// write your code here
vector<int> prefix_sum (nums.size() +1);
for(int i = 0; i < nums.size(); i++) {
prefix_sum[i +1] = prefix_sum[i] + nums[i];
}
int sum = INT_MIN;
for(int i = 0; i < prefix_sum.size() -1; i++) {
for(int j = i +1; j < prefix_sum.size(); j++) {
int tmp = prefix_sum[j] - prefix_sum[i];
if(tmp > sum ) {
sum = tmp;
}
}
}
return sum;
}
Using sliding window method can be more efficient
int maxSubArray(vector<int> &nums) {
// write your code here
if(nums.empty() )
return 0;
int sum = 0, maxSum = INT_MIN;
for(auto num : nums) {
if (sum < 0)
sum = 0;
sum += num;
maxSum = max(sum, maxSum);
}
return maxSum;
}
vector<int> subarraySumClosest(vector<int> &nums) {
// write your code here
vector<int> pre_sum (nums.size() +1);
for(int i = 0; i < nums.size(); i++) {
pre_sum[i +1] = pre_sum[i] + nums[i];
}
int diff = INT_MAX;
vector<int> result{0, 0};
for(int i = 0; i < pre_sum.size() -1; i++) {
for(int j = i +1; j < pre_sum.size(); j++) {
int tmp = abs(pre_sum[j] - pre_sum[i]);
if(tmp == 0 )
return vector<int> {i, j -1};
if(tmp < diff) {
diff = tmp;
result[0] = i;
result[1] = j -1;
}
}
}
return result;
}
138. Subarray Sum
Using hash map to record the previous result.
vector<int> subarraySum(vector<int> &nums) {
// write your code here
if(nums.empty())
return nums;
vector<int> res;
int sum = 0;
unordered_map<int,int> hash;
hash[0] = 0;
for(int i = 0; i < nums.size(); i++){
sum += i;
if(hash.find(-sum) != hash.end()){
res.push_back(hash[-sum] + 1);
res.push_back(i);
}
else
{
hash[sum] = i;
}
}
return res;
}