Summary of algorithms I‘ve learned(dp,Binary search,Two pointer, Stack , Binary Tree)(golang)

Dynamic programming

Dynamic programming is a method to solve complex problems by decomposing the original problem into relatively simple subproblems
Dynamic programming is often suitable for problems with overlapping subproblems and optimal substructures. Dynamic programming methods often take much less time than naive solutions

Which promblem can be sovled by dp?

1.If the optimal solution of a problem contains the optimal solution of a subproblem, the problem is said to have the optimal substructure property (that is, to satisfy the optimization principle). The optimal substructure property provides an important clue for dynamic programming algorithm to solve the problem
2.No aftereffect ,which means that once the solution of a subproblem is determined, it does not change, and is not affected by the decision to solve the larger problem that contains it later
3.Overlapping subproblems nature refers to the top-down using a recursive algorithm to solve the problem, each subproblem is not always a new problem, some sub-problems will be repeated computation times dynamic programming algorithm is used the overlapping properties of the seed, calculated only once for each child problem, and then the calculation results are stored in a table, when need to calculate again has calculated subproblems, just simply look at the results in the table, in order to gain higher efficiency

General ideas of problem solving by dp

1.Dividing stage: Divide the problem into several stages according to the time or space characteristics of the problem. When dividing the stage, pay attention that the divided stage must be ordered or sequenced, otherwise the problem cannot be solved
2.Determine the state and state variables: Represent the various objective situations in which the problem develops in different states. Of course, the state should be selected to satisfy the non-aftereffect
3.Ensure decisions and write the equation of state: because decisions and state transition has the natural relation, state transfer is based on a phase of the state and decision to export this phase state so if the decision is determined, the equation of state is to write but, in turn, in fact is often do, according to the relationship between the adjacent two stages of state and state transition equation to determine the decision method
4.Find the boundary conditions: the state transfer equation given is a recursive formula, which requires a recursive termination condition or boundary condition

The steps of algorithm implementation

1.Create an array of a one-dimensional or two-dimensional array, save the problem of each child as a result, the specific create an array of one-dimensional or two-dimensional array depends on the problem, if given in the problem is basically a one dimensional array, you can only create a one-dimensional array, if two are given in the title a one-dimensional array for operation or two different types of variable values, such as knapsack problem of different volume and total volume of the object, change problems of different face value change with the total amount, so you need to create a 2 d array
2.Set the array boundary value, one-dimensional array is to set the first number, two-dimensional array is to set the value of the first row and the first column, special scrolling one-dimensional array is to set the value of the entire array, and then according to the subsequent different data added into different values
3.Find the state transition equation, that is, find the relationship between each state and its previous state, and write the code according to the state transition equation
4.Returns the desired value, usually the last of the array or the bottom-right corner of a two-dimensional array

f(n,m)=max{f(n-1,m), f(n-1,m-w[n])+P(n,m)}

Pseudocode

for(j=1; j<=m; j=j+1) //the first stage
    xn[j] = initial value;
 
  for(i=n-1; i>=1; i=i-1)// The other n-1 stages
    for(j=1; j>=f(i); j=j+1)
      xi[j]=j=max(or min){g(xi-[j1:j2]), ......, g(xi-1[jk:jk+1])};
 
 t = g(x1[j1:j2]); 
