leetcode to finish the algorithm interview of big factory 4. Greed

leetcode to finish the algorithm interview of big factory 4. Greed

Video tutorial (efficient learning): Click to learn

catalog:

1. Introduction

2. Time and space complexity

3. Dynamic planning

4. Greed

5. Binary search

6. Depth first & breadth first

7. Double pointer

8. Sliding window

9. Bit operation

10. Recursion & divide and conquer

11 Pruning & backtracking

12. Reactor

13. Monotone stack

14. Sorting algorithm

15. Linked list

16.set&map

17. Stack

18. Queue

19. Array

20. String

21. Trees

22. Dictionary tree

23. Consolidation

24. Other types of questions

What is greedy algorithm

Greedy method, also known as greedy algorithm, greedy algorithm, when solving the problem, always makes the best choice at present. It is expected to achieve the global optimization through the local optimal selection at each stage, but the result is not necessarily optimal

Applicable scenario: in short, the problem can be decomposed into subproblems to solve. The optimal solution of the subproblem can be recursive to the optimal solution of the final problem, and the optimal solution of the greedy algorithm can be used to the final optimal solution. The optimal solution of this subproblem is called the optimal substructure

The difference between greedy algorithm and dynamic programming is that it makes the current optimal choice for the solution of each sub problem and cannot retreat. However, dynamic programming will retain the previous operation results and select according to the previous results. It has the function of retreat. Greedy is the idealization of dynamic programming.

122. The best time to buy and sell stocks II(medium)

Method 1. Dynamic programming

  • Idea: according to the meaning of the question, only one stock can be held without limiting the number of transactions. We can use dynamic programming. First, we define the status. There are two statuses in the question, one is the number of days and the other is whether to hold stocks. Therefore, we define dp[i][0] to mean the maximum profit without stocks after trading on day I, dp[i][1] Represents the maximum profit of holding a stock after trading on day I, and then defines the state transition equation:

    1. If the current status is dp[i][0], it means that there is no stock in hand, it can be transferred from the two situations of the previous day. The first is dp[i-1][0], which means that there is no stock in hand the previous day and no operation has been done today. The second is dp[i-1][1], which means that the stock was held the day before but sold today, so the income is dp[i-1][1]+prices[i]. We need to find out that the maximum value in these two cases is the maximum profit, and the state transition equation is:

      dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);

    2. If the current status is dp[i][1], it means that there are stocks in hand, it can be transferred from the two situations of the previous day. The first is dp[i − 1] [1], which means that there are stocks in hand the previous day, that is, there is no operation today. The second is dp[i − 1] [0], which means that there were no stocks the day before but bought them today, so the income is dp[i-1][1]-prices[i]. We need to find out that the maximum value in these two cases is the maximum profit, and the state transition equation is:

      dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);

    From the above state transition equation, we know that the maximum return of the current day is only related to the state of the previous day, so we don't need to define a two-dimensional array to store the state. We just need to store dp[i - 1][0] and dp[i - 1][1] in variables.

  • Complexity analysis: time complexity: O(n). N is the length of the array. There are two states of holding stocks or not holding stocks every day. The total number of state transitions is 2n. The time complexity is O(2n). The time complexity has nothing to do with the constant coefficient, so the time complexity is O(n). Space complexity O(n), because it is necessary to open up a space of n to store the state. Although it is a two-dimensional array, the second dimension is a constant. If the state is compressed, the space complexity can be optimized to O(1)

js:

var maxProfit = function (prices) {
    const n = prices.length;
    const dp = new Array(n).fill(0).map((v) => new Array(2).fill(0)); //Initialize status array
    (dp[0][0] = 0), (dp[0][1] = -prices[0]); //3. Define initial value
    for (let i = 1; i < n; ++i) {
        //1. Determine status
        //2. Derive the state transition equation
        //I don't hold any stocks at present, but it has been transferred from the two states of the previous day,
          //1. It was not held the day before and did not move today. 2. It was held the day before and sold today. Seek the greater value of these two cases
        dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
        //The current holding of shares can be transferred from the two states of the previous day,
          //1 is held the day before and not moved today; 2 is not held the day before and bought today. Seek the greater value of these two cases
        dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
    }
    //4. Determine the output value
    return dp[n - 1][0]; //Returns the maximum value of day n-1
};

//Space compression
var maxProfit = function (prices) {
    const n = prices.length;
    let dp0 = 0,
        dp1 = -prices[0];
    for (let i = 1; i < n; ++i) {
        let newDp0 = Math.max(dp0, dp1 + prices[i]);
        let newDp1 = Math.max(dp1, dp0 - prices[i]);
        dp0 = newDp0;
        dp1 = newDp1;
    }
    return dp0;
};

