leetcode's lecture on the algorithm interview for big factories 10. Recursion & divide and conquer

leetcode's lecture on the algorithm interview for big factories 10. Recursion & divide and conquer

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

16.set&map

17. Stack

18. Queue

19. Array

20. String

21. Trees

22. Dictionary tree

23. Consolidation

24. Other types of questions

Recursive three elements

• Recursive functions and parameters
• Recursive termination condition
• Recursive single layer search logic

Recursive pseudo code template:

function recursion(level, param1, param2, ...) {
//Recursive termination condition
if (level > MAX_LEVEL) {
// output result
return;
}

//Process current layer
process_data(level, data, ...);

//Go to the next floor
recursion(level + 1, p1, ...);

//reset state
reverse_state(level);
}

What is divide and Conquer:

Divide and conquer will decompose big problems into small problems. After disassembling to the smallest problem, it will continue to merge results. Recursion is a form of divide and conquer implementation or a part of divide and conquer implementation. Divide and conquer includes three parts, decomposition, calculation and merging. There are many scenarios of divide and conquer, such as quick sort and merge sort. ds_49

Divide and conquer pseudo code template:

function divide_conquer(problem, param1, param2, ...){
if(problem === null){
// return result
}

//Segmentation problem
subproblem = split_problem(problem, data)

//Computational subproblem
subResult1 = divide_conquer(subproblem, p1, ...)
subResult2 = divide_conquer(subproblem, p1, ...)
subResult3 = divide_conquer(subproblem, p1, ...)
...

result = process_resule(subResult1, subResult2, subResult3,...)
}

give an example

Calculate n! n! = 1 * 2 * 3... * n

function factorial(n) {
if (n <= 1) return 1;
return n * factorial(n - 1);
}

factorial(6);
6 * factorial(5);
6 * 5 * factorial(4);
//...
6 * 5 * 4 * 3 * 2 * factorial(1);
6 * 5 * 4 * 3 * 2 * 1;
6 * 5 * 4 * 3 * 2;
//...
6 * 120;
720;

Fibonacci sequence, F(n)=F(n-1)+F(n+2)

function fib(n) {
if (n === 0 || n === 1) {
return n;
}
return fib(n - 1) + fib(n - 2);
}

50. Pow(x, n) (medium)

Method 1: divide and treat ds_66

• Idea: divide and conquer n when n is an even number, disassemble it to the power of n/2 of x*x, and split it into x * myPow(x,n-1) when n is an odd number. Pay attention to the special case when n is a negative number or 0
• Complexity analysis: time complexity: O(logn), n is the time complexity of binary splitting. Spatial complexity: O(logn), n is the recursion depth

js:

var myPow = function (x, n) {
if (n === 0) return 1 // n=0 returns 1 directly
if (n < 0) {       //When n < 0, the n-th power of x is equal to 1 divided by the - n-th power of x
return 1 / myPow(x, -n)
}
if (n % 2) {    //When n is an odd number, the nth power of x = the n-1 power of x*x
return x * myPow(x, n - 1)
}
return myPow(x * x, n / 2) //n is an even number. Using divide and conquer, one is divided into two, which is equal to the n/2 power of x*x
}

Java:

class Solution {
public double myPow(double x, int n) {
long N = n;
return N >= 0 ? pow(x, N) : 1.0 / pow(x, -N);
}

public double  pow(double  x, long y) {
if (y == 0) {
return 1.0;
}
double ret = pow(x, y / 2);
return y % 2 == 0 ? ret * ret : ret * ret * x;
}
}
Method 2: binary ds_50

• Idea: keep moving the binary of n to the right to judge whether the last bit of the binary of n is 1. If it is 1, multiply the result by x.
• Complexity analysis: time complexity O(logn): n refers to the time complexity of binary splitting of N, and space complexity O(1)

js:

var myPow = function (x, n) {
if (n < 0) {
x = 1 / x;
n = -n;
}
let result = 1;
while (n) {
if (n & 1) result *= x;  //Judge whether the last binary bit of n is 1. If it is 1, multiply the result by x
x *= x;
n >>>= 1;
//Unsigned right shift 1 bit, signed right shift (> >) cannot be used here
//The binary bit "1000000000000000000000" when n is - 2 ^ 31 converted to a positive number,
//If the signed right shift is adopted, the leftmost number will be taken when the symbol is (1), so the returned result is - 1073741824
/*
C++ There is only one shift right operator in - > >. It is defined as follows: the removed low order is discarded;
If it is an unsigned number, the high order is supplemented by 0; If it is a signed number, the high bit is filled with the sign bit.
There are two shift right Operators in JavaScript -- > > and > > >. Where > > is a signed right shift,
That is, the high complement sign bit (there may be exceptions where negative numbers become positive and positive numbers become negative); > > > It is unsigned right shift, and the high position has no brain to fill 0.
*/
}
return result;
}

Java:

class Solution {
public double myPow(double x, int n) {
if(x == 0.0f) return 0.0d;
long b = n;
double result = 1.0;
if(b < 0) {
x = 1 / x;
b = -b;
}
while(b > 0) {
if((b & 1) == 1) result *= x;
x *= x;
b >>= 1;
}
return result;
}
}

169. Most elements(easy)

Method 1. Sorting
• Idea: sort the array. If a number appears more frequently than n/2, it is the number at the position of array num.length/2
• Complexity analysis: time complexity: O(nlogn), time complexity of fast scheduling. Spatial complexity: O(logn). The spatial complexity of logn is required for sorting

js:

var majorityElement = function (nums) {
nums.sort((a, b) => a - b);
return nums[Math.floor(nums.length / 2)];
};

Java:

class Solution {
public int majorityElement(int[] nums) {
Arrays.sort(nums);
return nums[nums.length / 2];
}
}
Method 2. Hash table
• Idea: loop array. Use the hash table to store numbers and corresponding numbers. If the number of numbers is greater than n/2, return this number
• Complexity analysis: time complexity: O(n), n is the length of the num array. Space complexity: O(n), the space required by the hash table

js:

var majorityElement = function (nums) {
let half = nums.length / 2;
let obj = {};
for (let num of nums) {
obj[num] = (obj[num] || 0) + 1;
if (obj[num] > half) return num;
}
};

Java:

class Solution {
public int majorityElement(int[] nums) {
Map<Integer,Integer> obj = new HashMap<>();
for(int num : nums){
obj.put(num, obj.getOrDefault(num, 0) + 1);
if(obj.get(num) > nums.length / 2) return num;
}
return 0;
}
}
Method 3: offset

js:

//[1,1,2,2,2]
const majorityElement = nums => {
let count = 1;
let majority = nums;
for (let i = 1; i < nums.length; i++) {
if (count === 0) {
majority = nums[i];
}
if (nums[i] === majority) {
count++;
} else {
count--;
}
}
return majority;
};

java:

class Solution {
public int majorityElement(int[] num) {
int majority = num;
int count = 1;
for (int i = 1; i < num.length; i++) {
if (count == 0) {
count++;
majority = num[i];
} else if (majority == num[i]) {
count++;
} else {
count--;
}
}
return majority;
}
}
Method 4. Divide and treat ds_51

• Idea: keep recursively dividing from the middle of the array until the number of each interval is 1, and then merge the numbers with a large number of left and right intervals upward and return upward.
• Complexity analysis: time complexity: O(nlogn), continuous dichotomy, complexity is logn, after dichotomy, each interval needs linear statistics of the number of left and right, complexity is n. space complexity: O(logn), recursive stack consumption, continuous dichotomy.

Js:

var majorityElement = function (nums) {
const getCount = (num, lo, hi) => {//Count the number of num between lo and hi
let count = 0;

for (let i = lo; i <= hi; i++) {
if (nums[i] === num) count++;
}

return count;
};

const getMode = (lo, hi) => {
if (lo === hi) return nums[lo];

//Split into smaller intervals
let mid = Math.floor((lo + hi) / 2);
let left = getMode(lo, mid);
let right = getMode(mid + 1, hi);

if (left === right) return left;

let leftCount = getCount(left, lo, hi);//Number of left in the statistical interval
let rightCount = getCount(right, lo, hi);//Number of right in statistical interval

return leftCount > rightCount ? left : right;//Return the one with more left and right
};

return getMode(0, nums.length - 1);
};

