算法
Posted miraclemaker
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了算法相关的知识,希望对你有一定的参考价值。
动态规划:
- 当题目涉及到最优子结构和重复子问题时就可以考虑动态规划了。
- 最优子结构:整体是最优解,那么每个子问题也都是最优解。
- 重复子问题:子问题会被重复求很多次
- 线性动规:
- 状态的排布是线性的
- 状态转移方程通常为p[n]=max(p[n-1]+p[1],p[n-2]+p[2],,,,,,,)i最大为n/2。
- 假如是爬楼梯,那么i=2,假如是划分钢材,就有i=n/2。我们要看最后一步有多少种方法。如果最后一步hi多种可能,就要考虑用嵌套for循环
- 状态转移方程大概率就是题目问的东西,可能dp[i]不和dp[i-1]有关联,可能是和dp[i-n]有关联
- 状态转移方程和dp[i-n]有关联的时候可能有条件限制,可以分情况对dp[i]进行赋值
- dp创建时可以和原数组下表11对应,也可以是大1,主要是看dp[0]有没有意义,如果我们创建与原数组下标11对应的dp,那么dp[0]就表示数组第一个元素对应的dp。那么当我们需要用到dp[0]去推导,并且题目中没有规定dp[0],我们就不能这么创建,只能选择大1。例:494目标和
- 背包问题:
- 01背包:构建dp[i][j],i个数能构成j的方案数。双循环,对于i中的每一个元素,计算一遍能否构成0到j。如果比j大就不算dp[i][j]=dp[i-1][j];如果比j小,就添不添加均可dp[i][j]=dp[i-1][j]+dp[i-1][j-nums[i-1]]。其实我们只想知道能不能构成j,但是会用到0-j,所以也要算一遍。
- 完全背包:与01背包相似,只是01背包循环的时候容量循环必须在内层,物品循环必须在外层,这样才能保证一个物品只被用一次。并且由于其完全背包的物品数量无限,状态转移方程是i,01背包是i-1。完全背包有排列和组合的区别,因为物品数量无限,而排列是需要考虑顺序的。排列:外层for为容量,内层for为物品。
字符串:
- ‘z’-‘a’代表的是acill码的相减。A-Z:65-90。a-z:97-122。
- 记录字符出现的次数时,可以用字符数组char[],acill码是下标,值是次数。char[‘a’]代表的是char[97]。
- s.charAt(),下标从0到length()-1; s.substring(a,b)从0到length(),包左不包右。
- 字符‘1’转为数字1:int i=c-\'0\'
- 判断字符是数字还是字符: Character.isLetter() 和 Character.isDigit();
栈和队列:
- 常用方法:
栈Stack
push()入栈 empty()栈空返回真,否则为假 peek()获取栈顶值,不出栈
pop()获取栈顶值,出栈 get()获得栈中对应值
队列Queue
add()入队 offer()将指定元素插入队列,成功返回true,否则返回false
peek()获取队头的值,但不出队 poll()获取并移除队头 remove()获取并移除队头
- 单调栈: 当题目中涉及比当前数小或者大的第一个数时用单调栈。数组下标依次入栈,如果栈空或者栈顶元素比自己大就入栈,如果比自己小,那么计算差值,答案数组中记录栈顶元素对应下标的结果(栈顶元素是第n天,那么答案数组第n个元素就是我们算的差值)有答案的下标就可以出栈了,之后继续对比,直到当前元素比栈顶元素小才入栈,这个方法的意思就是利用栈的特性,一次遍历得到答案数组,答案不是从左到右依次得出。
链表:
- 整体题型比较简单,主要是快慢指针,链表反转节点之间指针的指向,设置哑节点指向头结点 。
哈希表:
1.当我们需要判断是否存在某一个数时,就可以用hashmap,hashmap存储键值对,可以用containskey来快速检索是否存在某一个key。或者用来计算某一个数出现的次数。注意用hashmap的时候要判断一个put一个,不要先全put进去,这样会判断到本身导致错误。hashset多用来判断是否有重复元素,因为hashset不允许有重复元素,add的时候有重复会返回false。
十种排序:
贪心:
- 根据题目灵活变换,尝试一条路走到黑
二分查找:
法1:
- left与right边界值的确定:
- 左侧边界:大于target的最小值;右侧边界:小于target的最大值
- 如果是查找边界那么要扩充区间,查找值则不需要。
。左侧边界:right扩充为nums.length(若整个都不满足,则取最右边,故扩充)
。右侧边界:left扩充为-1;(若整个都不满足,则取最左边,故扩充)
- while循环内有无等号:
- 只有查找值且不一定存在时才用等号。
- left,right是否需要mid+-1:
- 看当前mid判断后是否可能为answer,如果可能直接=mid,若不可能,则需要+-1;
- mid的计算:
- mid = left + (right - left + 1) / 2为右边的中位数,mid = left + (right - left) / 2为左边的中位数。
- 当出现left=mid时,需要mid = left + (right - left + 1) / 2,否则全为mid = left + (right - left) / 2;
- 因为当left=mid时,可能左边界是一直不会动的,当出现right=left+1时,若取左边中位数,mid会恒等left,会造成死循环。
- 最后如果值不对,尝试加一个ans或者返回left。
法2:
- left和right边界值都写不扩展,while永远有等号,right和left都有+1-1。
法3:
- 若是查找值,则不扩充,while写等号,left right 都写+-1。
- 若是左或右边界,则0到nums.length,while不写等号,left=mid+1。right=mid。
- left=mid:left和right紧挨着会死循环;right=mid:left=right会死循环。因为要查值,可能一开始定义的left或者right就是答案,所以区间不能扩充;因为可能left=right=mid的时候是答案,所以while内要写等号;因为while内有等号,所以right=mid-1;而left=mid+1是因为计算机的左偏,left和right紧挨会死循环
排序:
- 1冒泡排序:两层for循环,每轮都对比相邻元素并交换,执行n轮。
- 0选择排序:两层for循环,每轮选择到最值元素放到未排序序列的最前方,执行n轮。
- 1插入排序:两层for循环,每轮将未排序序列的第一个元素插入到已排序的相对位置,执行n轮。
- 0希尔排序:插入排序的变种,先将数据分成n/2组(间隔元素为一组),组内插入排序,再分成n/4组...直到就剩一组。
- 0快速排序:每轮标记第一个元素为target,第二个为begin,最后一个为end,begin与end同时向中间移动,直到begin和end找到第一个比target小(大)的元素,做交换,最后target放到begin位置,执行n轮。
- 1归并排序:将数组分为单个数字,之后逐渐合并为2个一组后进行排序....再合并成n个一组进行排序。
- 0堆排序:所有元素先形成大根堆,之后每轮将堆顶元素与最小叶子结点交换并重新排序,执行n轮。
- 1计数排序:将所有元素都存储到新数组a(size=max-min),a[n]存储原数组元素n出现的次数,最后放回。
- 1基数排序:将元素分成个,十,百....位数字,按照个位数字进桶,出来后再按十位数字进桶......最后按照最高位数字进进桶后出来即可。
- 1桶排序:将数据按照函数映射关系分为好几部分,若桶内元素多余一个,再对桶内元素进行排序。
树:
- 记住算高度的函数,很多题都在这个基础上改,改的时候条件都写到计算高度的函数内部,主函数只进行简单的条件判断和调用
- 编写函数的时候,一开始的判空都要判断root参数,而不是root.left+root.right,当root为空时去判断root的左右子树会造成空指针。由于判断的是root,而不是左右子树,所以进行子树的操作时无法直接操作,就需要将此节点return,在递归的上一步中接收并操作。递归函数先递归再进行操作
- 遍历(先左后右和现右后左遍历的结果是不一样的)
- dfs 深度优先遍历:一条哦走到黑,走不动了回溯回来再走到黑直到遍历完全。用迭代的方法,如果当前节点不为空就入栈,之后调用迭代函数将该节点的左子树和右子树依次入栈。
- bfs 广度优先遍历:依次标记根节点第1,2...n步能走到的位置,直到遍历完全。若根节点不为空就入队,之后用循环的方法队列不空就在取出队头元素的同时放入队头元素的左子树和右子树。
- 为什么一个用栈一个用队列:
。dfs找不到节点需要进行回溯,回溯的时候是与遍历相反的顺序,因此用栈后进先出。
。bfs遍历是有层级顺序的,新加入的节点下一次才进行操作,因此用队列后进后出。
- bfs和dfs 先左后右和先右后左遍历的结果是不一样的。
- 前中后序遍历的递归实现:
。前:
。中+后:只需修改最后的三行代码即可。
- 前中后序遍历的迭代实现:
。前:while循环内(向左下方深入的过程中记录根节点与左节点,找到最左叶节点)while循环外(找到每个节点的右子树并重新进入while循环去记录右子树的节点,右子树记录完后回溯上一个节点继续记录其右子树)
。中:while循环内(找到最左节点但是不记录)while循环外(依次记录当前节点后回回溯上一个节点在记录后记录右子树)
。后:把前序遍历的左右交换,最后反转
递归的时间空间复杂度:
- 时间复杂度:递归次数*每次操作的时间复杂度。
。比如要求x的n次方:第一个为O(logn),第二个为O(n)
- 空间复杂度:递归深度*每次操作的空间复杂度 上面两个都为O(logn)
leetcode:
动态规划:
- 213环形抢劫
- 413等差数列划分
- 343整数拆分
- 300最长递增子序列
- 1143最长公共子序列
- 494目标和
- 309买卖股票含冷冻期
- 204算质数
字符串
- 205同构字符串
- 647回文子串
二叉树
- 543二叉树的直径
- 617合并二叉树
- 572另一颗的子树
- 404左叶子之和
- 687最长同值路径
- 337打家劫舍3
- 637二叉树的层平均值
- 669修剪二叉搜索树
- 235二叉搜索树的最近公共祖先
- 236二叉树的最近公共祖先
贪心
- 435无重叠区间
双指针:
- 88合并两个有序数组
以上是关于算法的主要内容,如果未能解决你的问题,请参考以下文章
Java 虚拟机原理垃圾回收算法 ( 标记-清除算法 | 复制算法 | 标记-整理算法 )