常用算法案例分析(一)

1、动态规划

动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。下面我们先来讲下做动态规划题很重要的三个步骤:

  1. 第一步骤定义数组元素的含义,上面说了,我们会用一个数组,来保存历史数组,假设用一维数组 dp[] 吧。这个时候有一个非常非常重要的点,就是规定你这个数组元素的含义,例如你的 dp[i] 是代表什么意思?
  2. 第二步骤状态转移方程,找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2]……dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步。
  3. 第三步骤找出初始值。学过数学归纳法的都知道,虽然我们知道了数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],我们可以通过 dp[n-1] 和 dp[n-2] 来计算 dp[n],但是,我们得知道初始值啊,例如一直推下去的话,会由 dp[3] = dp[2] + dp[1]。而 dp[2] 和 dp[1] 是不能再分解的了,所以我们必须要能够直接获得 dp[2] 和 dp[1] 的值,而这,就是所谓的初始值。

有了初始值,并且有了数组元素之间的关系式,那么我们就可以得到 dp[n] 的值了而 dp[n] 的含义是由你来定义的,你想求什么,就定义它是什么,这样,这道题也就解出来了

- 案例一
在这里插入图片描述

class Solution {
   
    //解题思路:动态规划
    //状态转移方程:比如,如果想指导amout为11时的最少硬币数,如果你凑出amout=10的最少硬币数,你只需要加一就可以得到amount=11的最少硬币数。
    public int coinChange(int[] coins, int amount) {
   
        //自底而上求
        int max=amount+1;
        //dp[n]:当amout为n时,凑成的硬币最少个数
        int[] dp=new int[amount+1];
        //把数组中的元素都变为max
        Arrays.fill(dp,max);
        dp[0]=0;
        for(int i=1;i<=amount;i++){
   
            for(int j=0;j<coins.length;j++){
   
                //判断条件
                if(coins[j]<=i){
   
                    dp[i]=Math.min(dp[i],dp[i-coins[j]]+1);
                }
            }
        }
        return dp[amount]==amount+1?-1:dp[amount];
    }
}

2、回溯法

解决⼀个回溯问题,实际上就是⼀个决策树的遍历过程。你只需要思考 3 个问题:

  • 路径:也就是已经做出的选择。
  • 选择列表:也就是你当前可以做的选择。
  • 结束条件:也就是到达决策树底层,无法再做选择的条件。

回溯算法的框架:

在这里插入图片描述

  • 案例一

在这里插入图片描述

class Solution {
   
    private List<List<Integer>> list1=new LinkedList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
   
        if(candidates.length==0){
   
            return list1;
        }
        int sum=0;
        int index=0;
        Arrays.sort(candidates);
        List<Integer> list=new LinkedList<>();
        backtrace(candidates,target,list,sum,index);
        return list1;
    }
    public void backtrace(int[] arr,int target,List<Integer> list,int sum,int index){
   
        //结束条件
        if(sum==target){
   
            list1.add(new LinkedList(list));
            return;
        }
        for(int i=index;i<arr.length;i++){
   
            if(target-sum<arr[i]){
   
                break;
            }
            list.add(arr[i]);
            sum=sum+arr[i];
            backtrace(arr,target,list,sum,i);
            list.remove(list.size()-1);
            sum=sum-arr[i];
        }

    }
}
  • 案例二
    在这里插入图片描述
class Solution {
   
    private List<List<Integer>> list=new LinkedList<>();
    public List<List<Integer>> permute(int[] nums) {
   
        if(nums.length<1){
   
            return list;
        }
        int[] flag=new int[nums.length];
        backtrace(nums,flag,nums.length,new LinkedList<>());
        return list;
    }
    private void backtrace(int[] num,int[] flag,int len,List<Integer> list2){
   
        if(list2.size()==len){
   
            list.add(new ArrayList<>(list2));
            return;
        }
        for(int i=0;i<num.length;i++){
   
            if(flag[i]==1){
   
                continue;
            }
            flag[i]=1;
            list2.add(num[i]);
            backtrace(num,flag,len,list2);
            flag[i]=0;
            list2.remove(list2.size()-1);
        }
    }
}
  • 案例三
    在这里插入图片描述
