数据结构与算法学习笔记(训练营四)-经典面试13(ppt-1)

  • 给定一个二维数组matrix,可以从任何位置出发,每一步可以走向上、下、左、右,四个方向。返回最大递增链的长度。
    例子:
    matrix =
    5 4 3
    3 1 2
    2 1 3
    从最中心的1出发,是可以走出1 2 3 4 5的链的,而且这是最长的递增链。所以返回长度5
/**
 * 给定一个二维数组matrix,可以从任何位置出发,每一步可以走向上、下、左、右,四个方向。返回最大递增链的长度。
 * 例子:
 * matrix =
 * 5  4  3
 * 3  1  2
 * 2  1  3
 * 从最中心的1出发,是可以走出1 2 3 4 5的链的,而且这是最长的递增链。所以返回长度5
 */
public class MaxLenLinked {
    // 暴力递归
    public static int maxLenLinked(int[][] matrix){
        if(matrix == null || matrix[0].length == 0){
            return 0;
        }
        int max = 0;
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                int process = process(matrix, i, j);
                max = Math.max(process,max);

            }

        }

        return max;

    }

    // 从i j 位置开始返回最长递增连,
    private static int process(int[][] matrix,int i,int j){

        int p1 = 0;
        int p2 = 0;
        int p3 = 0;
        int p4 = 0;
        int res = 0;
        // up
        if(i - 1 >= 0 && matrix[i][j] < matrix[i-1][j]){
             p1 = process(matrix, i - 1, j);
        }
        // down
        if(i+1 < matrix.length && matrix[i][j] < matrix[i+1][j]){
            p2 = process(matrix,i+1,j);
        }
        // left
        if(j-1 >= 0 && matrix[i][j] < matrix[i][j-1]){
            p3 = process(matrix,i,j-1);
        }
        //right
        if(j+1 < matrix[0].length && matrix[i][j] < matrix[i][j+1]){
            p4 = process(matrix,i,j+1);
        }
        res = Math.max(p1,Math.max(p2,Math.max(p3,p4)))+1;
        return res;
    }


    // 记忆化搜索

    public static int dp1(int[][] matrix){
        if(matrix == null || matrix[0].length == 0){
            return 0;
        }
        int max = 0;
        int[][] dp = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                int process = process2(matrix, i, j,dp);
                max = Math.max(process,max);

            }

        }
        return max;
    }


    private static int process2(int[][] matrix,int i,int j,int[][] dp){
        if(dp[i][j] != 0){
            // 来到这个点不可能是0,至少都是1,自己形成一个长度为1的路径
            return dp[i][j];
        }

        int p1 = 0;
        int p2 = 0;
        int p3 = 0;
        int p4 = 0;
        int res = 0;
        // up
        if(i - 1 >= 0 && matrix[i][j] < matrix[i-1][j]){
            p1 = process(matrix, i - 1, j);
        }
        // down
        if(i+1 < matrix.length && matrix[i][j] < matrix[i+1][j]){
            p2 = process(matrix,i+1,j);
        }
        // left
        if(j-1 >= 0 && matrix[i][j] < matrix[i][j-1]){
            p3 = process(matrix,i,j-1);
        }
        //right
        if(j+1 < matrix[0].length && matrix[i][j] < matrix[i][j+1]){
            p4 = process(matrix,i,j+1);
        }
        res = Math.max(p1,Math.max(p2,Math.max(p3,p4)))+1;
        dp[i][j] = res;
        return dp[i][j];

    }

    public static void main(String[] args) {
        int[][] matrix = new int[][]{
                {6,5,4,3},
                {2,3,1,2},
                {1,2,1,3}
        };
        System.out.println(maxLenLinked(matrix));
        System.out.println(dp1(matrix));
    }
}
  • 给定一个数组arr,再给定一个k值,返回累加和小于等于k,但是离k最近的子数组累加和
/**
 * 给定一个数组arr,再给定一个k值,返回累加和小于等于k,但是离k最近的子数组累加和
 */
