zoukankan      html  css  js  c++  java
  • 算法总结

    Array题目总结

    1.题目分类

    • 双指针

      • 同向双指针

      • 相向双指针

    • 滑动窗口

      • 使用hash map进行count
    • 前缀和、后缀和

      • 利用前缀和降低时间复杂度

      • 前缀和 + Hash Table

        • 这类题目其实都是2Sum的变种,利用hash table记录下标,从而如果发现有合法的子数组后,能够直接求出子数组区间
        • 325.Maximum Size Subarray Sum Equals k、525.Contiguous Array
    • 区间问题

      • 扫描线算法
    • 排序算法的考察

      • 归并排序
      • 快速排序
      • 快速选择
    • 和各个数据结构结合

      • Hash Table
      • Stack
        • 单调栈
      • Heap
      • Queue

    2.注意点


    Tree题目总结

    1.题目分类

    • 全局prev变量、全局sum变量

    • 树的path sum相关

    • LCA(最近公共祖先)

    • BST相关(遇到BST的题一定要考虑它的性质)

      • 删除BST的节点
    • 树的遍历

      • 非递归前序中序遍历
      • 使用Stack实现树的前序、中序遍历
      • 树的层序遍历BFS (套模版较简单)
        • 求树的最大宽度
      • 树的序列化
    • 递归左右子树:分治法思想

      • 判断镜像树
      • 旋转左右节点
    • 树中的距离

      • 求树的最大直径(最长的任意两点间的距离=>转化为求树高)
      • 求树中最长路径(转化为求左右子树路径,然后左右加起来和全局的比较)
    • 借助Stack

      • 判断某个序列是否是合法的遍历序列
    • 树中的路径(树的路径定义为树中任意两个节点间的距离)

      • 求最长的路径 :
        先分治得出leftright,然后更新全局res的时候是left + right,但是本次递归返回的是max(left, right)
          1. Binary Tree Maximum Path Sum
          1. Diameter of Binary Tree
          1. Longest Univalue Path

    2.补充知识点

    1) BST的性质

    二叉查找树要么是一棵空树,要么是一棵具有如下性质的非空二叉树:

    • 若左子树非空,则左子树上的所有结点的关键字值均小于根结点的关键字值
    • 若右子树非空,则右子树上的所有结点的关键字值均大于根结点的关键字值
    • 左、右子树本身也分别是一棵二叉查找树(二叉排序树)

    可以看出,二叉查找树是一个递归的数据结构,且对二叉查找树进行中序遍历,可以得到一个递增的有序序列

    比如lc 98题,不使用全局变量,采用递归左右子树的形式,判断BST :

    class Solution {
        public boolean isValidBST(TreeNode root) {
            return helper(root, null, null);
        }
        
        private boolean helper(TreeNode node, Integer max, Integer min) {
            if (node == null) return true;
            if (min != null && node.val <= min) return false;
            if (max != null && node.val >= max) return false;
            return helper(node.left, node.val, min) && helper(node.right, max, node.val);
        }
    }
    

    3.注意点


    DFS 总结

    1.DFS问题核心思想

    • 将大问题分解为小问题,小问题递归解决:

      • 从top-down分解下去,计算却是bottom-up计算上来,最终将结果传到顶层;
    • 每一步有多个路径可以走,则有的路径走不通了后就需要回溯,回溯记得将原来改变的状态恢复(擦屁股);

    2.一般解题思路

    • 棋盘类的题目

      • 一般就是按照四个方向扩展,再加上一些条件看能不能继续向某个方向扩展,遍历的起始点可能是内部某点,也可能是边界;
        • 具体可参考490. The Maze 、489. Robot Room Cleaner
    • 问题分解类的题目

      • 大问题分解为小问题,可能涉及记忆化搜索 memo + dfs,比如字符串分割;:

        • 87.Scramble String、140. Word Break、329. Longest Increasing Path in a Matrix、 638. Shopping Offers、935.Knight Dialer

        • 比较典型的dfs + memo: 329. Longest Increasing Path in a Matrix

        class Solution {
              int m, n;
              int[][] dirs = {{0, 1}, {1, 0}, {-1, 0}, {0, -1}};
              
              public int longestIncreasingPath(int[][] matrix) {
                   if (matrix.length == 0 || matrix[0].length == 0) return 0;
                   m = matrix.length;
                   n = matrix[0].length;
                   int[][] memo = new int[m][n];
                   int res = 0;
                   for (int i = 0; i < m; i++) {
                        for (int j = 0; j < n; j++) {
                             int tmp = helper(matrix, i, j, memo);
                             res = Math.max(res, tmp);
                        }
                   }
                   return res;
              }
              
              private int helper(int[][] matrix, int x, int y, int[][] memo) {
                   if (memo[x][y] != 0) return memo[x][y];
                   int res = 1;
                   for (int[] d : dirs) {
                        int nx = x + d[0];
                        int ny = y + d[1];
                        if (nx < 0 || nx >= m || ny < 0 || ny >= n) continue;
                        if (matrix[nx][ny] <= matrix[x][y]) continue;
                        int tmp = helper(matrix, nx, ny, memo) + 1;
                        res = Math.max(tmp, res);
                   }
                   memo[x][y] = res;
                   return res;
              } 
         }
        
        • 比较典型的dfs + memo: 935.Knight Dialer

        但是书已到这题的memo是二维的,也就是每一步的状态,其实涉及到两个变量,即每一步按到哪个数,和当前是第几个数,所以是memo[num][index],饭容易范这个错误,将memo写成以维的memo[num]

        class Solution {
             int[][] neighbors = {{4, 6}, {6, 8}, {7, 9}, {4, 8}, {0, 3, 9}, {}, {0, 1, 7}, {2, 6}, {1, 3}, {2, 4}};
             int MOD = 1_000_000_007;
             
             public int knightDialer(int N) {
                  int res = 0;
                  int[][] memo = new int[10][N + 1];
                  for (int i = 0; i < 10; i++) {
                       res += helper(i, N, memo);
                       res %= MOD;
                  }
                  return res;
             }
             
             private int helper(int num, int index, int[][] memo) {
                  if (index == 1) return 1; 
                  if (memo[num][index] != 0) return memo[num][index];
                  int res = 0;
                  for (int next : neighbors[num]) {
                       res += helper(next, index - 1, memo);
                       res %= MOD;
                  }
                  memo[num][index] = res;
                  return res;
             }
        }
        
    • Combination类型及其变种:

        1. Matchsticks to Square、698.Partition to K Equal Sum Subsets

    3.注意点

    0.记得加上visited数组,防止重复访问而产生overflow


    BFS总结

    1.题目分类

    • 树、图的层序遍历

    • 拓扑排序

      • 判断有向图是否有环
    • 求最短距离

      • 棋盘上的最短路径

    2.一般解题思路

    3.注意点


    DP总结

    1.一般解题思路

    dynamic programming的适用情况

    • 最优子结构:问题可以被分解为求解子问题的最优解,也就是现在的解依赖于子问题的左右解

    • 子问题重复计算 :子问题具有重复求解行,所以可以事先存储下来,以便于之后直接获取,从而避免子问题对的重复求解

    • 无后效性:子问题的最优解是确定的,且一旦子问题的最优解得到后,原问题的最优解可以用子问题的最优解求解

    2.题目分类

    • Matrix DP

    • Sequence DP (单sequence) 一维的问题

      • 典型题:LIS (注意LIS的两种姿势)

      • 最小调整代价

      • 2 sets of subproblems: 原问题的最优解依赖于两个子问题的最优解 (一般从左向右扫描一次,然后再从右向左扫描一次,最后再合并左右的结果)

        for (int i = 1; i < A.length; i++) {
             if (A[i] > A[i - 1]) inc[i] = inc[i - 1] + 1;
        }
        for (int i = A.length - 2; i > 0; i--) {
             if (A[i] > A[i + 1]) dec[i] = dec[i + 1] + 1; 
        }
        //合并
        for (int i = 0; i < A.size(); i++) {
             if (inc[i] && dec[i]) {
                  res = Math.max(res, inc[i] + dec[i] + 1);
             }
        }
        
        public int maxProduct(int[] nums) {
               if (nums == null || nums.length == 0) return 0;
               int n = nums.length;
               int[] mins = new int[n];
               int[] maxs = new int[n];
               int res = nums[0];
               mins[0] = nums[0];
               maxs[0] = nums[0];
               for (int i = 1; i < n; i++) {
                    maxs[i] = mins[i] = nums[i];
                    if (nums[i] > 0) {
                         maxs[i] = Math.max(maxs[i - 1] * nums[i], nums[i]);
                         mins[i] = Math.min(mins[i - 1] * nums[i], nums[i]);
                    } else if (nums[i] < 0) {
                         maxs[i] = Math.max(mins[i - 1] * nums[i], nums[i]);
                         mins[i] = Math.min(maxs[i - 1] * nums[i], nums[i]);
                    }
                    res = Math.max(res, maxs[i]);
               }
               return res;
        }
        
          /**
          题意:求乘积最大的子数组,关键是原数组中有正负数
          分析:那么肯定是用动态规划了,本质是用两个dp数组,一个维护至今的最大值,一个维护至今的最小值 ;
          想的简单点,就是维护一个至今的最大值和最小值数组,max[i]表示到i为止的最大的,min[i]表示迄今最小值
          当然简化了也可以用两个变量max和min,也就是用来个状态来维护,只需要当A[i] < 0的时候交换min和max就行了
          如果是负数,则就会使得到当前为止的最大值变为最小值,当前为止的最小值变为最大值;
          应该维护两个变量,一个是至今的最大值一个至今的最小值,然后还有一个全局的最大值;
          */
        
          //两个dp数组版本:
          public int maxProduct(int[] nums) {
               if (nums == null || nums.length == 0) return 0;
               int n = nums.length;
               int[] mins = new int[n];
               int[] maxs = new int[n];
               int res = nums[0];
               mins[0] = nums[0];
               maxs[0] = nums[0];
               for (int i = 1; i < n; i++) {
                    maxs[i] = mins[i] = nums[i];
                    if (nums[i] > 0) {
                         maxs[i] = Math.max(maxs[i - 1] * nums[i], nums[i]);
                         mins[i] = Math.min(mins[i - 1] * nums[i], nums[i]);
                    } else if (nums[i] < 0) {
                         maxs[i] = Math.max(mins[i - 1] * nums[i], nums[i]);
                         mins[i] = Math.min(maxs[i - 1] * nums[i], nums[i]);
                    }
                    res = Math.max(res, maxs[i]);
               }
               return res;
          }
        
          //两个状态变量:
          public int maxProduct(int[] nums) {
               if (nums == null || nums.length == 0) return 0;
               int n = nums.length;
               int res = nums[0], tmpMin = res, tmpMax = res;
               for (int i = 1; i < n; i++) {
                    if (nums[i] < 0) {
                         int tmp = tmpMin;
                         tmpMin = tmpMax;
                         tmpMax = tmp;
                    }
                    tmpMax = Math.max(tmpMax * nums[i], nums[i]);
                    tmpMin = Math.min(tmpMin * nums[i], nums[i]);
                    res = Math.max(res, tmpMax);
               }
               return res;
          }
        
      • 具有多个状态:dp[i][0]、dp[i][1] dp[i][2] ...i is problem size

    • Two Sequences Converging

      • 典型题:LCS
    • 背包问题

    3.注意点


    数据结构总结

    1.题目分类

    • Stack

      • 单调栈

      • 递归问题转为迭代形式(很多递归问题都也可以用stack解决)

      • 用栈模拟:根据题目的性质,这时候分析几个例子,查看是否具有栈的性质,比如和栈顶元素关系直接这种情况

    • Hash Table

      • HashMap

      • TreeMap

      有序key-value,一般按照key有序组织,可以找到第一个比当前key小的:floorKey()或者大的key:ceilingKey(),在有些题目中很有用

    • Queue

    • Linked List (一般有几个常考的套路)

      • 翻转链表模版
      private ListNode reverse(ListNode head){
           ListNode newNode=null;
           while(head!=null){
               ListNode temp=head.next;
               head.next=newNode;
               newNode=head;
               head=temp;
           }
           return newNode;
       }
      
      • 几个基本操作:类似题目题目234、25可以分解为这几个基本操作:求链表中点(求链表第n个点)、反转链表

      • 类似题目24需要前后两个指针prev、cur来交替操作

      • 类似题目86、328属于分割链表,借助dummy node

    • Union Find

      • 并查集模版
        class UF {
             int[] parent;
             public UF(int N) {
                  parent = new int[N];
                  for (int i = 0; i < N; i++) parent[i] = i;
             }
             public int find(int x) {
                  if (parent[x] != x) parent[x] = find(parent[x]);
                  return parent[x];
             }
             public void union(int x, int y) {
                  parent[find(x)] = find(y);
             }
        }
        
    • Trie

    • Graph

    2.一般解题思路

    3.注意点


    其他重要算法

    1.题目分类

    • 贪心法

    • 分治法

    • Sort

      • 手撕快排(这个肯定的)
      • 手撕归并排序 (这个必须的)
    • 扫描线算法

      • Meeting rooms problem:
      • 252.Meeting-Rooms (M)
      • 253.Meeting-Rooms-II (M+)
      • 056.Merge-Intervals (M)
      • 057.Insert-Intervals (M)
      • 732.My-Calendar-III (M)
      • 759.Employee-Free-Time (M+)
      • 370.Range-Addition (M+)
    • 概率题

    • Segment Tree

  • 相关阅读:
    LOJ6435 「PKUSC2018」星际穿越
    LOJ6433 「PKUSC2018」最大前缀和
    LOJ2541 「PKUWC2018」猎人杀
    LOJ2545 「JXOI2018」守卫
    LOJ2290 「THUWC 2017」随机二分图
    CF1007B Pave the Parallelepiped
    【学习笔记】卡特兰数
    Linux系统命令“su
    免密
    Window操作系统下的SSL证书管理
  • 原文地址:https://www.cnblogs.com/shawshawwan/p/10323230.html
Copyright © 2011-2022 走看看