[code Capriccio] Chapter 10 greedy algorithm

Posted by aaaaCHoooo on Mon, 21 Feb 2022 09:23:53 +0100

Chapter 10 greedy algorithm

Greed has no fixed template routine

If we can find out the local optimum and deduce the global optimum, it is greedy; If the local optimum is not found, it is not greedy, but may be a simple simulation.

Greedy algorithm is generally divided into the following four steps:

  • The problem is decomposed into several sub problems
  • Find the right greedy strategy
  • Solve the optimal solution of each subproblem
  • Stacking local optimal solutions into global optimal solutions

When you brush questions or interview, you can manually simulate the local optimum and deduce the overall optimum. If you can't think of a counterexample, try greed.

455. Distribution of biscuits [simple]

Idea: small biscuits give small appetite

It can also be reversed. Big biscuits give you a big appetite

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int j = 0;  //Positioning g, i.e. appetite, must be initialized here, or an error will be reported
        for(int i = 0; i < s.size(); i++) {  //Traversal biscuit
            if(j < g.size() && s[i] >= g[j]) {  //The previous judgment condition prevents exceeding the index
                j++;
            }
        }
        return j;   //Index is the number of people
    }
};

376. Swing sequence [medium]

Finding the number of minima

  • Idea 1: greed

Time complexity: O(n)

Space complexity: O(1)

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() < 2)   return nums.size();

        int preDiff = nums[1] - nums[0];
        int res = preDiff != 0 ? 2 : 1;
        for(int i = 2; i < nums.size(); ++i){
            int curDiff = nums[i] - nums[i-1];
            //Peak
            if((preDiff<=0 && curDiff>0) || (preDiff>=0 && curDiff<0)){
                res++;
                preDiff = curDiff;
            }
        }
    	return res;
    }
};

Idea 2: dynamic programming

53. Maximum subarray and [simple]

Same title: Sword finger Offer 42 Maximum sum of continuous subarrays

Idea 1: double for loop brute force cracking: calculate the maximum sub order sum from each index

//Two cycle brute for ce cracking
//Calculate the maximum substring starting from index 0, starting from 1, starting from 2
//Time complexity: O(n^2), exceeding the time limit
class Solution
{
public:
    int maxSubArray(vector<int>& nums){
        //Similar to the problem of finding the maximum and minimum value, the initial value must be defined as the theoretical minimum or maximum value
        int sumMax = INT_MIN;
        for (int i = 0; i < nums.size(); i++){
            int sum = 0;
            for (int j = i; j < nums.size(); j++){
                sum += nums[j];
                sumMax = max(sum, sumMax);
            }
        }

        return sumMax;
    }
};

Idea 2: dynamic programming

You can view this person's PPT: https://leetcode-cn.com/problems/maximum-subarray/solution/zui-da-zi-xu-he-cshi-xian-si-chong-jie-fa-bao-li-f/

Time complexity: O(n)

Space complexity: O(n)

class Solution {
public:
    int maxSubArray(vector<int>& nums) {		
		//Update array by replacing elements first
        for (int i = 1; i < nums.size(); i++) {
            if (nums[i - 1] > 0) {          //If the previous element > 0, the latter element is changed to the sum of the two
                nums[i] = nums[i - 1] + nums[i];
            }
        }

		//Then traverse to find the maximum value
        int res = nums[0];
        for (int i : nums) {
            res = max(res, i);
        }

        return res;
    }
};

We can optimize two parallel for loops and solve them with only one for loop

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = nums[0];

        for (int i = 1; i < nums.size(); i++) {
            if (nums[i - 1] > 0) {
                nums[i] = nums[i - 1] + nums[i];
            }
            if (nums[i] > res) {
                res = nums[i];
            }
        }

        return res;
    }
};

Then optimize and shorten the code and get close to the official reference answer

  • [x]
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = nums[0];

        for (int i = 1; i < nums.size(); i++) {
            nums[i] = max(nums[i], nums[i - 1] + nums[i]);  //Mainly this code
            res = max(res, nums[i]);                        //Dynamic recording maximum subsequence sum
        }

        return res;
    }
};

Thoughts on dynamic planning of code Capriccio:

Trilogy:

  1. Determine the meaning of dp array (dp table) and subscript
  2. Determine recurrence formula
  3. How to initialize dp array
  4. Determine traversal order
  5. Derivation of dp array by example
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        
        vector<int> dp(nums.size());
        dp[0] = nums[0];
        int res = dp[0];
        
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]); // State transition formula
            res = max(dp[i], res);                     // Max value of dp[i] saved by res      
        }
        
        return res;
    }
};
  • Idea 3: greed

Time complexity: O(n)

Space complexity: O(1)

View ideas: https://leetcode-cn.com/problems/maximum-subarray/solution/dai-ma-sui-xiang-lu-53-zui-da-zi-xu-he-b-8d7l/

Where is greedy?

If - 2 and 1 are together, the starting point must be calculated from 1, because negative numbers will only lower the sum, which is the place of greed!

Local optimization: when the current "continuous sum" is negative, give up immediately and recalculate the "continuous sum" from the next element, because the negative number plus the "continuous sum" of the next element will only be smaller and smaller.

Global Optimization: select the maximum "continuous sum"

In the case of local optimization, and record the maximum "continuous sum", the global optimization can be deduced.

class Solution{
public:
    int maxSubArray(vector<int>& nums){
        //Similar to the problem of finding the maximum and minimum value, the initial value must be defined as the theoretical minimum and maximum value
        int res = INT_MIN;
        int sum = 0;
        for (int i = 0; i < nums.size(); i++){
            sum += nums[i];
            res = max(res, sum);
            
            //If sum < 0, start looking for the sub sequence string again
            if (sum < 0){
                sum = 0;
            }
        }

        return res;
    }
};

1005. Maximum array sum after K negations [simple]

class Solution {
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end());
        int nums_min = INT_MAX;
        int res = 0;
        for(int i = 0; i < nums.size(); i++){
            if(k > 0 && nums[i] < 0){
                nums[i] = -nums[i];
                k--;
            }
            res += nums[i];
            nums_min = min(nums_min, nums[i]);
        }
        
        if(k % 2 == 0) return res;
        else return res - 2*nums_min;
    }
};
  • Mainly write absolute value sorting
class Solution {
static bool cmp(int a, int b) {   //Absolute values are sorted from large to small
    return abs(a) > abs(b);
}
    
public:
    int largestSumAfterKNegations(vector<int>& nums, int k) {
        sort(nums.begin(), nums.end(), cmp);       // Step 1: sort by absolute value, from large to small
        
        for (int i = 0; i < nums.size(); i++) {    // Step 2: - change+
            if (nums[i] < 0 && k > 0) {
                nums[i] = -nums[i];
                k--;
            }
        }
        
        if (k % 2 == 1)  nums[nums.size() - 1] *= -1; // Step 3: how much is k? See if you need to change the sign of the minimum value
        
        int res = 0;
        for (int num : nums)  res += num;        // Step 4: collect
        return res;
    }
};

134. Gas stations [medium]

Idea 1: violent cracking

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        for (int i = 0; i < cost.size(); i++) {
            int rest = gas[i] - cost[i]; // Record the remaining oil
            if (rest < 0)  continue;
            
            int index = (i + 1) % cost.size(); //Index of the next gas station
            while (rest >= 0 && index != i) {  // Simulate driving a circle with i as the starting point
                rest += gas[index] - cost[index];
                index = (index + 1) % cost.size();
            }
            
            // If you take i as the starting point and run one lap, the remaining oil quantity > = 0, return to the starting position
            if (rest >= 0 && index == i) return i;
        }
        return -1;
    }
};

