Contents
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
}