算法
一、算法思想
枚举、递推、递归、分治、动态规划、贪心、回溯、模拟
1、枚举
也叫穷举法,面对问题时会去尝试每一种解法
在可能解较少时使用枚举是一种简便的方式,在可能解数量较多时无法在有限的空间和时间内解决问题
2、递推
是一种具体的解法,从已知条件出发,逐步推算出问题的解
顺推法:同上,例如斐波那契数列可以通过顺推法不断推算出新的数据(F(n)=F(n - 1)+F(n - 2))
逆推法:从已知结果出发,用迭代表达式逐步推算出问题开始的条件,即顺推法的逆过程
3、递归
是一种具体的解法,把问题转化成规模更小的同类子问题,先解决子问题,再通过相同的求解过程逐步解决更高层次的问题
相对递推来说递归的范畴更小,要求子问题根父问题的结构相同
系统用栈来存储每一层的返回点和局部量,如果递归次数过多,容易造成栈溢出
4、分治
是最基础的算法思想,将一个大问题分解为多个规模较小的子问题,解决子问题后,将各个子问题的解合并成原问题的解
体现在归并排序、二分搜索、大整数乘法、Strassen矩阵乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、最接近点对问题、循环赛日程表、汉诺塔
5、动态规划
将问题分解为各个阶段,每个阶段的求解依赖之前阶段的结果状态来决策,通过状态不断转移优化角色解决问题
也可以说是在枚举的基础上,避免了重复计算,但是每个子问题都考虑到了
体现在矩阵连乘、走金字塔、最长公共子序列(LCS)、最长递增子序列(LIS)、凸多边形最优三角剖分、背包问题、双调欧几里得旅行商问题
6、贪心
每次都选择当前局部最优解,不去考虑整体的情况
体现在活动选择问题、钱币找零问题、再论背包问题、小船过河问题、区间覆盖问题、销售比赛、Huffman编码、Dijkstra算法(求解最短路径)、最小生成树算法
7、回溯
也叫试探算法,在行进过程中通过枚举思想不断试探、判断、退回并重新试探,直到找到完整的前进路线,枚举路线可以通过减枝裁剪
许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称
体现在八皇后问题、图的着色问题、装载问题、批处理作业调度问题、再再论背包问题、最大团问题、连续邮资问题、符号三角形问题
8、模拟
根据给出的规则对相关过程进行编程模拟
1、分治法
将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之
适用于二分搜索、大整数乘法、Strassen矩阵乘法、棋盘覆盖、合并排序、快速排序、线性时间选择、最接近点对问题、循环赛日程表、汉诺塔
2、动态规划法
每次决策依赖于当前状态,又随即引起状态的转移。一个决策序列就是在变化的状态中产生出来的,所以,这种多阶段最优化决策解决问题的过程就称为动态规划
适用于
将一个问题拆成几个子问题,分别求解这些问题,即可推断出大问题的解
public String longestPalindrome(String s) { // 特判 int len = s.length(); if (len < 2) { return s; } int maxLen = 1; int begin = 0; // dp[i][j] 表示 s[i, j] 是否是回文串 boolean[][] dp = new boolean[len][len]; char[] charArray = s.toCharArray(); for (int i = 0; i < len; i++) { dp[i][i] = true; } for (int j = 1; j < len; j++) { for (int i = 0; i < j; i++) { if (charArray[i] != charArray[j]) { dp[i][j] = false; } else { if (j - i < 3) { dp[i][j] = true; } else { dp[i][j] = dp[i + 1][j - 1]; } } // 只要 dp[i][j] == true 成立,就表示子串 s[i..j] 是回文,此时记录回文长度和起始位置 if (dp[i][j] && j - i + 1 > maxLen) { maxLen = j - i + 1; begin = i; } } } return s.substring(begin, begin + maxLen); }
求最长回文子串,解题核心思路:dp[i][j] = (s[i] == s[j]) and dp[i + 1][j - 1],从回文长度2开始慢慢加长,每次计算依赖之前的结果
3、贪心法
在对问题求解时,总是做出在当前看来是最好的选择。也就是说,不从整体最优上加以考虑,他所做出的仅是在某种意义上的局部最优解
适用于活动选择问题、钱币找零问题、再论背包问题、小船过河问题、区间覆盖问题、销售比赛、Huffman编码、Dijkstra算法(求解最短路径)、最小生成树算法
4、回溯法
回溯法是一种选优搜索法,按选优条件向前搜索,以达到目标。但当探索到某一步时,发现原先选择并不优或达不到目标,就退回一步重新选择,这种走不通就退回再走的技术为回溯法,而满足回溯条件的某个状态的点称为“回溯点”。许多复杂的,规模较大的问题都可以使用回溯法,有“通用解题方法”的美称
适用于八皇后问题、图的着色问题、装载问题、批处理作业调度问题、再再论背包问题、最大团问题、连续邮资问题、符号三角形问题
5、分支界限法
类似于回溯法,也是一种在问题的解空间树T上搜索问题解的算法。但在一般情况下,分支限界法与回溯法的求解目标不同。回溯法的求解目标是找出T中满足约束条件的所有解,而分支限界法的求解目标则是找出满足约束条件的一个解,或是在满足约束条件的解中找出使某一目标函数值达到极大或极小的解,即在某种意义下的最优解
转自https://blog.csdn.net/ght886/article/details/80289142
二、排序
1、冒泡排序
每次遍历比较相邻的元素,将大的交换到右侧
每次遍历后都会把一个最大的元素交换到最后
2、快速排序
选一个基准数,将比它小的数移到左边,比它大的数移到右边,采用分治法将一个串分为左右两个子串,再继续用同样的方法处理子串
一般用递归来实现
3、插入排序
遍历未排序数据,在已排序序列中从后向前扫描,找到相应的位置插入
4、希尔排序
将序列分为几组分别进行插入排序,之后将分组的数量减少并再次分组插排,直到最后两组合为一组
是插入排序的改进版,时间复杂度优于O(n2)
5、选择排序
遍历未排序数据,将最小的元素交换到队头,重复遍历
6、堆排序
将串构建成一个大顶堆,将堆首和堆尾互换,将堆数量-1,再调整为大顶堆,,继续重复,直到所有数都从大到小移出堆
7、归并排序
将串分为两个子串,将子串分别递归做归并排序,合并两个有序子串
8、计数排序
找出最大和最小的元素,创建连续的数组,将元素放入数组中并记录出现次数,根据数组反向得到有序串
9、桶排序
是计数排序的升级版,设定k个有序桶,使用映射函数将元素分配到每个桶中,将桶中的元素排序,根据桶反向得到有序串
10、基数排序
按照低位在0-9的数组里排序,然后增加位数类推,直到最高位
三种非比较排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
计数排序:每个桶只存储单一键值,数据必须是有确定范围的整数
桶排序:每个桶存储一定范围的数值,在空间充足的情况下尽量增大桶的数量
基数排序:根据键值的每位数字来分配桶,适用于小范围数的排序
三、查找
1、顺序查找
适用于存储结构为顺序存储或链表存储的线性表,时间复杂度为O(n)
2、二分查找
public static int binarySearch(Integer[] srcArray, int des) { //定义初始最小、最大索引 int start = 0; int end = srcArray.length - 1; //确保不会出现重复查找,越界 while (start <= end) { //计算出中间索引值 int middle = (end + start)>>>1 ;//防止溢出 if (des == srcArray[middle]) { return middle; //判断下限 } else if (des < srcArray[middle]) { end = middle - 1; //判断上限 } else { start = middle + 1; } } //若没有,则返回-1 return -1; }
时间复杂度O(logn),暴力的话是O(n2),相比好了很多
二分查找的前提是需要有序表顺序存储
提升:插值查找,适合对表长且关键字分布均匀的查找表使用,复杂度O(log2(log2n))
提升:斐波那契查找,优点是只涉及加减法,效率比除法高,复杂度O(log2n)
3、数表查找(二叉树、2-3树、红黑树、b树)
二叉树:对二叉查找树进行中序遍历,即可得到有序的数列,复杂度O(log2n)
2-3树:根节点到每个为空节点的距离都相同
红黑树:保证最坏情况下仍有对数的时间复杂度
b树:
b+树:元素离根节点更近
4、哈希映射
通过空间换取时间的方式,可以将查找时间从O(n)降低到O(1)
public int[] twoSum(int[] nums, int target) { Map<Integer, Integer> map = new HashMap<>(); for(int i=0; i<nums.length; i++) { if(map.containsKey(target - nums[i])) { return new int[]{map.get(target - nums[i]), i}; } map.put(nums[i], i); } return null; }
比如这道从数组中找出和为目标值的那两个整数,通过一次哈希遍历解决,而暴力算法要用2个for循环
5、分块查找
将n个数据元素按块有序划分为m块(m<=n),每一块中的节点不必有序,但块与块之间必须按块有序
先二分查找块,再对块顺序查找
四、双指针
1、快慢指针
2、左右指针
3、滑动窗口
双指针模拟滑动窗口
public int lengthOfLongestSubstring(String s) { if(s == null || s.length() == 0) { return 0; } Map<Character, Integer> map = new HashMap<>(); int max = 0; for(int i = 0, j = 0; i < s.length(); i++) { if(map.containsKey(s.charAt(i))) { int index = map.get(s.charAt(i)); if(index >= j) { j = index + 1; } } max = Math.max(max, i - j + 1); map.put(s.charAt(i), i); } return max; }
找出其中不含有重复字符的最长子串的长度
滑窗框架
public String slidingWindow(String s, String t) { Map<Character, Integer> need = new HashMap<>(); Map<Character, Integer> window = new HashMap<>(); //将需要的字符串加入need for(int i=0; i<t.length(); i++) { need.put(t.charAt(i), need.getOrDefault(t.charAt(i), 0) + 1); } int left = 0, right = 0; //左右指针 int valid = 0; //判断窗口满足need的个数
//自定义一些参数 while(right < s.length()) { //右移窗口 char c = s.charAt(right); right++; //更新窗口数据window等 //判断左窗口是否需要收缩 while(window needs shrink) { char d = s.charAt(left); left++; //更新窗口数据window等 } } return }
五、单调栈
解柱状图相关题利器,接雨水..
六、DFS、BFS