【leetcode刷题第32天】1020.飞地的数量、72.编辑距离、322.零钱兑换、343.整数拆分

本文探讨了三个使用深度优先搜索(DFS)和动态规划(DP)解决的编程问题:1020飞地的数量,72编辑距离,以及322零钱兑换。在飞地问题中,通过DFS遍历矩阵计算无法离开边界的陆地区域;编辑距离问题利用DP计算将一个字符串转换成另一个所需的最少操作数;零钱兑换问题通过DP找到组成给定金额所需的最少硬币数量。这些问题展示了DFS和DP在解决复杂计算问题中的效率和实用性。

第三十二天

1020 飞地的数量

给你一个大小为 m x n 的二进制矩阵 grid ,其中 0 表示一个海洋单元格、1 表示一个陆地单元格。

一次 移动 是指从一个陆地单元格走到另一个相邻(上、下、左、右)的陆地单元格或跨过 grid 的边界。

返回网格中 无法 在任意次数的移动中离开网格边界的陆地单元格的数量。

方法

使用深搜DFS来解决这个问题,题目要求我们找到不能够连通到外界的1的数量,那么我们可以再遍历的过程中统计一下经过的格子的数量,同时维护一个flag变量,当flagtrue时表明能够到达边界,那么当我们返回结果的时候就不将这一次的累加结果放入答案,否则就将统计的数量加入到答案中去,最后我们直接返回答案即可。

class Solution {
    public static int[] dx = new int[]{0, 0, 1, -1};
    public static int[] dy = new int[]{1, -1, 0, 0};
    public static int xLength;
    public static int yLength;
    public static boolean[][] isVisited;
    public static boolean flag;
    public static int cnt;

    public int numEnclaves(int[][] grid) {
        int ans = 0;
        xLength = grid.length;
        yLength = grid[0].length;
        isVisited = new boolean[xLength][yLength];
        for (int i = 0; i < xLength; ++i){
            for (int j = 0; j < yLength; ++j){
                if (!isVisited[i][j] && grid[i][j] == 1){
                    flag = false;
                    cnt = 0;
                    deepFirstSearch(grid, i, j);
                    if (!flag) ans += cnt;
                }
            }
        }
        return ans;
    }

    public static void deepFirstSearch(int[][] grid, int x, int y){
        isVisited[x][y] = true;
        cnt++;
        for (int i = 0; i < 4; ++i){
            int newX = x + dx[i];
            int newY = y + dy[i];
            if (!(newX >= 0 && newX < xLength && newY >= 0 && newY < yLength)) flag = true;
            else{
                if (grid[newX][newY] == 1 && !isVisited[newX][newY]) {
                    deepFirstSearch(grid, newX, newY);
                }
            }
        }
    }
}

72 编辑距离

给你两个单词 word1word2, 请返回将 word1 转换成 word2 所使用的最少操作数

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符
方法

考虑如下情况,如果我们已经知道了两个字符串ab,把a编辑成b所需要的最少次数是k,那么此时,如果我们分别向ab后面追加一个字符x和字符yxy存在以下四种情况:

  • x==y,此时我们不需要做任何操作,即a+xb+y所需要的编辑次数为k
  • x==''y!='',此时,如果我们需要将a+x编辑成为b+y,那么所需要的最少次数就是k+1,即我们需要向b尾部插入一个字符y,操作次数会增加1
  • x!=''y=='',同理次数的最少编辑次数为k+1,我们需要将a+x尾部的字符x删除掉,因此将a+x编辑为b+y所需要的最少需要增加1
  • x!=y,此时我们如果要将a+x编辑为b+y,我们需要将字符x替换y即可,最少的操作次数也是k+1

