前端算法积累笔记

字符串

  • match 字符串match正则,没有就返回null,加了g返回全部的数据匹配,不加g
  • replace 字符串替换 正则或者子串 为新的子串,加g全部替换,不加g只替换第一个

正则

  • test 正则test一个字符串,返回布尔值
  • exec 可以多次匹配,并返回索引和匹配值 没匹配到也返回null

数据 mutable 好处

  • 不可变性(事务性、可跟踪性和组合性)
  • 可变性(可发现性、协同定位和封装)

运算符优先级

  • 括号成员函数new 三元 赋值 逗号

散列

散列函数"将输入映射到数字"

递推法

分为顺推和逆推法

flatten

// 1. 递归性能问题
arr.reduce((prev, item) => prev.concat(typeof item === 'object' ? fn(item) : item), []) 
// 2. while 循环 
function flatten(arr) {
  while( arr.some(item => Array.isArray(item)) ){
    arr = [].concat(...arr)
  }
  return arr;
}

1 +5 || * 3 得到一个数

  function findSolution(target) {
    function find(current, history) {
      if (current === target) {
        return history;
      } else if (current > target) {
        return null;
      } else {
        return find(current + 5, `(${history} + 5)`) || find(current * 3, `(${history} * 3)`);
      }
    }
    return find(1, "1");
  }

  console.log(findSolution(34));

均衡算法

// 第一步 ,简单字符串 
const str = '[[()]]';

function is_balance1(str) {

  // 注意a和b的位置
  function same(a, b) {
    return (a === '[' && b === ']') || (a === '(' && b === ')')
  }

  let arr = str.split(""), stash = [];
  while (arr.length) {
    const first =  arr.shift();
    if( same ( stash[stash.length - 1] , first ) ){
      stash.pop()
    }else {
      stash.push(first)
    }
  }

  return stash.length === 0
}

console.log(is_balance(str))

判断html标签是否闭合


 function isValidDom(dom) {
    let reg = /<\/?[^>]*>/g;
    let tagList = dom.match(reg);
    let singleTag = ['<br>', '<br/>', '<hr>', '<hr/>'];
    tagList = tagList.filter(tag => !singleTag.includes(tag))
    let stash = [];
    function same(tag1, tag2) {
      // 取得第一个标签的名字 
      let tag1Name = ''
      for(let a = 0; a < tag1.length; a++){
        if(tag1[a] === ' ' || tag1[a] === '>'){
          break;
        }else {
          tag1Name+= tag1[a];
        }
      }
      return tag1.slice(1) === tag2.slice(2);
    }

    while (tagList.length) {
      const first = tagList.shift();
      if (stash.length && same(stash[stash.length - 1], first)) {
        stash.pop()
      } else {
        stash.push(first)
      }
    }

    return stash.length === 0
  }

整数反转算法

注意数字太大,js默认处理成科学计数法

var reverse = function(x) {
    var max = Math.pow(2, 31) - 1;
    var min = -Math.pow(2, 31);
    var y = 0;
    while(x !== 0) {
        y = 10 * y + x % 10;
        x = ~~(x/10);
    }
    if (y > max) return 0;
    if (y < min) return 0;
    return y;
};


函数curry

// 我自己写的 
function curry(fn) {
   const params = [];
   return function named(a) {
    params.push(a)
     if (params.length === fn.length) {
       fn.apply(this, params)
     }
    return named;
  }
};
   
// 优化写法
curry = func => {
  const g = (...all) => (all.length >= func.length) ? func(...all) : (...args) => g(...all, ...args)
  return g;
}

// Y组合

// lambda      
 const y = function (le) {
   return function (f) {f(f)  }
 }(function (f) {
    return le(
     function (...x) {
          return (f(f))(...x)
        }
     )
})
        


合并有序链表


struct ListNode* mergeTwoLists(struct ListNode* l1, struct ListNode* l2){
    if(l1==NULL){
        return l2;
    }
    if(l2==NULL){
        return l1;
    }
    if(l1->val <= l2->val ){
        l1->next=mergeTwoLists(l1->next,l2);
		return l1;
    }else{
        l2->next=mergeTwoLists(l1,l2->next);
		return l2;
    }
}

两链表第一个公共节点

int getLength (struct ListNode *head) {
    int cnt = 0;
    while (head) {
        ++cnt;
        head = head->next;
    }
    return cnt;
}

