算法学习笔记(九):网格图DFS、图论算法DFS、动态规划DP、贪心

一.网格图DFS

适用于需要计算连通块个数、大小的题目

1.岛屿数量

        给你一个由 1(陆地) 和 0(水)组成的二维网格,请你计算网格中岛屿的数量

        岛屿总是被水包围,并且每座岛屿只能由水平方向和\或竖直方向上相邻的陆地连接形成

        此外,你可以假设该网格的四条边均被水包围

        

     

class Solution {
    public int numIslands(char[][] grid) {
        /**
            插旗法:
            真他妈牛逼,看了下题解真不是人想出来的
            也就是说从起点开始,从四个方向开始遍历,直到发现边界就停止
            边界:为0,或者到头了,或者已经被插旗
            这样只要是起点能连通的位置都会被插旗,剩下的就是隔着0的区域访问不到
            那么就相当于当前这一大块就是一个岛屿
            然后继续寻找下一个岛屿
         */
        int count = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                //从[0,0]开始作为起点进行插旗
                if (grid[i][j] == '1') {
                    dfs(i, j, grid);
                    count++;
                }
            }
        }
        return count;
    }

    private void dfs(int i, int j, char[][] grid) {
        //边界问题
        if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length 
                || grid[i][j] != '1') {
            return;
        }
        //重点:将当前节点插旗,变为不为1的数
        grid[i][j] = '0';
        //开始四个方向进行dfs
        //上
        dfs(i - 1, j, grid);
        //下
        dfs(i + 1, j, grid);
        //左
        dfs(i, j - 1, grid);
        //右
        dfs(i, j + 1, grid);
    }    
}

2.岛屿的最大面积

        给你一个大小为 m x n 的二进制矩阵 grid

        岛屿是由一些相邻的 1(土地)构成的组合,这里的相邻要求两个1必须在水平或者竖直的四个

        方向上相邻,你可以假设grid的四个边缘都被0(水)包围

        岛屿的面积是岛上值为1的单元格的数目

        请计算grid的最大岛屿面积,如果没有岛屿,请返回0

     

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        //插旗法 比上一题难一点 要算面积
        int sum = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[i].length; j++) {
                if (grid[i][j] == 1) {
                    sum = Math.max(sum, dfs(i, j, grid));
                }
            }
        }
        return sum;
    }

    private int dfs(int i, int j, int[][] grid) {
        //边界问题一订要定义好 不然会越界
        if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] != 1) {
            return 0;
        }
        int sum = 0;
        grid[i][j] = 2;
        sum++;  //自身
        sum += dfs(i - 1, j, grid); //上
        sum += dfs(i + 1, j, grid); //下
        sum += dfs(i, j - 1, grid); //左
        sum += dfs(i, j + 1, grid); //右
        return sum;
    }
}

  3.水域大小

        你有一个用于表示一片土地的整数矩阵land,该矩阵中每个点的值代表对应地点的海拔

        高度,若值为0则表示水域,由垂直、水平或者对角连接的水域为池塘,池塘的大小是指

        相连的水域的个数,编写一个算法来计算矩阵中所有池塘的大小,返回值需要从小到大

        排序

class Solution {
    public int[] pondSizes(int[][] land) {
        //我草了 还有对角线 想想对角线怎么处理?
        //对角线 有4个呢 那就是8个方向去遍历咯
        List<Integer> list = new ArrayList<>();
        for (int i = 0; i < land.length; i++) {
            for (int j = 0; j < land[i].length; j++) {
                if (land[i][j] == 0) {
                    list.add(dfs(i, j, land));
                }
            }
        }
        int[] arr = new int[list.size()];
        list.sort((a, b) -> a - b);
        for (int i = 0; i < list.size(); i++) {
            arr[i] = list.get(i);
        }
        return arr;
    }

    private int dfs(int i, int j, int[][] land) {
        //边界问题
        if (i < 0 || i >= land.length || j < 0 || j >= land[0].length || land[i][j] != 0) {
            return 0;
        }
        int sum = 0;
        land[i][j] = 1;
        sum++;
        //上下左右,左上,左下,右上,右下
        sum += dfs(i - 1, j, land);
        sum += dfs(i + 1, j, land);
        sum += dfs(i, j - 1, land);
        sum += dfs(i, j + 1, land);
        sum += dfs(i - 1, j - 1, land);
        sum += dfs(i + 1, j - 1, land);
        sum += dfs(i - 1, j + 1, land);
        sum += dfs(i + 1, j + 1, land);
        return sum;
    }
}

二.动态规划DP