基于上述情况,我们定义dp数组,令dp[i][j]表示将 第一个字符串从0位置到i位置的子串 编辑成为 第二个字符串 从0位置到j位置的子串 所需要的最少操作次数,则状态转移方程为:
d p [ i ] [ j ] = { d p [ i − 1 ] [ j − 1 ] s 1 [ i ] = s 2 [ j ] m i n { d p [ i − 1 ] [ j ] + 1 , d p [ i ] [ j − 1 ] + 1 , d p [ i − 1 ] [ j − 1 ] + 1 } e l s e dp[i][j]= \left\{ \begin{aligned} &dp[i-1][j-1] \quad s_1[i]=s_2[j]\\ &min\{dp[i-1][j] + 1,dp[i][j-1] + 1,dp[i-1][j-1]+1\} \quad else\\ \end{aligned} \right. dp[i][j]={dp[i1][j1]s1[i]=s2[j]min{dp[i1][j]+1,dp[i][j1]+1,dp[i1][j1]+1}else

class Solution {
    public int minDistance(String word1, String word2) {
        int l1 = word1.length();
        int l2 = word2.length();
        int[][] dp = new int[l1 + 1][l2 + 1];
        for (int i = 0; i <= l1; ++i) dp[i][0] = i;
        for (int j = 0; j <= l2; ++j) dp[0][j] = j;
        for (int i = 1; i <= l1; ++i){
            for (int j = 1; j <= l2; ++j){
                if (word1.charAt(i - 1) == word2.charAt(j - 1)){
                    dp[i][j] = dp[i - 1][j - 1];
                }
                else{
                    dp[i][j] = Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1);
                    dp[i][j] = Math.min(dp[i][j], dp[i - 1][j - 1] + 1);
                }
            }
        }
        return dp[l1][l2];
    }
}

322 零钱兑换

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。

计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1

你可以认为每种硬币的数量是无限的。

方法

首先考虑如下情况,如果我们已经知道了组成0->k面值所需要的最少的硬币数量,则如果我们想组成k+1的面值,那么其所需要的最少的硬币数量就是 组成x面值所需要的最少硬币数量 加上 组成y面值所需要的最少硬币数量 的和,其中x+y=k+1,我们去遍历x,找出其中的最小值,就是组成k+1面值的最少硬币数量。

为此,我们定义dp[i]表示组成面值i所需要的最少硬币数量,对于每一个面值i,我们用所所有的硬币面值去试,找出能够让dp[i]达到最少的数量的值作为dp[i]的值,即状态转移方程为:
d p [ i ] = m i n ( d p [ i − j ] + 1 ) i f i − j > = 0 dp[i]=min(dp[i-j]+1) \quad if \quad i-j>=0 dp[i]=min(dp[ij]+1)ifij>=0

class Solution {
    public int coinChange(int[] coins, int amount) {
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; ++i){
            dp[i] = Integer.MAX_VALUE;
            for (int j : coins){
                if (i - j >= 0 && dp[i - j] != Integer.MAX_VALUE) dp[i] = Math.min(dp[i - j] + 1, dp[i]);
            } 
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }
}

343 整数拆分

给定一个正整数 n ,将其拆分为 k正整数 的和( k >= 2 ),并使这些整数的乘积最大化。

返回 你可以获得的最大乘积

方法

如果我们已经知道了j的拆分最大值,那么对于i=j+i-j拆分,就是ji-j的拆分的最大值。定义dp[i]表示i的拆分最大值,则状态转移方程如下:
d p [ i ] = m a x ( d p [ j ] ∗ d p [ i − j ] ) dp[i]=max(dp[j]*dp[i-j]) dp[i]=max(dp[j]dp[ij])
初始状态,我们令dp[i]=i即可,但是需要考虑特殊情况n=2n=3的情况,它们的答案我们让其返回12

class Solution {
    public int integerBreak(int n) {
        int[] dp = new int[n + 1];
        for (int i = 1; i <= n; ++i){
            dp[i] = i;
            for (int j = 1; j < i; ++j){
                dp[i] = Math.max(dp[i], dp[j] * dp[i - j]);
            }
        }
        return n == 2 ? 1 : (n == 3) ? 2 : dp[n];
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值