Java:

class Solution {
private int getCount(int[] nums, int num, int lo, int hi) {
int count = 0;
for (int i = lo; i <= hi; i++) {
if (nums[i] == num) {
count++;
}
}
return count;
}

private int getMode(int[] nums, int lo, int hi) {
if (lo == hi) {
return nums[lo];
}

int mid = (hi - lo) / 2 + lo;
int left = getMode(nums, lo, mid);
int right = getMode(nums, mid + 1, hi);

if (left == right) {
return left;
}

int leftCount = getCount(nums, left, lo, hi);
int rightCount = getCount(nums, right, lo, hi);

return leftCount > rightCount ? left : right;
}

public int majorityElement(int[] nums) {
return getMode(nums, 0, nums.length - 1);
}
}

124. Maximum path sum in binary tree (hard)

Method 1. Recursion ds_107

• Idea: recurse from the root node. Each recursion is divided into three cases: left, right and fixed. Use the current node plus the maximum path of the left and right subtrees and constantly update the maximum path sum
• Complexity: time complexity O(n), where n is the number of nodes in the tree. Space complexity O(n), recursion depth, and the number of nodes in the worst case

js:

const maxPathSum = (root) => {
let maxSum = Number.MIN_SAFE_INTEGER;//Initialize maximum path and

const dfs = (root) => {
if (root == null) {//Traversal node is null and returns 0
return 0;
}
const left = dfs(root.left);   //Maximum path sum of recursive left subtree
const right = dfs(root.right); //Maximum path sum of recursive right subtree

maxSum = Math.max(maxSum, left + root.val + right);      //Update maximum

//Returns the path and of the current subtree, which can be divided into three cases: left, right and fixed
const pathSum = root.val + Math.max(0, left, right);
return pathSum < 0 ? 0 : pathSum;
};

dfs(root);

return maxSum;
};

java:

class Solution {
int maxSum = Integer.MIN_VALUE;

public int dfs(TreeNode root){
if (root == null) {
return 0;
}
int left = dfs(root.left);
int right = dfs(root.right);

maxSum = Math.max(maxSum, left + root.val + right);

int pathSum = root.val + Math.max(Math.max(0, left), right);
return pathSum < 0 ? 0 : pathSum;
}

public int maxPathSum(TreeNode root) {
dfs(root);
return maxSum;
}
}

53. Maximum subsequence sum (easy) ds_159

Method 1: dynamic programming
• Idea: the current maximum suborder sum is only related to the previous suborder sum. Loop the array and constantly update the maximum suborder sum
• Complexity: time complexity O(n), space complexity O(1)

js:

var maxSubArray = function(nums) {
const dp = [];
let res = (dp = nums);//Initialization status
for (let i = 1; i < nums.length; ++i) {
dp[i] = nums[i];
if (dp[i - 1] > 0) {//If the previous state is positive, add
dp[i] += dp[i - 1];
}
res = Math.max(res, dp[i]);//Update maximum
}
return res;
};

//State compression
var maxSubArray = function(nums) {
let pre = 0, maxAns = nums;
nums.forEach((x) => {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
});
return maxAns;
};

java:

class Solution {
public int maxSubArray(int[] nums) {
int pre = 0, maxAns = nums;
for (int x : nums) {
pre = Math.max(pre + x, x);
maxAns = Math.max(maxAns, pre);
}
return maxAns;
}
}
Method 2. Divide and treat
• Idea: keep dividing until each part is a number, and then keep merging. Return to the largest of the three largest sub orders after left and right merging
• Complexity: time complexity O(nlogn), bisection complexity O(logn). After bisection, the maximum suborder and complexity of each layer after statistics and merging are O(n), so the subsequent complexity is O(nlogn). Space complexity O(logn), recursive stack space, because it is bisection, the data size is halved each time

js:

function crossSum(nums, left, right, mid) {
if (left === right) {//Equal left and right returns the value on the left
return nums[left];
}

let leftMaxSum = Number.MIN_SAFE_INTEGER;//Left maximum initialization
let leftSum = 0;
for (let i = mid; i >= left; --i) {
leftSum += nums[i];
leftMaxSum = Math.max(leftMaxSum, leftSum);//Update the left maximum suborder sum
}

let rightMaxSum = Number.MIN_SAFE_INTEGER;
let rightSum = 0;
for (let i = mid + 1; i <= right; ++i) {
rightSum += nums[i];
rightMaxSum = Math.max(rightMaxSum, rightSum);//Update the maximum suborder sum on the right
}

return leftMaxSum + rightMaxSum;//Returns the maximum suborder sum after left and right merging
}

function _maxSubArray(nums, left, right) {
if (left === right) {//Recursive termination condition
return nums[left];
}

const mid = Math.floor((left + right) / 2);
const lsum = _maxSubArray(nums, left, mid);//Left maximal suborder sum
const rsum = _maxSubArray(nums, mid + 1, right);//Right maximal suborder sum
const cross = crossSum(nums, left, right, mid);//Maximum suborder sum after merging left and right

return Math.max(lsum, rsum, cross);//Returns the largest of the 3 subsequence sums
}

var maxSubArray = function(nums) {
return _maxSubArray(nums, 0, nums.length - 1);
};

java:

public class Solution {

public int maxSubArray(int[] nums) {
int len = nums.length;
if (len == 0) {
return 0;
}
return _maxSubArray(nums, 0, len - 1);
}

private int crossSum(int[] nums, int left, int mid, int right) {
int sum = 0;
int leftSum = Integer.MIN_VALUE;
for (int i = mid; i >= left; i--) {
sum += nums[i];
if (sum > leftSum) {
leftSum = sum;
}
}
sum = 0;
int rightSum = Integer.MIN_VALUE;
for (int i = mid + 1; i <= right; i++) {
sum += nums[i];
if (sum > rightSum) {
rightSum = sum;
}
}
return leftSum + rightSum;
}

private int _maxSubArray(int[] nums, int left, int right) {
if (left == right) {
return nums[left];
}
int mid = left + (right - left) / 2;
return max3(_maxSubArray(nums, left, mid),
_maxSubArray(nums, mid + 1, right),
crossSum(nums, left, mid, right));
}

private int max3(int num1, int num2, int num3) {
return Math.max(num1, Math.max(num2, num3));
}
}

938. Scope and of binary search tree (easy)

Method 1:dfs
• Complexity: time complexity O(n), space complexity O(n)

js:

var rangeSumBST = function(root, low, high) {
if (!root) {
return 0;
}
if (root.val > high) {
return rangeSumBST(root.left, low, high);
}
if (root.val < low) {
return rangeSumBST(root.right, low, high);
}
return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
};

java:

class Solution {
public int rangeSumBST(TreeNode root, int low, int high) {
if (root == null) {
return 0;
}
if (root.val > high) {
return rangeSumBST(root.left, low, high);
}
if (root.val < low) {
return rangeSumBST(root.right, low, high);
}
return root.val + rangeSumBST(root.left, low, high) + rangeSumBST(root.right, low, high);
}
}
Method 2:bfs
• Complexity: time complexity O(n), space complexity O(n)

js:

var rangeSumBST = function(root, low, high) {
let sum = 0;
const q = [root];
while (q.length) {
const node = q.shift();
if (!node) {
continue;
}
if (node.val > high) {
q.push(node.left);
} else if (node.val < low) {
q.push(node.right);
} else {
sum += node.val;
q.push(node.left);
q.push(node.right);
}
}
return sum;
};

java:

class Solution {
public int rangeSumBST(TreeNode root, int low, int high) {
int sum = 0;
q.offer(root);
while (!q.isEmpty()) {
TreeNode node = q.poll();
if (node == null) {
continue;
}
if (node.val > high) {
q.offer(node.left);
} else if (node.val < low) {
q.offer(node.right);
} else {
sum += node.val;
q.offer(node.left);
q.offer(node.right);
}
}
return sum;
}
}

Posted on Sun, 28 Nov 2021 20:47:27 -0500 by Mistat2000