动态规划解题思路:

        1.寻找子问题

                寻找定义子问题,如果可以把原问题变成一个和原问题相似、规模更小

                的子问题,这种情况就可以用递归来解决

        2.递归怎么写

                状态定义与状态转移方程:

        3.递归+记录返回值=记忆化搜索

                因为在递归中,有着大量的重复递归调用(递归入参相同),由于递归函数都是幂等的,

                同样的入参无论计算多少次结果都是一样的,所以我们可以用记忆化搜索来优化,

                就是将同样入参的计算结果放到缓存中,优先从缓存中拿结果,没有在递归

        4.1:1翻译成递推

                既我们可以去掉递归中的递,只保留归的部分,即自底向上计算

        5.空间优化

                一般dp问题,都是只会用到前面几个状态,之前已经计算过的状态在后面是永远不会

                再用了的,所以我们只需要知道比如上一个状态和上上一个状态即可               

1.爬楼梯

        假设你正在爬楼梯,需要 n 阶你才能到达楼顶

        每次你都可以爬 1 或者2个台阶,你有多少种不同的方式可以爬到楼顶呢?

        第一种解法:这个解法如果数据量大的话往往会超时    

class Solution {
    public int climbStairs(int n) {
        /**
            如果最后一步是爬了1个台阶 那么首先要爬到n-1台阶上
            如果最后一步是爬了2个台阶,那么首先要爬到n-2台阶上
            所以 而爬到n-1和n-2是两种不同的方法 所以
            dfs(n) = dfs(n - 1) + dfs(n - 2)
         */

        return dfs(n);
    }

    private int dfs(int n) {
        if (n <= 1) return 1;   //边界问题 0 和1
        return dfs(n - 1) + dfs(n - 2);
    }
}

        第二种解法:+缓存

class Solution {
    public int climbStairs(int n) {
        /**
            如果最后一步是爬了1个台阶 那么首先要爬到n-1台阶上
            如果最后一步是爬了2个台阶,那么首先要爬到n-2台阶上
            所以 而爬到n-1和n-2是两种不同的方法 所以
            dfs(n) = dfs(n - 1) + dfs(n - 2)
            为了边界问题,0和1因为爬到0和1个台阶都是只有1种方式
            所以初始化dfs(0) dfs(1) = 1 然后公式演变成
            dfs(n + 2) = dfs(1) + dfs(0)
         */
        Map<Integer, Integer> map = new HashMap<>();
        return dfs(n, map);
    }

    private int dfs(int n, Map<Integer, Integer> map) {
        if (n <= 1) return 1;   //边界问题 0 和1
        if (map.get(n) != null) return map.get(n);
        int num = dfs(n - 1, map) + dfs(n - 2, map);
        map.put(n, num);
        return num;
        //这个方式会超时 因为每次都是递归了2遍 变成了 n^2次了 增加一个缓存
        // return dfs(n - 1) + dfs(n - 2);
    }
}

        第三种解法:继续优化,1:1翻译成递推

        dfs(i) = dfs(i - 1) + dfs(i - 2),那么我们可以定义成这样

        f[i] = f[i - 1] + f[i - 2],之前是递归计算每个状态,现在是枚举计算每个状态

        初始值f[0] = f[1] = 1,翻译自递归边界dfs(0) = 1,dfs(1) = 1

        f[n] 翻译自dfs(n)       

class Solution {
    public int climbStairs(int n) {
        /**
            1.定义子问题
                假设最后一次是爬1个台阶,那么就是爬到n-1需要多少种方式
                假设最后一次是爬2个台阶,那么就是爬到n-2需要多少种方式
                最后爬到n的总方式是 上面两种方式之和 
                即 fn = fn-1 + fn-2
                但是在这过程种很多都是会重复计算 那么用一个缓存来记录算过的值
         */
        // int[] cache = new int[n + 1];
        // return dp(n, cache);

        //用逆推 0和1的时候方法是确定的
        int[] dp = new int[n + 1];
        dp[0] = 1; 
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }

        return dp[n];
    }

    // private int dp(int n, int[] cache) {
    //     if (n <= 1) return 1;   //0和1个台阶的时候就只有一种方法
    //     if (cache[n] != 0) return cache[n];
    //     int num = dp(n - 1, cache) + dp(n - 2, cache);
    //     cache[n] = num;
    //     return num;
    // }
    
}

第四种解法:继续空间优化

因为计算当前值只需要知道上一个状态和上上一个状态,所以每次只保留这两个变量即可