public class MaxSubArray {
    public static int maxSubArray(int[] arr,int k){
        if(arr == null || arr.length == 0){
            return 0;
        }
        // 可以这样做求i位置的子数组累加和小于等于k,实质就是求在0~i上是否存在
        // 一个位置j使得0~j上的前缀和大于等于 sum - k
        // 如果存在,那么久找到了一个,必须以i位置结尾的累加和小于等于k的子数组,每个位置求一遍
        // 答案比在其中
        TreeSet<Integer> set = new TreeSet<>();
        // 一开始在0位置的时候,其以前的累加和为0;
        set.add(0);
        int sum = 0;
        int res = 0;
        for (int i = 0; i < arr.length; i++) {
            sum += arr[i];
            if(set.ceiling(sum - k) != null){
                // 存在大于等于sum - k的数,也就是存在i位置的累加和小于等于k的子数组
                res = Math.max(res,sum - set.ceiling(sum-k));
                set.add(sum);
            }
        }

        return res;

    }
    public static void main(String[] args) {
        int[] arr = new int[]{1,1,3,8,1,8};
        int k = 6;
        System.out.println(maxSubArray(arr,k));
    }
}

  • 给定一个二维数组matrix,再给定一个k值,返回累加和小于等于k,但是离k最近的子矩阵累加和
/**
 * 给定一个二维数组matrix,再给定一个k值,返回累加和小于等于k,但是离k最近的子矩阵累加和
 */
public class MaxSubMatrix {
    public static int maxSubMatrix(int[][] matrix,int k){
        if(matrix == null || matrix[0].length == 0){
            return 0;
        }

        // 矩阵问题可以吧他转化为一维数组为题
        // 0~0,0~1,0~2,...,0~n-1,行都算出一个答案
        // 1~1,1~2,...,1~n-1 ,都算出一个答案
        // ...
        // 并且每个矩阵可以压缩为一个一维数组操作
        int max = 0;
        for (int start = 0; start < matrix.length; start++) {
            int[] arr = new int[matrix[0].length];
            TreeSet<Integer> set = new TreeSet<>();
            // 一开始在0位置的时候,其以前的累加和为0;
            for (int end = start; end < matrix.length; end++) {
                // 内层循环每循环一遍相当与搞定一个矩阵,一共有n^2个举证
                // 压缩矩阵
                int sum = 0;
                set.clear();
                set.add(0);
                for (int i = 0; i < matrix[0].length; i++) {
                    arr[i] += matrix[end][i];
                    sum += arr[i];
                    if(set.ceiling(sum - k) != null){
                        // 存在大于等于sum - k的数,也就是存在i位置的累加和小于等于k的子数组
                        max = Math.max(max,sum - set.ceiling(sum-k));
                        set.add(sum);
                    }
                }
            }
        }

        return max;
    }

   
    public static void main(String[] args) {
        int[][] matrix = new int[][]{
                {5,4,3},
                {3,2,2},
                {2,1,3}
        };
        int k = 11;
        System.out.println(maxSubMatrix(matrix,k));
    }


}

  • 给定一个字符类型的二维数组board,和一个字符串组成的列表words。
    可以从board任何位置出发,每一步可以走向上、下、左、右,四个方向,
    但是一条路径已经走过的位置,不能重复走。
    返回words哪些单词可以被走出来。
    例子
    board = [
    ['o','a','a','n'],
    ['e','t','a','e'],
    ['i','h','k','r'],
    ['i','f','l','v']
    ]
    words = ["oath","pea","eat","rain"]
    输出:["eat","oath"]
/**
 * 给定一个字符类型的二维数组board,和一个字符串组成的列表words。
 * 可以从board任何位置出发,每一步可以走向上、下、左、右,四个方向,
 * 但是一条路径已经走过的位置,不能重复走。
 * 返回words哪些单词可以被走出来。
 * 例子
 * board = [
 *   ['o','a','a','n'],
 *   ['e','t','a','e'],
 *   ['i','h','k','r'],
 *   ['i','f','l','v']
 * ]
 * words = ["oath","pea","eat","rain"]
 * 输出:["eat","oath"]
 */
public class ContainsWords {

