剑指 Offer II 104+105+106+107+109

这篇博客介绍了多种算法在实际问题中的应用,包括使用动态规划解决组合计数问题,深度优先搜索求解岛屿的最大面积,以及如何通过染色法判断一个图是否为二分图。此外,还讨论了如何通过广度优先搜索找到矩阵中每个元素到最近0的距离,以及解决密码锁的最小旋转次数问题。这些算法展示了在处理复杂问题时的策略和技巧。
摘要由CSDN通过智能技术生成

排列的数目

给定一个由 不同 正整数组成的数组 nums ,和一个目标整数 target 。请从 nums 中找出并返回总和为 target 的元素组合的个数。数组中的数字可以在一次排列中出现任意次,但是顺序不同的序列被视作不同的组合。

题目数据保证答案符合 32 位整数范围。

分析:

动态规划问题。定义大小为target+1的一维数组dp,dp[i]表示目标整数为i时,nums中数字可以产生的组合个数,初始化dp[0] = 1。递推关系为:遍历数组中的数字num,如果当前的num<=i,则有dp[i] += dp[i-num]。

class Solution {
    public int combinationSum4(int[] nums, int target) {
        int n = nums.length;
        int[] dp = new int[target+1];
        dp[0] = 1;
        for (int i = 1; i <= target ; i++) {
            for (int num : nums){
                if (num<=i){
                    dp[i] += dp[i-num];
                }
            }
        }
        return dp[target];
    }
}

岛屿的最大面积

给定一个由 0 和 1 组成的非空二维数组 grid ,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的 1 (代表土地) 构成的组合,这里的「相邻」要求两个 1 必须在水平或者竖直方向上相邻。你可以假设 grid 的四个边缘都被 0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为 0 。
在这里插入图片描述

分析:

采用dfs来解决。首先对grid进行遍历,如果当前grid[i][j] = 1,则从该点开始进行dfs。定义int dfs(grid, x, y),x和y分别代表着当前的横纵坐标。如果当前grid[x][y] = 0,则返回0;如果grid[x][y]=1,ans=1,然后将grid[x][y]置位0表示已经搜索过了。然后向上下左右四个方向进行搜索,搜索的时候要注意进行越界的判断,最后返回的ans = dfs(grid, x+1, y)+dfs(grid, x-1, y)+dfs(grid, x, y+1)+dfs(grid, x, y-1)。遍历完grid之后返回dfs过程中的最大值。

class Solution {
    public int maxAreaOfIsland(int[][] grid) {
        int ans = 0;
        for (int i = 0; i < grid.length; i++) {
            for (int j = 0; j < grid[0].length; j++) {
                ans = Math.max(ans, dfs(grid, i, j));
            }
        }
        return ans;
    }
    public int dfs(int[][] grid, int x, int y){
        if (x<0||y<0||x>=grid.length||y>=grid[0].length||grid[x][y] == 0){
            return 0;
        }
        grid[x][y] = 0;
        int ans = 1;
        ans += dfs(grid, x+1, y);
        ans += dfs(grid, x-1, y);
        ans += dfs(grid, x, y+1);
        ans += dfs(grid, x, y-1);
        return ans;
    }
}

二分图

存在一个 无向图 ,图中有 n 个节点。其中每个节点都有一个介于 0 到 n - 1 之间的唯一编号。给定一个二维数组 graph ,表示图,其中 graph[u] 是一个节点数组,由节点 u 的邻接节点组成。形式上,对于 graph[u] 中的每个 v ,都存在一条位于节点 u 和节点 v 之间的无向边。该无向图同时具有以下属性:

  • 不存在自环(graph[u] 不包含 u)。
  • 不存在平行边(graph[u] 不包含重复值)。
  • 如果 v 在 graph[u] 内,那么 u 也应该在 graph[v] 内(该图是无向图)
  • 这个图可能不是连通图,也就是说两个节点 u 和 v 之间可能不存在一条连通彼此的路径。

二分图 定义:如果能将一个图的节点集合分割成两个独立的子集 A 和 B ,并使图中的每一条边的两个节点一个来自 A 集合,一个来自 B 集合,就将这个图称为 二分图 。如果图是二分图,返回 true ;否则,返回 false 。

分析:

染色问题。假设现将一个起点v染成红色,然后把graph[u]中的点全部染成绿色,接着把graph[u]中所有点的邻接点全部染成红色…不断重复知道所有点都染完,则表明该图是二分图;但如果在某一步骤中想要将点染成红色或者绿色时,该点已经是绿色或者红色,则代表该图不是二分图,直接返回false。

