Rotate Array
  
     Leetcode - 189
  
  
    Rotate Array
  
  
  
  
    
    
  
  
    The given problem expects the given array to be rotated to the right by
        k steps. 
  
  
    For example, given [1, 2, 3, 4, 5, 6] and k = 3, we need to perform the
        following:
  
  
  
    
  
  
    and hence we get the array, [4, 5, 6, 1, 2, 3]
  
  
    
  
  
    We can generalize the problem as following:
  
  
    Given an array of n-elements, perform m-rotations by magnitudes r[0],
        r[1], ...., r[m-1] given as array r, either by left or right given by
        array d[0], d[1], d[2], ...., d[m-1]. If d[i] = 0, then rotate left or
        rotate right otherwise.
  
  
    
  
  
    We try solving this version of the problem. Finally we reduce the
      generalized problem into the original problem.
  
  
    
    
  
  
    Solution 1: Bruteforce -  O(mn) time, O(1) space
  
  
    
  
  
    We try solving the problem by following word by word as described in
        the problem statement. We need to rotate every a[i] by r[i] positions to
        the left if d[i] = 0 or to the right otherwise. 
  
  
    We can use modular arithematic and rotate by the magnitude of r[i]%n
        instead of r[i], since r[i] can also be greater than n. If we rotate by
        n-positions, it is same as not rotating at all. And hence we can rotate
        by r[i]%n to make sure rotation magnitude is between 0 and
        n-1(inclusive). 
  
  
    
      
    void rotations(vector<int>& a, vector<int>& r, vector<int>& d) {
	int m = r.size();
	for(int i = 0; i<m; ++i)
	    if(d[i] == 0) {
                rotate_right(a, n, r[i]%n);
	    } else {
		rotate_left(a, n, r[i]%n);
	    }
	}
    }       
 
      
        
      
      
        We can notice that time complexity of the code is dependent on how
            efficiently we implement rotate_left and rotate_right functions.
            Time complexity of rotate = O(m* time complexity of rotate left or
            right).
      
      
        
      
      
        Let us try deducing a method to perform rotation of an array to the
            right by r positions.
      
      
        
      
      
        Solution 1a: MOVE ONE ELEMENT AT A TIME - O(nr) time, O(1)
            time  
      
      
        
      
      
        One way is to rotate one position and repeat for r-times. We need
            to store nth element into a temporary variable as the (n-1)th
            element will overwrite it.
      
    
      
      
     void rotate_right(vector<int>& a, int r) {
 	        while(r--) {
 		    rotate_right_by_one_pos(a);
 	        }
         }
     void rotate_right_by_one_pos(vector<int>& a) {
 	    int temp = a[a.size() - 1];
 	    for(int j = a.size() - 2; j>=0; j--) {
      	    a[j+1] = a[j];
              }
            a[0] = temp;
       }
 
    
    
      Solution1b: MOVE A BLOCK OF ELEMENTS - O(n) time, O(n)
                        space
    
    
      
    
    
      The idea is to allocate an auxiliary biffer of
                        r-elements and use it as temporary storage to store
                        rightmost r-elements. Displace the first (n-r) elements
                        to the left and copy the elements in the temp storage to
                        the beginning of the modified array a.
    
    
      
    
    
    
      
    void rotate_right(vector<int>& a, int r) {
        vector<int> temp(r, -1);
        for(int i = a.size() - 1 - r, j = 0; j<r; ++i, ++j) {
            temp[j] = a[i];
        }
        for(int i = a.size() - 1 - r; i>=0; --i) {
            a[i+r] = a[i];
        }
        for(int i = 0; i<r; ++i) {
            a[i] = temp[i];
        }
    }
    
    
      Although this method takes just linear time to rotate_right and
              hence quadratic time to rotate(), we use a O(n) extra space.
    
    
      
    
    
      Solution 1c: MOVE EACH ELEMENT TO ITS TARGET POSITION: (Cyclic
              Permutation) - O(n) time, O(1) space
    
    
      
    
    
      The idea is to move each element r-positions to the right (modulo
              n) i.e a[i] goes to a[(i+r)%n]. We try processing elements as a
              cycle of elements which contains a[i], a[(i+r)%n], a[(i+2r)%n],
              ..., a[i]. Once an element belongs to a cycle and is processed, it
              doesn't belong to any other cycle and hence we can ensure a O(n)
              time complexity. Processing an element implies displacing an
              element to a next position i.e (i+r)%n the position in the cycle
              formed. 
    
    
      Consider the example of [1, 2, 3, 4, 5, 6] and k = 2.
    
    
      
    
    
    
      
    
    
      
    
    
      The important question to be answered is how many cycles are
              formed in an array of length n with each element at a distance of
              r?
    
    
      Let us assume that we start at index i. The element a[i] moves to
              index (i+r)%n, and the element at its position moves to (i+2r)%n
              position and so on..
    
    
      Let the length of the cycle formed = k, Therefore, (i+kr)%n = i
              as it is a cycle. And hence, kr should be a multiple of n i.e n |
              kr or n divides kr as (i+kr)%n = i.
    
    
      Now we have the facts that n | kr and also obviously r | kr and k
              is smallest such value i.e the length of our cycle is the smallest
              possible.
    
    
      Now from general arithematic, LCM(a, b) * GCD(a, b) = a * b, for
              all a, b being integers.
    
    
      Therefore,
    
    
      kr = LCM(n, r) => kr = nr / GCD(n, r) => k = n / GCD(n,
              r)
    
    
      
    
    
    void rotate_right(vector<int >& a, int r) {
        for(int start = 0; start < gcd(n, r); ++i) {
            int next_inx = start;
            int ele = a[start];
            do {
                next_inx = (next_inx + r) % n;
                int temp = a[next_inx];
                a[next_inx] = ele;
                ele = temp;
            } while(next_inx != start) ;
        }
    }
  
 
    
      Therefore, the length of each cycle is [n / GCD(n, r)] and hence
              we will have GCD(n, r) number of cycles.
    
    
      Hence, for every cycle, we process each element and displace it
              to its next position in the cycle.
    
    
      
    
    
      Time complexity of this algorithm is O(n) as each element is moved
        exactly once and assuming GCD(x, y) takes less that or equals to O(n).
        No extra space used!
    
    
      
    
    
      NOTE: GCD(a, b) can be computed using
                                  Euclid's method which has a theoritical bound
                                  of O(logb) where   a >= b.
    
    
      It states, GCD(a, b) = GCD(b, a%b) and GCD(a,
                                  0) = a.
    
    
      
    
    
      Solution1d: MULTIPLE REVERSES - O(n) time, O(1) space:
    
    
      
    
    
      Let array a = xy where x = a[0], a[1], ..., a[n-r-1] and y =
              a[n-r], a[n-r+1], .. a[n-1].
    
    
      The aim is to get a = yx from a = xy. Let xr represent reverse of x.
    
      
    
    
      Step 1: Reverse a => 
    
    
      a = xy becomes a = (xy)r =  yrxr
    
    
      Step 2: Reverse first r-elements => 
    
    
      a =  yrxr becomes a = (yr)rxr = yxr
    
    
      Step 3: Reverse the rightmost (n-r) elements
    
    
      a =  yxr becomes a = y(xr)r  = yx which is the target
    
    
      
    
    
    
    
    void rotate_right(vector<int>& a, int r) {
        reverse(nums.begin(), nums.end());
        reverse(nums.begin(), nums.begin() + k);
        reverse(nums.begin() + k, nums.end());
    }
    
    
      We use no extra space and time complexity is O(n) + O(n-r) + O(r) =
        O(n) and hence linear making rotate() quadratic.
    
    
      
    
    
      Solution 2: Using Effective Rotation magnitude -  O(n)
                time, O(1) space:
    
    
      
    
    
      Unitl now, the best performance we have achieved is by a quadratic time
              algorithm and constant space algorithm. This is because we repeat
              the rotation for every m-elements. Instead of this we can
              calculate rotation magnitude and then rotate by that amount. Also
              instead have 2 directions left and right to rotate, we can
              consider left rotation as a negative form of right direction and
              subtract from effective rotation magnitude.
    
    
      
    
    
          void rotations(vector<int>& a, vector<int>& r, vector<int>& d) {
	int m = r.size(), n = a.size();
        int eff = 0;
	for(int i = 0; i<m; ++i)
	    if(d[i] == 0) {
                eff = (eff - r[i])%n;
	    } else {
		eff = (eff + r[i])%n;
	    }
	}   
        if(eff < 0) eff = eff + n;
        rotate_right(a, eff);
    }     
    
    
      
    
    
      Now we can see that m-rotation can be done in O(n) time
              complexity and O(1) extra space.
    
    
      
    
    
      And the solution to the original leetcode question, is the
              function rotate_right() discussed above which can be done in O(n)
              time and O(1) time. 
    
  
    void rotations(vector<int>& a, vector<int>& r, vector<int>& d) {
	int m = r.size();
	for(int i = 0; i<m; ++i)
	    if(d[i] == 0) {
                rotate_right(a, n, r[i]%n);
	    } else {
		rotate_left(a, n, r[i]%n);
	    }
	}
    }       
 
        
      
        We can notice that time complexity of the code is dependent on how
            efficiently we implement rotate_left and rotate_right functions.
            Time complexity of rotate = O(m* time complexity of rotate left or
            right).
      
      
        Let us try deducing a method to perform rotation of an array to the
            right by r positions.
      
      
        Solution 1a: MOVE ONE ELEMENT AT A TIME - O(nr) time, O(1)
            time  
      
      
        One way is to rotate one position and repeat for r-times. We need
            to store nth element into a temporary variable as the (n-1)th
            element will overwrite it.
      
    
     void rotate_right(vector<int>& a, int r) {
 	        while(r--) {
 		    rotate_right_by_one_pos(a);
 	        }
         }
     void rotate_right_by_one_pos(vector<int>& a) {
 	    int temp = a[a.size() - 1];
 	    for(int j = a.size() - 2; j>=0; j--) {
      	    a[j+1] = a[j];
              }
            a[0] = temp;
       }
 
      This method will make rotate_right(), O(nr) time
                        complexity i.e quadratic and hence rotate() cubic. No
                        extra space is needed.
    
    
      Solution1b: MOVE A BLOCK OF ELEMENTS - O(n) time, O(n)
                        space
    
    
      The idea is to allocate an auxiliary biffer of
                        r-elements and use it as temporary storage to store
                        rightmost r-elements. Displace the first (n-r) elements
                        to the left and copy the elements in the temp storage to
                        the beginning of the modified array a.
    
    
    void rotate_right(vector<int>& a, int r) {
        vector<int> temp(r, -1);
        for(int i = a.size() - 1 - r, j = 0; j<r; ++i, ++j) {
            temp[j] = a[i];
        }
        for(int i = a.size() - 1 - r; i>=0; --i) {
            a[i+r] = a[i];
        }
        for(int i = 0; i<r; ++i) {
            a[i] = temp[i];
        }
    }
      Although this method takes just linear time to rotate_right and
              hence quadratic time to rotate(), we use a O(n) extra space.
    
    
      Solution 1c: MOVE EACH ELEMENT TO ITS TARGET POSITION: (Cyclic
              Permutation) - O(n) time, O(1) space
    
    
      The idea is to move each element r-positions to the right (modulo
              n) i.e a[i] goes to a[(i+r)%n]. We try processing elements as a
              cycle of elements which contains a[i], a[(i+r)%n], a[(i+2r)%n],
              ..., a[i]. Once an element belongs to a cycle and is processed, it
              doesn't belong to any other cycle and hence we can ensure a O(n)
              time complexity. Processing an element implies displacing an
              element to a next position i.e (i+r)%n the position in the cycle
              formed. 
    
    
      Consider the example of [1, 2, 3, 4, 5, 6] and k = 2.
    
    
      The important question to be answered is how many cycles are
              formed in an array of length n with each element at a distance of
              r?
    
    
      Let us assume that we start at index i. The element a[i] moves to
              index (i+r)%n, and the element at its position moves to (i+2r)%n
              position and so on..
    
    
      Let the length of the cycle formed = k, Therefore, (i+kr)%n = i
              as it is a cycle. And hence, kr should be a multiple of n i.e n |
              kr or n divides kr as (i+kr)%n = i.
    
    
      Now we have the facts that n | kr and also obviously r | kr and k
              is smallest such value i.e the length of our cycle is the smallest
              possible.
    
    
      Now from general arithematic, LCM(a, b) * GCD(a, b) = a * b, for
              all a, b being integers.
    
    
      Therefore,
    
    
      kr = LCM(n, r) => kr = nr / GCD(n, r) => k = n / GCD(n,
              r)
    
    
    void rotate_right(vector<int >& a, int r) {
        for(int start = 0; start < gcd(n, r); ++i) {
            int next_inx = start;
            int ele = a[start];
            do {
                next_inx = (next_inx + r) % n;
                int temp = a[next_inx];
                a[next_inx] = ele;
                ele = temp;
            } while(next_inx != start) ;
        }
    }
  
 
      Therefore, the length of each cycle is [n / GCD(n, r)] and hence
              we will have GCD(n, r) number of cycles.
    
    
      Hence, for every cycle, we process each element and displace it
              to its next position in the cycle.
    
    
      Time complexity of this algorithm is O(n) as each element is moved
        exactly once and assuming GCD(x, y) takes less that or equals to O(n).
        No extra space used!
    
    
      NOTE: GCD(a, b) can be computed using
                                  Euclid's method which has a theoritical bound
                                  of O(logb) where   a >= b.
    
    
      It states, GCD(a, b) = GCD(b, a%b) and GCD(a,
                                  0) = a.
    
    
      Solution1d: MULTIPLE REVERSES - O(n) time, O(1) space:
    
    
      Let array a = xy where x = a[0], a[1], ..., a[n-r-1] and y =
              a[n-r], a[n-r+1], .. a[n-1].
    
    
      The aim is to get a = yx from a = xy. Let xr represent reverse of x.
    
      Step 1: Reverse a => 
    
    
      a = xy becomes a = (xy)r =  yrxr
    
    
      Step 2: Reverse first r-elements => 
    
    
      a =  yrxr becomes a = (yr)rxr = yxr
    
    
      Step 3: Reverse the rightmost (n-r) elements
    
    
      a =  yxr becomes a = y(xr)r  = yx which is the target
    
    void rotate_right(vector<int>& a, int r) { reverse(nums.begin(), nums.end()); reverse(nums.begin(), nums.begin() + k); reverse(nums.begin() + k, nums.end()); }
      We use no extra space and time complexity is O(n) + O(n-r) + O(r) =
        O(n) and hence linear making rotate() quadratic.
    
    
      Solution 2: Using Effective Rotation magnitude -  O(n)
                time, O(1) space:
    
    
      Unitl now, the best performance we have achieved is by a quadratic time
              algorithm and constant space algorithm. This is because we repeat
              the rotation for every m-elements. Instead of this we can
              calculate rotation magnitude and then rotate by that amount. Also
              instead have 2 directions left and right to rotate, we can
              consider left rotation as a negative form of right direction and
              subtract from effective rotation magnitude.
    
        void rotations(vector<int>& a, vector<int>& r, vector<int>& d) {
	int m = r.size(), n = a.size();
        int eff = 0;
	for(int i = 0; i<m; ++i)
	    if(d[i] == 0) {
                eff = (eff - r[i])%n;
	    } else {
		eff = (eff + r[i])%n;
	    }
	}   
        if(eff < 0) eff = eff + n;
        rotate_right(a, eff);
    }     
      Now we can see that m-rotation can be done in O(n) time
              complexity and O(1) extra space.
    
    
      And the solution to the original leetcode question, is the
              function rotate_right() discussed above which can be done in O(n)
              time and O(1) time. 
    
  
Comments
Post a Comment