    // 给定一个二维数组,和单词列表,返回能走出的单词列表
    public static List<String> containsWords(char[][] board,String[] wrods){
        if(board == null || board[0].length == 0 || wrods == null || wrods.length == 0){
            return null;
        }

        // 流程(深度优先遍历,且不能走回头路)
        // 1.先用要走的单词列表构建一颗前缀树
        // 2.遍历board,根据前缀树排除不符合要求的点
        // 3.每遍历一个点就判断是否已经来到了结尾,是的话就收集答案,

        // 构建前缀树
        Node root = new Node('1');
        prefixTree(root,wrods);
        List<String> res = new ArrayList<>();
        List<Character> path = new ArrayList<>();
        for (int i = 0; i < board.length; i++) {
            for (int j = 0; j < board[0].length; j++) {
                // 每个位置都去走,搜集答案
                if(root.nexts[board[i][j]-'a'] != null){
                    process(board,i,j,root.nexts[board[i][j]-'a'],res,path);
                }
            }

        }
        return res;
    }

    private static int process(char[][] board,int i,int j,Node cur,List<String> res,List<Character> path){

        path.add(cur.c);
        if(cur.end != 0){
            // 来到的当前节点end 不等于0,说明这是一个单词的结尾搜集答案
           res.add(String.valueOf(path));
           cur.end --;

        }
        // up
        if(i-1 >= 0 && cur.nexts[board[i-1][j]-'a'] != null){
            process(board,i-1,j,cur.nexts[board[i-1][j]-'a'],res,path);
        }
        // down
        if(i+1 < board.length && cur.nexts[board[i+1][j]-'a'] != null){
            process(board,i+1,j,cur.nexts[board[i+1][j]-'a'],res,path);
        }
        if(j-1 >= 0 && cur.nexts[board[i][j-1]-'a'] != null){
            process(board,i,j-1,cur.nexts[board[i][j-1]-'a'],res,path);
        }
        if(j+1 < board[0].length && cur.nexts[board[i][j+1]-'a'] != null){
            process(board,i,j+1,cur.nexts[board[i][j+1]-'a'],res,path);
        }
        path.remove(path.size()-1);
        return res.size();
        // 在前缀树中是否有往下的节点

    }

    private static void prefixTree(Node head,String[] words){
        Set<String> set = new HashSet<>();
        for(String word : words){
            Node cur = head;
            char[] c = word.toCharArray();
            if(!set.contains(word)) {
                for (int i = 0; i < c.length; i++) {
                    // 有就复用,没有就新建
                    int path = c[i] - 'a';
                    if (cur.nexts[path] == null) {
                        cur.nexts[path] = new Node(c[i]);
                    }
                    cur.path++;
                    cur = cur.nexts[path];
                }
                cur.end++;
                set.add(word);
            }

        }
    }

    static class Node{
        char c;
        int path;
        int end;
        // 规定只有26个小写字母
        Node[] nexts;
        public Node(char c){
            this.nexts = new Node[26];
            this.c = c;
        }
    }

    public static void main(String[] args) {
        char[][] board = {
                {'o','a', 'a', 'n'},
                {'e', 't', 'a', 'e'},
                {'i', 'h', 'k', 'r'},
                {'i', 'f', 'l', 'v'}

                };

        String[] words = {"oath","pea","eat","rain"};
        System.out.println(containsWords(board,words));
    }


}

  • 给定一个二维数组 map,含义是一张地图,例如,如下矩阵:
    -2 -3 3
    -5 -10 1
    0 30 -5
    游戏的规则如下:
    骑士从左上角出发,每次只能向右或向下走,最后到达右下角见到公主。
    地图中每个位置的值代表骑士要遭遇的事情。
    如果是负数,说明此处有怪兽,要让骑士损失血量。
    如果是非负数,代表此处有血瓶,能让骑士回血。
    骑士从左上角到右下角的过程中,走到任何一个位置时,血量都不能少于1。
    为了保证骑士能见到公主,初始血量至少是多少?根据map,返回至少的初始血量。
/**
 * 给定一个二维数组 map,含义是一张地图,例如,如下矩阵:
 * -2 -3   3
 * -5 -10  1
 *  0  30 -5
 * 游戏的规则如下:
 * 骑士从左上角出发,每次只能向右或向下走,最后到达右下角见到公主。
 * 地图中每个位置的值代表骑士要遭遇的事情。
 * 如果是负数,说明此处有怪兽,要让骑士损失血量。
 * 如果是非负数,代表此处有血瓶,能让骑士回血。
 * 骑士从左上角到右下角的过程中,走到任何一个位置时,血量都不能少于1。
 * 为了保证骑士能见到公主,初始血量至少是多少?根据map,返回至少的初始血量。
 */
