力扣(leetCode)总结篇

当前达成目标力扣100道,持续努力中······
刷题是锻炼逻辑思维和coding能力的有效途径,一时刷题一时爽,一直刷题一直爽。
一、目标
每周刷5道题,本周未达成。。。。。。
二、思维导图

三、总结
每种类型的题,同一类的都是有代码模版的,适当的借助模版可以节省力气。下面,大体总结一下,刷题过程中的类似的模版(大体分为,dfs:借助于stack,bfs:借助于queue,动态规划:借助于临时数组dp[][], 递归借助于List等等)。
3.1 动态规划
动态规划类的题,主要有几类:0-1背包,最长子序列(有序和连续),最小路径和,博弈论,接雨水等等。
本文的总结,主要提供代码模版,现拿一道比较简单的动态规划为例:
最小路径和:https://leetcode-cn.com/problems/minimum-path-sum/

动态规划类型的题目,最初的解体思路就是找到动态转移方程,基于动态转移方程,去实现具体的代码,本文重点在代码实现上,具体的动态转移方程的分析,还是要基于读者对该子任务类型(自底向上,自顶向下)的理解。要想找到状态转移方程算是一个比较麻烦的操作,借助于dp[][],将其作为将要构造的状态转移方程的出发点。至于简化的降维成一维的思路可以先不考虑,而更简单的dp[]类型的题目可以简单了解一下(这类题基本都很简单,可以先不考虑)。
class Solution {
	/*本题构造的dp[i][j],i表示row,j表示col,而dp[i][j]表示【0,0】到【i,j】的最小距离。
  这是本题的思路,最有意思的可以看看0-1背包问题的i,j的含义,还有博弈论相关的i,j的含义以及状态转移方程的构建。
	*/
    public int minPathSum(int[][] grid) {
        int [][] dp = new int[grid.length][grid[0].length];
        /*求row的从0到n的dp[i][0],作为自底向上的基础。
        */
        for(int i=0;i<grid.length;i++){
            if(i==0){
                dp[0][0]=grid[0][0];
            }else{
                dp[i][0] = dp[i-1][0]+grid[i][0];
            }
        }
        /*求col的从0到n的dp[0][j],作为自底向上的基础。
        */
        for(int i=0;i<grid[0].length;i++){
            if(i==0){
                dp[0][0] = grid[0][0];

            }else{
                dp[0][i]= dp[0][i-1]+grid[0][i];
            }
        }
        /*状态转移方程,
        Min(dp[i-1][j], dp[i][j-1]);
        */
        for(int i=1;i<grid.length;i++){
            for(int j=1;j<grid[0].length;j++){
                dp[i][j]= Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
            }
        }
        /*
        返回基于i,j含义的dp结果。
        */
        return dp[grid.length-1][grid[0].length-1];
    }
}

3.2 递归+剪枝
递归算是最常见的一种解题方法,在刷力扣的时候,本人遇见很多道可以用改递归+剪枝的方法来处理的题型。
现举例一道典型题:
组合总和:https://leetcode-cn.com/problems/combination-sum/

递归类型的题解的处理主要包括,递归+剪枝+回溯(可以通过下面的模版来适配所有类型的该类问题,具体请好好理解);
所谓递归就是求全局变量,如果对时间复杂度要求较高的场景中,不建议使用该方法,毕竟递归的时间复杂度是指数级。
class Solution {	
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
    	/*本方法中,利用list作为递归返回值的保存数组
    	*/
        List<List<Integer>> list = new ArrayList();
        /*利用l作为递归的过程中数据的记录的临时保存数组
        */
        List<Integer> l = new ArrayList();
        /*利用count作为判断终止条件,if(count==target)作为终止。
        */
        int count =0;
        inSum(candidates, list, l, count, target);
        return list;
    }
    public void inSum(int[] candidates,List<List<Integer>> list, List<Integer> l, int count, int target){
        if(count == target){
           /*由于java对象在堆中的特性决定的,只能重新申请对象,这样返回的list能保存所有的枚举
           */
            List<Integer> t = new ArrayList();
            for(Integer t1:l){
                t.add(t1);
            }
            Collections.sort(t);
            if(!list.contains(t))
            list.add(t);
        } else if(count>target){
            return; 
        }
        for(int i=0;i<candidates.length;i++){
           /*此处可以做一些剪枝的操作,无外乎一些if else的逻辑判断;
           通过l保存此次低谷的数据,如果满足终止条件,该l的数据会被重新赋予list见上面逻辑,如果失败,则继续递归遍历
           */
            l.add(candidates[i]);
            count+=candidates[i];
            inSum(candidates,list,l,count, target);
            /*利用list的方法,释放上次递归遍历的数据,也就是理论上的回溯
            */
            l.remove(l.size()-1);
            count-=candidates[i];
        }
    }
}