class Solution {
    public int climbStairs(int n) {
        /**
            分析子问题,假设最后一步是1各台阶,那么爬到n-1个台阶的方法就是dfs(n-1)
            最后一步是2个台阶,爬到n-2个台阶的方法就是n-2
            最后总共有 fds(n-1)+dfs(n-2)种方法
            那么运用缓存 因为在dfs过程中肯定会有重复计算的
            那么就是缓存中有的就不用计算了
            再优化,递推: 因为1个台阶和2个台阶的方法都是1种 
            再优化 n-1是上一个 n-2是上上一个 因为每次计算完上一个上上一个的值再接下来就不会用到
            所以每次就只使用这俩变量就可以了 
         */

        // int[] dp = new int[n + 1];
        // dp[0] = 1;
        // dp[1] = 1;
        // for (int i = 2; i <= n; i++) {
        //     dp[i] = dp[i - 1] + dp[i - 2];
        // }
        // return dp[n];
        //优化一下空间
        int dp0 = 1, dp1 = 1;
        for (int i = 1; i < n; i++) {
            int dpi = dp1 + dp0;
            dp0 = dp1;
            dp1 = dpi;
        }
        return dp1;
    }
}

2.使用最小花费爬楼梯

        给你一个整数数组 cost,其中cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你

        支付此费用,即可旋转向上爬一个或者两个台阶

        你可以选择从下标为 0 或者 1 的台阶开始爬楼梯

        请你计算并返回到达楼顶的最低花费

        

class Solution {
    public int minCostClimbingStairs(int[] cost) {
        /**
            分析子问题
            当最后是爬一个楼梯时,那么就是爬到n-1的费用+当前n-1的费用
            同理,当最后是爬2个楼梯,那么就是n-2的费用+当前n-2的费用
            然后取两者最小值
            int min = Math.min(dfs(n-1) + cost[n-1], dfs(n-2) + cost[n-2])
            剩下的就是开始优化
                优化:加缓存
                优化:递推 因为dfs(n-1)其实就是fn-1 同时 f0和f1是已知的都是=0 因为可以从0或者1开始 
                int[] dp = new int[n+1]
                min = Math.min(dp[n-1] + cost[n-1], dp[n-2]+cost[n-2]) 
        
         */
        // int[] cache = new int[cost.length + 1];
        // Arrays.fill(cache, -1);
        // return dfs(cost, cache, cost.length);

        //进行递推 因为 f0 f1都是已知的
        // int n = cost.length;
        // int[] dp = new int[n + 1];
        // dp[0] = 0;
        // dp[1] = 0;
        // for (int i = 2; i <= n; i++) {  //为什么从2开始呢 因为按照题目来0和1都是已知的 那么肯定从未知的第一个台阶2开始
        //     dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        // }
        // return dp[n];

        //继续优化 dpi-1可以认为是上一个 dpi-2是上上一个 那么总是只需要这两个变量即可 
        //上一次的上一个和上上一个算完以后就不会再用了
        int dp0 = 0, dp1 = 0; //为什么是0 按照上面推理 因为可以从0或者1开始爬 所以到达0或者1不需要成本
        for (int i = 1; i < cost.length; i++) {
            int min = Math.min(dp1 + cost[i], dp0 + cost[i - 1]);
            dp0 = dp1;
            dp1 = min;
        }
        return dp1;

        // int f0 = 0, f1 = 0;
        // for (int i = 1; i < cost.length; i++) {
        //     int fn = Math.min(f1 + cost[i], f0 + cost[i - 1]);
        //     f0 = f1;
        //     f1 = fn;
        // }
        // return f1;
    }

    // private int dfs(int[] cost, int[] cache, int n) {
    //     if (n <= 1) return 0;
    //     if (cache[n] != -1) return cache[n];
    //     cache[n] = Math.min(dfs(cost, cache, n - 1) + cost[n - 1], dfs(cost, cache, n - 2) + cost[n - 2]);
    //     return cache[n];
    // }
}

   3.打家劫舍

        你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都藏有一定的现金,影响你偷窃

        的唯一制约因素就是相邻的房屋装有相互连通的防盗系统,如果两件相邻的房屋在同一

        晚上被小偷闯入,系统会自动报警

        给定一个代表每个房屋存放金额的非负整数数组,计算你 不触动警报的情况下,一夜

        之内能够偷窃到的最高金额