public class JiuGongZhu {

    public static int jiuGongZhu(int[][] matrix){
        return process(matrix,0,0);
    }

    // 暴力递归
    // 骑士来到i,j位置还没有站稳,返回至少需要多少血量从i,j位置到右下角
    private static  int process(int[][] matrix,int i ,int j){
        // 如果已经来到了右下角
        int M = matrix.length;
        int N = matrix[0].length;
        if(i == M-1 && j == N-1){
            // 1.如果此时i,j位置是负数则骑士登上i,j位置需要(-matrix[i][j]+1),因为血量不能低于1
            // 2.如果此时i,j位置是非负数,那么只需要骑士剩余至少1滴血就可以了
            return matrix[i][j] < 0 ? (-matrix[i][j] + 1) : 1;
        }

        // 骑士还没有到右下角
        // 骑士在最后一行
        if(i == M-1){
            // 调用子过程
            int p1 = process(matrix,i,j+1);
           return matrix[i][j] < 0 ? (-matrix[i][j])+p1 : matrix[i][j] > p1 ? 1 : p1-matrix[i][j];
        }


        if(j == N-1){
            // 调用子过程
            int p2 = process(matrix,i+1,j);
           return matrix[i][j] < 0 ? (-matrix[i][j])+p2 : matrix[i][j] > p2 ? 1 : p2-matrix[i][j];
        }

        // 普遍位置
        int common = 0;
        int p3 = Math.min(process(matrix,i+1,j),process(matrix,i,j+1));
        return matrix[i][j] < 0 ? (-matrix[i][j])+p3 : matrix[i][j] > p3 ? 1 : p3-matrix[i][j];

    }

    public static int dp(int[][] matrix){
        int M = matrix.length;
        int N = matrix[0].length;
        int[][] dp = new int[M][N];
        dp[M-1][N-1] = matrix[M-1][N-1] < 0 ? (-matrix[M-1][N-1] + 1) : 1;
        // 最后一行
        for (int j = N-2; j >= 0; j--) {
            int p1 = dp[M-1][j+1];
            dp[M-1][j] = matrix[M-1][j] < 0 ? (-matrix[M-1][j])+p1 : matrix[M-1][j] > p1 ? 1 : p1-matrix[M-1][j];
        }

        // 最后一列
        for (int i = M-2;i >= 0;i--){
            int p2 = dp[i+1][N-1];
            dp[i][N-1] =  matrix[i][N-1] < 0 ? (-matrix[i][N-1])+p2 : matrix[i][N-1] > p2 ? 1 : p2-matrix[i][N-1];
        }

        // 普遍位置
        for (int i = M-2; i >= 0 ; i--) {
            for (int j = N-2; j >= 0 ; j--) {
                int p3 = Math.min(dp[i+1][j],dp[i][j+1]);
                dp[i][j] = matrix[i][j] < 0 ? (-matrix[i][j])+p3 : matrix[i][j] > p3 ? 1 : p3-matrix[i][j];
            }
        }

        return dp[0][0];
    }

    public static void main(String[] args) {
        int[][] map = { { -2, -3, 3 }, { -5, -10, 1 }, { 10, 30, -5 } };
        System.out.println(jiuGongZhu(map));
        System.out.println(dp(map));
    }
}

  • 给定一个矩阵matrix,先从左上角开始,每一步只能往右或者往下走,走到右下角。然后从右下角出发,每一步只能往上或者往左走,再回到左上角。任何一个位置的数字,只能获得一遍。返回最大路径和。
/**
 * 给定一个矩阵matrix,先从左上角开始,每一步只能往右或者往下走,走到右下角。
 * 然后从右下角出发,每一步只能往上或者往左走,再回到左上角。任何一个位置的数字,
 * 只能获得一遍。返回最大路径和。
 */
public class MaxPath {
    public static int maxPath(int[][] matrix){
        return process(matrix,0,0,0);
    }

