算法总结

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

posted @ 2019-01-26 13:35  shawshawwan  阅读(355)  评论(0编辑  收藏  举报