Idea 2: greed

  • Case 1: if the sum of gas is less than the sum of cost, you can't run a lap no matter where you start
  • Case 2: rest[i] = gas[i]-cost[i] is the remaining oil of the day. I is calculated from 0 and accumulated to the last station. If there is no negative number in the accumulation, it means that the oil has not been cut off from 0, so 0 is the starting point.
  • Case 3: if the minimum accumulated value is negative, the car should start from the non-0 node. From the back to the front, see which node can fill in the negative number. The node that can fill in the negative number is the starting node.
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int minSum = INT_MAX; // Starting from the starting point, the minimum amount of oil in the tank
        for (int i = 0; i < gas.size(); i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            minSum = min(minSum, curSum);
        }
        if (curSum < 0) return -1;  // Case 1
        if (min >= 0) return 0;     // Situation 2
                                    // Situation 3
        for (int i = gas.size() - 1; i >= 0; i--) {
            int rest = gas[i] - cost[i];
            min += rest;
            if (min >= 0) {
                return i;
            }
        }
        return -1;
    }
};
  • Idea 3: greedy method 2
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int totalSum = 0;   //Remaining fuel after full lap
        int start = 0;
        for (int i = 0; i < gas.size(); i++) {
            curSum += gas[i] - cost[i];   
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {   // Once the current cumulative rest[i] and curSum are less than 0
                start = i + 1;  // The starting position is updated to i+1
                curSum = 0;     // curSum starts at 0
            }
        }
        
        if (totalSum < 0)    return -1; // It means you can't run a lap
        else return start;
    }
};

860. Lemonade change [simple]

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int money[2] = {0,0};  //Record the number of 5 and 10 yuan notes respectively
        for(auto bill : bills){
            if(bill == 5){
                money[0]++;
            }
            else if(bill == 10){
                money[0]--;
                money[1]++;
                if(money[0] < 0)  return false;
            }
            else if(bill == 20){
                if(money[1] > 0){  //There are 10 yuan bills. One 5 yuan and one 10 yuan change
                    money[1]--;
                    money[0]--;
                }
                else{
                    money[0] -= 3;  //If you don't have 10 yuan, change three pieces of 5 yuan
                }

                if(money[0] < 0)  return false;
            }
        }
        return true;
    }
};

452. Detonate the balloon with a minimum number of arrows [medium]

  • Sort by left boundary, from small to large
class Solution {
private:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        return a[0] < b[0];
    }
    
public:
    int findMinArrowShots(vector<vector<int>>& points) {
        if (points.size() == 0) return 0;
        sort(points.begin(), points.end(), cmp);

        int res = 1; // points is not empty. At least one arrow is required
        for (int i = 1; i < points.size(); i++) { //Two balloons and more running here
            if (points[i][0] > points[i - 1][1]) {  // Balloon i is not next to balloon i-1. Note that this is not >=
                res++; // Need an arrow
            }
            else {  // Balloon i is next to balloon i-1
                points[i][1] = min(points[i - 1][1], points[i][1]); // Update the minimum right boundary key code of overlapping balloon
            }
        }
        
        return res;
    }
};
  • Time complexity: O(n\log n), because there is a fast scheduling
  • Space complexity: O(1)

435. Non overlapping interval [medium]

Idea: count the number of non overlapping intervals, and then subtract the total number to get the result

Sort by right boundary

class Solution {
public:
    // Sort according to the right boundary of the interval
    static bool cmp (const vector<int>& a, const vector<int>& b) {
        return a[1] < b[1];
    }
    
    int eraseOverlapIntervals(vector<vector<int>>& intervals) {
        //if (intervals.size() == 0) return 0;
        sort(intervals.begin(), intervals.end(), cmp);
        
        int count = 1; // Record the number of non intersecting intervals
        int end = intervals[0][1]; // Record interval division point
        for (int i = 1; i < intervals.size(); i++) {  //The calculation has several non overlapping intervals
            if (end <= intervals[i][0]) {
                end = intervals[i][1];
                count++;
            }
        }
        
        return intervals.size() - count;
    }
};
  • Time complexity: O(n\log n), with a fast scheduling
  • Space complexity: O(1)

763. Division of letter interval [medium]

It must be traversed twice: the first traversal updates the index and the second traversal collects the results

class Solution {
public:
    vector<int> partitionLabels(string s) {
        int hash[26] = {0};   //Store the last index corresponding to the character
        for(int i = 0; i < s.size(); i++){  //Count the last position of each character
            hash[s[i] - 'a'] = i;
        }

        int right = 0;   //The start and end indexes of each substring
        int left = 0;
        vector<int> res;
        for(int i = 0; i < s.size(); i++){
            right = max(right, hash[s[right] - 'a']);   // It is important to find the farthest boundary of the character
            if(i == right){
                res.push_back(right - left + 1);
                left = right + 1;
            }
        }

        return res;
    }
};
  • Time complexity: O(n)
  • Space complexity: O(1). The hash array used is of fixed size