struct ListNode *getIntersectionNode(struct ListNode *headA, struct ListNode *headB) {
    int ALen = getLength (headA);
    int BLen = getLength (headB);
    int n = 0;
    struct ListNode *p = NULL, *q = NULL;
    if (ALen > BLen) {
        n = ALen - BLen;
        p = headA;
        q = headB;
    } else {
        n = BLen - ALen;
        p = headB;
        q = headA;
    }
    while (n--) {
        p = p->next;
    }
    while (p && p != q) {
        p = p->next;
        q = q->next;
    }
    return p;
}

原数组删除元素


int removeElement(int* nums, int numsSize, int val){
	int a = 0, k = 0;
	for(; a< numsSize; a++){
		if(nums[a] != val){
			nums[k] = nums[a];
			k++;
		}
	}	
	return k;
}
var removeElement = function(nums, val) {
    var lang = nums.length;
    for(var i=0;i<lang;i++){
        if(nums[i]==val){ 
            nums.splice(i,1);
            i--;
        }
    }
    return nums.length;
};

二叉树一条路径得到数字

bool hasPathSum(struct TreeNode* root, int sum){
    if (!root) return false;
    sum = sum - root->val;
    // 表示叶子节点 左右节点均为 NULL ,且 sum 为 0 时, 满足条件
    if (!root->left && !root->right && !sum) return true;
    return hasPathSum(root->left, sum) || hasPathSum(root->right, sum);
}

判断平衡二叉树

   int getHeight(struct TreeNode *root){
        if(!root) return 0;
        int l = getHeight(root->left);
        int r = getHeight(root->right);
        return 1 + (l > r ? l : r );
    }

   bool isBalanced(struct TreeNode* root) {
        if(!root) return true;
        int lHeight = getHeight(root->left);
        int rHeight = getHeight(root->right);
        return ( lHeight - rHeight < 2 && lHeight - rHeight > -2) && isBalanced(root->left) && isBalanced(root->right) ;
    }

反转链表

struct ListNode* reverseList(struct ListNode* head){
    if(head == NULL){
        return head;
    }
    
    struct ListNode* newHead = NULL;
    struct ListNode* tmp = NULL;
    
    while(head != NULL){
        tmp = head->next;
        head->next =newHead;
        newHead = head;
        head =tmp;
    }
    return newHead;
}

平衡二叉树

bool isBalanced(struct TreeNode* root){
    if(root==NULL) return true;
    int d = abs(maxDepth(root->left)-maxDepth(root->right));
    return (d<=1)&& isBalanced(root->left)&& isBalanced(root->right);
}

int maxDepth(struct TreeNode* Node){
    if(Node==NULL) return 0;
    return fmax(maxDepth(Node->left),maxDepth(Node->right))+1;
}

判断单链表有环

  • hash法
  • 双重循环法
  • 快慢指针法,有环,快慢指针会相遇

  • 堆是一个可以自我调整的完全二叉树
  • 堆的根节点是最大或者最小的
  • 每个父节点都大于等于或者小于等于子节点
  • 每个子节点也是堆
  • 根节点最大的堆,叫做 最大堆(大根堆,大顶堆)
  • 根节点最小的堆,叫做 最小堆(小根堆,小顶堆)

建堆

无序数列,初始化建堆,的思想用的是自底向上的思想 ,时间复杂度是 O(n)

  1. 把数组变成一个二叉树
  2. 找最后一个非终端节点a
  3. 比较a的左右节点,取大的,如果大于父节点,就交换 , 执行它的同级元素
  4. 找a的父节点,执行3,a的子节点执行3

如果输出堆顶元素,调整剩余元素为一个新的堆
一般在输出堆顶元素之后,视为将这个元素排除,然后用表中最后一个元素填补它的位置,自上向下进行调整:首先将堆顶元素和它的左右子树的根结点进行比较,把最小的元素交换到堆顶;然后顺着被破坏的路径一路调整下去,直至叶子结点,就得到新的堆。
我们称这个自堆顶至叶子的调整过程为“筛选”,从无序序列建立堆的过程就是一个反复“筛选”的过程。


从0,不停push建堆,不一样,如 3724158
在这里插入图片描述


堆排序
在建堆的基础上,把堆首元素和堆尾元素互换,然后堆容量减一,接着为了维持堆的性质,需要对容量减一的堆(其实这时候因为元素顺序被打乱,已经不能叫堆了,可以叫二叉树)重新进行建堆,重复以上步骤,直到堆的容量只剩下1,这时就已经得到了排序完成的二叉树

串/字符串

  • 串顺序存储 (静态数组(初始化数组长度)、动态数组(数组指针))
  • 串的链式存储,单字符结点链( 一个struct 存一个char和next指针, 浪费内存) ,块链(块链就是每个结点的数据域包含若干个字符)
  • 实际中使用动态数组较多

