[LeetCode learning plan] algorithm - Introduction - C + +, day 2, double pointer

977. Square of ordered array

[LeetCode]

simple single \color{#00AF9B} {simple} simple

Give you an integer array nums sorted in non decreasing order, and return a new array composed of the square of each number. It is also required to sort in non decreasing order.

Example 1:

Input: nums = [-4,-1,0,3,10]
Output:[0,1,9,16,100]
Explanation: after squaring, the array becomes [16,1,0,9,100]
After sorting, the array becomes [0,1,9,16,100]

Example 2:

Input: nums = [-7,-3,2,3,11]
Output:[4,9,9,49,121]

Tips:

  • 1 <= nums.length <= 104
  • -104 <= nums[i] <= 104
  • nums is sorted in non decreasing order

Advanced:

Please design an algorithm with time complexity O(n) to solve this problem

Method 1: direct sort

The idea of this method is very simple. After each item in the array is squared, the whole array is sorted.

#include <vector>
#include <algorithm>
using namespace std;
class Solution
{
public:
    vector<int> sortedSquares(vector<int> &nums)
    {
        vector<int> ans;
        ans.reserve(nums.size());
        for (int num : nums)
        {
            ans.emplace_back(num * num);
        }
        sort(ans.begin(), ans.end());
        return ans;
    }
};

Complexity analysis

Time complexity: O(n logn)

Spatial complexity: O(logn). The array of answers is not included. We need O(logn) stack space to sort.

Reference results

Accepted
137/137 cases passed (32 ms)
Your runtime beats 46.63 % of cpp submissions
Your memory usage beats 56.4 % of cpp submissions (25.3 MB)

Method 2: double pointer

We can set two pointers left and right to point to the beginning and end of the array respectively. In each loop, compare the sizes of num [left] ^ 2 and num [right] ^ 2, put the largest value at the end of the answer array in reverse order, and then change the corresponding pointer.

Process demonstration:

#include <vector>
using namespace std;
class Solution
{
public:
    vector<int> sortedSquares(vector<int> &nums)
    {
        int n = nums.size();

        vector<int> ans(n);

        for (int left = 0, right = n - 1, k = n - 1; left <= right; k--)
        {
            int a = nums[left] * nums[left], b = nums[right] * nums[right];
            if (a > b)
            {
                ans[k] = a;
                left++;
            }
            else
            {
                ans[k] = b;
                right--;
            }
        }

        return ans;
    }
};

Complexity analysis

Time complexity: O(n)

Space complexity: O(1). The array of answers is not included. We only need a constant space to store several variables.

Reference results

Accepted
137/137 cases passed (24 ms)
Your runtime beats 85.42 % of cpp submissions
Your memory usage beats 78.88 % of cpp submissions (25.2 MB)

189. Rotation array

LeetCode

in etc. \color{#FFB800} {medium} secondary

Give you an array, rotate the elements in the array K positions to the right, where k is a non negative number.

Example 1:

input: nums = [1,2,3,4,5,6,7], k = 3
 output: [5,6,7,1,2,3,4]
explain:
Rotate 1 step to the right: [7,1,2,3,4,5,6]
Rotate right for 2 steps: [6,7,1,2,3,4,5]
Rotate right for 3 steps: [5,6,7,1,2,3,4]

Example 2:

Input: nums = [-1,-100,3,99], k = 2
 Output:[3,99,-1,-100]
explain: 
Rotate 1 step to the right: [99,-1,-100,3]
Rotate right for 2 steps: [3,99,-1,-100]

Tips:

  • 1 <= nums.length <= 105
  • -231 <= nums[i] <= 231 - 1
  • 0 <= k <= 105

Advanced:

  • Think of as many solutions as possible. There are at least three different ways to solve this problem.
  • Can you use the in-situ algorithm with spatial complexity O(1) to solve this problem?

be careful

  1. The title requires modification on the original array, rather than returning a new array.
  2. Move the whole array nums by k bits to the right
  3. The value of k may exceed the length of the array. That is, move the whole array k/n times, return to the original position, and then move k%n times to get a new array. Therefore, when calculating the subscript, we must not forget that the final result should take the remainder of n.

Method 1: use additional arrays

This may be the simplest idea of the topic. We can define an array dummy with the same length as the original array nums, and put the results into the new array dummy in order. Finally, replace all items in nums with data in dummy.

According to the meaning of the title, in the original array with length n, the item i is moved k bits to the right and stored in the new array, then the subscript of item i in the new array can be obtained from the following mapping relationship:
i → ( i + k ) % n i \rightarrow (i+k)\%n i→(i+k)%n
Namely:
d u m m y [ ( i + k ) % n ] = n u m s [ i ] dummy[(i + k) \% n] = nums[i] dummy[(i+k)%n]=nums[i]

#include <vector>
using namespace std;

typedef unsigned int ui;

class Solution
{
public:
    void rotate(vector<int> &nums, int k)
    {
        int n = nums.size();
        vector<int> dummy(n);
        for (int i = 0; i < n; i++)
        {
            dummy[(i + k) % n] = nums[i];
        }
        nums.assign(dummy.begin(), dummy.end());
    }
};

Complexity analysis

Time complexity: O(n)

Space complexity: O(n)

Reference results

Accepted
38/38 cases passed (28 ms)
Your runtime beats 49.29 % of cpp submissions
Your memory usage beats 27.54 % of cpp submissions (24.9 MB)

Method 2: three flips

Let's take the array [1,2,3,4,5,6,7] and k=3 as examples to observe the original array and the results:
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] [ 5 , 6 , 7 , 1 , 2 , 3 , 4 ] [1,2,3,4,\color{red}{5} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{7} \color{#000000}{}] \\ [\color{red}{5} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{7} \color{#000000}{},1,2,3,4] [1,2,3,4,5,6,7][5,6,7,1,2,3,4]

Let's flip the K items on the left and the n-k items on the right in the results respectively:
[ 7 , 6 , 5 , 4 , 3 , 2 , 1 ] [\color{red}{7} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{5} \color{#000000}{},4,3,2,1] [7,6,5,4,3,2,1]
You can find that it is the global flip of the original array. In other words, the process from the original array to the result array is as follows:

  1. Global flip
  2. Left k-term flip
  3. Right n-k term flip

Namely:
[ 1 , 2 , 3 , 4 , 5 , 6 , 7 ] ↓ 1 [ 7 , 6 , 5 , 4 , 3 , 2 , 1 ] ↓ 2 [ 5 , 6 , 7 , 4 , 3 , 2 , 1 ] ↓ 3 [ 5 , 6 , 7 , 1 , 2 , 3 , 4 ] [1,2,3,4,\color{red}{5} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{7} \color{#000000}{}] \\ {\downarrow}_{1} \\ [\color{red}{7} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{5} \color{#000000}{},4,3,2,1] \\ {\downarrow}_{2} \\ [\color{red}{5} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{7} \color{#000000}{},4,3,2,1] \\ {\downarrow}_{3} \\ [\color{red}{5} \color{#000000}{},\color{red}{6} \color{#000000}{},\color{red}{7} \color{#000000}{},1,2,3,4] [1,2,3,4,5,6,7]↓1​[7,6,5,4,3,2,1]↓2​[5,6,7,4,3,2,1]↓3​[5,6,7,1,2,3,4]

These three steps are the same process, that is, flip the array from a start item to an end item. Therefore, these three steps can be regarded as three function calls. We can define this function as reverse(). The flipping process can be completed by double pointers.

We can set the two pointers left and right to point to the start item and the end item respectively, exchange the values pointed to by the two pointers each time, and then advance one unit to the middle until they point to the same element (an element does not need to be exchanged with itself) or left runs to the right of right, and the loop ends. Thus, the effective condition of the cycle can be determined as left < right.

#include <vector>
using namespace std;
class Solution
{
public:
    void rotate(vector<int> &nums, int k)
    {
        int n = nums.size();
        k %= n;
        reverse(nums, 0, n - 1);
        reverse(nums, 0, k - 1);
        reverse(nums, k, n - 1);
    }
    void reverse(vector<int> &nums, int left, int right)
    {
        while (left < right)
        {
            swap(nums[left], nums[right]);
            left++;
            right--;
        }
    }
};

Please note that:

  1. The array subscript starts from 0, so the K items on the left of the array are flipped from item 0 to item k-1. The same is true for the right side of the array.
  2. The k value may be greater than the array length N, that is, the whole array is moved k/n times, and then moved k%n times to get the answer. So here we want to set k as k%n.

Complexity analysis

Time complexity: O(n). In addition to the global flip and the subsequent two respective flips, each element in the array is flipped twice, so the time complexity is O(2n)=O(n).

Space complexity: O(1), we only need constant space to store several variables.

Reference results

Accepted
38/38 cases passed (28 ms)
Your runtime beats 49.29 % of cpp submissions
Your memory usage beats 91.94 % of cpp submissions (24.2 MB)

Method 3: in situ solution

Method 1 uses additional arrays to make the space complexity reach O(n); Method 2 traverses each element in the array twice, and the time complexity is O(2n)=O(n). Is there any way to reduce the space complexity to constant space and only need to traverse the array elements once? The answer is yes.

We can optimize from method 1. In method 1, our operation on each element is to "put the element directly in its final position". There is no problem with this idea, but the problem lies in our method of traversing elements, which is the fundamental reason why we use additional arrays.

In method 1, when the i-th element is processed, that is, placed at the position (i+k)%n, the selection strategy of the next element to be processed is sequential selection, that is, the i+1 element. Under this strategy, the elements originally placed at (i+k)%n are not only overwritten, but also directly discarded. That's why we need the original array to record what was in each position, and then write the results to a new array. Therefore, to optimize method 1, we need to change the selection strategy of the next element to be processed.

Method 3 begins to be clear. We put the i-th element at position (i+k)%n and cover the element at that position. Therefore, we should take the (i+k)%n position as the element to be processed, save it, put it at the (i+2*k)%n position, and so on.

Taking nums=[1,2,3,4,5,6,7], k=3 as an example, the transformation process of the array is as follows:

It can be found that one round of traversal can complete the flip of the array. The end condition of the loop is also very simple, that is, the first item of the array is accessed. But let's focus on another example: num = [- 1, - 100,3,99], k=2. It can be found that a round of traversal can only exchange - 1 and 3. From this, we can conclude that:

  1. When the array length n cannot be divided by k, a round of traversal must be able to exchange all data;

  2. When n can be divided by k, the number of round s exchanged is the greatest common divisor (gcd) of N and k.

So far, the number of cycles of the first layer can be determined as the number of rounds of exchange round. Next, we need to determine the number of exchanges in a round (count). In the case of non divisible (count = = 1), we need to exchange n times per round, that is, one round of exchange is completed; For the case of integer division (count! = 1), we can only exchange n/round times in a round.

Namely:
c o u n t = { n + 1 r o u n d = 1 n / r o u n d r o u n d ≠ 1 count= \left\{\begin{matrix} n+1 & round=1 \\ n/round & round \neq 1 \end{matrix}\right. count={n+1n/round​round=1round​=1​

Here are some process demonstrations of other examples:

nums=[-1,-100,3,99],k=2:
nums=[1,2,3,4,5,6],k=2:
nums=[1,2,3,4,5,6],k=3:

#include <vector>
#include <numeric>
using namespace std;
class Solution
{
public:
    void rotate(vector<int> &nums, int k)
    {
        int n = nums.size();
        k %= n;
        if (k == 0)
            return;

        int round = gcd(n, k);
        int count = round == 1 ? n + 1 : n / round;
        for (int i = 0; i < round; i++)
        {
            int last = nums[i];
            for (int j = 1; j <= count; j++)
            {
                swap(last, nums[(i + j * k) % n]);
            }
        }
    }
};

Complexity analysis

Time complexity: O(n). N is the length of the array, and each element will be traversed only once.

Space complexity: O(1), we only need constant space to store several variables.

Reference results

Accepted
38/38 cases passed (20 ms)
Your runtime beats 92.01 % of cpp submissions
Your memory usage beats 98.33 % of cpp submissions (24.2 MB)

Animation powered by ManimCommunity/manim

Tags: C++ Algorithm data structure leetcode

Posted on Sun, 21 Nov 2021 04:02:29 -0500 by leszczu