56. Consolidation interval [medium]

Self written:

class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b){
        if(a[0] == b[0])  return a[1] < b[1];
        return a[0] < b[0];
    }

    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if(intervals.size() == 1)  return intervals;   //1. Handle the case of size==1
        
        sort(intervals.begin(), intervals.end(), cmp);   //2. Sorting
        
        vector<vector<int>> res;
        for(int i = 1; i < intervals.size(); ++i){
            if(intervals[i][0] <= intervals[i-1][1]){  //Core code
                intervals[i][0] = intervals[i-1][0];
                intervals[i][1] = max(intervals[i-1][1], intervals[i][1]);
                intervals[i-1][1] = -1;
            }
            else{
                res.push_back(intervals[i-1]);
            }
        }
        
        //Process last
        if(intervals[intervals.size()-1][0] > intervals[intervals.size()-2][1]){
            res.push_back(intervals[intervals.size()-1]);
        }

        return res;
    }
};
  • Official answer
class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        if (intervals.size() < 2) return intervals;
        
        // The sorted parameters use lamda expressions
        sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b){return a[0] < b[0];});
        
        vector<vector<int>> res;
        res.push_back(intervals[0]);
        for (int i = 1; i < intervals.size(); i++) {
            if (res.back()[1] >= intervals[i][0]) { // Merge interval
                res.back()[1] = max(res.back()[1], intervals[i][1]);  //Merge here
            } 
            else {
                res.push_back(intervals[i]);
            }
        }
        
        return res;
    }
};
  • Time complexity: O(n\log n), with a fast scheduling
  • Space complexity: O(1). I didn't calculate the space occupied by the container of the result array (return value)

738. Monotonically increasing numbers [medium]

Idea 1: violent cracking

Subtract one by one to determine whether the conditions are met

class Solution {
	//brute force   
	//Time complexity: O (nm) m is the digital length of n
	//Space complexity: O(1)
public:
	bool checkNum(int num) {
		int low_pos = 10;  //Record the lowest order

		while (num) {
			int temp_pos = num % 10;  //Take out the lowest position
			if (low_pos >= temp_pos) low_pos = temp_pos;  //The lowest bit is greater than the second lowest bit, and the lowest bit is updated
			else return false;        //If the low bit is smaller than the high bit, false

			num = num / 10;
		}
		return true;
	}

	int monotoneIncreasingDigits(int N) {
		for (int i = N; i > 0; i--) {    //Judge one by one
			if (checkNum(i)) return i;
		}
		return 0;
	}
};
  • Idea 2: greed
class Solution {
public:
	int monotoneIncreasingDigits(int N) {
		string strNum = to_string(N);   //Function of converting number to string header file

        //1. Find the mark position and make the previous digit of the mark position - 1
		int flag = strNum.size();
		for (int i = strNum.size() - 1; i > 0; i--) {    //You must traverse from the back to the front, and vice versa
			if (strNum[i - 1] > strNum[i]) {  //Previous > next
				flag = i;                     //The last one is changed to 9
				strNum[i - 1]--;              //Previous digit minus 1 332 - > 322 - > 222
			}
		}

        //2. Mark position change 9
		for (int i = flag; i < strNum.size(); i++) {  //Mark position starts to change 9
			strNum[i] = '9';        //222 -> 299
		}

		return stoi(strNum);  //String to number
	}
};
  • Time complexity: O(n), n is the number length
  • Spatial complexity: O(n), which requires a string. It is more convenient to convert it into a string

968. Monitoring binary tree [difficult, no]

There are three types:

  • This node has no coverage
  • This node has cameras
  • This node has overrides

We have three numbers:

  • 0: this node has no overrides
  • 1: This node has cameras
  • 2: This node has overrides

You can use post order traversal, that is, the order in the left and right, so that you can deduce from bottom to top in the process of backtracking.