// 动态数组  
typedef struct {
	char* str;
	int length;
	int maxLength;
} DString;


// 初始化数据  
void init(DString* S, int max, char* str) {
	S->maxLength = max;
	S->str = (char*)malloc(sizeof(char)* max );
	int len = strlen(str);
	S->length = len;
	int i;
	for(; i< len; i++){
		S->str[i] = str[i];
	}
}

// 插入子串
int insert(DString* S, int pos, DString T) {
	if(pos < 0){
		printf("pos can not less than 0");
		return 0;
	}else {
		// 插入子串 和 当前字符串的长度 大于最大长度  重新申请空间  
		if(S->length + T.length > S->maxLength){
			int totalLength = S->length + T.length;
			S->maxLength = totalLength;
			realloc(S->str, totalLength * sizeof(char) );
		}
		int i;
		// S串 pos之后的字符串后移 
		for(i = S->length -1; i >= pos; i++){
			S->str[i+T.length] = S->str[i];
		}
		for(i =0;i < T.length;i++) {
			S->str[i+pos] = T.str[i];
		}
		S->length += T.length;
		return 1;
	}
}

int delete(DString *str1, int pos, int len){
	if(str1->length <=0){
		printf("the length of str can not delete");
		return 0;
	}else if(pos < 0 || len < 0 || pos + len > str1->length ){
		printf("illegal params");
		return 0;
	}else {
		int i;
		for(i=pos+len; i <= str1->length -1; i++){
		 str1->str[i-len]  = str1->str[i];
		} 
		str1->length -= len;
		return 1;
	}
}

void destroy(DString *str1){
	free(str1->str);
	str1->maxLength = 0;
	str1->len = 0;
}

串模式匹配算法 字符串匹配算法 - 暴力匹配

// 此方法仅仅返回了第一次匹配的位置 
int ViolentMatch(char* str, char* p){
    int sLen = strlen(str);
    int pLen = strlen(p);
    
    int i = 0;
    int j = 0;
    
    while (i < sLen && j < pLen) {
        if (str[i] == p[j]) {
            //①如果当前字符匹配成功(即S[i] == P[j]),则i++,j++
            i++;
            j++;
        } else {
            //②如果失配(即S[i]! = P[j]),令i = i - (j - 1),j = 0
            i = i - j + 1;
            j = 0;
        }
    }
    //匹配成功,返回模式串p在文本串s中的位置,否则返回-1
    if (j == pLen)
        return i - j;
    else
        return -1;
    
}

广义表

广义表本质上是非线性结构。广义表的操作一般均需要采用递归方法实现。

二分法

  • 二分法的时间复杂度是O(logN)
  • 时间复杂度和系数无关 O(log2 N) 和 O(log4 N) 是一样的,所以可以省略系数
  • log n 和 log n2也是一样的
T(n)= T(n/2) + O(1) = O(logn)
		   = T(n/4) +  2 * O(1) 
		   = T(n/8) +  3 * O(1) 
		   = T(1) + log2N * O 

T(n)= T(n/2) + O(n) = O(logn)
		   = T(n/4) +  O(n/2) + O(n)
		   = T(n/8) +  O(n/4) + O(n/2) + O(n)
		   = O(n)

循环不变式

  • 循环不变式用来理解算法的正确性, 必须证明3个性质
  • 初始化 在循环的第一轮迭代开始之前,是正确的
  • 保持 在循环的某一轮迭代开始之前是对的,那么下一次开始之前也是对的
  • 终止 当循环结束,不变式给了一个有用的性质 有助于表明算法是对的

蛮力法(穷举法)

线性表的基本操作、顺序查找算法、模式匹配BF算法、起泡排序、简单选择排序

分治法

将一个难以直接解决的大问题,划分成一些规模较小的子问题,以便各个击破,分而治之,分治与递归经常同时应用在算法设计之中,并由此产生许多高效的算法,如 二叉树的遍历、图的深度优先遍历、快速排序、归并排序等,步骤为

  1. 分解问题
  2. 求子问题的解
  3. 合并子问题的解

试探法(回溯法)

它是一种系统地搜索问题解的方法

模拟算法

前面介绍的一些算法都可建立对应的数学模型。(模拟,抛硬币)

贪心算法

贪心法(greedymethod)是把一个复杂问题分解为一系列较为简单的局部最优选择,每一步选择都是对当前解的一个扩展,直到获得问题的完整解。
哈夫曼算法、Prim算法、Kruskal算法、Dijkstra算法等都是贪心法的应用

动态规划法