Java:

class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int[][] dp = new int[n][2];
        dp[0][0] = 0;
        dp[0][1] = -prices[0];
        for (int i = 1; i < n; ++i) {
            dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
            dp[i][1] = Math.max(dp[i - 1][1], dp[i - 1][0] - prices[i]);
        }
        return dp[n - 1][0];
    }
}

//Space compression
class Solution {
    public int maxProfit(int[] prices) {
        int n = prices.length;
        int dp0 = 0, dp1 = -prices[0];
        for (int i = 1; i < n; ++i) {
            int newDp0 = Math.max(dp0, dp1 + prices[i]);
            int newDp1 = Math.max(dp1, dp0 - prices[i]);
            dp0 = newDp0;
            dp1 = newDp1;
        }
        return dp0;
    }
}
Method 2. Greed

  • Idea: because there is no limit on the number of transactions, as long as the price today is higher than yesterday, the transaction will be carried out. The profit is positive accumulation, and the final sum is the maximum profit. Note that there is no profit on the first day. The reason why this problem can be used is greed because it is locally optimal: collect the positive profit every day, which can be deduced, and globally optimal: obtain the maximum profit.
  • Complexity analysis: time complexity O(n), n is the length of the array. The spatial complexity is O(1)

js:

var maxProfit = function (prices) {
    let ans = 0;
    let n = prices.length;
    for (let i = 1; i < n; ++i) {
        //Is the difference between today's price and yesterday positive? If it is positive, add it up. If it is negative, add 0
        ans += Math.max(0, prices[i] - prices[i - 1]);
    }
    return ans;
};

Java:

class Solution {
    public int maxProfit(int[] prices) {
        int ans = 0;
        int n = prices.length;
        for (int i = 1; i < n; ++i) {
            ans += Math.max(0, prices[i] - prices[i - 1]);
        }
        return ans;
    }
}

455. Distribution of biscuits (easy)

  • Idea: large biscuits can meet both children with large appetite and children with small appetite, so priority should be given to those with large appetite. Sort two arrays, traverse from right to left, and use big biscuits to meet the children with big appetite first
  • Complexity: time complexity O(mlogm + nlogn). Spatial complexity O(logm + logn)

js:

var findContentChildren = function (g, s) {
    g = g.sort((a, b) => a - b);
    s = s.sort((a, b) => a - b); //Sort array
    let result = 0;
    let index = s.length - 1;
    for (let i = g.length - 1; i >= 0; i--) {
        //Start with a child with a big appetite
        if (index >= 0 && s[index] >= g[i]) {
            result++; //Add 1 to the result
            index--;
        }
    }
    return result;
};

java:

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int index = 0;
        int result = 0;
        for (int i = 0; i < s.length && index < g.length; i++) {
            if (s[i] >= g[index]) {
                index++;
                result++;
            }
        }
        return result;
    }
}

435. Non overlapping interval (medium)

Method 1. Dynamic programming

  • Idea: dp[i] represents the number of the largest non coincident intervals in the first I intervals. First, sort the interval array according to the left boundary to find out the maximum number of non repetitive intervals in the intervals. The dynamic programming equation dp[i] = Math.max(dp[i], dp[j] + 1). The length of intervals minus the most non repeated intervals is the number of the least deleted intervals
  • Complexity: the time complexity is O(n^2). The timeout complexity of two-level nested loop leetcode execution is too high. Space complexity O(n), dp array space

js:

//leetcode execution timeout complexity is too high
var eraseOverlapIntervals = function (intervals) {
    if (!intervals.length) {
        return 0;
    }

    intervals.sort((a, b) => a[0] - b[0]); //Sort by left boundary
    const n = intervals.length;
    const dp = new Array(n).fill(1); //Initialize dp array

    for (let i = 1; i < n; i++) {
        for (let j = 0; j < i; j++) {
            //Loop i,j to find out the maximum number of non repeated intervals in intervals
            //The right boundary of j is less than the left boundary of i, which is equivalent to an additional non coincident interval
            if (intervals[j][1] <= intervals[i][0]) {
                dp[i] = Math.max(dp[i], dp[j] + 1); //Update dp[i]
            }
        }
    }
    return n - Math.max(...dp); //n minus the most non repeated intervals is the number of the least deleted intervals
};