深搜,可以基于上面的递归方式来实现,试具体情况,是否需要回溯+剪枝。
广搜,建议使用queue来处理。
3.3 二叉树
3.3.1 二叉树的遍历
二叉树的四种遍历,前置、中置、后置、层次遍历需要都掌握,包括递归(见二中的模版,二叉树的递归,是堆左子树和右子树的判断)和基于堆的方式(可以代码实现一遍,其实理解好了还是很简单的)。
下面提供一个基于堆的前置、中置、后置的变量代码:

前序:
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack();
        List<Integer> list = new ArrayList();
        stack.push(root);
        while(stack.size()>0){
            TreeNode r = stack.pop();
            if(r == null){
                return list;
            }
            list.add(r.val);
            if(r.right != null){
                stack.push(r.right);
            }
            if(r.left!=null){
                stack.push(r.left);
            }
        }
        return list;
    }
}
中序:
public class Solution {
    public List < Integer > inorderTraversal(TreeNode root) {
        List < Integer > res = new ArrayList < > ();
        Stack < TreeNode > stack = new Stack < > ();
        TreeNode curr = root;
        while (curr != null || !stack.isEmpty()) {
            while (curr != null) {
                stack.push(curr);
                curr = curr.left;
            }
            curr = stack.pop();
            res.add(curr.val);
            curr = curr.right;
        }
        return res;
    }
}
后序:
class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        Stack<TreeNode> stack = new Stack();
        stack.push(root);
        Map<TreeNode, Integer> map = new HashMap<>();
        List<Integer> list = new ArrayList();
        if(root == null){
            return list;
        }
        while(stack.size()>0){
                TreeNode r = stack.peek();
                if(r.left !=null&&map.get(r.left)==null){
                    stack.push(r.left);
                } else{
                    if(r.right != null&& map.get(r.right)==null){
                        stack.push(r.right);
                    }else{
                        list.add(r.val);
                        stack.pop();
                        map.put(r, 1);
                    }
                }
        }
        return list;
    }
}

基于栈的前中后二叉树遍历要善于利用list来作为回溯的工具。
二叉树变量相关的题型主要包括,求最长层数、最小枝路径、是否对称等等,可以善于利用递归的思想
3.3.2 重建二叉树
二叉树还有比较难理解的几种类型,比如通过两种遍历来重建二叉树、二叉搜索树、平衡二叉树
比如通过前序和中序重建二叉树,基本思想还是通过递归,但是前提要理解前、中、后的特点,比如前序的第一个节点肯定是根节点,而中序的前序的根节左边的数组一定是根节点的左子树,而右边的数组一定是根节点的右子树,而后序的根节点一定是数组的最后一个节点,基于此种考虑,可以通过前序+中序,中序+后序来重新恢复二叉树:
1、递归的思路是先在前序或者后序的数组中找到根节点
2、再在中序的数组中找到左子树和右子树数组
3、分别在中序的左子树数组和右子树数组中重复1、2的步骤。
具体事例代码如下:

