diff --git a/Academy DSA Typed Notes/AIML/DSA Arrays 1.md b/Academy DSA Typed Notes/AIML/DSA Arrays 1.md new file mode 100644 index 0000000..da9e0fc --- /dev/null +++ b/Academy DSA Typed Notes/AIML/DSA Arrays 1.md @@ -0,0 +1,826 @@ +# DSA: Arrays 1 + +## Introduction To Arrays + +### Definition + +Array is the collection of same types of data. The datatype can be of any type i.e, int, float, char, etc. Below is the declaration of the array: +``` +int arr[n]; +``` +Here, ‘int’ is the datatype, ‘arr’ is the name of the array and ‘n’ is the size of an array. +We can access all the elements of the array as arr[0], arr[1] ….. arr[n-1]. + +**Note:** Array indexing starts with 0. + +> Why indexing starts at 0 ? (Optional, discuss only if asked by someone) + +An array arr[i] is interpreted as *(arr+i). Here, arr denotes the address of the first array element or the 0 index element. So *(arr+i) means the element at i distance from the first element of the array. + +--- + +### Question +What will be the indices of the first and last elements of an array of size **N**? + +### Choices +- [ ] 1,N +- [x] 0,N-1 +- [ ] 1,N-1 +- [ ] 0,N + +--- + +### Print all elements of the array + +The elements of arrays can be printed by simply traversing all the elements. Below is the pseudocode to print all elements of array. + +``` +function print_array(arr[], n){ + for(i -> 0 to n-1){ + print(arr[i]); + } +} +``` + + +--- +### Question + +What is the time complexity of accessing element at the ith index in an array of size **N**? + +### Choices +- [ ] O(N) +- [x] O(1) +- [ ] O(N*N) +- [ ] O(log(N)). + +--- + + +### Question +``` +int arr[5] = {5,-4,8,9,10}; +``` +Which of the following statements correctly prints the sum of the 1st and 5th elements of the array? + +### Choices +- [ ] print(arr[0] + arr[5]) +- [x] print(arr[0] + arr[4]) +- [ ] print(arr[1] + arr[5]) +- [ ] print(arr[1] + arr[4]) + +--- + +## Problem 1 Reverse the array + + +### Problem Statement + +Given an array arr of size 'N'. Reverse the entire array. + +### TestCase: + +#### Input: +``` +N = 5 +arr = {1,2,3,4,5} +``` + +#### Output: + +``` +arr = {5,4,3,2,1} +``` + +### Approach + +The simplest approach to solve the problem is to keep two pointers at the end, traverse the array till middle element and swap first element with last element, second with second last, third with third last and so on. +![](https://i.imgur.com/xUDocWR.png) + +**What should be the stopping condition?** +Say N = 6 + +| i | j | swap | +| -------- | -------- | -------- | +| 0 | 5 | A[0] & A[5] | +| 1 | 4 | A[1] & A[4] | +| 2 | 3 | A[2] & A[3] | +| 3 | 2 | STOP | + +Say N = 5 + +| i | j | swap | +| -------- | -------- | -------- | +| 0 | 4 | A[0] & A[4] | +| 1 | 3 | A[1] & A[3] | +| 2 | 2 | STOP | + +We can stop as soon as $i>=j$ + + +### Pseudocode + +``` +Function reverse(arr[], N, start, end){ + int i = start; + int j = end; + while(i < j) { + temp = arr[i]; + arr[i] = arr[j]; + arr[j] = temp; + i++; + j--; + } +} + +Function solve(arr[], int N) { + return reverse(arr, N, 0, N-1); +} +``` + +### Complexity: +**Time Complexity - O(N). +Space Complexity - O(1).** + +--- +## Problem 2 Rotate K times + + +Given an array 'arr' of size 'N'. Rotate the array from right to left 'K' times. (i.e, if K = 1, last element will come at first position,...) + +### TestCase: + +#### Input: +``` +N = 5 +arr = {1,2,3,4,5} +k = 2 + +``` + +#### Output: + +``` +arr = {4,5,1,2,3} +``` + +### Explanation: + +Initially the array is: + +| 1 | 2 | 3 | 4 | 5 | + +After 1st rotation: + +| 5 | 1 | 2 | 3 | 4 | + +After 2nd rotation: + +| 4 | 5 | 1 | 2 | 3 | + + +## BruteForce Approach + +Simple approach is to rotate the array one element at a time. + +### Pseudocode + +``` +Function rotateK(arr[], N, K){ + for(i -> 0 to K-1){ + temp = arr[N-1]; + for(j -> N-1 to 1){ + arr[j] = arr[j-1]; + } + arr[0] = temp; + } +} +``` + +### Complexity: +**Time Complexity - O(N*k). +Space Complexity - O(1).** + + +## Optimized Appraoch Observations + +* After K rotations, last K elements become 1st K elements and rest elements will go at back. + For example - Suppose we have an array arr as shown below and k = 3. + `1 2 3 4 5 6 7` + After 1st rotation, k=1: + `7 1 2 3 4 5 6` + After 2nd rotation, k=2: + `6 7 1 2 3 4 5` + After 3rd rotation, k=3: + `5 6 7 1 2 3 4` +So, we have observed that last 3(K=3) elements i.e, `5 6 7` comes in front and rest elements appear at the end. + +Therefore, we will first reverse the entire array, then reverse first K elements individually and then next N-K elements individually. +``` +1 2 3 4 5 6 7 //Given Array, K=3 + +7 6 5 4 3 2 1 //Reversed Entire Array + +5 6 7 4 3 2 1 //Reversed first K elements + +5 6 7 1 2 3 4 //Reversed last N-K elements +``` + + +## For reversing part of an array +To our function for reversing array, we can simply pass the start and end index of the part of the array we want to reverse. + + +### Pseudocode +``` +Function countgreater(arr[], N, k){ + reverse(arr, N, 0, N-1); + reverse(arr, N, 0, K-1); + reverse(arr, N, K, N-1); +} +``` + +### Edge Case + +K might be very large but if we observe carefully then after N rotations the array comes to its initial state. +Hence, K rotation is equivalent to K%N rotations. + + +Suppose we have an array: + `1 2 3 4` +After 1st rotation, the array becomes: + `2 3 4 1` +After 2nd rotation, the array becomes: + `3 4 1 2` +After 3rd rotation, the array becomes: + `4 1 2 3` +Afer 4th rotation, the array becomes: + `1 2 3 4` +Hence, we have concluded that after **N** rotations, the array become same as before 1st rotation. + +### Final Pseudocode +``` +Function countgreater(arr[], N, K){ + K = k % N; + reverse(arr, N, 0, N-1); + reverse(arr, N, 0, K-1); + reverse(arr, N, K, N-1); +} +``` + +### Complexity: +**Time Complexity - O(N). +Space Complexity - O(1).** + + +--- +## Dynamic Arrays + + +### Question: +What is the drawback of normal arrays? + +### Issue: +The size has to be declared before hand. + + +## Dynamic Arrays +* A dynamic array is an array with a big improvement: automatic resizing. +* It expands as you add more elements. So you don't need to determine the size ahead of time. + +### Strengths: +**Fast lookups:** Just like arrays, retrieving the element at a given index takes +O(1) time. +**Variable size:** You can add as many items as you want, and the dynamic array will expand to hold them. + +### Weaknesses: +> Note: We'll study more about it later, below is just an overview. + +**Slow worst-case appends:** +* Usually, adding a new element at the end of the dynamic array takes O(1) time. +* But if the dynamic array doesn't have any room for the new item, it'll need to expand, which takes O(n) time. + * It is because we have to take a new array of bigger size and copy all the elements to a new array and then add a new element. + * So, Time Complexity to add a new element to Dynamic array is **O(1) amortised**. +* Amortised means when most operations take **O(1)** but some operations take **O(N)**. + + +### Dynamic Arrays in Different Languages + +### Java +``` +ArrayList al = new ArrayList<>(); //Arraylist is created +``` + +``` +al.add("50"); //50 is inserted at the end +``` + +``` +al.clear(); // al={} +``` + +``` +for (int i = 0; i < al.size(); i++) { + System.out.print(al.get(i) + " "); +} //iterating the Arraylist +``` + +### C++ +``` +vector a; //vector is created +``` +``` +a.push_back(60); +//a = {10, 20, 30, 40, 50, 60} after insertion at end +``` + +``` +a.clear(); // a={} +``` +``` +for(int i=0; i 0 to Queries.length-1){ + L = Queries[i][0] + R = Queries[i][1] + + sum = 0 + for( j -> L to R){ + sum += Array[j] + } + print(sum) + } +} +``` +***Time Complexity : O(N * Q)** +**Space Complexity : O(1)*** + +>Since Time complexity of this approach is O(N * Q) then in a case where there are 10^5 elements & 10^5 queries where each query is (L=0 and R=10^5-1) we would encounter **TLE** hence this approach is Inefficient + +--- +### Question +Given the scores of the 10 overs of a cricket match +2, 8, 14, 29, 31, 49, 65, 79, 88, 97 +How many runs were scored in just 7th over? + +### Choices +- [x] 16 +- [ ] 20 +- [ ] 18 +- [ ] 17 + + +### Explanation +Total runs scored in over 7th : 65 - 49 = 16 +(score[7]-score[6]) + +--- + +### Question +Given the scores of the 10 overs of a cricket match +2, 8, 14, 29, 31, 49, 65, 79, 88, 97 +How many runs were scored from 6th to 10th over(both included)? + +### Choices +- [x] 66 +- [ ] 72 +- [ ] 68 +- [ ] 90 + +### Explanation +Total runs scored in over 6th to 10th : 97 - 31 = 66 +(score[10]-score[5]) + +--- + +### Question +Given the scores of the 10 overs of a cricket match +2, 8, 14, 29, 31, 49, 65, 79, 88, 97 +How many runs were scored in just 10th over? + + +### Choices + +- [ ] 7 +- [ ] 8 +- [x] 9 +- [ ] 10 + +### Explanation +Total runs scored in over 6th to 10th : 97 - 88 = 9 +(score[10]-score[9]) + +--- + +### Question +Given the scores of the 10 overs of a cricket match +2, 8, 14, 29, 31, 49, 65, 79, 88, 97 +How many runs were scored from 3rd to 6th over(both included)? + +### Choices + +- [ ] 70 +- [ ] 40 +- [ ] 9 +- [x] 41 + +### Explanation +Total runs scored in over 3rd to 6th : 49-8 = 41 +(score[6]-score[2]) + +--- + +### Question +Given the scores of the 10 overs of a cricket match +2, 8, 14, 29, 31, 49, 65, 79, 88, 97 +How many runs were scored from 4th to 9th over(both included)? + +### Choices + +- [ ] 75 +- [ ] 80 +- [x] 74 +- [ ] 10 + +### Explanation +Total runs scored in over 4th to 9th : 88 - 14 = 74 +(score[9]-score[3]) + +--- + +## Observation for Optimised Solution + +* On observing cricket board score, we can say that queries can be answered in just constant time since we have cummulative scores. + +* In the similar manner, if we have cummulative sum array for the above problem, we should be able to answer it in just constant time. + +* We need to create cumulative sum or prefix sum array for above problem. + + + +### How to create Prefix Sum Array ? + + +pf[i] = sum of all elements from 0 till ith index. + + + +### Example +Step1:- +Provided the intial array:- +| 2 | 5 | -1 | 7 | 1 | +| --- | --- | --- | --- | --- | + +We'll create prefix sum array of size 5 i.e. size equal to intial array. +`Initialise pf[0] = initialArray[0]` + +| 2 | - | - | - | - | +| --- | --- | --- | --- | --- | + +| 2 | 7 | - | - | - | +| --- | --- | --- | --- | --- | + +| 2 | 7 | 6 | - | - | +| --- | --- | --- | --- | --- | + +| 2 | 7 | 6 | 13 | - | +| --- | --- | --- | --- | --- | + +| 2 | 7 | 6 | 13 | 14 | +| --- | --- | --- | --- | --- | + + +Finally we have the prefix sum array :- + +| 2 | 7 | 6 | 13 | 14 | +| --- | --- | --- | --- | --- | + +--- + +### Question +Calculate the prefix sum array for following array:- + +| 10 | 32 | 6 | 12 | 20 | 1 | +| --- | --- | --- | --- | --- |:---:| + +### Choices +- [x] `[10,42,48,60,80,81]` +- [ ] `[10,42,49,60,79,81]` +- [ ] `[42,48,60,80,81,10]` +- [ ] `[15,43,58,61,70,82]` + +--- + +### Brute Force + +Below is the Brute Force Code to create Prefix Sum Array + +```cpp= +pf[N] +for(i -> 0 to N-1){ + + sum = 0; + + for(j -> 0 to i) { + sum = sum + A[j] + } + + pf[i] = sum; +} +``` + + + +## Observation for Optimising Prefix Sum array calculations + +pf[0] = A[0] +pf[1] = A[0] + A[1] +pf[2] = A[0] + A[1] + A[2] +pf[3] = A[0] + A[1] + A[2] + A[3] +pf[4] = A[0] + A[1] + A[2] + A[3] + A[4] + +* Can we observe that we are making redundant calculations? + +* We could utilise the previous sum value. + * pf[0] = A[0] + * pf[1] = pf[0] + A[1] + * pf[2] = pf[1] + A[2] + * pf[3] = pf[2] + A[3] + * pf[4] = pf[3] + A[4] + +* **Generalised Equation is:** ```pf[i] = pf[i-1] + A[i]``` + +## Optimised Code: + +```cpp= +pf[N] +pf[0] = A[0]; +for(i -> 1 to N-1){ + pf[i] = pf[i-1] + A[i]; +} +``` +* Time Complexity: O(N) + +### How to answer the Queries ? + + +A[ ] = [-3, 6, 2, 4, 5, 2, 8, -9, 3, 1] + +pf[ ] =[-3, 3, 5, 9, 14, 16, 24, 15, 18, 19] + +| L | R | Solution | | +| -------- | -------- | -------- | -------- | +| 4 | 8 | pf[8] - pf[3] | 18 - 9 = 9 | +| 3 | 7 | pf[7] - pf[2] |15 - 5 = 10 | +| 1 | 3 | pf[3] - pf[0] |9 - (-3) = 12 | +| 0 | 4 | pf[4] |14 | +| 7 | 7 | pf[7] - pf[6] |15 - 24 = -9 | + + + +## Generalised Equation to find Sum: + +sum[L R] = pf[R] - pf[L-1] + +Note: if L==0, then sum[L R] = pf[R] + + +### Complete code for finding sum of queries using Prefix Sum array: + +```cpp = +Function querySum(Queries[][], Array[], querySize, size){ + //calculate pf array + pf[N] + pf[0] = A[0]; + for(i -> 1 to N-1){ + pf[i] = pf[i-1] + A[i]; + } + + //answer queries + for( i -> 0 to Queries.length-1){ + L = Queries[i][0]; + R = Queries[i][1]; + + if(L == 0) { + sum = pf[R] + } + else { + sum = pf[R] - pf[L - 1]; + } + + print(sum); + } +} +``` +***Time Complexity : O(N+Q)** +**Space Complexity : O(N)*** + + + +### Space Complexity can be further optimised if you modify the given array. + +```cpp +Function prefixSumArrayInplace(Array[], size){ + for(i -> 1 to size-1){ + Array[i] = Array[i-1] + Array[i]; + } +} +``` +***Time Complexity : O(N)** +**Space Complexity : O(1)*** + + +--- +## Problem 4 Sum of even indexed elements + + +Given an array of size N and Q queries with start (s) and end (e) index. For every query, return the sum of all **even indexed elements** from **s to e**. + +### Example + +```plaintext +A[ ] = { 2, 3, 1, 6, 4, 5 } +Query : + 1 3 + 2 5 + 0 4 + 3 3 + +Ans: + 1 + 5 + 7 + 0 +``` +### Explanation: +* From index 1 to 3, sum: A[2] = 1 +* From index 2 to 5, sum: A[2]+A[4] = 5 +* From index 0 to 4, sum: A[0]+A[2]+A[4] = 7 +* From index 3 to 3, sum: 0 + +### Brute Force +How many of you can solve it in $O(N*Q)$ complexity? +**Idea:** For every query, Iterate over the array and generate the answer. + +### Sum of even indexed elements Observation for Optimisation + + +Whenever range sum query is present, we should think in direction of **Prefix Sum**. + +**Hint 1:** Should we find prefix sum of entire array? +**Expected:** No, it should be only for even indexed elements. + +**We can assume that elements at odd indices are 0 and then create the prefix sum array.** + + +Consider this example:- + +``` + A[] = 2 3 1 6 4 5 +PSe[] = 2 2 3 3 7 7 +``` + +> Note: PSe[i] denotes sum of all even indexed elements from 0 to ith index. + + +If **i is even** we will use the following equation :- +
+PSe[i] = PSe[i-1] + A[i] +
+ +If **i is odd** we will use the following equation :- +
+PSe[i] = PSe[i-1] +
+ + +--- +### Question +Construct the Prefix Sum for even indexed elements for the given array +[2, 4, 3, 1, 5] + +### Choices +- [ ] 1, 6, 9, 10, 15 +- [x] 2, 2, 5, 5, 10 +- [ ] 0, 4, 4, 5, 5 +- [ ] 0, 4, 7, 8, 8 + + +### Explanation + +We will assume elements at odd indices to be 0 and create a prefix sum array taking this assumption. +So ```2 2 5 5 10``` will be the answer. + +--- + +### Pseudocode + + +```cpp +Function sumOfEvenIndexed(Array[], Queries[][], N){ + // prefix sum for even indexed elements + PSe[N]; + + PSe[0] = Array[0] + + for(i -> 1 to N-1){ + if(i % 2 == 0){ + PSe[i] = PSe[i-1] + Array[i]; + } + else { + PSe[i] = PSe[i-1]; + } + } + for(i -> 0 to Queries.length-1) { + s = Queries[i][0] + e = Queries[i][1] + if(s == 0){ + print(PSe[e]) + } + else { + print(PSe[e]-PSe[s-1]) + } + + } + +} +``` +### Complexity +-- TC - $O(n)$ +-- SC - $O(n)$ + diff --git a/Academy DSA Typed Notes/AIML/DSA Arrays 2.md b/Academy DSA Typed Notes/AIML/DSA Arrays 2.md new file mode 100644 index 0000000..7573276 --- /dev/null +++ b/Academy DSA Typed Notes/AIML/DSA Arrays 2.md @@ -0,0 +1,653 @@ +# DSA: Arrays 2 + +## Problem 1 Count of Pairs ag + +Given a string **s** of lowercase characters, return the **count of pairs (i,j)** such that **i < j** and **s[ i ] is 'a'** and **s[ j ] is 'g'**. + +### Example 1 + +```plaintext +String s = "abegag" +Ans = 3 +``` + +### Explanation: +Here, [i,j] such that i 0 to n-1) { + if(str[i] == 'a'){ + for (j -> i+1 to N-1){ + if(str[j] == 'g') { + result++; + } + } + } + } + return result; +} +``` + +### Time and Space Complexity +-- TC - $O(n^2)$ +-- SC - $O(1)$ + + +## Optimised solution + + +### Observation: +* For every **'g'**, we need to know the count of **'a'** on left side of **'g'**. + +* We will store the count of **'a'** and whenever **'g'** is encountered, we will add the count of **'a'** to the result. + +### Dry Run +Example: **"acbagkagg"** + +![reference link](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/032/986/original/dry.jpeg?1682663462) + +### Pseudocode +```cpp +function count_ag(str) { + result = 0; + count_a = 0; + for(i -> 0 to n-1) { + if(str[i] == 'a') { + count_a++; + } + else if(str[i] == 'g') { + result += count_a; + } + } + return result; +} +``` +#### Time and Space Complexity + +What will be T.C and S.C for this approach? +-- TC - $O(n)$ +-- SC - $O(1)$ + + +--- +## Introduction to Subarrays + + +### Definition +A subarray is a contiguous part of an array. It is formed by selecting a range of elements from the array. A subarray can have one or more elements and must be a contiguous part of the original array. + +### Example +Consider the following array of integers: + +| 4 | 1 | 2 | 3 | -1 | 6 | 9 | 8 | 12 | +| - | - | - | - | - | - | - | - | - | + +* `2, 3, -1, 6` is a subarray of length 4. +* `9` is a subarray of single element. +* `4, 1, 2, 3, -1, 6, 9, 8, 12` is a subarray consisting of all the array elements. +* `4, 12` is **not** a subarray because loop does not count as subarray. +* `1, 2, 6` is **not** a subarray beacuse the array elements must be contiguous. +* `3, 2, 1, 4` is **not** a subarray because order of the elements in a subarray should be same as in the array. + + +--- +### Question +A[] = { 2, 4, 1, 6, -3, 7, 8, 4} +Which of the following is a valid subarray? + +### Choices +- [ ] {1, 6, 8} +- [ ] {1, 4} +- [ ] {6, 1, 4, 2} +- [x] {7, 8, 4} + + +### Explanation + +{1, 6, 8} & {1, 4} are not contiguous parts of array. {6, 1, 4, 2} is not in the same order as in original array. Only {7, 8, 4} is a valid subarray. + +--- + +## Representation of a Subarray + +### Representation of a Subarray +A Subarray can be uniquely represented in following ways: +1. By specifying the `start` and `end` index of the subarray. +2. By specifying the `start` index and `length` of the subarray. + +If we consider the same array from the above example, +| 4 | 1 | 2 | 3 | -1 | 6 | 9 | 8 | 12 | +| - | - | - | - | - | - | - | - | - | + +The subarray `2, 3, -1, 6` can be represented as +* the range of elements starting at index `2` and ending at index `5` (0-based indexing). +* the range of elements having length of `4` with start index as `2`. + +--- +### Question + +How many subarrays of the following array start from index 0 +[4, 2, 10, 3, 12, -2, 15] + +### Choices +- [ ] 6 +- [x] 7 +- [ ] 21 +- [ ] 36 + + +### Explanation + +[4] (starting from index 0) +[4, 2] +[4, 2, 10] +[4, 2, 10, 3] +[4, 2, 10, 3, 12] +[4, 2, 10, 3, 12, -2] +[4, 2, 10, 3, 12, -2, 15] +Therefore, there are a total of 7 subarrays that start from index 0. + +--- + +### Question +How many subarrays of the following array start from index 1 +[4, 2, 10, 3, 12, -2, 15] + +### Choices +- [x] 6 +- [ ] 7 +- [ ] 21 +- [ ] 36 + +### Explanation + +[2] (starting from index 1) +[2, 10] +[2, 10, 3] +[2, 10, 3, 12] +[2, 10, 3, 12, -2] +[2, 10, 3, 12, -2, 15] + +Therefore, there are a total of 6 subarrays that start from index 1. + +--- + +## Problem 2 Find sum of all Subarrays sums + +### Problem Statement +Given an array of integers, find the total sum of all possible subarrays. +**#Note:** This question has been previously asked in *Google* and *Facebook*. + +### Example +```cpp +Input: [3, 2, 5] +Output: 20 +Explanation: + +[3] = 3 +[3, 2] = 5 +[3, 2, 5] = 10 +[2] = 2 +[2, 5] = 7 +[5] = 5 + +Thus total sum of all subarrays is 3 + 5 + 10 + 2 + 7 + 5 = 32 +``` + +### Brute Force Solution +To solve this problem, we can use three nested loops. Two loops will generate all possible subarrays and third will be used to calculate sum of the subarray. + +### Pseudocode +```cpp +function subArraysSumBruteForce(arr[], n) { + + total = 0; + + // Generate all possible subarrays and calculate their sum + for (i -> 0 to n - 1) { + for (j -> i to n - 1) { + + subarray_sum = 0; + + for (k -> i to j) { + subarray_sum += arr[k]; + } + + total += subarray_sum; + } + } + print(total) +} +``` +### Time and Space Complexity +* TC - O(n^3) +* SC - O(1) + + +### Subarrays Sums - Prefix Sum + + +We can optimize the solution using the **Prefix Sum** technique. +* First, calculate the Prefix Sum array of the Input Array. +* Then, use two nested loops to iterate over all possible subarrays. At each iteration, calculate the sum of the subarray using the **Prefix Sum** array. +* Add the subarray sum to the total. + + +### Pseudocode +```cpp +function totalSubArraySum(arr[], n) { + // Calculate the prefix sum of the array + prefix[n]; + prefix[0] = arr[0]; + for (i -> 1 to n - 1) { + prefix[i] = prefix[i-1] + arr[i]; + } + + total = 0 + + // Print the sum of all possible subarrays using the prefix sum + for (i -> 0 to n - 1) { + for (j -> i to n - 1) { + if (i == 0) { + sum = prefix[j] + } else { + sum = prefix[j] - prefix[i-1]; + } + + total += sum + } + } + + print(total); +} +``` +### Time and Space Complexity +* TC - O(n^2) +* SC - O(n) + +### Subarrays Sums - Carry Forward + + +### More optimized Solution - Observation +In the Brute force approach, with the first for loop, we have fixed i and with the second loop, we are moving j from i to n-1 and then using k we are calculating the sum from i to j. + +```plaintext +A[ ] = {-4, 1, 3, 2} +i = 0 +j = 0 +k: sum = A[0] +j = 1 +k: sum = A[0] + A[1] —------------------- (a) +j = 2 +k: sum = A[0] + A[1] + A[2] —------------------- (b) +j = 2 +k: sum = A[0] + A[1] + A[2] +j = 3 +k: sum = A[0] + A[1] + A[2] + A[3] +``` + +### Hint 1: +Are we doing some redundant calculations? +How can we avoid it? +If (a) has already been calculated, how can we calculate (b) using it? + +### Expected: +If sum = A[0] + A[1], we can just add A[2] to it to get the sum from index 0 to 2. + +### Observation: +Everytime j is moving, we can add A[j] to the previous sum value and obtain the next sum value. + +### Dry Run +``` +A[ ] = {-4, 1, 3, 2} +``` + +i = 0 +currSum = 0 +|j| subarray index| currSum| value| total| +|-|----------|-------------|---------|------------| +|0| [0 0]| +=A[0] |-4 | -4 | +|1| [0 1]| +=A[1] |-4+1 = -3 | -7 | +|2| [0 2]| +=A[2] |-4+1+3 = 0 | -7 | +|3| [0 3]| +=A[3] |-4+1+3+2 = 2 | -5 | + +i = 1 +currSum = 0 +|j| subarray index| currSum| value | total| +|-|----------|-------------|----------|-----------| +|1| [1 1]| +=A[1] | 1 | -4 | +|2| [1 2]| +=A[2] | 1+3 = 4 | 0 | +|3| [1 3]| +=A[3] | 1+3+2 = 6 | 6 | + +i = 2 +currSum = 0 +|j| subarray index| currSum| value | total| +|-|----------|-------------|------------|---------| +|2| [2 2]| +=A[2] | 3 | 9 | +|3| [2 3]| +=A[3] | 3+2 = 5| 14 | + +i = 3 +currSum = 0 +|j| subarray index| currSum| value | total| +|-|----------|-------------|----------|-----------| +|3| [3 3]| +=A[3] | 2 | 16 | + + +### Pseudocode +```cpp +function totalSubarraySum(arr[], n) { + total = 0 + + for (i -> 0 to n - 1) { + currSum = 0; + for (j -> i to n - 1) { + currSum = currSum + arr[j] + + // add to the total + total += currSum + } + } + print(total); +} +``` +The above technique is Carry Forward. +### Time and Space Complexity +* TC - O(n^2) +* SC - O(1) +### Subarrays Sums - Contribution Technique + + +### Contribution Technique + +We can optimize the above solution further by observing a pattern in the subarray sums. +Let's take the example array ``[1, 2, 3]``. The subarrays and their sums are: + +``` +[1] -> 1 +[1, 2] -> 3 +[1, 2, 3] -> 6 +[2] -> 2 +[2, 3] -> 5 +[3] -> 3 +Total Sum = 1+3+6+2+5+3 = 20 +``` + +Instead of generating all subarrays, we can check that a particular element appears in how many subarrays and add its contribution that many times to the answer. + +* the first element 1 appears in 3 subarrays: [1], [1, 2], and [1, 2, 3]. +* the second element 2 appears in 4 subarrays: [2], [1, 2], [2, 3], and [1, 2, 3]. +* the third element 3 appears in 3 subarrays: [3], [2, 3], and [1, 2, 3]. + +Total = $(1*3) + (2*4) + (3*3) = 20$ + +### How to calculate the number of subarrays in which A[i] appears? + + +### Question +In how many subarrays, the element at index 1 will be present? +A: [3, -2, 4, -1, 2, 6 ] + +### Choices +- [ ] 6 +- [ ] 3 +- [x] 10 +- [ ] 8 + + + +**Explanation:** The subarrays in which the element at index 1 is present are - +[3, -2], [3, -2, 4], [3, -2, 4, -1], [3, -2, 4, -1, 2], [3, -2, 4, -1, 2, 6], [-2], [-2, 4], [-2, 4, -1], [-2, 4, -1, 2], [-2, 4, -1, 2, 6 ]. There are total 10 such subarrays. + + + + +### Question +In how many subarrays, the element at index 2 will be present? +A: [3, -2, 4, -1, 2, 6 ] + +### Choices +- [ ] 6 +- [x] 12 +- [ ] 10 +- [ ] 8 + + +**Explanation:** The subarrays in which the element at index 1 is present are - +[3, -2, 4], [3, -2, 4, -1], [3, -2, 4, -1, 2], [3, -2, 4, -1, 2, 6], [-2, 4], [-2, 4, -1], [-2, 4, -1, 2], [-2, 4, -1, 2, 6], [4], [4, -1], [4, -1, 2], [4, -1, 2, 6 ]. There are total 12 such subarrays. + +### Subarrays Sums continued + +### Generalized Calculation - +The start of such subarrays can be $0, 1, ..i$ +The end of such subarrays can be $i, i+1, i+2, ...n-1$ + +Elements in range [0 i] = $i+1$ +Elements in range [i n-1] = $n-1-i+1 = n-i$ +Thus, the total number of subarrays containing arr[i] is i+1 multiplied by n-i. + +This gives us the expression `(i+1) * (n-i)`. + +We can use this pattern to compute the total sum of all subarrays in O(n) time complexity. The steps are as follows: +* Initialize a variable total_sum to zero. +* Iterate over all elements of the input array arr. For each element arr[i], compute `arr[i] * (i+1) * (n-i)` and add it to total_sum. +* Return total_sum as the output of the function. + +### Pseudocode +```cpp +function sumOfAllSubarraySums(arr[]) { + n = arr.size(); + total_sum = 0; + + // Iterate over all elements of the array and compute the sum of all subarrays containing that element + for (i -> 0 to n - 1) { + total_sum += arr[i] * (i+1) * (n-i); + } + + return total_sum; +} +``` +### Time and Space Complexity +* TC - O(n) +* SC - O(1) + + +### Count subarrays of length K + + +Number of subarrays of length K = Total number of start indices of subarrays of length K. + +All start positions for length K, will be within range **[0 N-K]**. Therefore total is N-K+1. + +Hence, total number of subarrays of length K = **N-K+1**. + + +### Question + +Given N=7, K=4, What will be the total number of subarrays of length K? + +### Choices +- [ ] 3 +- [x] 4 +- [ ] 5 +- [ ] 6 + +### Problem 3 Max subarray sum with length K + + +Given an array of N elements. Print maximum subarray sum for subarrays with length = K. + +### Example +``` +N=10 K=5 +``` + +| -3 | 4 | -2 | 5 | 3 | -2 | 8 | 2 | -1 | 4 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- |:---:| + +### Explanation + + + +| s | e | sum | +| -------- | -------- | -------- | +| 0 | 4 | 7 | +| 1 | 5 | 8 | +| 2 | 6 | 12 | +| 3 | 7 | 16 | +| 4 | 8 | 10 | +| 5 | 9 | 11 | + +Maximum: **16** + +### Max subarray sum with length K - Bruteforce Approach + + +We have to calculate the sum of all subarrays of size k and find the maximum out of them + +### Pseudeocode + +```cpp +function maxSubarrayOfLengthK(A[],N,K) +{ + ans = -infinity + + //first window + i = 0 + j = k-1 + + while(j < N) + { + sum = 0 + for(idx -> i to j) + { + sum += A[idx] + } + ans = max(sum, ans) + + //going to next subarray of length k + i++ + j++ + } + print(ans) + +} +``` + +### Complexity + +For what value of k will the iterations be highest ? + +![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/031/030/original/1.png?1681232198) + +### Max subarray sum with length K - Sliding Window + + +We want to reduce the space complexity without modifying the given array, but how? + +### Observations +* We can get sum of next subarray using current subarray sum as follows:- + * By adding a new element to current sum. + * By subtracting the first element of current subarray. + +Given array :- +| -3 | 4 | -2 | 5 | 3 | -2 | 8 | 2 | -1 | 4 | +| --- | --- | --- | --- | --- | --- | --- | --- | --- |:---:| + +First subarray from 0 to 4:- +| -3 | 4 | -2 | 5 | 3 | +| --- | --- | --- | --- | --- | + +Converting first to second subarray :- + +| ~~-3~~ | 4 | -2 | 5 | 3 | -2 | +| --- | --- | --- | --- | --- | --- | + +Based upon above observation can we say:- +
+sum of all elements of next subarray = sum of elements of current subarray - first element of current subarray + new element +
+ +This approach is known as **Sliding window approach**. + +### Dry Run + +![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/031/066/original/Screenshot_%286%29.png?1681239141) + +**We can clearly observe the window sliding in above run.** + +### Pseudeocode + +```cpp +function maxSubarrayOfLengthK(A[],N,K) +{ + ans = -infinity + i = 0 + j = K-1 + + sum = 0 // here k iterations + for(idx -> i to j) + { + sum += A[idx] + } + ans = max(sum,ans) + + j++ + i++ + + while(j 0 to N - 1) { // start to N + for (j -> i to N - 1) { // end + for (k -> i to j) { + sum += A[k]; + } + ans = Math.max(ans, sum); + sum = 0; // Reset sum for the next iteration + } +} +return ans; +``` + +### Complexity +**Time Complexity:** `O(N^2 * N) = O(N^3)` +**Space Complexity:** `O(1)` + + +## Find Maximum Subarray Sum using Carry Forward + +### Optimized Solution using Carry Forward +We don't really need the third loop present in brute force, we can optimise it further using Carry Forward technique. + +### Psuedocode +```java +ans = A[0] +for(i = 0 to N - 1){ //start to N + sum = 0 + for(j = i to N - 1){ //end + sum += A[k] + ans = max(ans, sum) + } +} +return ans; +``` + +### Complexity +**Time Complexity:** O(N^2) +**Space Complexity:** O(1) + +## Find Maximum Subarray Sum using Kadanes Algorithm + +### Observation: + +**Case 1:** +If all the elements in the array are positive +Arr[] = `[4, 2, 1, 6, 7]` + +**Answer:** +To find the maximum subarray we will now add all the positive elements +Ans: `(4 + 2 + 1 + 6 + 7) = 20` + + +**Case 2:** + +If all the elements in the array are negative +Arr[] = `[-4, -8, -9, -3, -5]` + +**Answer:** +Here, since a subarray should contain at least one element, the max subarray would be the element with the max value +Ans: `-3` + + +**Case 3:** + +If positives are present in between +Arr[] = [-ve -ve -ve `+ve +ve +ve +ve` -ve -ve -ve] + +**Answer:** +Here max sum would be the sum of all positive numbers + + +**Case 4:** +If all negatives are present either on left side or right side. +Arr[ ] = [-ve -ve -ve `+ve +ve +ve +ve`] +OR +Arr[ ] = [`+ve +ve +ve +ve` -ve -ve -ve -ve] + +**Answer:** +All postives on sides + + + +Case 5 : +### Hint: +What if it's some ve+ followed by some ve- and then again some more positives... + +```plaintext ++ve +ve +ve -ve -ve -ve +ve +ve +ve +ve +ve +``` + +### Solution: +We will take all positives, then we consider negatives only if the overall sum is positive because in the future if positives come, they may further increase this positivity(sum). + + + +**Scenario:** +Say you recently got committed. Your partner did something wonderful for you and you are so happy about it. + +The other day, they kept your message on seen and didn’t reply. What will happen to your happiness level? +It’ll reduce a bit or you will start hating that person? Happiness level ve - ? +It will reduce a bit, some positivity in relationships still exists.* + + +*The other day, they showered you with flowers and quality time, so now can we say that your bond is even stronger and happiness level is even higher than the first day ?* + + + +**Example** - +```plaintext +A[ ] = { -2, 3, 4, -1, 5, -10, 7 } +``` +Answer array: 3, 4, -1, 5 + + +**Explanation**: +3+4 = 7 +7 + (-1) = 6 (still positive) +6+5 = 11 (higher than 7) + +### Dry Run +```plaintext + 0 1 2 3 4 5 6 7 8 +{ -20, 10, -20, -12, 6, 5, -3, 8, -2 } +``` + +| i | currSum | maxSum | | +|:---:|:-------:|:------:|:------------------------------------------------------------------------------------------------------------------------------------------------------------:| +| 0 | -20 | -20 | reset the currSum to 0 and do not propagate since adding a negative will make it more negative and adding a positive will reduce positivity of that element. | + +currSum = 0 + +| i | currSum | maxSum | | +|:---:|:-------------:|:------:|:------------------:| +| 1 | 10 | 10 | | +| 2 | 10 + (-20)= -10 | 10 | reset currSum to 0 | + + + +currSum = 0 + +| i | currSum | maxSum | | +|:---:|:-------:|:------:|:------------------:| +| 3 | -12 | 10 | reset currSum to 0 | + + + +currSum = 0 + +| i | currSum | maxSum | | +|:---:|:---------:|:------:|:---------------------------------------------------------------------------:| +| 4 | 6 | 10 | | +| 5 | 6 + 5 | 11 | | +| 6 | 6 + 5 - 3 = 8 | 11 | Keep currSum as 8 only since if we find a positive, it can increase the sum | + +| i | currSum | maxSum | | +|:---:|:-------:|:------:| --------------------------------------------------------------------------- | +| 7 | 8 + 8 = 16 | 16 | | +| 8 | 16 - 2 = 14 | 16 | Keep currSum as 8 only since if we find a positive, it can increase the sum | + +Final maxSum = 16 + +--- +### Question +Tell the output of the below example after running the Kadane's Algorithm on that example +A[ ] = { -2, 3, 4, -1, 5, -10, 7 } + +### Choices +- [ ] 9 +- [ ] 7 +- [x] 11 +- [ ] 0 + +--- + +### Pseudocode + +```cpp +function maximumSubarraySum(arr[], n) { + maxSum = -infinity; + currSum = 0; + + for (i -> 0 to n - 1) { + currSum += arr[i]; + + if (currSum > maxSum) { + maxSum = currSum; + } + + if (currSum < 0) { + currSum = 0; + } + } + + return maxSum; + } +``` + +### Complexity +**Time Complexity:** O(n) +**Space Complexity:** O(1) + +The optimized method that we just discussed comes under **Kadane's Algorithm** for solving maximum subarray problem + + +--- +## Problem 2 Find in rowwise and colwise sorted matrix + +### Problem Statement +Given a row wise and column wise sorted matrix, find out whether element **k** is present or not. + +### Example +Observe that rows and columns are both sorted. + + +### Test Case 1 +13 => Present (true) + +### Test Case 2 +2 => Present (true) + +### Test Case 2 +15 => Not Present (false) + +--- +### Question +What is the brute force approach and the time complexity of it? + +### Choices +- [ ] Iterate over first row; T.C - O(M) +- [ ] Iterate over last col; T.C - O(N) +- [x] Iterate over all rows & cols; T.C - O(N * M) +- [ ] Iterate over first col; T.C - O(N) + +--- +## Find in rowwise and colwise sorted matrix Optimised Approach + +### Idea +* We shall exploit the property of the matrix being sorted. +* Start with the cell from where we can decide the next step. +**Example:** + +Search for: 0 + +Say we stand at **top left cell i.e, -5**. +Now, **-5 < 0**, can we determined the direction to search based on this? +No, because on rightside as well as downwards, the elements are in increasing order, so 0 can be present anywhere. + +Now, say we stand at **top right cell i.e, 13**. +Now,**13 > 0**, should we go left or down ? Can we decide ? +Yes, if we move down the elements are > 13, but we are looking for an element < 13, so we should move left. + +It means, all elements below 13, can be neglected. + + + +**Move Left** + + + +Now, where shall we move ? + +--- +### Question +Say we are at 1 and want to find 0, where should we move ? + + + +### Choices +- [x] left +- [ ] bottom +- [ ] let's move in both the directions +- [ ] let's move everywhere + +--- +## Optimised Approach (Continued) + +Since, **1 > 0**, again all elements below 1 are greater than 1, hence can be neglected. + + + +**Move Left** + + +Now, **-2 < 0**, all elements on left of -2 are lesser than -2, hence can be neglected. + + + +**Move Down** + + +### Approach + +* We can start at top right cell. +* If A[i][j] < K, move down, else move left. +* Repeat until the element is found, our the search space is exhausted. + +**NOTE:** We could have also started at bottom left cell. + +### Pseudocode + +```cpp +i = 0, j = M - 1 +while(i < N && j>= 0){ + if(arr[i][j] == K){ + return true; + } else if(arr[i][j] < K){ + i++; //move down; next row + } else{ + j--; //move left; previous column + } +} +return false; +``` + +### Time & Space Complexity +**Time Complexity:** O(M+N) since at every step, we are either discarding a row or a column. Since total rows+columns are N+M, hence Iterations will be N+M. +**Space Complexity:** O(1) + + +--- +## Problem 3 Merge sorted Overlapping Intervals + +### Problem Statement +You are given a collection of intervals A in a 2-D array format, where each interval is represented by a pair of integers `[start, end]`. The intervals are sorted based on their start values. + +Your task is to merge all overlapping intervals and return the resulting set of non-overlapping intervals. + +### Input: +Interval[] = {(0,2), (1,4), (5,6), (6,8), (7,10), (8,9), (12,14)} + +### Output: + +{(0,4), (5,10), (12,14)} + +### Explanation: + + +| Interval 1 | Interval 2 | | Answer Interval List | +|:----------:|:----------:|:---------------:|:--------------------:| +| 0-2 | 1-4 | Overlapping | 0-4 | +| 0-4 | 5-6 | Not Overlapping | 0-4, 5-6 | +| 5-6 | 6-8 | Overlapping | 0-4, 5-8 | +| 5-8 | 7-10 | Overlapping | 0-4, 5-10 | +| 5-10 | 8-9 | Overlapping | 0-4, 5-10 | +| 5-10 | 12-14 | Not Overlapping | 0-4, 5-10, 12-14 | + + +### The Array Is Sorted Based on Start Time. What Is the Overlapping Condition? + +Say start time of A < start time of B + + + +Overlapping Condition - **If start of B <= end of A** + +--- + +### Question + +Given a sorted list of overlapping intervals, sorted based on start time, merge all overlapping intervals and return sorted list. Select output for following input. +Input: +Interval[] = { (1,10), (2, 3), (4, 5), (9, 12) } + +### Choices +- [x] (1, 12) +- [ ] (1, 10), (9, 12) +- [ ] (1, 9), (9, 12) +- [ ] No Change + + +--- + +## Approach + +* Create an array to store the merged intervals. +* If the current and ith intervals overlaps, merge them. In this case update the current interval with the merged interval. +* Else, insert the current interval to answer array since it doesn't overlap with any other interval and update the current Interval to ith Interval. + +## Dry Run + +### Input: +Interval[] = {(0,2), (1,4), (5,6), (6,8), (7,10), (8,9), (12,14)} + +### Explanation: + + +| current | ith | | After merging | answer list | +|:-------:|:-----:|:---------------:|:-------------:|:-----------:| +| 0-2 | 1-4 | Overlapping | 0-4 | | +| 0-4 | 5-6 | Not Overlapping | Not needed | 0-4 | +| 5-6 | 6-8 | Overlapping | 5-8 | 0-4 | +| 5-8 | 7-10 | Overlapping | 5-10 | 0-4 | +| 5-10 | 8-9 | Overlapping | 5-10 | 0-4 | +| 5-10 | 12-14 | Not Overlapping | Not needed | 0-4, 5-10 | +| 12-14 | end | | | | + +At the end, we are left with the last interval, so add it to the list. + +### Pseudocode +```cpp +currS = A[0][0], currE = A[0][1]; + +for (i -> 1 to N - 1) { + if (A[i][0] <= currE) { + currE = max(currE, A[i][1]); + } else { + ans.insert({currS, currE}); + currS = A[i][0]; + currE = A[i][1]; + } +} +ans.insert({currS, currE}); + +return ans; +``` + +### Complexity +**Time Complexity:** O(N) +**Space Complexity:** O(1) + + +--- +## Problem 4 Transpose of a square matrix + +### Problem Statement +Given a square 2D matrix mat[N][N], find transpose. + +### Transpose of matrix +The transpose of a matrix is a new matrix obtained by interchanging the rows and columns of the original matrix. + + +### TestCase + +``` +mat[3][3] = { + {1,2,3}, + {5,6,7}, + {9,10,11} +} +``` + +**Output:** + +``` +{ +{1,5,9}, +{2,6,10}, +{3,7,11} +} +``` + +### Observation +* After performing the transpose, what is same in the original matix and its transpose ? +The diagonal that starts from (0,0) is same. +![](https://i.imgur.com/9Xu3SYE.png) +* Along the diagonals, the elements have swapped their positions with corresponding elements. + +### PseudoCode +``` +function find_transpose(matrix[N][N]){ + for(i -> 0 to N - 1){ + for(j -> i + 1 to N - 1){ + swap(matrix[i][j],matrix[j][i]); + } + } +} +``` +#### Note: If we start j at 0, the matrix will come back to its original position. + +--- +### Question + +What is the time and space complexity to find transpose of a square matrix? + +### Choices +- [ ] TC: O(N), SC: O(N) +- [ ] TC: O(N2), SC: O(N) +- [x] TC: O(N2), SC: O(1) +- [ ] O(N), SC: O(1) + +--- + +### Complexity + +Time Complexity: **O(N2)**. +Space Complextiy: **O(1)** + + +--- +## Problem 5 Rotate a matrix to 90 degree clockwise + +### Problem Statement +Given a matrix mat[N][N], rotate it to 90 degree clockwise. + +### TestCase +``` +{ {1,2,3}, + {4,5,6}, + {7,8,9} } +``` +**Output:** +``` +{ {7,4,1}, + {8,5,2}, + {9,6,3} } +``` + +### Hint: +* What if we first find the transpose of the matrix? +* Is there any relation between rotated matrix and transposed matrix ? + +### Observation: +Yes, if we reverse all the rows, then it will become rotated matrix. +The rotated matrix looks like: +![](https://i.imgur.com/B4p4avm.png) + +**Transpose and rotated matrix:** +![](https://i.imgur.com/ZfdZaor.png) + +### PseudoCode +``` +Function rotate(mat[N][N]){ + mat = transpose(mat); + for(i -> 0 to N - 1){ + reverse(mat[i]); + } + return mat; +} +``` +### Complexity +Time Complexity: **O(N*N)**. +Space Complextiy: **O(1)**. diff --git a/Academy DSA Typed Notes/AIML/DSA Bit Manipulations.md b/Academy DSA Typed Notes/AIML/DSA Bit Manipulations.md new file mode 100644 index 0000000..aa6d9f8 --- /dev/null +++ b/Academy DSA Typed Notes/AIML/DSA Bit Manipulations.md @@ -0,0 +1,553 @@ +# DSA: Bit Manipulations + +## Truth Table for Bitwise Operators + +Below is the truth table for bitwise operators. + + + +--- +## Basic AND XOR OR Properties + +### Basic AND Properties +1. **Even/Odd Number** +In binary representation, if a number is even, then its least significant bit (LSB) is 0. +Conversely, if a number is odd, then its LSB is 1. + + * **A & 1 = 1** (if A is odd) + ```cpp + 181 = 10110101 //181 is odd, therefore LSB is 1 + + 10110101 & 1 = 1 // Performing Bitwise AND Operation + + Since, 181 is odd, Bitwise AND with 1 gave 1. + ``` + * **A & 1 = 0** (if A is even) + ```cpp + 180 = 10110100 //180 is even, therefore LSB is 0 + + 10110100 & 1 = 0 // Performing Bitwise AND Operation + + Since, 180 is even, Bitwise AND with 1 gave 0. + ``` +2. **A & 0 = 0** (for all values of A) +3. **A & A = A** (for all values of A) + +### Basic OR Properties +1. **A | 0 = A** (for all values of A) +2. **A | A = A** (for all values of A) + +### Basic XOR Properties +1. **A ^ 0 = A** (for all values of A) +2. **A ^ A = 0** (for all values of A) + +### Commutative Property +The order of the operands does not affect the result of a bitwise operation. + +```cpp +A & B = B & A // Bitwise AND +A | B = B | A // Bitwise OR +A ^ B = B ^ A // Bitwise XOR +``` + +### Associative Property +* It states that the grouping of operands does not affect the result of the operation. +* In other words, if we have three or more operands that we want to combine using a bitwise operation, we can group them in any way we want, and the final result will be the same. + +```cpp +(A & B) & C = A & (B & C) // Bitwise AND +(A | B) | C = A | (B | C) // Bitwise OR +(A ^ B) ^ C = A ^ (B ^ C) // Bitwise XOR +``` + +--- +### Question + +Evaluate the expression: a ^ b ^ a ^ d ^ b + +### Choices + +- [ ] a ^ b ^ a ^ b +- [ ] b +- [ ] b ^ d +- [x] d + +### Explanation + +We can evaluate the expression as follows: +```cpp +a ^ b ^ a ^ d ^ b = (a ^ a) ^ (b ^ b) ^ d // grouping the a's and the b's += 0 ^ 0 ^ d // since a ^ a and b ^ b are both 0 += d // the result is d +``` + +Therefore, the expression a ^ b ^ a ^ d ^ b simplifies to d + +--- +### Question + +Evaluate the expression: 1 ^ 3 ^ 5 ^ 3 ^ 2 ^ 1 ^ 5 + +### Choices + +- [ ] 5 +- [ ] 3 +- [x] 2 +- [ ] 1 + +### Explanation + +We can evaluate the expression as follows: +```cpp +1 ^ 3 ^ 5 ^ 3 ^ 2 ^ 1 ^ 5 = ((1 ^ 1) ^ (3 ^ 3) ^ (5 ^ 5)) ^ 2 // grouping the pairs of equal values and XORing them += (0 ^ 0 ^ 0) ^ 2 // since x ^ x is always 0 += 0 ^ 2 // since 0 ^ y is always y += 2 // the result is 2 +``` +Therefore, the expression 1 ^ 3 ^ 5 ^ 3 ^ 2 ^ 1 ^ 5 simplifies to 2. + +--- +### Question +Value of `120 ^ 5 ^ 6 ^ 6 ^ 120 ^ 5` is - + +### Choices +- [ ] 120 +- [ ] 210 +- [ ] 6 +- [ ] 5 +- [x] 0 + +--- +## Single Number 1 + +### Problem Statement +We are given an integer array where every number occurs twice except for one number which occurs just once. Find that number. + +**Example 1:** + +**Input:** [4, 5, 5, 4, 1, 6, 6] +**Output:** 1 +`only 1 occurs single time` + +**Example 2:** + +Input: [7, 5, 5, 1, 7, 6, 1, 6, 4] +Output: 4 +`only 4 occurs single time` + +### Brute Force +Traverse the array and count frequency of every element one by one. +T.C - O(N^2) +S.C - O(1) + +### Idea +What is A^A ? +ans = A^A is 0 + +### Approach 1: +Since ^ helps to cancel out same pairs, we can use it. +Take XOR of all the elements. + + +### Pseudocode +```cpp= +x = 0; + +for (i = 0 to arr.size - 1) { + x = x ^ arr[i]; // XOR operation +} + +print(x); +``` + +### Complexity +**Time Complexity:** O(N) +**Space Complexity:** O(1) + + +### Approach 2: +*Interesting Solution!* +Bitwise operators work on bit level, so let's see how XOR was working on bits. +For that, let's write binary representation of every number. + +### Observations: +For every bit position, if we count the number of 1s the count should be even because numbers appear in pairs, but it can be odd if the bit in a single number is set for that position. + + + + + +* We will iterate on all the bits one by one. +* We will count the numbers in the array for which the particular bit is set +* If the count is odd, in the required number that bit is set. + +### Pseudocode +```javascript +ans = 0; + +for(i -> 0 to 31) { // go to every bit one by one + cnt = 0; + + for(j -> 0 to arr.size - 1) { // iterate on array + + // check if ith bit is set + if((arr[j]& (1<>) +* The right shift operator (>>) shifts the bits of a number to the right by a specified number of positions. +* When we right shift a binary number, the most significant bit (the leftmost bit) is filled with 0. +* Right shift operator can also be used for division by powers of 2. + +Let’s take the example of the number 20, which is represented in binary as 00010100. Lets suppose, it can be represented just by 8 bits. +```cpp +(a >> 0) = 00010100 = 20 +(a >> 1) = 00001010 = 10 (divided by 2) +(a >> 2) = 00000101 = 5 (divided by 2) +(a >> 3) = 00000010 = 2 (divided by 2) +(a >> 4) = 00000001 = 1 (divided by 2) +(a >> 5) = 00000000 = 0 (divided by 2) +``` +In general, it can be formulated as: +```cpp +a >> n = a/2^n +1 >> n = 1/2^n +``` +Here, overflow condition doesn't arise. + + +--- +### Question + +What will we get if we do 1 << 3 ? + +### Choices + +- [ ] 1 +- [x] 8 +- [ ] 3 +- [ ] 4 + + +--- +## Power of Left Shift Operator + +Let's see how left shift operator can be used in combination with other operators like (OR, AND, XOR) to set, unset, check or toggle an ith bit. + + +## SET ith bit + +**Left Shift Operator** can be used with the **OR** operator to **SET** the **ith** bit in the number. + +``` +N = (N | (1<th** bit if it is UNSET else there is no change. + +### Example + + + +--- + +## TOGGLE ith bit + +**Left Shift Operator** can be used with the **XOR** operator to **FLIP(or TOGGLE)** the **ith** bit in the number. + +``` +N = (N ^ (1<th** bit is SET, then it will be UNSET or vice-versa. + +### Example + + +--- +## CHECK ith bit whether it is set or unset + +Taking **AND with 1** can help us. +0 & 1 = 0 +1 & 1 = 1 + +1. We can shift 1 to the ith bit. +2. If `X = (N & (1< 0, then **ith** bit is set. + * else **ith** bit is not set. + +### Example +Suppose we have +``` +N = 45 +i = 2 +``` +The binary representation of 45 is: +``` +1 0 1 1 0 1 +``` +The binary representation of (1<<2) is: +``` +0 0 0 1 0 0 +``` + +45 & (1<<2) is +``` +0 0 0 1 0 0 +``` + +It is greater than 0. Hence **ith** bit is SET. + +### Pseudocode +```cpp +function checkbit(N, i){ + if(N & (1< 0, it means the **ith** bit is SET. To UNSET that bit do: +`N = (N ^ (1< 0 to 31){ + if(checkbit(N, i)){ + ans = ans + 1; + } + } + return ans; +} +``` + +Here, checkbit function is used to check whether the **ith** bit is set or not. + +### Approach 2 +To count the number of SET bits in a number, we can use a Right Shift operator as: + +* Initialize a count variable to zero. +* While the number is not equal to zero, do the following: + * Increment the count variable if the **0th** bit of the number is 1. + * Shift the number one bit to the right. + * Repeat steps a and b until the number becomes zero. +* Return the count variable. + +```cpp +function countbit(N){ + ans = 0; + while(N > 0){ + if(N & 1){ + ans = ans + 1; + } + N = (N >> 1); + } + return ans; +} +``` + +--- +### Question + +What is the time complexity to count the set bits ? + +### Choices + +- [x] O(log N) +- [ ] O(N) +- [ ] O(N^2) +- [ ] O(1) + +### Explanation + +O(log(N)), where 'N' is the value of the given number. + +Since there are log(N) bits in a number and we are going through all the bits one by one, the time complexity of this approach is O(log(N)). + +* **Time Complexity** - O(log(N)) +* **Space Complexity** - O(1) + + +--- +## Single Number 3 + +### Problem Statement +Given an integer array, all the elements will occur twice except two. Find those two elements. + +**Input:** [4, 5, 4, 1, 6, 6, 5, 2] + +**Output:** 1, 2 + +### Hint: + +* Will finding XOR help ? May be! +* What do we get if we XOR all numbers ? XOR of the two unique numbers! +* From that can we identify/separate the two numbers ? Not Really! Why? + * Example: If XOR is 7, we aren't sure which 2 numbers are they. (2, 5), (1, 6), (3, 4), ... have xor = 7, so we won't be able to identify! + + +***Is there any way in which we can identify the two numbers from their XOR ?*** + +Suppose if two unique numbers are **a** and **b**. Their XOR is **c**. +In **c** if say 0th bit is set, what does that tell about a and b ? +In one of the numbers the bit is set and in other the bit is unset! So, can we identify the numbers based on that ? + +### Idea: + +* We will find the position of any set bit in XOR c, it will denote that this bit is different in a and b. +* Now, we divide the entire array in two groups, based upon whether that particular bit is set or not. +* This way a and b will fall into different groups. +* Now since every number repeats twice, they will cancel out when we take XOR of the two groups individually leaving a and b. + + +### Pseudocode + +```cpp= + xorAll = 0; + + // XOR of all numbers in the array + for (i -> 0 to N - 1) { + xorAll ^= A[i]; + } + + // Find the rightmost set bit position + // Note: Any other bit can be used as well + declare pos + + for(pos = 0; pos < 32; pos++) + { + if(checkbit(xorAll,pos)) + break; + } + + num1 = 0; + num2 = 0; + + // Divide the array into two groups based on the rightmost set bit + for (i -> 0 to N - 1) { + if (checkbit(A[i], pos)) { + num1 ^= A[i]; + } else { + num2 ^= A[i]; + } + } + + print(num1); + print(num2); +``` + + diff --git a/Academy DSA Typed Notes/AIML/DSA Strings.md b/Academy DSA Typed Notes/AIML/DSA Strings.md new file mode 100644 index 0000000..f0e916b --- /dev/null +++ b/Academy DSA Typed Notes/AIML/DSA Strings.md @@ -0,0 +1,774 @@ +# DSA: Strings + + +## String + +A string can be defined as a sequence of characters or in other words we can say that it is an array of characters. + +### Example +Below are the examples of string: +``` +"Welcome to Scaler" +"Hello World!" +``` +**Note:** String is represented using double quote i.e, `""`. All the characters of string must be inside this quote. + +### Character +A character is a single symbol that represents a letter, number, or other symbol in a computer's character set. Characters are used to represent textual data, and they are typically represented using its ASCII value. +### Example +``` +'a' +'B' +'1' +'_' +``` + +Computer store everything in binary. So, how do we store strings in computer? + +Each character has corresponding decimal value associated to it which is known as ASCII value. + +**'A' to 'Z'** have ASCII from **65 to 90** +**'a' to 'z'** have ASCII from **97 to 122** +**'0' to '9'** have ASCII from **48 to 57** + +Each character '?', '!', '\*', ... has a corresponding ASCII associated with it. + +### Some Operations: +**Note:** Characters can also be printed using its ascii value. for example, the ascii value of 'A' is 65, so it can be printed as +```CPP= +char ch = (char)65; +print(ch); +/* +character 'A' gets printed; we are assigning Integer to Char,hence in some languages typecasting will be required. +*/ +``` + +```cpp= +char ch = (char)('a' + 1); +/* +When we do arithmetic operations on characters, automatically computations happen on their ASCII values. + */ +print(ch); //'b' will get printed +``` + +```cpp= +int x = 'a'; +/* +No need to typecast since we are assigning Char to Int (smaller data type to bigger, so it will not overflow) +*/ +print(x); //97 will be printed + +``` + +--- +## Problem 1 Togglecase + +### Problem Statement + +Given a string consisting of only alphabets(either lowercase or uppercase). Print all the characters of string in such a way that for all lowercase character, print its uppercase character and for all uppercase character, print its lowercase character. + +### TestCase +#### Input +``` +"Hello" +``` + +#### Output +``` +"hELLO" +``` + +#### Explanation + +Here, there is only one uppercase character present in the string i.e, 'H' so convert it to lowercase character. All other characters are in lowercase, hence they are converted into uppercase characters. + + +--- +### Question +What is the output for String = "aDgbHJe" ? + +### Choices +- [ ] ADGBHJE +- [ ] aDgbHJe +- [x] AdGBhjE +- [ ] adgbhje + +--- + +## Approach - Subtract or Add 32 + + +### Observation +The key observations are: +* Lowercase characters can be changed into uppercase by subtracting 32 from its ASCII values. +* Uppercase charactes can be changed into lowercase by adding 32 from its ASCII values. + +The above points are derived from the fact that for every alphabet, the difference between its ascii value in lowercase and uppercase is 32. + +### Pseudocode +```javascript +function solve(string A) { + N = A.size(); //use function present in your language to get length + for (i -> 0 to N - 1){ + if(A[i] >= 'a' && A[i] <= 'z'){ + A[i] = (char)(A[i] - 32); + } + else{ + A[i] = (char)(A[i] + 32); + } + } + return A; +} +``` + +> Problem 1 Changing same string is not possible in Java or Python + +NOTE: +1. In above code, we are making changes to the same given string. +2. This is not possible in languages like Java and Python, since we cannot change a string. Example: If we have "Scaler", then if we try changing it to "Scalar", then it won't do changes to same string. +3. The concept is known as "String Immutability", that we shall learn later in this session. +4. So, what to do for these languages ? We can create a new string and keep adding a character to it ? + +### Pseudocode +```javascript +function solve(String A) { + res = ""; + + for(i -> 0 to A.length() - 1) { + if(A[i] >= 'a' && A[i] <= 'z') { + res += (char)(A[i] - 32); + } + else { + res += (char)(A[i] + 32); + } + } + return res; +} +``` + +The above code will create a new string everytime a character is added, which means adding one characters can take O(N) time, making it highly un-optimal in Java. Hence, the above code will give TLE in Java. + +Though in Python, string handling is optimized for operations like these on small to moderately sized strings. Python efficiently manages new string creation and memory, which prevents performance degradation in many practical scenarios. Hence, the above code shall work for Python for small strings, but for large strings, it can fail. + +> How to make the code work for Java and large inputs in Python ? (next section) + +### Using character array instead + + +**`String is nothing but array of characters, so at the start of the program we can convert the string to array of characters and rather perform operations on that character array. Once done, we can convert it back to string.`** + +### Pseudocode +```javascript! +----JAVA---- +String solve(String A) { + char arr[] = A.toCharArray(); //for java + + for(int i=0; i < arr.length;i++) { + if(arr[i] >= 'A' && arr[i] <= 'Z') { + arr[i] = (char)(arr[i] + 32); + } + else { + arr[i] = (char)(arr[i] - 32); + } + } + + return new String(arr); +} + +----PYTHON---- +class Solution: + def solve(self, A): + char_list = [] + for c in A: + if 'A' <= c <= 'Z': + char_list.append(chr(ord(c) + 32)) + else: + char_list.append(chr(ord(c) - 32)) + return ''.join(char_list) +``` +### Complexity +Time Complexity- **O(N)**. +Space Complexity- **O(1)**. + +--- +### Question +Considering the immutability of strings in most programming languages, if you start with an empty string and keep adding one character at a time, how many string objects will be created when forming the string "abcdef"? + +### Choices +- [ ] 7 +- [x] 6 +- [ ] 12 +- [ ] 1 + +### Explanation + +In most programming languages, strings are immutable. This means that each time you modify a string, a new string object is created. If you start with an empty string and add one character at a time to form the string "abcdef", the process would look like this: + +"" -> "a" +"a" -> "ab" +"ab" -> "abc" +"abc" -> "abcd" +"abcd" -> "abcde" +"abcde" -> "abcdef" +Each step creates a new string object, resulting in a total of 6 string objects. + +--- +## Substring + +A substring is a contiguous sequence of characters within a string. A substring concept in string is similar to subarray concept in array. + +**A substring can be:** +1. Continous part of string. +2. Full string can be a substring. +3. A single character can also be a subsring. + +### Example + +Suppose, we have a string as +``` +"abc" +``` +There are total 6 substring can be formed from the above string. All substrings are +``` +"a" +"b" +"c" +"ab" +"bc" +"abc" +``` + +--- +### Question +How many total substrings will be there for the String "bxcd" ? + +### Choices +- [ ] 7 +- [ ] 8 +- [x] 10 +- [ ] 9 + +### Explanation +All the substrings are as follows- +``` +"b", "x", "c", "d", "bx", "xc", "cd", "bxc", "xcd", "bxcd" +``` +We can also find the count using n*(n+1)/2 + +--- + +## Problem 2 Check Palindrome + +Check whether the given substring of string **s** is palindrome or not. +A palindrome is the sequence of characters that reads the same forward and backward.for example, "nayan", "madam", etc. + +### TestCase + +#### Input +``` +s = "anamadamspe" +start = 3 +end = 7 +``` +#### Output +``` +true +``` +#### Explanation +The substring formed from index 3 to 7 is "madam" which is palindrome. + +## Approach +Below is the simple algorithm to check whether the substring is palindrome or not: +* Initialize two indices *start* and *end* to point to the beginning and *end* of the string, respectively. +* While *start* is less than *end*, do the following: + * If the character at index *start* is not equal to the character at index *end*, the string is not a palindrome. Return false. + * Else, increment *start* and decrement *end*. +* If the loop completes without finding a non-matching pair, the string is a palindrome. Return true. + +### Pseudocode +```javascript +function ispalindrome(character array s[], start, end){ + while(start 0 to N - 1){ + for(j -> i to N - 1){ + if(ispalindrome(s,i,j)){ + ans = max(ans, j-i+1); + } + } + } + return ans; +} +``` + +### Complexity + +Time Complexity- **O(N^3)**. +Space Complexity- **O(1)**. + +## Optimized Approach + +### Idea +The key idea here is that: +* For odd length substring, take every character as a center and expand its center and gets maximum size palindromic substring. +* For even length substring, take every adjacent character as a center and expand its center and get maximum size palindromic substring. + + +### Pseudocode +```javascript +function longestpalindrome(character array s[]){ + maxlength=0; + N = s.size(); + for(c -> 0 to N - 1){ + + //odd length string + left = right = c; + + while(left>=0 and right=0 and right Suggesting Instructor to also go through this link once. +https://www.scaler.com/topics/why-string-is-immutable-in-java/ + +In languages like **Java, C#, JavaScript, Python and Go**, strings are immutable, which means that once a String object is created, its value cannot be changed. If you try to modify it, such as through concatenation or other operations, a new String object is created with the modified value, and the original String remains unchanged. + +```cpp= +String s1 = "Hello"; // String literal +String s2 = "Hello"; // String literal +String s3 = s1; // same reference +``` + +![](https://hackmd.io/_uploads/SkGyXywRh.png) + +* As seen above, because strings with the same content share storage in a single pool, this minimize creating a copy of the same value. +* That is to say, once a String is generated, its content cannot be changed and hence changing content will lead to the creation of a new String. + +```cpp= +//Changing the value of s1 +s1 = "Java"; + +//Updating with concat() operation +s2.concat(" World"); + +//The concatenated String will be created as a new instance +//and an object should refer to that instance to get the concatenated value. +String newS3 = s3.concat(" Scaler"); + +System.out.println("s1 refers to " + s1); +System.out.println("s2 refers to " + s2); +System.out.println("s3 refers to " + s3); +System.out.println("newS3 refers to " + newS3); +``` + +### Output + +```cpp= +s1 refers to Java +s2 refers to Hello +s3 refers to Hello +news3 refers to Hello Scaler +``` + +![](https://hackmd.io/_uploads/BkzzEkPC3.png) + +As shown above, considering the example: + +* String s1 is updated with a new value and that's why a new instance is created. Hence, s1 reference changes to that newly created instance "Java". +* String s2 and s3 remain unchanged as their references were not changed to a new instance created after performing concat() operation. +* "Hello World" remains unreferenced to any object and lost in the pool as s2.concat() operation (in line number 5) is not assigned to any object. That's why there is a reference to its result. +* String newS3 refers to the instance of s3.concat() operation that is "Hello Scaler" as it is referenced to new object newS3. + +**Hence, Strings are immutable and whenever we change the string only its reference is changed to the new instance.** + +### Why is String Immutability needed ? + +1. **Security:** Strings are often used to store sensitive data like passwords, file paths, and network connections. If strings were mutable, they could be altered when being passed between methods, which would lead to security vulnerabilities. + +2. **Caching and Performance:** The JVM can safely cache String literals because their values don't change. This allows for efficient memory usage and better performance since multiple references can point to the same String literal in the string pool. + + +### String Builder in Java + +In Java, for operations where many characters need to be appended to a string, it is more efficient to use a StringBuilder or StringBuffer.. These classes are designed for such use cases because they maintain a mutable sequence of characters, and they can expand their capacity without needing to copy the contents every time a new character is added. + +With StringBuilder, adding a character is typically O(1) on average, making it much more efficient for concatenating strings or characters repeatedly. After all modifications, the StringBuilder can be converted back to a String using its .toString() method. + +Here’s a quick example of using StringBuilder for adding characters: +```javascript +StringBuilder sb = new StringBuilder(); +for (int i = 0; i < n; i++) { + sb.append('a'); // Adds a character efficiently +} +String result = sb.toString(); // Converts StringBuilder to String +``` + +Using StringBuilder is the recommended approach when constructing strings dynamically in Java, especially in loops or where multiple concatenations are involved. + +Please share with students for more methods for string builder - https://docs.google.com/document/d/1umCzDQqPUUD21XneFwYniwAMRp2r_Rf4iWaRNRS1h6s/edit + + +--- + +## Problem 4 Reverse String Word By Word + +### Problem Statement +Given an input string s, reverse the order of the words. A word is defined as a sequence of non-space characters. The words in "s" are separated by at least one space. + +Return a string of the words in reverse order, concatenated by a single space. The returned string should only have a single space separating the words, even if there were multiple spaces in the input string. Leading or trailing spaces should be removed. + +### Examples +**Example 1:** +Input: +s = "Scaler is the best" +Output: +"best the is Scaler" + +Explanation: +The input string "Scaler is the best" contains four words: "Scaler", "is", "the", and "best". When reversed, the words become "best", "the", "is", and "Scaler". These are concatenated with a single space. + +**Example 2:** +Input: +s = " hello world " +Output: +"world hello" + +Explanation: +The input string " hello world " contains two words: "hello" and "world". Leading and trailing spaces are removed, and the words are reversed and concatenated with a single space. + +### Approach 1: + + + +### Code +JAVA +```cpp +class Solution { + public String reverseWords(String s) { + A = A.trim(); + String[] words = A.split("\\s+"); + StringBuilder sb = new StringBuilder(); + for (int i = words.length - 1; i >= 0; i--) { + sb.append(words[i]); + if (i > 0) { + sb.append(" "); + } + } + return sb.toString(); + } +} +``` + +PYTHON +```py +class Solution: + def solve(self,A): + A = A.strip() + A = A.split() + A = A[::-1] + return ' '.join(A) +``` + +C++ +```cpp +string reverseWords(string s) { + // Step 1: Split the string into words + vector words; + string word = ""; + for (char c : s) { + if (c != ' ') { + word += c; // Build the current word + } else if (c == ' ' && word != "") { + words.push_back(word); // Add the word to the list + word = ""; // Reset the word + } + } + if (word != "") { + words.push_back(word); // Add the last word if any + } + + // Step 2: Reverse the list of words + reverse(words.begin(), words.end()); + + // Step 3: Join the reversed words with a single space + string result = ""; + for (int i = 0; i < words.size(); i++) { + result += words[i]; + if (i < words.size() - 1) { + result += " "; // Add space between words, not after the last word + } + } + + return result; +} +``` + +### Approach + + +Suppose we want to reverse words in the string "I like Scaler", this can be done by first reversing each word individually and then reversing the whole word. + +Step 1: +"I like Scaler" -> "I ekil relacS" + +Step 2: +"I ekil relacS" -> "Scaler like I" + +Note that for this to work, we first need to preprocess the string so that it does not contain any leading, trailing or extra in between spaces. + +### Code + +C++ + +```cpp +class Solution { + public: + string reverseWords(string s) { + // reverse the whole string + reverse(s.begin(), s.end()); + + int n = s.size(); + int idx = 0; + for (int start = 0; start < n; ++start) { + if (s[start] != ' ') { + // go to the beginning of the word + if (idx != 0) s[idx++] = ' '; + + // go to the end of the word + int end = start; + while (end < n && s[end] != ' ') s[idx++] = s[end++]; + + // reverse the word + reverse(s.begin() + idx - (end - start), s.begin() + idx); + + // move to the next word + start = end; + } + } + s.erase(s.begin() + idx, s.end()); + return s; + } +}; +``` + +JAVA +```java +class Solution { + public StringBuilder trimSpaces(String s) { + int left = 0, right = s.length() - 1; + // remove leading spaces + while (left <= right && s.charAt(left) == ' ') ++left; + + // remove trailing spaces + while (left <= right && s.charAt(right) == ' ') --right; + + // reduce multiple spaces to single one + StringBuilder sb = new StringBuilder(); + while (left <= right) { + char c = s.charAt(left); + + if (c != ' ') sb.append(c); + else if (sb.charAt(sb.length() - 1) != ' ') sb.append(c); + + ++left; + } + return sb; + } + + public void reverse(StringBuilder sb, int left, int right) { + while (left < right) { + char tmp = sb.charAt(left); + sb.setCharAt(left++, sb.charAt(right)); + sb.setCharAt(right--, tmp); + } + } + + public void reverseEachWord(StringBuilder sb) { + int n = sb.length(); + int start = 0, end = 0; + + while (start < n) { + // go to the end of the word + while (end < n && sb.charAt(end) != ' ') ++end; + // reverse the word + reverse(sb, start, end - 1); + // move to the next word + start = end + 1; + ++end; + } + } + + public String reverseWords(String s) { + // converst string to string builder + // and trim spaces at the same time + StringBuilder sb = trimSpaces(s); + + // reverse the whole string + reverse(sb, 0, sb.length() - 1); + + // reverse each word + reverseEachWord(sb); + + return sb.toString(); + } +} +``` + +PYTHON +```python +class Solution: + def trim_spaces(self, s: str) -> list: + left, right = 0, len(s) - 1 + # remove leading spaces + while left <= right and s[left] == " ": + left += 1 + + # remove trailing spaces + while left <= right and s[right] == " ": + right -= 1 + + # reduce multiple spaces to single one + output = [] + while left <= right: + if s[left] != " ": + output.append(s[left]) + elif output[-1] != " ": + output.append(s[left]) + left += 1 + + return output + + def reverse(self, l: list, left: int, right: int) -> None: + while left < right: + l[left], l[right] = l[right], l[left] + left, right = left + 1, right - 1 + + def reverse_each_word(self, l: list) -> None: + n = len(l) + start = end = 0 + + while start < n: + # go to the end of the word + while end < n and l[end] != " ": + end += 1 + # reverse the word + self.reverse(l, start, end - 1) + # move to the next word + start = end + 1 + end += 1 + + def reverseWords(self, s: str) -> str: + # converst string to char array + # and trim spaces at the same time + l = self.trim_spaces(s) + + # reverse the whole string + self.reverse(l, 0, len(l) - 1) + + # reverse each word + self.reverse_each_word(l) + + return "".join(l) +``` diff --git a/Academy DSA Typed Notes/AIML/DSA Time & Space Complexity.md b/Academy DSA Typed Notes/AIML/DSA Time & Space Complexity.md new file mode 100644 index 0000000..d0336cc --- /dev/null +++ b/Academy DSA Typed Notes/AIML/DSA Time & Space Complexity.md @@ -0,0 +1,743 @@ +# DSA: Time & Space Complexity + + +## Problem 1 Count of factors + +Q. What is a factor? +A. We say i is a factor of N if i divides N completely, i.e the remainder is 0. + +How to programmatically check if i is a factor of N ? +We can use % operator which gives us the remainder. +=> **N % i == 0** + +### Problem Statement: +Given N, we have to count the factors of N. +**Note:** N > 0 + + +--- + +### Question +Number of factors of the number 24. + +### Choices +- [ ] 4 +- [ ] 6 +- [x] 8 +- [ ] 10 + + +### Explanation +1, 2, 3, 4, 6, 8, 12, and 24 are the factors. + +--- + +### Question +Number of factors of the number 10. + +### Choices +- [ ] 1 +- [ ] 2 +- [ ] 3 +- [x] 4 + + + +### Explanation + +1, 2, 5, and 10 are the factors. + +--- + +### Brute Force Solution + + +What is the minimum factor of a number ? +=> 1 + +What is the maximum factor of a number ? +=> The number itself + +So, we can find all factors of N from 1 to N. + + +### Pseudocode +```cpp= +function countfactors (N){ + fac_count = 0 + for(i -> 1 to N) { + if (N % i == 0) + fac = fac + 1 + } + return fac +} +``` + +> NOTE: Now that you all are well aware of language basics of your language, like writing basic for loop, while, loop, if else, array declaration, etc.. the codes will be writing as pseudocodes. What are they? => please explain by writing below code. + +### Observations for Optimised Solution + +* Now, your code runs on servers. +* When you submit your code, do you expect some time within which it should return the Output ? +* You wouldn't want to wait when you even don't know how long to wait for ? +* Just like that one friend who says, 'Just a little more time, almost there.' And you feel annoyed, not knowing how much longer you'll have to wait. + +Servers have the capability of running ~10^8 Iterations in 1 sec. +Please note that this is a standard assumption accross all platforms. + +> Note: We shall be learning more about it in next session + +Explain the time taken by above code for below values of N and need for Optimisation. + +|N| Iterations| Execution Time| +|-|----------|---------- | +|10^8| 10^8 iterations| 1 sec | +|10^9| 10^9 iterations| 10 sec | +|10^18| 10^18 iterations| 317 years | + +So, for N = 10^18, to just get the number of factors, we need around 317 years. + +*Would you be alive to watch the O/P ? +Your children ? +3rd Gen ? +4th Gen ? +............. too long to wait.* + + +#### Optimisation for Counting Factors + + +i * j = N -> {i and j are factors of N} + +=> j = N / i -> {i and N / i are factors of N} + +For example, N = 24 + +|i| N / i| +|-|----------| +|1| 24| +|2| 12| +|3| 8| +|4| 6| +|6| 4| +|8| 3| +|12| 2| +|24| 1| + +Q. Can we relate these values? +A. We are repeating numbers after a particular point. Here, that point is from 5th row. + +Now, repeat the above process again for N = 100. + +|i| N / i| +|-|----------| +|1| 100| +|2| 50| +|4| 25| +|5| 20| +|10| 10| +|20| 5| +|25| 4| +|50| 2| +|100| 1| + +The factors are repeating from 6th row. After a certain point factors start repeating, so we need to find a point till we have to iterate. + +We need to only iterate till - + +**`i <= N/i`** +**`i * i <= N`** + +### Pseudocode + +```cpp= +function countfactors(N){ + fac_count = 0 + for(i -> 1 till i*i <= N) { + if (N % i == 0) + fac = fac + 2 + } + return fac +} +``` + +Q. Will the above work in all the cases? +A. No, not for perfect squares. Explain this for N = 100, what mistake we are doing. We will count 10 twice. + +**Observation:** Using the above example, we need to modify the code for perfect squares. + + +### Pseudocode with Edge Case Covered + +```cpp= +function countfactors(N){ + fac_count = 0 + for(i -> 1 till i*i <= N) { + if (N % i == 0){ + if(i == N / i){ + fac = fac + 1 + } + else { + fac = fac + 2 + } + } + } + return fac +} +``` + +Dry run the above code for below examples, +N = 24, 100, 1. + +### What's the number of iterations now ? + +We are running loop till i * i <= N +Let's evaluate it - +![](https://d2beiqkhq929f0.cloudfront.net/public_assets/assets/000/043/846/original/upload_b7d89a4e3534f96005e65eaec0681e2a.png?1692787858) + + +|N| Iterations| Execution Time| +|-|----------|---------- | +|10^18| 10^9 iterations| 10 secs | + +**Conclusion:** Most important skill for problem solving is observation. + +>How beautifully we have been able to reduce the Iterations just by making some Observations! + +### Follow Up Question +Given N, You need to check if it is prime or not. + +--- +### Question +How many prime numbers are there? +10, 11, 23, 2, 25, 27, 31 + +### Choices +- [ ] 1 +- [ ] 2 +- [ ] 3 +- [x] 4 + + + +### Explanation +Q. What is a prime Number? +A. Number which has only 2 factors, 1 and N itself. + +So, 11, 23, 2, and 31 are the only prime numbers since they all have exactly 2 factors. + +--- + +## Some Basic Math Properties + +1. `[a,b]` - This type of range means that a and b are both inclusive. +2. `(a,b)` - This type of range means that a and b are both excluded. + + + +--- +### Question +How many numbers are there in the range [3,10]? + +### Choices +- [ ] 7 +- [ ] 6 +- [x] 8 +- [ ] 10 + +### Explanation + +The range [3,10] includes all numbers from 3 to 10, inclusive. Inclusive means that both the lower bound (3) and the upper bound (10) are included in the range. Thus the numbers that are included are 3 4 5 6 7 8 9 10. + +--- + +### Question +How many numbers are there in the range [a,b]? + +### Choices +- [ ] b - a +- [x] b - a + 1 +- [ ] b - a - 1 +- [ ] Ahhh.. too tricky to calculate + +### Explanation + +To find the number of numbers in a given range, we can subtract the lower bound from the upper bound and then add 1. Mathematically, this can be expressed as: +``` +Number of numbers in the range += Upper bound - Lower bound + 1 +``` + +--- + +### Question +1 + 2 + 3 + 4 + 5 + 6 + ... + 100 = ? + +### Choices +- [ ] 1010 +- [x] 5050 +- [ ] 5100 +- [ ] 1009 + +### Explanation + + + +Generalize this for the first N natural numbers. + + +--- + +## Geometric Progression + +> **Example for intution:** +``` +5 10 20 40 80 .. +``` +In these type of series, the common ratio is same. In the given example the common ratio r is += 10/5 += 20/10 += 40/20 += 80/40 += 2 + +**Generic Notation:** +a, a * r, a * r^2^, a * r^3^ ........a * r^n-1^ + +### Sum of first N terms of a GP + + +**Sum of first N terms of GP:** += + + +r cannot be equal to 1 because the denominator cannot be zero. + +**Note:** +When r is equal to 1, the sum is given by a * n. + +## Real Life Examples of GP + + +### **Spreading of news** +Suppose one person spreads a news to 4 different people. Each one of those, spreads to 4 others, and so on.. + + + +This can be denoted in form of GP which looks as follows- +1, 4, 16, 64, ..... + +### Number of people at 8th level +Now, if we want to figure out that at 8th level, how many people will know about this news, we can use formula for getting 8th term. + +Formula for nth term = a * r^n-1^ +a = 1, r = 4, n = 8 + +8th term = 1 * (4)^8-1^ = 16384 + +### Sum till 8th level +GP = 1, 4, 16, 64, 256, 1024, 4096, 16384 + +sum = a * ( r^#terms^ - 1)/ (r - 1) + += 1 * (4^8^ - 1)/(4 - 1) += (65536-1)/3 = 65535/3 = 21845 + +> **`Likewise, there can be multiple examples related to calculation of simple interest, spreading of a disease, growing population, depreciating value of car, etc...`** + + +### What do we mean by Iteration? + +The number of times a loop runs, is known as Iteration. + +--- +### Question +How many times will the below loop run ? + +``` +for(i -> 1 to N) +{ + if(i == N) break; +} +``` + +### Choices +- [ ] N - 1 +- [x] N +- [ ] N + 1 +- [ ] log(N) + +--- + +### Question +How many iterations will be there in this loop ? + +``` +for(i -> 0 to 100){ + s = s + i + i^2; +} +``` + +### Choices +- [ ] 100 - 1 +- [ ] 100 +- [x] 101 +- [ ] 0 + +--- + +### Question +How many iterations will be there in this loop? +``` +func(){ + for(i -> 1 to N){ + if(i % 2 == 0){ + print(i); + } + } + for(j -> 1 to M){ + if(j % 2 == 0){ + print(j); + } + } +} +``` + +### Choices +- [ ] N +- [ ] M +- [ ] N * M +- [x] N + M + + + +### Explanation + +We are executing loops one after the other. Let's say we buy first 5 apples and then we buy 7 apples, the total apples will be 12, so correct ans is N + M + +--- + +> How to compare two algorithms? + +### Story +There was a contest going on to SORT the array and 2 people took part in it (say Gaurav and Shila). + +They had to sort the array in ascending order. + +arr[5] = {3, 2, 6, 8, 1} -> {1, 2, 3, 6, 8} + +Both of them submitted their algorithms and they are being run on the same input. + +See this image to understand one important aspect of comparing two algorithms. + +## Discussion + +Can we use execution time to compare two algorithms? + +Say initially **Algo1** took **15 sec** and **Algo2** took **10sec**. + +This implies that **Shila's Algo 1** performed better, but then Gaurav pointed out that he was using **Windows XP** whereas Shila was using **MAC**, hence both were given the same laptops......... + + + +## Conclusion +We can't evaluate algorithm's performance using **execution time** as it depends on a lot of factors like operating system, place of execution, language, etc. + +### Question +How can we compare two algorithms? +Which measure doesn't depend on any factor? + +**Answer:** Number of Iterations + +**Why?** +* The number of iterations of an algorithm remains the same irrespective of Operating System, place of execution, language, etc. + + +--- + +## Asymptotic Analysis of Algorithms + +**Asymptotic Analysis** OR **Big(O)** simply means analysing perfomance of algorithms for **larger inputs**. + +### Calculation of Big(O) +**Steps** for **Big O** calculation are as follows: + +* Calculate **Iterations** based on **Input Size** +* Ignore **Lower Order Terms** +* Ignore **Constant Coefficients** + +**Example-** +Kushal's algo took **100 * log2N** iterations: Big O is **O(log2N)** +Ishani's algo took **N / 10** iterations: Big O is **O(N)** + +### Examples to calculate the Big O from the number of Iterations + +**For example**, +1. Iterations: 4N^2 + 3N + 1 +2. Neglect lower order term: 3N + 1; Remaining Term: 4N^2 +3. Neglect constant 4 + +Big O is O(N^2) + +### Comparsion Order: + +log(N) < sqrt(N) < N < N log(N) < N sqrt(N) < N^2 < N^3 < 2^(N) < N! < N^N + +**Using an example** +N = 36 +5 < 6 < 36 < 36\*5 < 36\*6 < 362 < 363 < 236 < 36! < 3636 + +**Ques:** What is the big notation time complexity of the following expression? +4N^2 + 3N + 6 sqrt(N) + 9 log_2(N) + 10 +Ans = O(N^2) + +> **IMPORTANT:** For every QUIZ in the lecture, please tell their Big O Notation to Students. + + +--- +### Question +F(N) = 4N + 3N * log(N) + 1 +O(F(N)) = ? + +### Choices +- [ ] N +- [x] N * log(N) +- [ ] Constant +- [ ] N2 + +--- + +### Question +F(N) = 4Nlog(N) + 3N * Sqrt(N) + 106 +O(F(N)) = ? + +### Choices +- [ ] N +- [ ] N * logN +- [ ] N2 +- [x] N * Sqrt(N) + +--- + +## Why do we neglect Lower Order Terms + + +Let's say the number of Iterations of an Algorithm are: N2+10N + +N|Total Iterations = N2+10N|Lower Order Term = 10N|% of 10N in total iterations = 10N/(N2+10N)*100 +-|-|-|- +10|200|100|50% +100|104+103|103|Close to 9% +10000|108+105|105|0.1% + +## Conclusion +We can say that, as the **Input Size** increases, the contribution of **Lower Order Terms** decreases. + +## Why do we neglect Constant Coefficients + + +When the comparison is on very larger input sizes, the constants do not matter after a certain point. For example, + +> In the below scenario, ask the students one by one which algo is better based on their Big O. + +| Algo 1(Nikhil)|Algo 2(Pooja)|Winner for Larger Input| +| -------- | -------- | -------- | +| 10 * log2 N | N | Nikhil | +| 100 * log2 N | N | Nikhil | +| 9 * N | N2 | Nikhil | +| 10 * N | N2 / 10| Nikhil | +| N * log2 N | 100 * N | Pooja | + +> A question can come that what if the constant is so big? +> We will then talk about Issues with Big O. + +--- + +## Issues with Big(O) + + +### Issue 1 + +**We cannot always say that one algorithm will always be better than the other algorithm.** + +**Example:** +* Algo1 (Iterations: 103 N) -> Big O: O(N) +* Algo2 (Iterations: N2) -> Big O: O(N2) +* Algo 1 is better than Algo 2 but only for large inputs, not for small input sizes. + + + +|Input Size (N)| Algo 1 (103* N) | Algo 2 (N2) | Optimised| +| --| --| --| --| +|N = 10| 104| 102|Algo 2| +|N = 100| 105| 104|Algo 2| +|N = 103| 106| 106|Both are same| +|N = 103 + 1| (103)*(103 + 1)| (103 + 1)*(103 + 1)|Algo 1| +|N = 104| 107| 108|Algo 1| + +**Claim:** For all large inputs >= 1000, Algo 1 will perform better than Algo 2. + +### Issue 2 +If 2 algorithms have same higher order terms, then Big O is not capable to identify algorithm with higher iterations. + +Consider the following questions - +Count the number of odd elements from 1 to N + +Code 1: Iterations: N +``` +for(i -> 1 to N) { + if(i%2 != 0) { + c = c+1; + } +} +``` + +Code 2: Iterations: N/2 +``` +for(i goes from 1 to N and get incremented by 2 in every iteration) { + c = c+1; +} +``` + +In both, Big O is O(N) but we know second code is better. + +--- + +## Importance of Constraints + +### Question + +If 1 <= N <= 105, +then which of the following Big O will work ? + +| Complexity | Iterations | Works ? | +| -------- | -------- | -------- | +| O(N3) | (105)3 | No | +| O(N2) log N | (1010)*log 105 | No | +| O(N2) | (105)2 | No | +| O(N * log N) | (105)*log 105 | Yes | + + +### Question +If 1 <= N <= 106, +then which of the following Big O will work ? + +| Complexity | Iterations | Works ? | +| -------- | -------- | -------- | +| O(N3) | (106)3 | No | +| O(N2) log N | (1012)*log 106 | No | +| O(N2) | (1012) | No | +| O(N * log N) | (106)*log 106 ~ 107 | May Be | +| O(N) | (106) | Yes | + + +#### Question +If constraints are +1 <= N <= 100, N3 will also pass. + +If constraints are +1 <= N <= 20, 2N will also pass. + +**Note:** +In Online Assessments, if we are not getting any other approach to a problem, try out the code; it may pass some test cases, which is better than nothing. + +--- + +## How to approach a problem? + +* Read the **Question** and **Constraints** carefully. +* Formulate an **Idea** or **Logic**. +* Verify the **Correctness** of the Logic. +* Mentally develop a **Pseudocode** or rough **Idea of Loops**. +* Determine the **Time Complexity** based on the Pseudocode. +* Assess if the time complexity is feasible and won't result in **Time Limit Exceeded (TLE)** errors. + * Note: In worst case we can only have **10^7 or 10^8 iterations**. +* **Re-evaluate** the **Idea/Logic** if the time constraints are not met; otherwise, proceed. +* **Code** the idea if it is deemed feasible. + +### Space Complexity + + +* Space complexity is the max space(worst case) that is utilised at any point in time during running the algorithm. +* We also determine Space Complexity using **Big O**. + +Consider the following example: + +NOTE: **`Please mention that for Python, we don't have to explicitly mention the data types while declaring array, hence it'll be an additional information for them, but good to know`**. + +``` +func(int N) { // 4 bytes + int x; // 4 bytes + int y; // 4 bytes + long z; // 8 bytes +} +``` + +* We only consider the space utilised by our program and not the Input Space since it is not in our control, hence we'll ignore space taken by "int N". +* The above code takes total **16B** of memory. +* Hence, we say the **Space Complexity** of the above code is **O(1)** (1 resembles constant). + + + +--- + +### Question +Find the Space Complexity [Big(O)] of the below program. +``` +func(int N) { // 4 bytes + int arr[10]; // 40 Bytes + int x; // 4 bytes + int y; // 4 bytes + long z; // 8 bytes + int arr[N]; // 4 * N bytes +} +``` +### Choices +- [x] N +- [ ] 4N + 60 +- [ ] Constant +- [ ] N^2 + +--- + +### Question +Find the Space Complexity [Big(O)] of the below program. + +```javascript +func(int N) { // 4 bytes + int x = N; // 4 bytes + int y = x * x; // 4 bytes + long z = x + y; // 8 bytes + int arr[N]; // 4 * N bytes + long l[N][N]; // 8 * N * N bytes +} +``` +### Choices +- [ ] N +- [ ] 4N + 60 +- [ ] Constant +- [x] N2 + +--- + +### Question on Space Complexity + +Find the Space Complexity [Big(O)] of the below program. + +``` +function maxArr(int arr[], int N) { + int ans = arr[0]; + for(i -> 1 to N-1) { + ans = max(ans, arr[i]); + } + return ans; +} +``` + +### Space complexity: O(1) + +* Don't consider the space acquired by the input size. Space complexity is the order of extra space used by the algorithm. +* **arr[]** is already given to us, we didn't create it, hence it'll not be counted in the Space Complexity. +* **int N** will also not be counted in space but since it is contant hence doesn't matter. +* Additional space is also called **computational or auxiliary space.** +* The above code finds the max element of the Array.