背包问题

原问题,分解为小问题,把结果填表,最后得到结果,如FLOYD算法,步骤如下

  1. 划分子问题:将原问题分解为若干个子问题,并且子问题之间具有重叠关系;
  2. 动态规划函数:根据子问题之间的重叠关系找到子问题满足的递推关系式(称为动态规划函数);
  3. 填写表格:设计表格的形式及内容,根据递推式自底向上计算,实现动态规划过程
  4. 主要类型有
  • 线性动规:拦截导弹,合唱队形,挖地雷,建学校,剑客决斗等;
  • 区域动规:石子合并, 加分二叉树,统计单词个数,炮兵布阵等;
  • 树形动规:贪吃的九头龙,二分查找树,聚会的欢乐,数字三角形等;
  • 背包问题:01背包问题,完全背包问题,分组背包问题,二维背包,装箱问题,挤牛奶等;

数字三角形问题
1 、递归解法 不必须要的重复计算,低效 O (2的n次方)

function maxSumUsingRecursive(arr, i, j) {
  let rowIndex = arr.length;
  if (i == rowIndex - 1) {
    return arr[i][j];
  } else {
    // 顶点的值+max(左侧支路的最大值,右侧支路的最大值)
    return triangle[i][j] + Math.max(maxSumUsingRecursive(arr, i + 1, j), maxSumUsingRecursive(arr, i + 1, j + 1));
  }
}

2、递归解法 缓存sum

function MaxSumCache(i, j, arr) {
  const n = arr.length - 1;
  let cache;

  // 初始化一个缓存
  if (MaxSumCache.cache) {
    cache = MaxSumCache.cache
  } else {
    let _cache = []
    for (let i = 0; i <= n; i++) {
      _cache.push([])
    }
    cache = _cache
    MaxSumCache.cache = cache;
  }

  //这个数字的最大值已经被求出来了
  if (cache[i][j] != undefined) {
    return cache[i][j];
  }
  //如果i==n,那就说明它已经是最后一行的那个数字了, 那最大和就是那个数字它自己
  if (i == n) {
    cache[i][j] = arr[i][j];
  } else {
  //否则就要算一下 它正下方的那个数字走到底边得到的最大和是什么
    cache[i][j] = Math.max(MaxSumCache(i + 1, j, arr), MaxSumCache(i + 1, j + 1, arr)) + arr[i][j];
  }
  return cache[i][j];
}

3 、 递推法避免了重复运算,O(n2)

  function ditui(arr, i, j) {
    let n = arr.length, dp = [];
    let m = n - 1;
    // 把结果全部放到dp数组,从数组最后一层递推上去
    dp[m] = [...arr[m]];
    for (let a = m - 1; a >= 0; a--) {
      dp[a] = []
      for (let b = 0; b <= a; b++) {
        dp[a][b] = arr[a][b] + Math.max(dp[a+1][b], dp[a+1][b+1])
      }
    }
    return dp[i][j]
  }

二叉链表

#include <stdio.h>
#include <stdlib.h>

#define QUEUE_MAX_LEN 50
typedef char DATA;

typedef struct ChainTree {
	DATA data;
	struct ChainTree* left;
	struct ChainTree* right;
} ChainBinTree;

// 初始化二叉树根节点  
ChainBinTree* BinTreeInit(ChainBinTree* node){
	if(node != NULL){
		return node;
	}else {
		return NULL;
	}
}

// bt 是父节点 node是子节点  n是1 是添加左树 2是右树  
int BinTreeAddNode(ChainBinTree* bt, ChainBinTree* node, int n){
 if(bt == NULL) {
 	puts("父节点不存在");
 	return 0;
 }
 if(n == 1){
 	if(bt -> left){
 	  puts("left 子树节点不为空\n");
   }else {
   	 bt -> left = node;
   }
 }else if(n == 2){
   if(bt -> right){
 	  puts("right 子树节点不为空\n");
   }else {
   	 bt -> right = node;
   }
 }else {
 	puts("参数错误!");
 	return 0;
 }
 return 1;
} 

// 返回left 子树  
ChainBinTree* BinTreeLeft(ChainBinTree* bt){
 if(bt) {
 	return  bt -> left;
 }else {
 	return NULL;
 } 
}

// 返回right 子树  
ChainBinTree* BinTreeRight(ChainBinTree* bt){
 if(bt) {
 	return  bt -> right;
 }else {
 	return NULL;
 } 
}

// 二叉树是否为空  
int BinTreeIsEmpty(ChainBinTree* bt){
	if(bt) {
		return 0;
	}else {
		return 1;
	}
}