class Solution {
    public TreeNode buildTree(int[] preorder, int[] inorder) {
        return buildSubTree(preorder, 0, preorder.length, inorder, 0, inorder.length);
    }
    public TreeNode buildSubTree(int[] preorder, int start1, int end1,int[] inorder, int start2,int end2){
        if(start1>preorder.length-1 || start1>end1 || start2>end2){
            return null;
        }
        int top = preorder[start1];
        TreeNode tree = new TreeNode(top);
        for(int i=start2;i<end2;i++){
            if(inorder[i]==top){
                TreeNode leftNode=buildSubTree(preorder, start1+1, start1+i-start2, inorder,start2, i);
                TreeNode rightNode = buildSubTree(preorder, start1+i-start2+1,end1,inorder, i+1, end2);
                tree.left = leftNode;
                tree.right = rightNode;
                return tree;
            }
        }
        tree.left = null;
        tree.right = null;
        return tree;
    }
}

3.3.3 搜索二叉树
搜索二叉树就是有序的二叉树,既左子树一定小于根节点,右子树一定大于根节点
二叉搜索树主要是通过,构建有序数组,然后按照一定规则去重建二叉树,具体上代码:

class Solution {
    public TreeNode sortedListToBST(ListNode head) {
        int length =0;
        ListNode h = head;
        while(h!=null){
            length++;
            h= h.next;
        }
        int[] nums = new int[length];
        int i=0;
        while(head != null){
            nums[i++]=head.val;
            head= head.next;
        } 
          return sortedArray(nums, 0, nums.length-1);
    }
    public TreeNode sortedArray(int[] nums, int start, int end){
    	/*具体规则可以具情况而定,当前以medium作为根节点
    	*/
        int medium = (start + end+1)/2;
        if(start>end || start > nums.length-1 ){
            return null;
        }
        TreeNode tree = new TreeNode(nums[medium]);
        if(start == end){
            tree.left = null;
            tree.right= null;
            return tree;
        }
        TreeNode left = sortedArray(nums, start, medium-1);
        TreeNode right = sortedArray(nums, medium+1, end);
        tree.left = left;
        tree.right = right;
        return tree;
    }
}

常见的题目就是恢复二叉搜索树,至于判断是否为二叉搜索树通过递归判断左子树是否小于根节点,右子树是否大于根节点来判断,比较简单
3.3.4 平衡二叉树
平衡二叉树是指树的高度差小于等于1的二叉树。
平衡二叉树也就是传说中的AVL树
通过数组来恢复成平衡二叉树
代码同3.3.3 中的代码,具体不同处在于:

int mid = start + (end - start )>> 1;

同上面的代码实现,既把中间节点作为根节点,既可构造平衡二叉树
3.3.5 平衡二叉搜索树
平衡二叉树和二叉搜索树的结合,即有序和中间节点作为根节点而构建的二叉树
3.3.6 红黑树
研究过红黑树,感觉除了背具体的实现,比较麻烦,理解上还好,但是实现上确实复杂度较高,不建议去背
3.4 其他类型
leetCode上有很多有趣的类型的题目,比如链表(成环、倒数第k个数遍历、链表倒序、跳表、复制跳表、k个一组翻转链表、有序链表合并(推导到n个有序链表合并,大顶堆)等等)、字符串(字符串的处理有很多比较经典的题型,可以适当研究,比如回文数等)、数组(力扣,难度差距最大的类型题目,接雨水等等,最适合动态规划类型的题型,动态规划每次出现都是medium以上的题目,是比较难的,建议好好研究研究这种类型的题目)、排序(归并、快排、堆、基类、选择、插入、冒泡、桶排序,前三个比较重要,尤其堆排序,实现起来很有意思,可以研究研究)等。
四、力扣
力扣进度
五、好题推荐
接雨水:接雨水是很经典的题目,面试考的几率非常大,可以研究一下
买卖股票的最佳时机:买卖股票的题目可以无限扩展,有一种可以使用于所有的买卖股票的题的方法模版,可以研究一下
求最小的k个数:首先理解一下该题的时间复杂度,看什么方法能优化时间复杂度,小顶堆思想,nlogk,复杂度
Nim 游戏:博弈论的题目
首先选题:田忌赛马的思路,即贪心算法
盛最多水的容器:第一次出去面试互联网的题目,记忆犹新,动态规划类型题目,临时推导的

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值