Single layer logic processing mainly includes the following four types:

  • Case 1: the left and right nodes are covered
    • If the left child has coverage and the right child has coverage, then the intermediate node should be in the state of no coverage.
  • Case 2: at least one of the left and right nodes has no coverage
    • In the following cases, the intermediate node (parent node) should place the camera
  • Case 3: at least one of the left and right nodes has a camera
    • In the following cases, if one of the left and right child nodes has a camera, its parent node should be 2 (covered state)
  • Case 4: the header node is not covered
// Version one
class Solution {
private:
    int result;
    int traversal(TreeNode* cur) {

        // Empty node, which has coverage
        if (cur == NULL) return 2;

        int left = traversal(cur->left);    // Left
        int right = traversal(cur->right);  // right

        // Case 1
        // Both left and right nodes are covered
        if (left == 2 && right == 2) return 0;

        // Situation 2
        // Left = = 0 & & right = = no coverage for nodes around 0
        // Left = = 1 & & right = = 0 the left node has a camera and the right node has no coverage
        // Left = = 0 & & right = = 1 whether the left node is covered, and whether the right node is covered
        // Left = = 0 & & right = = 2 left node not covered, right node covered
        // Left = = 2 & & right = = 0 the left node is covered, and the right node is not covered
        if (left == 0 || right == 0) {
            result++;
            return 1;
        }

        // Situation 3
        // Left = = 1 & & right = = 2 the left node has a camera and the right node has coverage
        // Left = = 2 & & right = = 1 left node has coverage and right node has camera
        // Left = = 1 & & right = = 1 there are cameras on the left and right nodes
        // In other cases, the previous code has been overwritten
        if (left == 1 || right == 1) return 2;

        // I didn't use else in the above code, mainly to show the conditions of each branch, so that the code is helpful for readers to understand
        // This return -1 logic will not come here.
        return -1;
    }

public:
    int minCameraCover(TreeNode* root) {
        result = 0;
        // Situation 4
        if (traversal(root) == 0) { // root no overwrite
            result++;
        }
        return result;
    }
};

Jumping game

55. Jumping game [medium]

class Solution {
public:
    bool canJump(vector<int>& nums) {
        if (nums.size() < 2) return true;  // Only 0,1 elements can be achieved
        
        int cover = 0;
        for (int i = 0; i < nums.size(); i++) { 
            if(i <= cover){                       //i within coverage
                cover = max(i + nums[i], cover);  //Update coverage
                if (cover >= nums.size() - 1) return true; // It indicates that the end point can be covered
            }
            else   return false;
        }
        
        return false;  //This sentence can't go
    }
};

45. Jumping game II [medium]

The least number of times to reach the last position, assuming that you can always reach the last position

It can be seen from the figure that when the moving subscript reaches the longest subscript covered by the current, the number of steps should be increased by one to increase the coverage distance. The final number of steps is the minimum number of steps.

There is a special case to consider here. When the mobile subscript reaches the longest range subscript currently covered

  • If the current longest covered subscript is not the end of the set, the number of steps will be increased by one and you need to continue.
  • If the current longest covering subscript is the end of the set, you don't need to add one step because you can't go back.
class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() < 2) return 0;
        
        int curDistance = 0;    // Current coverage longest subscript
        int ans = 0;            // Record the maximum number of steps taken
        int nextDistance = 0;   // Next, cover the longest subscript
        
        for (int i = 0; i < nums.size(); i++) {
            nextDistance = max(nums[i] + i, nextDistance);  // Record the longest subscript covered in the next step
            if (i == curDistance) {                        // Encountered the longest subscript of current coverage
                if (curDistance < nums.size() - 1) {       // If the current covering longest distance subscript is not the end point
                    ans++;                                 // Need to take the next step
                    curDistance = nextDistance;            // Update the longest distance subscript of current coverage (equivalent to refueling)
                    if (nextDistance >= nums.size() - 1) break; // The coverage of the next step can reach the end point and end the cycle
                } 
                else break;     // At present, the longest distance subscript covered is the end of the set. There is no need to do ans + + operation, and it will end directly
            }
        }
        return ans;
    }
};

When traversing the array, we do not access the last element, because before accessing the last element, our boundary must be greater than or equal to the last position, otherwise we cannot jump to the last position. If we access the last element, when the boundary is exactly the last position, we will increase the "number of unnecessary jumps", so we don't have to access the last element.

  • [x]