java:

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        
        Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] interval1, int[] interval2) {
                return interval1[0] - interval2[0];
            }
        });

        int n = intervals.length;
        int[] dp = new int[n];
        Arrays.fill(dp, 1);
        for (int i = 1; i < n; ++i) {
            for (int j = 0; j < i; ++j) {
                if (intervals[j][1] <= intervals[i][0]) {
                    dp[i] = Math.max(dp[i], dp[j] + 1);
                }
            }
        }
        return n - Arrays.stream(dp).max().getAsInt();
    }
}
Method 2. Greed

  • Idea: intervals are sorted according to the right boundary, and then traversed from left to right. The earlier the right boundary ends, the larger the space left for the following interval, and the more non overlapping intervals. The length of intervals minus the most non repeating intervals is the least number of deleted intervals
  • Complexity: time complexity O(nlogn), array sorting O(nlogn), looping array O(n). Space complexity O(logn), stack space required for sorting

js:

var eraseOverlapIntervals = function (intervals) {
    if (!intervals.length) {
        return 0;
    }

    //Sort by the right boundary, and then traverse from left to right. The earlier the right boundary ends, the larger the space left for the subsequent interval, and the more non coincident intervals
    intervals.sort((a, b) => a[1] - b[1]);

    const n = intervals.length;
    let right = intervals[0][1]; //Right is initialized to the right boundary of the first interval
    let ans = 1; //Number of maximum non coincident intervals
    for (let i = 1; i < n; ++i) {
        //Circular interval array
        if (intervals[i][0] >= right) {
            //When the left boundary of the interval is greater than the right boundary of the previous interval, it indicates that it is a pair of non coincident intervals
            ++ans; //ans plus 1
            right = intervals[i][1]; //Update right
        }
    }
    return n - ans; //The length of intervals minus the most non repeated intervals is the number of the least deleted intervals
};

java:

class Solution {
    public int eraseOverlapIntervals(int[][] intervals) {
        if (intervals.length == 0) {
            return 0;
        }
        
        Arrays.sort(intervals, new Comparator<int[]>() {
            public int compare(int[] interval1, int[] interval2) {
                return interval1[1] - interval2[1];
            }
        });

        int n = intervals.length;
        int right = intervals[0][1];
        int ans = 1;
        for (int i = 1; i < n; ++i) {
            if (intervals[i][0] >= right) {
                ++ans;
                right = intervals[i][1];
            }
        }
        return n - ans;
    }
}

Whether the greedy algorithm can be used needs to meet the greedy selectivity. The correct proof of the greedy algorithm can be proved by the counter proof method

Take this topic as an example:

  • Our idea is to retain the most non overlapping intervals, so we sort according to the end of the interval. The earlier the end of the interval ends and does not overlap with the previous interval, it will be added to the most non overlapping interval. We call it algorithm A. if a step in algorithm a is to select interval [a, b], we call it interval a.
  • Suppose this choice is incorrect, that is, algorithm a does not get the optimal solution.
  • We assume that another algorithm C can get the optimal solution. One step in algorithm C is to select the interval [c, d]. We call it interval C, so that it is an interval in the optimal solution, where d > b, because algorithm a selects the interval that ends first and does not coincide. If algorithm a is incorrect, and because the interval in the interval array is fixed, Then other algorithms C must have d > B.
  • Replacing interval C with interval a does not affect the result of algorithm C at all. Because B < D, it does not affect the result of the interval after interval C. So we choose interval a, which also constitutes an optimal solution. What we assume is that the selection interval a is not the optimal solution, so it contradicts the previous assumptions, so algorithm a is the correct greedy algorithm

55. Jumping game (medium)

Method 1. Dynamic programming
  • Idea: dp[i] indicates whether the position I can be reached. For each position I, judge whether it can jump through the previous position. The current position j can be reached. If the current position j plus the reachable position exceeds I, dp[i] is updated to true, that is, the I position can also be reached.
  • Complexity: time complexity O(n^2), space complexity O(n)

js:

function canJump(nums) {
    let dp = new Array(nums.length).fill(false); //Initialize dp
    dp[0] = true; //The first item can be reached
    for (let i = 1; i < nums.length; i++) {
        for (let j = 0; j < i; j++) {
            //The current position j can be reached, and if the current position j plus the reachable position exceeds I, dp[i] is updated to true, that is, the I position can also be reached
            if (dp[j] && nums[j] + j >= i) {
                dp[i] = true;
                break;
            }
        }
    }

    return dp[nums.length - 1];
}

java:

class Solution {
    public boolean canJump(int[] nums) {
        boolean[] dp = new boolean[nums.length];
        
        dp[0] = true;

        for (int i = 1; i < nums.length; i++) {
            for (int j = 0; j < i; j++) {
                if (dp[j] && nums[j] + j >= i) {
                    dp[i] = true;
                    break;
                }
            }
        }

        return dp[nums.length - 1];
    }
}
Method 2. Greed

  • Idea: instead of jumping to that position at each step, jump to the farthest position as far as possible, see the position that can be covered most, and constantly update the distance that can be covered.
  • Complexity: time complexity O(n), traversing one side. Space complexity O(1)

js:

var canJump = function (nums) {
    if (nums.length === 1) return true; //A length of 1 is the end point
    let cover = nums[0]; //Maximum distance covered
    for (let i = 0; i <= cover; i++) {
        cover = Math.max(cover, i + nums[i]); //Whichever is greater between the current coverage distance cover and the current position energy jump distance
        if (cover >= nums.length - 1) {
            //If the coverage distance is greater than or equal to num.length - 1, it indicates that the end point can be reached
            return true;
        }
    }
    return false; //If the loop does not return true after completion, the end point cannot be reached
};

java:

class Solution {
    public boolean canJump(int[] nums) {
        if (nums.length == 1) {
            return true;
        }
        int cover = nums[0];
        for (int i = 0; i <= cover; i++) {
            cover = Math.max(cover, i + nums[i]);
            if (cover >= nums.length - 1) {
                return true;
            }
        }
        return false;
    }
}

881. Lifeboats (medium)

  • Idea: the meaning of the question is that only two people can sit in a boat. It is required to load these people in as few boats as possible. So you can use greedy strategy. Let more people form a 2-person group, and the combined weight of these 2-person groups shall not exceed the load of the ship. Therefore, you can sort people first, traverse from both sides to the middle with double pointers, and let the heavy and light people form a two person group. If the weight of the current heaviest and lightest people exceeds the load, only the heavy people can take a boat first.
  • Complexity: time complexity O(nlogn), sorting complexity. Space complexity O(logn), sorted stack space

js:

var numRescueBoats = function (people, limit) {
    people.sort((a, b) => (a - b));
    let ans = 0,
        left = 0,//The left pointer is initialized at 0
        right = people.length - 1 //The right pointer is initialized at the position of people.length - 1
    while (left <= right) {//Two pointers close to the middle
        //When people [left] + people [right --] < = limit, it means that people on both sides can take a boat together, and then let left++ right--
        //If two people can't sit down, they can only let the heavy people sit in one boat first, that is, let right--
        if ((people[left] + people[right--]) <= limit) {
            left++
        }
          
        ans++
    }
    return ans
};

java:

class Solution {
    public int numRescueBoats(int[] people, int limit) {
        Arrays.sort(people);
        int ans = 0,
            left = 0,
            right = people.length - 1;
        while (left <= right) {
            if ((people[left] + people[right--]) <= limit) {
                left++;
            }
            ans++;
        }
        return ans;
    }
}

452. Detonate the balloon with the least number of arrows (medium)

  • Idea: the intervals are sorted from small to large according to the end, and the array is cycled. If the beginning of the next interval is greater than the end of the previous interval, an arrow needs to be added.
  • Complexity: time complexity O(nlogn), sorting complexity O(nlogn), and cyclic array complexity O(n). Space complexity O(logn), sort stack space

js:

var findMinArrowShots = function (points) {
    if (!points.length) {
        return 0;
    }

    points.sort((a, b) => a[1] - b[1]); //Sort by end of interval
    let pos = points[0][1];
    let ans = 1;
    for (let balloon of points) {
        if (balloon[0] > pos) {
            //If the beginning of the next interval is greater than the end of the previous interval, you need to add an arrow
            pos = balloon[1]; //Update pos to the end of the new interval
            ans++;
        }
    }
    return ans;
};

java:

class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        Arrays.sort(points, new Comparator<int[]>() {
            public int compare(int[] point1, int[] point2) {
                if (point1[1] > point2[1]) {
                    return 1;
                } else if (point1[1] < point2[1]) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        int pos = points[0][1];
        int ans = 1;
        for (int[] balloon: points) {
            if (balloon[0] > pos) {
                pos = balloon[1];
                ++ans;
            }
        }
        return ans;
    }
}

134. Gas stations(medium)

  • Idea: first, judge whether the total fuel volume is less than the total fuel consumption. If so, you must not walk around. If not, you can certainly run a lap. Next is the cycle array. Start from the first station and calculate the remaining oil quantity of each station. If the oil quantity is negative, start from this station. If reaching a certain point is negative, it means that all stations from the starting point to the middle of the point cannot reach the point.
  • Complexity: time complexity O(n), space complexity O(1)

js:

var canCompleteCircuit = function (gas, cost) {
    let totalGas = 0;
    let totalCost = 0;
    for (let i = 0; i < gas.length; i++) {
        totalGas += gas[i];
        totalCost += cost[i];
    }
    if (totalGas < totalCost) {//If the total fuel volume is less than the total fuel consumption, you can't take a lap
        return -1;
    }

    let currentGas = 0;
    let start = 0;
    for (let i = 0; i < gas.length; i++) {
        currentGas = currentGas - cost[i] + gas[i];
        if (currentGas < 0) {//If the fuel volume is negative when arriving at the next station, it will be calculated from this station
            currentGas = 0;
            start = i + 1;
        }
    }

    return start;
};

java:

class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int n = gas.length;
        int sum = 0;
        for(int i = 0;i < n;i++){
            sum += gas[i] - cost[i];
        }

        if(sum < 0){
            return -1;
        }

        int currentGas = 0;
        int start = 0;
        for(int i = 0;i < n;i++){
            currentGas += gas[i] - cost[i];
            if(currentGas < 0){
                currentGas = 0;
                start = i + 1;
            }
        }
        return start;
    }
}

621. Task scheduler (medium)

  • Idea: first arrange task A with the largest number of times, insert other tasks within the cooling time of A, first calculate the time interval of the first n-1 line n, then calculate the number of letters with the same maximum number of times, and then add ret. Finally, choose the larger of the length of tasks and ret
  • Complexity: time complexity O(n), space complexity O(1)

js:

function leastInterval(tasks, n) {
    let arr = Array(26).fill(0);
    for (let c of tasks) {
        //Count the number of times each letter appears
        arr[c.charCodeAt() - "A".charCodeAt()]++;
    }
    let max = 0;
    for (let i = 0; i < 26; i++) {
        //Maximum number of times found
        max = Math.max(max, arr[i]);
    }
    let ret = (max - 1) * (n + 1); //Calculate the time interval of the first n-1 line n
    for (let i = 0; i < 26; i++) {
        //Calculate the same number of letters as the maximum number of times, and then add ret
        if (arr[i] == max) {
            ret++;
        }
    }
    return Math.max(ret, tasks.length); //Take the larger of the length and ret of tasks
}

java:

class Solution {
    public int leastInterval(char[] tasks, int n) {
        int[] arr = new int[26];
        for (char c : tasks) {
            arr[c - 'A']++;
        }
        int max = 0;
        for (int i = 0; i < 26; i++) {
            max = Math.max(max, arr[i]);
        }
        int ret = (max - 1) * (n + 1);
        for (int i = 0; i < 26; i++) {
            if (arr[i] == max) {
                ret++;
            }
        }
        return Math.max(ret, tasks.length);
    }
}

860. Lemonade change (easy)

  • Idea: give priority to those with large denominations
  • Complexity: time complexity O(n), space complexity O(1)

js:

var lemonadeChange = function (bills) {
    let five = 0, ten = 0;
    for (const bill of bills) {
        if (bill === 5) {//The face value is 5, which can be exchanged directly for lemonade
            five += 1;
        } else if (bill === 10) {//The face value is 10 yuan. You need to change 5 yuan for lemonade
            if (five === 0) {
                return false;
            }
            five -= 1;
            ten += 1;
        } else {//The face value is 20 yuan. You need to find three 5 yuan or 10 yuan and 5 yuan for lemonade
            if (five > 0 && ten > 0) {
                five -= 1;
                ten -= 1;
            } else if (five >= 3) {
                five -= 3;
            } else {
                return false;
            }
        }
    }
    return true;
};

java:

class Solution {
    public boolean lemonadeChange(int[] bills) {
        int five = 0, ten = 0;
        for (int bill : bills) {
            if (bill == 5) {
                five++;
            } else if (bill == 10) {
                if (five == 0) {
                    return false;
                }
                five--;
                ten++;
            } else {
                if (five > 0 && ten > 0) {
                    five--;
                    ten--;
                } else if (five >= 3) {
                    five -= 3;
                } else {
                    return false;
                }
            }
        }
        return true;
    }
}

Tags: leetcode

Posted on Tue, 23 Nov 2021 02:13:14 -0500 by Hybrid Kill3r