文章目录
动态规划
- 取决于该问题是否能用动态规划解决的是这些”小问题“会不会被被重复调用。
动态规划主要分为两个核心部分,一是确定「DP 状态」,二是确定「DP 转移方程」。
-
DP状态
最优子结构:
什么是「最优子结构」?将原有问题化分为一个个子问题,即为子结构。而对于每一个子问题,其最优值均由「更小规模的子问题的最优值」推导而来,即为最优子结构。
无后效性
对于「无后效性」,顾名思义,就是我们只关心子问题的最优值,不关心子问题的最优值是怎么得到的。 -
DP转移方程
有了「DP 状态」之后,我们只需要用「分类讨论」的思想来枚举所有小状态向大状态转移的可能性即可推出「DP 转移方程」。 -
特别注意最优子结构和状态转移方程的推导
-
常用类型 :
- 一般涉及到两个字符串时,常用动态规划
- 当前状态由前一个和前两个状态转移而来,类似于爬楼梯,计算到达某个状态的不同路径数,到达该状态的选择只有有限种。在字符串中是选择一个字符或者选择2个字符来组成某种状态
- 针对左右子树来确定更大的树的个数
- 组合排列个数的,可以由较小的个数推出较大的,类似于爬楼梯的改版
-
解决流程
- 寻找最优子结构(状态表示)
(1) 明确「状态」
简单地理解就是分析题意,搞清楚怎么做,用什么方法,通过几点要素:a. 字符串模式匹配;b. 求解最多能匹配多少?最值问题,使用动态规划
(2) 定义 dp 数组/函数的含义
每个dp位置的元素代表什么意思。 - 归纳状态转移方程(状态计算)
(3) 明确「选择」
有几种匹配状态,每种匹配状态会怎样有前面可得,确定边界条件。
(4) 定义状态转移方程 - 边界初始化
- 寻找最优子结构(状态表示)
- 涉及题目:5、10、32、44、53、62、63、64、70、72、91、96、97、115、120、123
贪心策略
-
贪心算法,又名贪婪法,是寻找最优解问题的常用方法,这种方法模式一般将求解过程分成若干个步骤,但每个步骤都应用贪心原则,选取当前状态下最好/最优的选择(局部最有利的选择),并以此希望最后堆叠出的结果也是最好/最优的解。
-
贪心策略适用的前提是:局部最优策略能导致产生全局最优解
贪心算法往往是这种自顶向下的设计,先做出一个选择,然后再求解下一个问题,而不是自底向上解出许多子问题,然后再做出选择。 -
贪婪法的基本步骤:
- 步骤1:从某个初始解出发;
- 步骤2:采用迭代的过程,当可以向目标前进一步时,就根据局部最优策略,得到一部分解,缩小问题
- 步骤3:将所有解综合起来
-
涉及题目:12、45、46、55、56、130、131
快慢指针(双指针法)
-
**双指针法:**一般是指的在遍历对象的过程中,不是使用单个指针进行访问,而是使用两个相同方向或者相反方向的指针进行扫描,从而达到相应的目的。
-
一般双指针法有两种表现方式:
- 同向移动:在同向移动时,指针互相之间间隔一个距离进行移动
- 相向移动:在相向移动中,双指针一个指针在开头,另外一个指针在结尾,根据满足的条件进行移动指针;
-
快慢指针可以实现在原来数组的基础上,不创建辅助空间,进行移除元素、去重等操作,主要是快指针指向要检查的元素,慢指针指向要操作的元素的位置。
-
涉及题目:11、15、16、18、24、25、31、42、75、80
回溯法
-
回溯是一种通用的算法,把问题分步解决,在每一步都试验所有的可能,当发现已经找到一种方式或者目前这种方式不可能是结果的时候,退回上一步继续尝试其他可能。很多时候每一步的处理都是一致的,这时候用递归来实现就很自然。
-
「因为回溯的本质是穷举,穷举所有可能,然后选出我们想要的答案」
-
涉及题目:17、22、37、39、40、46、47、51、52、60、77、78、93、105、106、116、130
递归法
-
在函数中存在着调用函数本身的情况
-
找重复:
找到一种划分成更小规模问题的方法, 或者是单个划分,或者是多个划分, 另外也可能选择划分
找到递推公式或者等价转换
即:将问题进行细化,要用到返回的值。好到地推的公式,最后必须有一个结束状态!!!! -
一个问题可以分解成具有相同解决思路的子问题,子子问题,换句话说这些问题都能调用同一个函数
经过层层分解的子问题最后一定是有一个不能再分解的固定值的(即终止条件),如果没有的话,就无穷无尽地分解子问题了,问题显然是无解的。 -
涉及题目:21、23、24、25、29、39、40、79、90、110、111、124
快乘法
-
迅速找到一个数a在另一个数b中最大正数倍,每次使得t=b,count =1进行快乘,首先进行遍历每次判断t+t是否超过b,没超过的话t+=t,count+=t,再次递归调用(b-t,a)。最终递归得到最终结果。
-
涉及题目:29
幂乘法
-
每次递归计算的是该幂次数//2的结果,幂返回的便是该幂下的一半的结果,然后判断该幂是否是2的整数倍,如果是2的整数倍,返回的是y*y(其中y的该幂一半的结果),如果不是整数倍,返回的是y*y*x。结束条件n =0.
-
涉及题目:50
滑动窗口
-
其实就是一个队列,比如例题中的 abcabcbb,进入这个队列(窗口)为 abc 满足题目要求,当再进入 a,队列变成了 abca,这时候不满足要求。所以,我们要移动这个队列!
-
主要的应用的就是:每次放入最新的,当出现问题时,将其左边的窗口的进行移动,直到解决最新的问题,右边的窗口的最新的结果
-
如何移动?
我们只要把队列的左边的元素移出就行了,直到满足题目要求!一直维持这样的队列,找出队列出现最长的长度时候,求出解 -
涉及题目:3、30、38、76
哈希表
-
若关键字为k,则其值存放在f(k)的存储位置上,可以使用 from collections import Counter 的Counter来进行建立哈希表。
-
哈希表的作用是可以用作那些包含重复的,与顺序无关的问题来进行比较。!!主要的作用使用来去重,标记每个数重复的个数,还可以改造hash来作为标志是否存在(如打表法)
-
如:建立一个哈希表来做为原始的哈希表,新的比较的哈希表,每次出现一个,就在其后面的数字加一,如果超过了则不成功。
-
涉及题目:30、40,41
二分查找
-
二分查找的条件:主要针对的是有序数组,或者划分之后至少一侧是有序的,然后取中间元素,检查两侧的位置,确定left或right的下一个位置
-
注意:(1)循环的条件为left<=right,注意等号,当等号成立的时候,mid就是检查两个指针共同指向的元素。结束的条件为right>left。
(2)每次mid为(left+right+1)//2 或(left+right)//2的效果是一样的不用进行考虑。 -
注意,当查找某个边界的值,即不大于某个的值,使用ans,当满足不大于时给ans赋值,不断的进行二分查找,直到不满足left<right.最后ans会更新为最大的不大于边界的值
-
涉及题目:33、34、69、74、81
深度优先搜索DFS
-
深度优先遍历,每次在一个状态选取一个值进入下一个状态,知道进行完所有状态。然后回溯上一个状态来遍历其他情况。
-
常与:回溯和递归一起使用
-
针对问题:对于每个阶段有多种情况,每种情况会对后面产生影响,根据后面的来决定前面的选择,即需要回溯重新选择
-
一般相当于暴力的方式,可以进行剪枝
-
涉及题目:37、51、52、60、77、78、79、98、104、129、133
广度优先搜索(BFS)
- 涉及题目:133
单调栈
-
单调栈主要解决以下问题:
- 寻找下一个更大元素
- 寻找前一个更大元素
- 寻找下一个更小元素
- 寻找前一个更小元素
- qualified 的 窗口的 max/min
- sliding window max/min
-
模板
for 元素 in 列表:
while 栈不为空 and 栈顶元素(大于或者小于)目标值
出栈
根据业务处理
入栈 -
涉及题目:84、85
二分查找
- 导入有序列表
- from sortedcontainers import SortedList
- bisect_left
- 从列表使用二分查找查找的最接近该元素的位置,可以根据该位置来取出元素进行操作
- 有序列表,可以快速的将元素插入到合适的位置。
分治法
- 将大的问题分解成有限个子问题,解决了子问题,将子问题的结果合并大问题的结果
- 一般适用于类似于树的构建,如划分成左右两个子树,然后合并成一个树
涉及题目:105、106
前缀和
- 针对一个连续区间,求满足某个条件的区间,使用前缀和。
- 前缀和:
- 将所有的元素相加,构建新的数组,在新的数组的位置元素的值为原数组中该位置之前所有的元素之和。
- 优点:在取某个区间时,不用再对区间中的所有元素求和,直接根据前缀和数组的两个位置做差便可以直接得出前缀和。
- 前缀和+HashMap
- 再求某个特殊的区间时,如相加为某个值的最长区间,遍历时将前面存在的区间值最早出现的对应位置存在HashMap中,每次检索,如果存在直接从HashMap中取出,如果不存在,创建并存到HashSet中。
- 初始化,要在HashSet中放一个初始状态的位置。
- 涉及题目:525
亦或问题
- 两个相同的数亦或的值为0.(检查重复出现两次的)
- 任何数亦或0都等于其本身
涉及题目:136
与&的问题
- 2的幂指数问题。(如果x是2的幂指数,则存在:x&(x-1)=0 或 (x&-x)=x
涉及题目:231
区间和解决方案
- 问题定义
- 数组不变,求区间和:「前缀和」、「树状数组」、「线段树」
- 多次修改某个数,求区间和:「树状数组」、「线段树」
- 多次整体修改某个区间,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
- 多次将某个区间变成同一个数,求区间和:「线段树」、「树状数组」(看修改区间的数据范围)
- 优先级区分
- 简单求区间和,用「前缀和」
- 多次将某个区间变成同一个数,用「线段树」
- 其他情况,用「树状数组」
- 树状数组模板
// 上来先把三个方法写出来 { int[] tree; int lowbit(int x) { return x & -x; } // 查询前缀和的方法 int query(int x) { int ans = 0; for (int i = x; i > 0; i -= lowbit(i)) ans += tree[i]; return ans; } // 在树状数组 x 位置中增加值 u void add(int x, int u) { for (int i = x; i <= n; i += lowbit(i)) tree[i] += u; } } // 初始化「树状数组」,要默认数组是从 1 开始 { for (int i = 0; i < n; i++) add(i + 1, nums[i]); } // 使用「树状数组」: { void update(int i, int val) { // 原有的值是 nums[i],要使得修改为 val,需要增加 val - nums[i] add(i + 1, val - nums[i]); nums[i] = val; } int sumRange(int l, int r) { return query(r + 1) - query(l); } }
哈希表
- 哈希表主要是统计无序的信息,使其可以通过key直接检索到,并且value中还可以存储一定的信息。
- 其可以快速检索无序的信息,并且取出相应的key值。
Set表
- 将无序的信息放到Set表,只可以快速的检索是否存在
排序
- 基数排序
- 基本思想:从低位向高位排序
如果要从高位排序, 那么次高位的排序会影响高位已经排好的大小关系. 在数学中, 数位越高,数位值对数的大小的影响就越大.从低位开始排序,就是对这种影响的排序. 数位按照影响力从低到高的顺序排序, 数位影响力相同则比较数位值. - 基本流程:
- 每轮针对一个数位,从低位到高位,
- 每轮排序建立0-9十个桶, 数按照相对位放进相应的桶中
- 桶使用队列的方式,先进先出,得到一轮的结果
- 进行下一轮,如果高位没有,则认为为零
- 伪代码:
- 代码实现:
while(maxValue>=e){ int[] cnt = new int[10]; for(int i =0;i<n;i++){ int digit = (nums[i]/e)%10; cnt[digit]++; } for(int i =1;i<10;i++){ cnt[i] += cnt[i-1]; } for(int i =n-1;i>=0;i--){ int digit = (nums[i]/e)%10; cp[cnt[digit]-1] = nums[i]; cnt[digit]--; } e*= 10; System.arraycopy(cp,0,nums,0,n); }
- 涉及题目:164
- 基本思想:从低位向高位排序