class Solution {
public:
    int jump(vector<int>& nums) {
        int curDistance = 0;    // The longest subscript currently covered
        int ans = 0;            // Record the maximum number of steps taken
        int nextDistance = 0;   // The longest subscript covered in the next step
        for (int i = 0; i < nums.size() - 1; i++) { // Note that this is less than num Size () - 1, this is the key
            nextDistance = max(nums[i] + i, nextDistance); // Record the longest subscript covered in the next step
            if (i == curDistance) {                 // Encountered the longest subscript of the current coverage
                curDistance = nextDistance;         // Update the longest subscript of the current overlay
                ans++;
            }
        }
        return ans;
    }
};

Two dimensional trade-off problem

135. Distribution of candy [difficulties]

  • Idea 1: greed

Time complexity: O(n)

Space complexity: O(n)

Two traversals, one backward and one forward

class Solution {
public:
    int candy(vector<int>& ratings) {
        //1. Distribute candy
        vector<int> candyVec(ratings.size(), 1);  //1.1 one for each person first
        // Front back
        for (int i = 1; i < ratings.size(); i++) {       
            if (ratings[i] > ratings[i - 1]) {   //1.2 there is one more on the right than on the left
                candyVec[i] = candyVec[i - 1] + 1;
            }
        }
        // From back to front
        for (int i = ratings.size() - 2; i >= 0; i--) {  //Make sure left > right
            if (ratings[i] > ratings[i + 1] ) {
                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);  //max is selected because you want to keep the above unchanged
            }
        }
        
        // 2. Statistical results
        int res = 0;
        for (int i = 0; i < candyVec.size(); i++) {
            res += candyVec[i];
        }
        return res;
    }
};

Method 2: constant space traversal

Time complexity: O(n)

Space complexity: O(1)

https://leetcode-cn.com/problems/candy/solution/fen-fa-tang-guo-by-leetcode-solution-f01p/

class Solution {
public:
    int candy(vector<int>& ratings) {
        int res = 1;
        int inc = 1;  //Last increment sequence length
        int dec = 0;  //Current decrement sequence length
        int pre = 1;  //The number of apples of the former student
        for (int i = 1; i < ratings.size(); i++) {
            if (ratings[i] >= ratings[i - 1]) {
                dec = 0;
                pre = (ratings[i] == ratings[i - 1]) ? 1 : pre + 1;
                res += pre;
                inc = pre;
            } 
            else {
                dec++;
                if (dec == inc) {
                    dec++;
                }
                res += dec;
                pre = 1;
            }
        }
        
        return res;
    }
};

406. The cohort was reconstructed according to height [medium]

Idea: sort first, then insert

Insertion process:

  • Insert [7,0]: [[7,0]]

  • Insert [7,1]: [[7,0], [7,1]]

  • Insert [6,1]: [[7,0], [6,1], [7,1]]

  • Insert [5,0]: [[5,0], [7,0], [6,1], [7,1]]

  • Insert [5,2]: [[5,0], [7,0], [5,2], [6,1], [7,1]]

  • Insert [4,4]: [[5,0], [7,0], [5,2], [6,1], [4,4], [7,1]]

  • Time complexity: O(n\log n + n^2)

  • Space complexity: O(n)

class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);   //1. Sort high - > low
        
        vector<vector<int>> que;
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1];
            que.insert(que.begin() + position, people[i]);  //2. Insertion sequence
        }
        
        return que;
    }
};

However, using vector is very time-consuming. In C + +, if the inserted element of vector (which can be understood as a dynamic array and the bottom layer is implemented by an ordinary array) is larger than the size of the ordinary array in advance, there will be an expansion operation at the bottom of vector, that is, apply for twice the size of the original ordinary array, and then copy the data to another larger array.

  • [x]
class Solution {
public:
    // Height from big to small row (stand in front of the same k small height)
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        
        list<vector<int>> que; // The bottom layer of list is the implementation of linked list, and the insertion efficiency is much higher than that of vector
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1]; // Insert into position with subscript position
            list<vector<int>>::iterator it = que.begin();
            while (position--) { // Find where to insert
                it++;
            }
            que.insert(it, people[i]);  //The insertion speed of linked list is fast
        }
        
        return vector<vector<int>>(que.begin(), que.end());  //list container - > vector container
    }
};