// 求二叉树深度  
int BinTreeDepth(ChainBinTree* bt) {
	int dep1, dep2;
	if(bt == NULL) {
		return 0;
	}else {
		dep1 = BinTreeDepth(bt->left);
		dep2 = BinTreeDepth(bt->right);
		return dep1 > dep2 ? dep1 + 1 : dep2 + 1;
	}
}

// 向二叉树中添加节点时,每个节点都是由malloc函数申请分配内存,
// 因此要清空二叉树,则必须使用free函数来释放节点所占的内存

void BinTreeClear(ChainBinTree* bt){
	if(bt){
		BinTreeClear(bt->left);
		BinTreeClear(bt->right);
		free(bt);
		bt = NULL;
	}
	return;
}

二叉树的遍历


// 12453 
void DLR(Btree* t){
	if(t != NULL){
		printf("%d ", t->val);
		DLR(t->left);
		DLR(t->right);
	}
}
//  42513 
void LDR(Btree* t){
	if(t != NULL){
		LDR(t->left);
		printf("%d ", t->val);
		LDR(t->right);
	}
}
// 45231 
void LRD(Btree* t){
	if(t != NULL){
		LRD(t->left);
		LRD(t->right);
		printf("%d ", t->val);
	}
}

层次遍历

// 层次遍历,一层一层遍历 
function layer(node) {
    if(node){
      const list = [node];
      while(list.length){
         const node = list.shift();
         console.log(node)
         if(node.left){
           list.push(node.left)
         }
         if(node.right){
           list.push(node.right)
         }
      }
    }
}
// c实现 
void BinTree_level(ChainBinTree* bt) {
	ChainBinTree* p;
	ChainBinTree* queue[QUEUE_MAX_LEN];
	int head = 0, tail = 0;
	if(bt){
		tail = (tail + 1)% QUEUE_MAX_LEN;
		queue[tail] = bt;
	}
	while(head != tail){
		head = (head + 1)% QUEUE_MAX_LEN;
		p = queue[head]; // 队首元素 
		printf("%d\n", p->data);
		if(p->left != NULL){
			tail = (tail + 1)% QUEUE_MAX_LEN;
			queue[tail] = p->left;
		}
		if(p->right != NULL){
			tail = (tail + 1)% QUEUE_MAX_LEN;
			queue[tail] = p->right;
		}
		
	}
	return;
}

深度优先和广度优先

  • 深度优先搜索是每次从栈中弹出一个元素,搜索所有在它下一级的元素,把这些元素压入栈中。并把这个元素记为它下一级元素的前驱,找到所要找的元素时结束程序。
  • 广度优先(宽度)搜索是每次从队列的头部取出一个元素,查看这个元素所有的下一级元素,把它们放到队列的末尾。并把这个元素记为它下一级元素的前驱,找到所要找的元素时结束程序

深度优先遍历和广度优先遍历

// 深度优先非递归 深度优先的遍历结果与前序遍历相同 
function DepthFirstSearch(biTree) {
  let stack = [];
  stack.push(biTree);

  while (stack.length) {
    let node = stack.pop();
    console.log(node.data);
    if (node.right) {
      stack.push(node.right);
    }
    if (node.left) {
      stack.push(node.left);
    }
  }
}

//广度优先非递归
function BreadthFirstSearch(biTree) {
  let queue = [];
  queue.push(biTree);
  while (queue.length) {
    let node = queue.shift();
    console.log(node.data);
    if (node.right) {
      queue.push(node.right);
    }
    if (node.left) {
      queue.push(node.left);
    }
  }
}

线索二叉树

  • 二叉树遍历得到节点序列,可以将遍历的结果看成是一个线性表
  • 一棵具有n个节点的二叉树,对应的二叉链表中共有2n个指针域,其中n-1个用于指向除根节点外的n-1个节点,另外n+1个指针域为空。可以利用二叉链表中的这些空指针域来存放节点的前驱和后继。将每个节点中为空的左指针或右指针分别用于指向节点的前驱和后继,即可得到线索二叉树
  • 线索的二叉链表称为线索链表,相应的二叉树称为线索二叉树。根据线索性质的不同,线索二叉树可分为先序线索二叉树、中序线索二叉树和后序线索二叉树3种

任务调度算法

  • 先来先服务算法(First Come First Served,FCFS),也叫做先进先出算法(First In First Out,FIFO
    优点:简单,易于理解和实现。
    缺点:一批任务的平均周转时间取决于各个任务到达的顺序,如果短任务位于长任务之后,那么将增大平均周转时间

更多

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值