class Solution {
    private static final int UNCOLORED = 0;
    private static final int RED = 1;
    private static final int GREEN = 2;
    private int[] color;
    private boolean valid;

    public boolean isBipartite(int[][] graph) {
        int n = graph.length;
        valid = true;
        color = new int[n];
        Arrays.fill(color, UNCOLORED);
        for (int i = 0; i < n && valid; ++i) {
            if (color[i] == UNCOLORED) {
                dfs(i, RED, graph);
            }
        }
        return valid;
    }

    public void dfs(int node, int c, int[][] graph) {
        color[node] = c;
        int cNei = c == RED ? GREEN : RED;
        for (int neighbor : graph[node]) {
            if (color[neighbor] == UNCOLORED) {
                dfs(neighbor, cNei, graph);
                if (!valid) {
                    return;
                }
            } else if (color[neighbor] != cNei) {
                valid = false;
                return;
            }
        }
    }
}

矩阵中的距离

给定一个由 0 和 1 组成的矩阵 mat ,请输出一个大小相同的矩阵,其中每一个格子是 mat 中对应位置元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。
在这里插入图片描述

输入:mat = [[0,0,0],[0,1,0],[1,1,1]]
输出:[[0,0,0],[0,1,0],[1,2,1]]

分析:

采用dfs,与上面的岛屿类问题相似,不过这里需要额外在定义一个数组visited来表示当前节点有没有被访问过。

class Solution {
    int min;
    boolean[][] visited;
    public int[][] updateMatrix(int[][] mat) {
        int[][] res = new int[mat.length][mat[0].length];
        visited = new boolean[mat.length][mat[0].length];
        for (int i = 0; i < mat.length; i++) {
            for (int j = 0; j < mat[0].length; j++) {
                if (mat[i][j] == 1){
                    min = Integer.MAX_VALUE;
                    dfs(mat, i, j,0);
                    res[i][j] = min;
                }
            }
        }
        return res;

    }
    public void dfs(int[][] mat, int x, int y, int dis){
        if (x<0||y<0||x>= mat.length||y>=mat[0].length||visited[x][y]) return;
        if (mat[x][y] == 0){
            min = Math.min(min, dis);
            return;
        }
        if (dis>min) return;
        visited[x][y] = true;
        dfs(mat, x+1, y, dis+1);
        dfs(mat, x-1, y, dis+1);
        dfs(mat, x, y+1, dis+1);
        dfs(mat, x, y-1, dis+1);
        visited[x][y] = false;
    }
}

开密码锁

一个密码锁由 4 个环形拨轮组成,每个拨轮都有 10 个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。字符串 target 代表可以解锁的数字,请给出解锁需要的最小旋转次数,如果无论如何不能解锁,返回 -1 。

分析:

使用bfs来解决。首先利用一个哈希表来存储deadends中的值,如果0000或者target存在于deadends中直接返回false。然后在定义一个队列q和哈希表seen,q用来进行bfs而seen是用来记录已经访问的值,防止进行重复操作。首先把0000入队并且定义一个变量step记录旋转次数,因为每次旋转都只能旋转一个拨轮的一位数字,所以会出现四种情况0001、0010、0100、1000;然后判断这四种情况产生的字符串是否存在于deadends与seen中,如果不存在则入队;如果入队的字符串有target则直接返回step。

class Solution {
    public int openLock(String[] deadends, String target) {
        HashSet<String> set = new HashSet<>();
        for (String s : deadends) set.add(s);
        String start = "0000";
        if (start.equals(target)) return 0;
        if (set.contains(target)||set.contains(start)) return -1;
        Queue<String> queue = new LinkedList<>();
        Set<String> seen = new HashSet<>();
        int step = 0;
        queue.offer(start);
        seen.add(start);
        while (!queue.isEmpty()){
            step++;
            int size = queue.size();
            for (int i = 0; i<size; i++) {
                String status = queue.poll();
                for (String s : getNext(status)){
                    if (!seen.contains(s) && !set.contains(s)){
                        if (s.equals(target)) return step;
                        queue.offer(s);
                        seen.add(s);
                    }
                }
            }
        }
        return -1;
    }

    private List<String> getNext(String status) {
        List<String> list = new ArrayList<>();
        char[] chars = status.toCharArray();
        for (int i = 0; i < 4; i++) {
            char num = chars[i];
            chars[i] = getPreNum(num);
            list.add(new String(chars));
            chars[i] = getNextNum(num);
            list.add(new String(chars));
            chars[i] = num;
        }
        return list;
    }

    private char getNextNum(char num) {
        return num == '9'?'0':(char) (num+1);
    }

    private char getPreNum(char num) {
        return num == '0'?'9':(char) (num-1);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值