This method is faster, but it trades space for time

The bottom layer of the dynamic array will be fully copied and expanded, so it takes time

Stock series

121. The best time to buy and sell stocks [simple]

Same title: Sword finger Offer 63 Maximum profit of stock

Can only buy once, sell once, maximum profit

Idea 1: double for brute force cracking; Ideas and The maximal suborder and are very similar

Time complexity: O(n^2)

Space complexity: O(1)

class Solution {
public:
	//Double for loop brute force cracking exceeds the time limit
	int maxProfit(vector<int>& prices) {
		int max_fit = 0;
		for (int i = 0; i < prices.size(); i++) {   
			for (int j = i + 1; j < prices.size(); j++) {
				max_fit = max(max_fit, prices[j] - prices[i]);
			}
		}

		return max_fit;
	}
};
  • Idea 2: greed

Stock, go to the minimum value on the left and the maximum value on the right of the array. The difference is the maximum profit

Time complexity: O(n) one for loop traversal

Space complexity: O(1)

Ideas can be viewed from the code Capriccio: https://leetcode-cn.com/problems/best-time-to-buy-and-sell-stock/solution/dai-ma-sui-xiang-lu-121-mai-mai-gu-piao-odhle/

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int low = INT_MAX;
        int res = 0;
        for (int i = 0; i < prices.size(); i++) {   //Traverse from left to right
            low = min(low, prices[i]);              // Dynamic update minimum price
            res = max(res, prices[i] - low);     // Dynamic update maximum profit
        }
        return res;
    }
};

Idea 3: dynamic programming

It's a little difficult. I'll add it next time

122. The best time to buy and sell stocks II [medium]

Title Meaning:

  • Only one stock!
  • At present, there are only stock buying or stock buying operations

Idea: draw the stock price of each question into a broken line chart to find the accumulation of all rising segments; Reference 376 questions

  • Greed:

Time complexity: O(n)

Space complexity: O(1)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int res = 0;
		for(int i = 1; i < prices.size(); i++){
            res += max(0, prices[i]-prices[i-1]);
        }
        return res;
    }
};

714. The best time to buy and sell stocks includes handling fees [medium, no]

  • Idea 1: greed

  • Time complexity: O(n)

  • Space complexity: O(1)

There are actually three situations when we harvest profits:

  • Situation 1: the day of profit harvest is not the last day in the profit range (not really selling, equivalent to holding stocks), so we should continue to harvest profits later.
  • Situation 2: the previous day was the last day in the profit range (equivalent to the real sale), and the minimum price should be recorded again today.
  • Situation 3: do not operate and keep the original state (buy, sell, do not buy, do not sell)
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int res = 0;
        int minPrice = prices[0]; // Record the lowest price
        for (int i = 1; i < prices.size(); i++) {
            // Case 2: equivalent to buying
            if (prices[i] < minPrice)  minPrice = prices[i];

            // Situation 3: keep the original state (because buying is not cheap at this time, and selling is at a loss)
            //if (prices[i] >= minPrice && prices[i] <= minPrice + fee) {
            //    continue;
            //}

            // To calculate the profit, there may be many times to calculate the profit, and the last time to calculate the profit is the real meaning of selling
            if (prices[i] > minPrice + fee) {
                res += prices[i] - minPrice - fee;
                minPrice = prices[i] - fee; // Situation one, this step is critical
            }
        }
        return res;
    }
};

When we sell a stock, we immediately get the right to buy a stock at the same price without handling charges

  • [x]
class Solution {
public:
    int maxProfit(vector<int>& prices, int fee) {
        int buy = prices[0] + fee;
        int profit = 0;
        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] + fee < buy) {   //Update purchase price
                buy = prices[i] + fee;
            }
            else if (prices[i] > buy) {    //Collect profits
                profit += prices[i] - buy;
                buy = prices[i];
            }
        }
        return profit;
    }
};

Idea 2: dynamic programming

Topics: Algorithm leetcode greedy algorithm