    // 递归含义: A,B两个人同时出发,每次都走一步来到aR,aC,bR,bC,返回从 A B两点到右下角的最大路径和
    // 由于是同步走的,则B的纵坐标可以表示为 bC = aR+aC-bR
    private static int process(int[][] matrix,int aR,int aC,int bR){
        int M = matrix.length;
        int N = matrix[0].length;
        if(aR == M -1 && aC == N-1){
            // 当A,B来到右下角
            // 经过同一个点只记录一次
            return matrix[M-1][N-1];
        }

        // 没有到达终点,那么有以下几种情况
        // 1.A向下,B向下
        // 2.A向下,B向右
        // 3.A向右,B向右
        // 4.A向右,B向下
        int bC = aR + aC - bR;
        int aDownBDown = 0;
        if(aR+1 < M && bR+1 < M){
            aDownBDown = process(matrix,aR + 1,aC,bR + 1);
        }
        int aDownBRight = 0;
        if(aR+1 < M && bC+1 < N){
            aDownBRight = process(matrix,aR + 1,aC,bR);
        }
        int aRightBRight = 0;
        if(aC + 1 < N && bC+1 < N){
            aRightBRight = process(matrix,aR,aC+1,bR);
        }
        int aRightBDown = 0;
        if(aC +1< N && bR+1 < M){
            aRightBDown = process(matrix,aR,aC+1,bR + 1);
        }

        // 如果来到的是同一个点,或者不同位置
        int curNum = aR == bR ? matrix[aR][aC] : matrix[aR][aC]+matrix[bR][bC];

        return Math.max(Math.max(aDownBDown,aDownBRight),Math.max(aRightBRight,aRightBDown))+curNum;
    }

    // 记忆化搜搜
    public static int dp(int[][] matrix){
        int M = matrix.length;
        int N = matrix[0].length;

        int[][][] dp = new int[M][N][M];

        // 最后一个位置
        dp[M-1][N-1][M-1] = matrix[M-1][N-1];
        //普遍位置
        for (int i = 0; i < M; i++) {
            for (int j = 0; j < N; j++) {
                for (int k = 0; k < M ; k++) {
                    dp[i][j][k] = Integer.MIN_VALUE;
                }
            }

        }
        return process2(matrix,0,0,0,dp);
    }

    private static int process2(int[][] matrix,int aR,int aC,int bR,int[][][] dp){
        if(dp[aR][aC][bR] != Integer.MIN_VALUE){
            return dp[aR][aC][bR];
        }
        int M = matrix.length;
        int N = matrix[0].length;
        if(aR == M -1 && aC == N-1){
            // 当A,B来到右下角
            // 经过同一个点只记录一次
            return matrix[M-1][N-1];
        }

        // 没有到达终点,那么有以下几种情况
        // 1.A向下,B向下
        // 2.A向下,B向右
        // 3.A向右,B向右
        // 4.A向右,B向下
        int bC = aR + aC - bR;
        int aDownBDown = 0;
        if(aR+1 < M && bR+1 < M){
            aDownBDown = process(matrix,aR + 1,aC,bR + 1);
        }
        int aDownBRight = 0;
        if(aR+1 < M && bC+1 < N){
            aDownBRight = process(matrix,aR + 1,aC,bR);
        }
        int aRightBRight = 0;
        if(aC + 1 < N && bC+1 < N){
            aRightBRight = process(matrix,aR,aC+1,bR);
        }
        int aRightBDown = 0;
        if(aC +1< N && bR+1 < M){
            aRightBDown = process(matrix,aR,aC+1,bR + 1);
        }

        // 如果来到的是同一个点,或者不同位置
        int curNum = aR == bR ? matrix[aR][aC] : matrix[aR][aC]+matrix[bR][bC];
        int max = Math.max(Math.max(aDownBDown,aDownBRight),Math.max(aRightBRight,aRightBDown))+curNum;
        dp[aR][aC][bR] = max;

        return max;
    }

    public static void main(String[] args) {
        int[][] matrix = {
                {1,1,1,1,1,0,0,0,0,0},
                {0,0,0,0,1,0,0,0,0,0},
                {0,0,0,0,1,0,0,0,0,1},
                {1,0,0,0,1,0,0,0,0,0},
                {0,0,0,0,1,1,1,1,1,1}
        };

        System.out.println(maxPath(matrix));
        System.out.println(dp(matrix));
    }

}

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,657评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,662评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,143评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,732评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,837评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,036评论 1 291
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,126评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,868评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,315评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,641评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,773评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,470评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,126评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,859评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,095评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,584评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,676评论 2 351

推荐阅读更多精彩内容