LeetCode算法题型

动态规划

  • 取决于该问题是否能用动态规划解决的是这些”小问题“会不会被被重复调用。
    动态规划主要分为两个核心部分,一是确定「DP 状态」,二是确定「DP 转移方程」。
  1. DP状态
    最优子结构
    什么是「最优子结构」?将原有问题化分为一个个子问题,即为子结构。而对于每一个子问题,其最优值均由「更小规模的子问题的最优值」推导而来,即为最优子结构。
    无后效性
    对于「无后效性」,顾名思义,就是我们只关心子问题的最优值,不关心子问题的最优值是怎么得到的。

  2. DP转移方程
    有了「DP 状态」之后,我们只需要用「分类讨论」的思想来枚举所有小状态向大状态转移的可能性即可推出「DP 转移方程」。

  3. 特别注意最优子结构和状态转移方程的推导

  4. 常用类型

    • 一般涉及到两个字符串时,常用动态规划
    • 当前状态由前一个和前两个状态转移而来,类似于爬楼梯,计算到达某个状态的不同路径数,到达该状态的选择只有有限种。在字符串中是选择一个字符或者选择2个字符来组成某种状态
    • 针对左右子树来确定更大的树的个数
    • 组合排列个数的,可以由较小的个数推出较大的,类似于爬楼梯的改版
  5. 解决流程

    1. 寻找最优子结构(状态表示)
      (1) 明确「状态」
      简单地理解就是分析题意,搞清楚怎么做,用什么方法,通过几点要素:a. 字符串模式匹配;b. 求解最多能匹配多少?最值问题,使用动态规划
      (2) 定义 dp 数组/函数的含义
      每个dp位置的元素代表什么意思。
    2. 归纳状态转移方程(状态计算)
      (3) 明确「选择」
      有几种匹配状态,每种匹配状态会怎样有前面可得,确定边界条件
      (4) 定义状态转移方程
    3. 边界初始化
  • 涉及题目: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
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值