// A scheme for solving the optimal solution of the whole problem by the optimal solution of the subproblem
 
 print(x1[j1]);
 
 for(i=2; i<=n-1; i=i+1{  
      t = t-xi-1[ji];
 
      for(j=1; j>=f(i); j=j+1)
         if(t=xi[ji])
              break;}

Example

Package Problem

There are N items and a backpack with a capacity of V, the weight of the ith item is W [I], and the value is C [I] to solve which items to put into the backpack can make the total weight of these items not exceed the backpack capacity, and the total value is the largest?
dp[i][j]=max(dp[i-1][ j ],dp[i-1][j-w[i] ]+c[i])

import (
    "fmt"func main() {
	var n,v int
	//n for number
	//v for package

	fmt.Scanf("%d %d",&n,&v)
	w:=make([]int,n)
	c:=make([]int,n)
	dp:=make([]int,v+1)
	for i :=0;i<n;i++{
		fmt.Scanf("%d",&w[i])
		fmt.Scanf("%d",&c[i])
	}
	for i:=0;i<n;i++{
		for j:=v;j>=w[i];j--{
			dp[j] = max(dp[j],dp[j-w[i]]+c[i])
		}
	}
	fmt.Println(dp[v])
}

func max(a,b int)int{
	if a>b{
		return a
	}
	return b
}

Binary search

Binary search is suitable for large amounts of data, but the data needs to be sorted first

The steps of algorithm implementation

Set the range of the array array[low, high])
1.Determine the intermediate position K in the interval
2.If the value T is equal to array[k], this position is returned successfully. Otherwise, the new lookup zone is determined and the binary search continues
3.After each search is compared with the median value, it can be determined whether the search is successful or not. If it is not successful, the current search interval will be reduced by half, and recursive search is enough

Examples

在这里插入图片描述

func searchInsert(nums []int, target int) int {
    left :=0
    right := len(nums) - 1
    mid := 0                 //初始化
    for left <= right {      
        mid = (left+right)/2       //确定中间位置
        if nums[mid] == target {
            return mid             
        } else if nums[mid] > target {
            right = mid - 1
        }else {
            left = mid + 1     
        }
    }  //nums[mid] 和 target 之间的大小进行判断,相等则直接返回下标,nums[mid] < target 则 left 右移,nums[mid] > target 则 right 左移
    return left   //查找结束如果没有相等值则返回 left
}

Two pointer

The steps of algorithm implementation

The essence of two Pointers algorithm is to set two Pointers i,j(i.e., i <j) to point to the element to be solved respectively (i.e., i <j), and then move the two Pointers in the same direction or in the opposite direction. The move ends when i >=j

The time and space complexity of the non-recursive writing of merge sort, quick sort, sequence merge, and the median of two pointer algorithm in PAT1029 to find two sequences are reduced to O(N) compared with the complexity of the algorithm not used, and two Pointer is better to be used in the problems requiring running time and running memory

Initially, the two Pointers point to the location of the first element and the location of the last element
Each time, the sum of the two elements pointed to by the two Pointers is calculated and compared with the target value
If the sum of the two elements is equal to the target value, then a unique solution is found.
If the sum of the two elements is less than the target value, then move the left pointer to the right one place
If the sum of the two elements is greater than the target value, the right pointer is moved one bit to the left
After you move the pointer, repeat until you find the answer

Example

在这里插入图片描述

func twoSum(numbers []int, target int) []int {
    i,j := 0, len(numbers) - 1            //set the left and right Pointers
    for i < j {
        sum := numbers[i] + numbers[j]    //calculates the sum of the elements to which the two Pointers point
        if sum == target {                
         return []int{i + 1, j + 1}  //if equal to the target value, returns the index indicated by the pointer
        } else if sum < target {
            i++                      //the left pointer moves left
        } else {
            j--                     //the right pointer moves right
 
        }
    }
    return []int{-1, -1}
}

Stack

The steps of algorithm implementation

Stack
A special data structure in a linear table in which data can only be inserted or deleted from a fixed end and the other end is blocked
The main characteristic of Stack is First In Last Out
Stack overflows when full, and underflows when empty
Overflow: When the stack is already full of data elements, the stack storage will error if it continues to deposit data into the stack.
Uderflow: When the stack is empty, the stack will continue to fetch data
在这里插入图片描述

RC


```bash
Status InitStack(SqStack &S){
    S.base = (SElemType *)malloc(STACK_INIT_SIZE * sizeof(SElemType))
    if(!S.base) exit(OVERFLOW)
    S.top = S.base;
    S.stacksize = STACK_INIT_SIZE;
    return OK;
}

**push**
Status Push(SqStack &S, SElemType &e){
    if(S.top - S.base >= S.stacksize){  //栈满,追加存储空间
        S.base = (SElemType *)realloc(S.base, (S.stacksize + STACKINCREMENT) * sizeof(SElemType));
        if(!S.base) exit(OVERFLOW); //存储分配失败
        S.top = S.base + S.stacksize;
        S.stacksize += STACKINCREMENT;
    }
    * S.top++ = e;
    return OK;
}


**pop**

Status Pop(SqStack &S, SElemType &e){
    if(S.top == S.base) return ERROR;
    e = * --S.top;
    return OK;
}

Example

在这里插入图片描述

func isValid(s string) bool {
	if s == "" {
		return true
	}                      // Empty strings are valid by default
	if len(s)%2 == 1 {
		return false
	}                     // An odd number of parentheses will not match
	keyMap := map[string]string{
		"}": "{",
		"]": "[",
		")": "(",
	}                     //Establish correspondence
	var stack []string
	for i := 0; i < len(s); i++ {
		if len(stack) > 0 {
			tmp, ok := keyMap[string(s[i])]       // The stack is not empty
		 		if ok {
				top := stack[len(stack)-1]      // The current element is the right half bracket
	// If the mapping to the element currently traversed is the same as the bracket at the top of the stack
				if top == tmp {
					// pop
					stack = stack[:len(table)-1]
					// skip this loop
					continue
				}
			} 
		}
		// Empty stack or no match, add stack
		stack = append(stack, string(s[i]))
	}
	// If the last stack is empty, it all matches, and returns true
	return len(stack) == 0
}

Binary Tree Search

It’s an ordered tree
The tree contains nodes with degrees of no more than 2, that is, 0, 1, or 2

Binary tree properties

1.In a binary tree, the i-th layer has at most 2i-1 nodes
2.If the depth of the binary tree is K, then the binary tree has at most 2K minus 1 nodes
3.In binary tree, the number of leaf nodes is N0 and the number of nodes with degree 2 is N2, so N0 =n2+1

full binary tree

If the degree of every node in the binary tree except the leaf node is 2, the binary tree is called full binary tree

In addition to satisfying the properties of ordinary binary trees, full binary trees also have the following properties
1.The number of nodes at the I layer in a full binary tree is 2n-1
2.A full binary tree with a depth of K must have 2k-1 nodes and a number of leaves of 2k-1
3.In a full binary tree, there are no nodes with degree 1. In each branch point, there are two subtrees with the same depth, and the leaf nodes are at the bottom
4.The depth of a full binary tree with n nodes is log of 2 times n plus 1.

Complete binary tree

If the nodes of the last layer removed from the binary tree are full, and the nodes of the last layer are distributed from left to right in turn, the binary tree is called complete binary tree

1.When i >1, the parent is a node [i/2]
2.If 2i>n(number of summary points), then node I must have no left child (leaf node); Otherwise its left child is node 2i
3.If 2i+1>n, then node I must not have a right child; Otherwise, the right child is node 2i + 1

Binary tree storage structure

Binary tree sequential storage structure

Binary treesequential storage refers to the order list (array) use binary tree storage is important to note that the sequential storage is only applicable to full binary tree, in other words, only can use the order form complete binary tree storage, therefore, if we want to common binary tree stored in order, need to convert ordinary binary tree to complete binary tree

Binary tree chain storage structure

**A common binary tree, if it uses chain storage, it only needs to start from the root node of the tree, each node and its left and right children using linked list storage **

Four traversals of a binary tree(Recursion and Iteration)

Binary tree Preorder Traversal

1.Access the root node
2.Access the left subtree of the current node
3.If the current node has no left subtree, access the right subtree of the current node

**1.Recursion**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int

func preorderTraversal(root *TreeNode) []int {
	res = []int{}
	dfs(root)
	return res
}

func dfs(root *TreeNode) {
	if root != nil {
		res = append(res, root.Val)
		dfs(root.Left)
		dfs(root.Right)
	}
}

**2.Iteration**
func preorderTraversal(root *TreeNode) []int {
	var res []int
	var stack []*TreeNode

	for 0 < len(stack) || root != nil { //root != nil in order to determine the fist root must be put in the end
		for root != nil {
			res = append(res, root.Val)       //preorder traversal to pop
			stack = append(stack, root.Right) //ringt node push 
			root = root.Left                  //move to the left
		}
		index := len(stack) - 1 //top
		root = stack[index]     //pop
		stack = stack[:index]
	}
	return res
}

Binary Tree Inorder Traversal

1.Access the left subtree of the current node
2.Access the root node
3.Access the right subtree of the current node

**1.Recursion**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int
func inorderTraversal(root *TreeNode) []int {
	res = make([]int, 0)
	dfs(root)
	return res
}

func dfs(root *TreeNode) {
	if root != nil {
		dfs(root.Left)
		res = append(res, root.Val)
		dfs(root.Right)
	}
}

**2.Iteration**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func inorderTraversal(root *TreeNode) []int {
	var res []int
	var stack []*TreeNode        //make a stack

	for 0 < len(stack) || root != nil { //root != nil is ued to determine for once must be put in the end
		for root != nil {
			stack = append(stack, root) //push
			root = root.Left            // move to the left
		}
		index := len(stack) - 1             //Top
		res = append(res, stack[index].Val) //inorder traversal to pop
		root = stack[index].Right           //right node is continued to be pushed 
		stack = stack[:index]               //pop
	}
	return res
}

Binary Tree Postorder Traversal

Starting from the root node, the left and right subtrees of each node are traversed successively, and the node element is not accessed until the left and right subtrees of the current node are traversed

**1.Recursion**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
var res []int
func postorderTraversal(root *TreeNode) []int {
    res = []int{}
	dfs(root)
    return res
}

func dfs(root *TreeNode){
   	if root != nil {
		dfs(root.Left)
		dfs(root.Right)
		res = append(res,root.Val)
	} 
	
**2.Iteration**
func postorderTraversal(root *TreeNode) []int {
	var res []int
	var stack = []*TreeNode{root}
	for 0 < len(stack) {
		if root != nil {
			res = append(res, root.Val)
			stack = append(stack, root.Left)  //left node push
			stack = append(stack, root.Right) //right node push
		}
		index := len(stack) - 1 //top
		root = stack[index]     //pop
		stack = stack[:index]
	}

	//Reverse turns into a post-order traversal
	l, r := 0, len(res)-1
	for l < r {
		res[l], res[r] = res[r], res[l]
		l++
		r--
	}
	return res
}

BinaryTree Level Order Traversa

According to binary tree level from left to right in turn in the traverse the nodes in each layer of concrete implementation approach is: through the use of queue data structure, starting from the root of the tree node, in turn the left child and a squad and then each time the queue right child nodes, and all its left child and right children team, until all the nodes in the tree are out of the team, the team order of nodes is level traversal of the final result

**1.Recursion**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func levelOrder(root *TreeNode) [][]int {
    return dfs(root, 0, [][]int{})
}

func dfs(root *TreeNode, level int, res [][]int) [][]int {
	if root == nil {
		return res
	}
	if len(res) == level {
		res = append(res, []int{root.Val})
	} else {
		res[level] = append(res[level], root.Val)
	}
	res = dfs(root.Left, level+1, res)
	res = dfs(root.Right, level+1, res)
    return res
}


**2.Iteration(BFS)**
/**
 * Definition for a binary tree node.
 * type TreeNode struct {
 *     Val int
 *     Left *TreeNode
 *     Right *TreeNode
 * }
 */
func levelOrder(root *TreeNode) [][]int {
	var result [][]int
	if root == nil {
		return result
	}
    //Define a two-way queue
	queue := list.New()
    // The header inserts the root node
	queue.PushFront(root)
    // BFS
	for queue.Len() > 0 {
		var current []int
		listLength := queue.Len()
		for i := 0; i < listLength; i++ {
		    // Consumption of the tail
            // queue.Remove(queue.Back()).(*TreeNode):Remove the last element and convert it to the TreeNode type
			node := queue.Remove(queue.Back()).(*TreeNode)
			current = append(current, node.Val)
			if node.Left != nil {
			    //Insert the head
				queue.PushFront(node.Left)
			}
			if node.Right != nil {
				queue.PushFront(node.Right)
			}
		}
		result = append(result, current)
	}
	return result
}


猜你喜欢

转载自blog.csdn.net/qq_46595591/article/details/107714857