class Solution {
    public int rob(int[] nums) {
        /**
            最后一个房子偷不偷 下标从0开始
            偷 那么就是fn-2 +hn-1 前n-2个房子+最后一间房子的金额 因为索引从0开始 n-1就是最后一间房
            不偷 那就是fn-1  
            然后就是计算这俩最高值 max = Math.max(f(n-2) + h(n-1), f(n-1));
            然后换算
        
         */

        int n = nums.length;
        // int[] dp = new int[n + 1];  //从0开始
        // dp[0] = 0;
        // dp[1] = nums[0];
        // for (int i = 2; i <= n; i++) {
        //     dp[i] = Math.max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        // }
        // return dp[n];
        //空间优化 
        // int dp0 = 0, dp1 = nums[0];
        // for (int i = 2; i <= n; i++) {
        //     int max = Math.max(dp1, dp0 + nums[i - 1]);
        //     dp0 = dp1;
        //     dp1 = max;
        // }
        // return dp1;

        //再优化
        int dp0 = 0, dp1 = 0;
        for (int x : nums) {
            int dp = Math.max(dp1, dp0 + x);
            dp0 = dp1;
            dp1 = dp;
        }
        return dp1;
    }
}

三.贪心

1.重新分装苹果

        给你一个长度为n的数组 apple 和另一个长度为 m 的数组 capacity

        一共有 n 个包裹,其中第 i 个包裹中装着 apple[i]个苹果,同时,还有 m 个箱子,第 i 个

        箱子的容量为 capacity[i] 个苹果,请你选择一些箱子来将这 n 个包裹中的苹果重新分装

        到箱子中,返回你需要选择的最小箱子数

        注意:同一个包裹中的苹果可以分装到不同的箱子中

class Solution {
    public int minimumBoxes(int[] apple, int[] capacity) {
        //妙啊 贪心 这题目没有说输出箱子的index 只是计算个数
        //而且1个包裹里的苹果可以放到不同箱子里 所以从大到小排序箱子
        //直到装完苹果为止
        int appleSum = 0;
        for (int num : apple) {
            appleSum += num;
        }
        Arrays.sort(capacity);
        int m = capacity.length;
        int i = m - 1;
        while (appleSum > 0) {
            appleSum -= capacity[i--];
        }
        return m - i - 1;
    }
}

        

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/921093.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Cmakelist.txt之Linux-redis配置

1.cmakelist.txt cmake_minimum_required(VERSION 3.16) ​ project(redis_linux_test LANGUAGES C) ​ ​ ​ add_executable(redis_linux_test main.c) ​ # 设置hiredis库的头文件路径和库文件路径 set(Hiredis_INCLUDE_DIR /usr/local/include/hiredis) set(Hiredis_LIBRA…

【Node.js】Node.js 和浏览器之间的差异

Node.js 是一个强大的运行时环境&#xff0c;它在现代 JavaScript 开发中扮演着重要角色。然而&#xff0c;许多开发者在使用 Node.js 时常常会感到困惑&#xff0c;尤其是与浏览器环境的对比。本文将深入探讨 Node.js 和浏览器之间的差异&#xff0c;帮助你全面理解两者的设计…

【物联网原理与应用】实验二:红外传感实验

目录 一、实验目的 二、实验原理 三、实验内容及步骤 四、实验结果 五、核心代码 一、实验目的 学习试验模块上线路的连接操作理解掌握红外传感器的工作原理实现对红外传感器数据的接收和处理 二、实验原理 1、将红外辐射能转换成电能的光敏元件称为红外传感器&#…

PAL(Program-Aided Language Model)

PAL&#xff08;Program-Aided Language Model&#xff09;是一种结合生成式语言模型&#xff08;如 GPT&#xff09;和程序执行能力的技术框架。它的核心思想是通过让语言模型生成代码或程序来解决复杂任务&#xff0c;程序执行的结果反过来增强语言模型的输出准确性和逻辑性。…

java基础概念36:正则表达式1

一、正则表达式的作用 作用一&#xff1a;校验字符串是否满足规则&#xff1b;作用二&#xff1a;在一段文本中查找满足要求的内容。——爬虫 二、正则表达式 2-1、字符类 示例&#xff1a; public static void main(String[] args) {System.out.println("a".matc…

VsCode 插件推荐(个人常用)

VsCode 插件推荐&#xff08;个人常用&#xff09;

工业储能柜的大小该如何选择,工商储能系统设备哪家好?

在能源转型和可持续发展的大潮中&#xff0c;工商业储能系统因其提升清洁能源利用率、降低电能损耗、实现“双碳”目标等优势而备受青睐。它们不仅增强了电力系统的可靠性和灵活性&#xff0c;还帮助企业降低成本、提高经济效益。储能系统通过负荷管理适应电价波动&#xff0c;…

人工智能之数学基础:线性代数在人工智能中的地位