class Solution {
   
    // 每步要么增加一个左括号,要么增加一个右括号,是一个二叉的选择,所以暴搜很容易写出来,就是dfs(left - 1, right, curStr + "("); dfs(left, right - 1, curStr + ")"); 
    //但是并不是每个分支都是符合要求的(正确的括号匹配),比如如果right使用的比left多的话就已经不是正确括号了,没必须继续dfs这个分支了,所以加上if来剪枝哈~
    List<String> res = new ArrayList<>();
    public List<String> generateParenthesis(int n) {
   
        dfs(n, n, "");
        return res;
    }
    private void dfs(int left, int right, String curStr) {
   
        if (left == 0 && right == 0) {
    // 左右括号都不剩余了,递归终止
            res.add(curStr);
            return;
        }

        if (left > 0) {
    // 如果左括号还剩余的话,可以拼接左括号
            dfs(left - 1, right, curStr + "(");
        }
        if (right > left) {
    // 如果右括号剩余多于左括号剩余的话,可以拼接右括号
            dfs(left, right - 1, curStr + ")");
        }
}
}

3、深度优先遍历算法

在这里插入图片描述

class Solution {
   
    //深度优先遍历
    private int[][] nums=new int[][]{
   {
   1,0},{
   0,1},{
   -1,0},{
   0,-1}};
    public int numIslands(char[][] grid) {
   
        if(grid.length==0){
   
            return 0;
        }
        int row=grid.length;
        int col=grid[0].length;
        //岛屿的个数
        int count=0;
        //记录被遍历过的位置
        boolean[][] arr=new boolean[row][col];
        for(int i=0;i<row;i++){
   
            for(int j=0;j<col;j++){
   
                if(grid[i][j]=='1'&&arr[i][j]==false){
   
                    count++;
                    dfs(arr,grid,i,j,row,col);
                }
            }
        }
        return count;
    }
    public void dfs(boolean[][] arr,char[][] grid,int x,int y,int row,int col){
   
        arr[x][y]=true;
        for(int[] num:nums){
   
            int curRow=num[0]+x;
            int curCol=num[1]+y;
            if(judgeRange(curRow,curCol,row,col)&&arr[curRow][curCol]==false&&grid[curRow][curCol]=='1'){
   
                dfs(arr,grid,curRow,curCol,row,col);
            }
        }
    }
    public boolean judgeRange(int x,int y,int row,int col){
   
        if(x>=0&&x<row&&y>=0&&y<col){
   
            return true;
        }
        return false;
    }
}

4、广度优先遍历算法

在这里插入图片描述

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode(int x) { val = x; }
 * }
 */
class Solution {
   
    //解题思路:广度优先遍历
    //广度优先遍历是按层层推进的方式,遍历每一层的节点;题目要求的是返回每一层的节点值,所以这题用广度优先来做非常合适
    //广度优先需要用队列作为辅助结构,我们先将根节点放到队列中,然后不断遍历队列
    public List<List<Integer>> levelOrder(TreeNode root) {
   
        List<List<Integer>> list=new LinkedList<>();
        if(root==null){
   
            return list;
        }
        LinkedList<TreeNode> deque=new LinkedList<>();
        //将根节点放入队列中,然后不断遍历队列
        deque.add(root);
        while(deque.size()>0){
   
            //获取当前队列的长度,这个长度相当于 当前这一层的节点个数
            int len=deque.size();
            List<Integer> list1=new LinkedList<>();
            //将队列中的元素都拿出来(也就是获取这一层的节点),放到临时list中
            //如果节点的左/右子树不为空,也放入队列中
            for(int i=0;i<len;i++){
   
                TreeNode node=deque.poll();
                list1.add(node.val);
                if(node.left!=null){
   
                   deque.add(node.left);
                }
   
  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值