本文重点 从本文开始&#xff0c;我们将开启线性代数的学习&#xff0c;在线性代数中有向量、矩阵&#xff0c;以及各种性质&#xff0c;那么这些数学知识究竟和人工智能有什么关系呢&#xff1f; 重要性 机器学习和深度学习的本质就是训练模型&#xff0c;要想训练模型需要使…

数字IC后端实现时钟树综合系列教程 | Clock Tree,Clock Skew Group之间的区别和联系

Q: Clock&#xff0c;Clock Tree和Skew Group有何区别&#xff1f;Innovus CCOPT引擎是如何使用这些的&#xff1f; Clock是时序约束SDC中的时钟定义点。 create_clock -name clk_osc -period $period_24m [get_ports xin_osc0_func] 时钟树综合(Clock Tree Synthesis)之前应…

飞桨大模型PaddleOCR

一、新建项目PaddleOCRProject 二、查看开源 pip install paddlepaddle pip install paddleocr指定镜像源下载才快&#xff1a; pip install paddlepaddle -i https://pypi.tuna.tsinghua.edu.cn/simple pip install paddleocr -i https://pypi.tuna.tsinghua.edu.cn/simple 三…

31、js中日期操作

<!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>日期</title> </head> <body></body> <script>// js中日期操作 var datenew Date();document.write("日期时间&am…

【大数据学习 | Spark】Spark中的join原理

join是两个结果集之间的链接&#xff0c;需要进行数据的匹配。 演示一下join是否存在shuffle。 1. 如果两个rdd没有分区器&#xff0c;分区个数一致 &#xff0c;会发生shuffle。但分区数量不变。 scala> val arr Array(("zhangsan",300),("lisi",…

NLP论文速读(CVPR 2024)|使用DPO进行diffusion模型对齐

论文速读|Diffusion Model Alignment Using Direct Preference Optimization 论文信息&#xff1a; 简介&#xff1a; 本文探讨的背景是大型语言模型&#xff08;LLMs&#xff09;通过人类比较数据和从人类反馈中学习&#xff08;RLHF&#xff09;的方法进行微调&#xff0c;以…

小车AI视觉识别--9.目标检测

一、目标检测概述 本节主要解决的问题是如何使用OpenCV中的dnn模块&#xff0c;用来导入一个实现训练好的目标检测网络。但是对opencv的版本是有要求的。目前用深度学习进行目标检测&#xff0c;主要有三种方法&#xff1a; Faster R-CNNsYou Only Look Once(YOLO)Single Shot…

2023年9月GESPC++一级真题解析

一、单选题&#xff08;每题2分&#xff0c;共30分&#xff09; 题号 123456789101112131415 答案 CDBCDBACACBBDDA 1. 我们通常说的 “ 内存 ” 属于计算机中的&#xff08;&#xff09;。 A. 输出设备 B. 输 ⼊ 设备 C. 存储设备 D. 打印设备 【答案】 C 【考纲知识点】…

VS2022进行Libigl库编译

目录 一 编译OK 二 编译难点 2.1 cmake问题 2.2 文件编码问题 三 调用链接 一 编译OK 二 编译难点 2.1 cmake问题 vs2022直接多次cmake生成即可。 2.2 文件编码问题 格式保存为GB2312. 三 调用链接 https://github.com/libigl/libigl-example-project

Vue3 el-table 默认选中 传入的数组

一、效果&#xff1a; 二、官网是VUE2 现更改为Vue3写法 <template><el-table:data"tableData"border striperow-key"id"ref"tableRef":cell-style"{ text-align: center }":header-cell-style"{background: #b7babd…

晶圆测试中自动化上下料的重要性与应用

随着科技的飞速发展&#xff0c;硅光技术在通信、数据处理等领域展现出巨大的应用潜力。硅光晶圆作为硅光技术的核心源头组件&#xff0c;其性能的稳定性和可靠性对于整个系统的运行至关重要。因此&#xff0c;对硅光晶圆的测试成为生产过程中不可或缺的一环。近年来&#xff0…

udp_socket

文章目录 UDP服务器封装系统调用socketbind系统调用bzero结构体清0sin_family端口号ip地址inet_addrrecvfromsendto 新指令 netstat -naup (-nlup)包装器 的两种类型重命名方式包装器使用统一可调用类型 关键字 typedef 类型重命名系统调用popen关于inet_ntoa UDP服务器封装 系…

Perfetto学习大全

Perfetto 是一个功能强大的性能分析和追踪工具&#xff0c;主要用于捕获和分析复杂系统中的事件和性能数据&#xff0c;特别是在 Android 和 Linux 环境下。它的核心目标是帮助开发者深入了解系统和应用程序的运行状态&#xff0c;以便优化性能和诊断问题。 Perfetto的主要作用…