LeetCode面试热题五

73. 矩阵置零

给定一个 m x n 的矩阵,如果一个元素为 0 ,则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。
解题思路:遍历数组,如果发现值为0,则将其行列首元素置为零,然后根据行列首元素,更新数组。需要使用一个标记,记录首行元素中是否有零,以免更新时候造成干扰。

public void setZeroes(int[][] matrix) {
    int m = matrix.length;
    int n = matrix[0].length;
    boolean row = false;
    // 记录首行元素中是否存在0
    for(int i = 0;i < n;i++){
        if(matrix[0][i] == 0){
            row = true;
            break;
        }
    }
    // 遍历数组,将值为0的行列首元素置为0
    for(int i = 1;i < m;i++){
        for(int j = 0;j < n;j++){
            if(matrix[i][j] == 0){
                matrix[i][0] = matrix[0][j] =  0;
            }
        }
    }
    // 根据数组进行更新,注意不要干扰。
    for(int j = n - 1;j >= 0; j--){
        for(int i = 1;i < m; i++){
            if(matrix[i][0] == 0 ||matrix[0][j] == 0){
                matrix[i][j] = 0;
            }
        }
        // 第一行中的元素要根据原本是否存在0而决定更新。
        if(row){
            matrix[0][j] = 0;
        }
    }
}

75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
仅使用常数空间的一趟扫描算法
解题思路:与快速排序思路类似,将小于1的值0放在左边,大于1的值2放在右边。一次遍历搞定。

public void sortColors(int[] nums){
    int left,right;
    int i = 0;
    left = 0;
    right = nums.length;
    while(i < right){
        if(nums[i] == 0){
            nums[i] = nums[left];
            nums[left] = 0;
            left++;
            i++;
            // 小于1的值只能是0,i直接++
        }else if(nums[i] == 1){
            i++;
        }else{
            nums[i] = nums[right-1];
            nums[right-1] = 2;
            right--;
            //i值不变,因为小于2的数可能是1也可能是0,为了防止交换后的值为0,需要再次判断
        }
    }
}

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。

注意:
对于 t 中重复字符,我们寻找的子字符串中该字符数量必须不少于 t 中该字符数量。
如果 s 中存在这样的子串,我们保证它是唯一的答案。

解题思路:使用滑动窗口,先使得窗口的右边界不断增加,直到子串中包含所有的t中的字符,然后不断使得左边界变小,并记录下最小的滑动窗口值。直到子串中不包含所有的t中字符,然后重复上一步,是得右边界不断增加,直到右边界到达s串的末尾。程序结束。

public String minWindow(String s, String t) {
    if(s.length() < t.length()){
        return "";
    }
    // 将s,t串转换为char数组,加速访问字符串中的元素
    char[] chars = s.toCharArray();
    char[] chart = t.toCharArray();
    int slen = s.length();
    int tlen = t.length();
    // 使用数组来记录t串中各个字符出现的次数。
    // 字符的ascll码值就对应下标。
    int[] tnums = new int[128];
    int[] snums = new int[128];
    // nums记录滑动窗口中已包含t串字符的个数。
    int nums = 0;
    // 初始化数组,记录t串中各个字符出现的次数
    for(char c: chart){
        tnums[c]++;
    }
    // minlen记录滑动窗口的最小值,start记录窗口的起始位置
    int minlen = slen + 1,start = 0;
    // left滑动窗口的左右端点,滑动窗口的区间为 [left,right)
    int left = 0,right = 0;
    while(right < slen){
        if(tnums[chars[right]] == 0){
            right++;
            continue;
        }
        if(snums[chars[right]] < tnums[chars[right]]){
            nums++;
        }
        snums[chars[right]]++;
        right++;
        // 滑动窗口中已包含全部的 t中的字符,对left进行滑动
        while(nums == tlen){
            //将left向前滑动
            if(tnums[chars[left]] == 0){
                left++;
                continue;
            }
            if(tnums[chars[left]] == snums[chars[left]]){
                nums--; 
                 // 更新最小子串
            	if(right-left < minlen){
                	minlen = right - left;
                	start = left;
            	}
            }
            snums[chars[left]]--;
            left++;
        }
    }
    // 未找到,返回空。
    if(minlen == slen + 1){
        return "";
    }
    // 找到,截取子串,返回。
    return s.substring(start,start+minlen);
}

78. 子集

给你一个整数数组 nums ,数组中的元素互不相同 。返回该数组所有可能的子集(幂集)。
解集不能包含重复的子集。你可以按任意顺序返回解集。

示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:
输入:nums = [0]
输出:[[],[0]]

解题思路:每次从数组中加入一个新的元素,与前面所有的子集构成新的集合。
例子:数组中有1,2,3,4四个元素,依次从数组中取出新的值,如取出4,然后与已有的所以子集构成新的集合,*表示空集,最左边的数表示,前面已有的集合个数。
在这里插入图片描述

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> result = new ArrayList<>();
    List<Integer> t,newList;
    // 空集加入集合
    t = new ArrayList<>();
    result.add(t);
    // 第一项加入集合
    t = new ArrayList<>();
    t.add(nums[0]);
    result.add(t);
    for(int i = 1;i < nums.length; i++){
        for(int j = 0;j < Math.pow(2,i); j++){
            t = result.get(j);
            newList = new ArrayList<>(t);
            newList.add(nums[i]);
            result.add(newList);
        }
    }
    return result;
}

79. 单词搜索

给定一个 m x n 二维字符网格 board 和一个字符串单词 word 。如果 word 存在于网格中,返回 true ;否则,返回 false 。单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:
请添加图片描述

输入:board = [["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], word = "SEE"
输出:true

解题思路:回溯,使用一个布尔数组,记录访问的情况

public boolean exist(char[][] board, String word) {
    int wlen,m,n;
    m = board.length;
    n = board[0].length;
    wlen = word.length();
    char[] w = word.toCharArray();
    boolean[][] flag =new boolean[board.length][board[0].length];
	// 遍历网格,当网格中元素与字符串首字母相同,进行回溯。
    for(int i = 0;i < m;i++){
        for(int j = 0;j<n;j++){
        	// 首字母相同,回溯,并判断结果。
            if(board[i][j] == w[0]){
                flag[i][j] = true;
                if(backtrack(board,m,n,i,j,w,wlen,flag,1))
                    return true;
                flag[i][j] = false;
            }
        }
    }
    return false;
}
public boolean backtrack(char[][] board,int m,int n,int i, int j,char[] word,int wlen,boolean[][] flag,int num){
    if(num == wlen){
        return true;
    }
    // 上下左右四个方向寻找匹配的值,未访问过的,继续回溯。
    // 上
    if(i - 1>= 0 && word[num] == board[i-1][j] && !flag[i-1][j]){
        flag[i-1][j] = true;
        if(backtrack(board,m,n,i-1,j,word,wlen,flag,num+1))
            return true;
        flag[i-1][j] = false;
    }
    // 下
    if(i+1<m && word[num] == board[i+1][j] && !flag[i+1][j]){
        flag[i+1][j] = true;
        if(backtrack(board,m,n,i+1,j,word,wlen,flag,num+1))
            return true;
        flag[i+1][j] = false;
    }
    //左
    if(j-1>=0 && word[num] == board[i][j-1] && !flag[i][j-1]){
        flag[i][j-1] = true;
        if(backtrack(board,m,n,i,j-1,word,wlen,flag,num+1))
            return true;
        flag[i][j-1] = false;
    }
    //右
    if(j+1<n && word[num] == board[i][j+1] && !flag[i][j+1]){
        flag[i][j+1] = true;
        if(backtrack(board,m,n,i,j+1,word,wlen,flag,num+1))
            return true;
        flag[i][j+1] = false;
    }
    return false;
}

84. 柱状图中最大的矩形

给定 n 个非负整数,用来表示柱状图中各个柱子的高度。每个柱子彼此相邻,且宽度为 1 。
求在该柱状图中,能够勾勒出来的矩形的最大面积。
示例:
请添加图片描述

输入:heights = [2,1,5,6,2,3]
输出:10
解释:最大的矩形为图中红色区域,面积为 10

方法一:暴力解法
解题思路分析:面积为底乘高的值。对于本题中,最终最大面积的矩形的高度必定为数组中某一个元素的值,只需要以每个元素为中心,以该元素的值为高,向两边扩展即可,同时使用变量,记录最大的面积,数组等到全部遍历完成,最大面积就求出。
暴力解法在leetcode中是过不去的。只是为了引出第二种方法。

public int largestRectangleArea(int[] heights) {
    int maxArea = 0;
    for(int i=0;i<heights.length;i++){
        int left = i - 1;
        while(left>=0 && heights[left] >= heights[i]){
            left--;
        }
        int right = i + 1;
        while(right<heights.length && heights[right] >= heights[i]){
            right++;
        }
        if(maxArea < (right-left-1)*heights[i]){
            maxArea = (right-left-1)*heights[i];
        }
    }
    return maxArea;
}

方法二:单调栈
在方法一,运用了暴力解法。
首先我们枚举某一根柱子 i 作为高 h = heights[i];随后我们需要进行向左右两边扩展,使得扩展到的柱子的高度均不小于 hh。
如果有两根柱子 j0和 j1,其中 j0在 j1 的左侧,并且 j0 的高度大于等于 j1,那么在后面的柱子 i 向左找小于其高度的柱子时,j1 会挡住 j0,j0 就不会作为答案了。

如果对数组从左向右进行遍历,同时维护一个「可能作为答案」的数据结构,其中按照从小到大的顺序存放了一些 j 值。根据上面的结论,如果我们存放了 j0, j1, …, js 。那么一定有 height[j0] < height[j1] < … < height[js],因为如果有两个相邻的 j 值对应的高度不满足 < 关系,那么后者会「挡住」前者,前者就不可能作为答案了。
可以使用栈来维护一个这样的数据结构,在程序中保证栈的单调性。

版本一

public int largestRectangleArea(int[] heights) {
    int area = 0;
    int width,height;
    Stack<Integer> stack = new Stack<>();
    for(int i = 0;i<heights.length;i++){
    	// 当前元素小于栈顶元素,栈顶元素出栈
        while (!stack.empty() && heights[stack.peek()] > heights[i]){
        	// 栈中存储的是数组的下标
            height = heights[stack.pop()];
            // 栈中可能有值相同的元素,出栈
            while(!stack.empty() && heights[stack.peek()] == height){
                stack.pop();
            }
            // 栈为空,最后一个元素的宽度为到当前下标
            if(stack.empty()){
                width = i;
            }else {
            	// 宽度为到当前下标与栈顶元素之间的值 - 1
                width = i - stack.peek() - 1;
            }
            // 更新面积值
            if(width*height > area){
                area = width*height;
            }
        }
        stack.push(i);
    }
    // 数组遍历完成之后,栈可能不为空。
    while (!stack.empty()){
        height = heights[stack.pop()];
        while(!stack.empty() && heights[stack.peek()] == height){
            stack.pop();
        }
        // 栈中的最后一个元素为数组的长度,因为是数组中最小的值。
        if(stack.empty()){
            width = heights.length;
        }else {
            width = heights.length - stack.peek() - 1;
        }
        if(width*height > area){
            area = width*height;
        }
    }
    return area;
}

版本二:使用哨兵,来使得代码更加简洁。
使用哨兵就不用考虑边界问题。

public int largestRectangleArea(int[] heights) {
    //使用哨兵
    int maxArea = 0;
    int width,height;
    int len;
    // 哨兵元素为 0
    int[] newHeights = new int[heights.length + 2];
    for(int i = 0;i<heights.length; i++){
        newHeights[i+1] = heights[i];
    }
    newHeights[heights.length + 1] = 0;
    Stack<Integer> stack = new Stack<>();
    // 提前将哨兵元素入栈
    stack.push(0);
    len = newHeights.length;
    // 最后的哨兵元素为0,小于所有的元素,遍历完成之后,栈必不为空。
    for(int i = 1; i < len; i++){
        while(newHeights[stack.peek()] > newHeights[i]){
            height = newHeights[stack.pop()];
            while(newHeights[stack.peek()] == height){
                stack.pop();
            }
            width = i - stack.peek() - 1;
            maxArea = Math.max(maxArea,width*height);
        }
        stack.push(i);
    }
    return maxArea;
}

88. 合并两个有序数组

给你两个按非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。

示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3][2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。

解题思路:双指针法,使用一个中间数组,每次将nums1与nums2较小的值放入中间数组。然后再将中间数组的值赋给nums1。

public void merge(int[] nums1, int m, int[] nums2, int n) {
    int[] t = new int[m+n];
    int i = 0,j = 0,k=0;
    while(i< m && j < n){
        if(nums1[i] < nums2[j]){
            t[k++] = nums1[i++];
        }else{
            t[k++] = nums2[j++];
        }
    }
    while(i < m){
        t[k++] = nums1[i++];
    }
    while(j < n){
        t[k++] = nums2[j++];
    }
    for(int l = 0; l<t.length;l++){
        nums1[l] = t[l];
    }
}

91. 解码方法

一条包含字母 A-Z 的消息通过以下映射进行了 编码 :

'A' -> 1
'B' -> 2
...
'Z' -> 26

要 解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,“11106” 可以映射为:
“AAJF” ,将消息分组为 (1 1 10 6)
“KJF” ,将消息分组为 (11 10 6)
注意,消息不能分组为 (1 11 06) ,因为 “06” 不能映射为 “F” ,这是由于 “6” 和 “06” 在映射中并不等价。
给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数 。
题目数据保证答案肯定是一个 32 位 的整数。
方法一:回溯
解题思路:回溯,字符的编码可能是一位,也可能是两位,将所有的可能遍历出来。
虽然思路正确,但是在leetcode中超时。

public int numDecodings(String s) {
    // A-Z 1-26
    int[] nums = new int[s.length()];
    int[] num = new int[1];
    for(int k = 0;k < nums.length; k++){
        nums[k] = s.charAt(k)-'0';
    }
    backtrack(nums,nums.length,0,num);
    return num[0];
}
public void backtrack(int[] s,int slen,int i,int[] num){
    if(i == slen){
        num[0]++;
        return;
    }
    if(1 <= s[i] && s[i] <= 26){
        backtrack(s,slen,i+1,num);
    }
    if(i+1 < slen && s[i] != 0 && 1 <= (s[i] * 10 + s[i+1]) && (s[i] * 10 + s[i+1]) <= 26){
        backtrack(s,slen,i+2,num);
    }
}

方法二:动态规划
考虑字符串中的第 i 个字符的状态,可以将第 i 个字符单独编码,也可以将 第 i 个字符与 i -1个字符组合起来编码(前提 在合法的字符之间 )。
对于 i 个字符的编码方式就是将单独编码和合起来编码两种情况加起来。
用 dp[i] 表示 0-i个字符的编码方式。
状态转移方程为:
在这里插入图片描述
动态规划的边界条件为:
dp[0] = 1
即空字符串可以有1种解码方法,解码出一个空字符串。

public int numDecodings(String s) {
    if(s.charAt(0)-'0' == 0)
        return 0;
    int[] dp = new int[s.length() + 1];
    // 设置边界为1
    dp[0] = 1;
    // 第一个字符不为0,编码方式为1
    dp[1] = 1;
    for(int i = 1; i<s.length(); i++){
        if(s.charAt(i) != '0'){
            // 数字0不能编码排除,1-9可以编码
            dp[i+1] += dp[i];
        }
        if( s.charAt(i-1) != '0' && (s.charAt(i-1)-'0')*10 + (s.charAt(i)-'0') <=26){
        	// 第i个字符和第i-1个字符,可以一起编码
            dp[i+1] += dp[i-1];
        }
    }
    return dp[s.length()];
}

94. 二叉树的中序遍历

给定一个二叉树的根节点 root ,返回它的 中序 遍历。
示例:
请添加图片描述

输入:root = [1,null,2,3]
输出:[1,3,2]

方法一:递归
解题思路:树的中序遍历,访问顺序为,左儿子,父亲节点,右儿子,用递归函数进行中序遍历。

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    if(root == null){
        return result;
    }
    recursion(root,result);
    return result;
}
public void recursion(TreeNode root,List<Integer> result){     
    if(root == null){
        return;
    }
    //左儿子,父亲节点,右儿子
    recursion(root.left,result);
    result.add(root.val);
    recursion(root.right,result);
}

方法二:栈
递归的时候,程序给我们维护了一个栈,非递归方式,需要自己维护栈。

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result = new ArrayList<>();
    Stack<TreeNode> stack = new Stack<>();
    while(root != null || !stack.empty()){
        while(root != null){
            stack.push(root);
            root = root.left;
        }
        root = stack.pop();
        result.add(root.val);
        root = root.right;
    }
    return result;
}

98. 验证二叉搜索树

给你一个二叉树的根节点 root ,判断其是否是一个有效的二叉搜索树。
有效 二叉搜索树定义如下:
节点的左子树只包含 小于 当前节点的数。
节点的右子树只包含 大于 当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。

示例:
请添加图片描述

输入:root = [5,1,4,null,null,3,6]
输出:false
解释:根节点的值是 5 ,但是右子节点的值是 4

方法一:递归
解题思路:递归遍历进行判断。对于任意的子树,必须都满足于二叉搜索树。对于左节点,不仅要小于父亲节点,还有大于一个下界。因为左节点的父亲节点,可能是祖父节点的右节点。
如下,4不仅仅要小于6,还要大于2。
在这里插入图片描述
同样,对于右节点,不仅仅要大于父亲节点,还有小于一个上界,因为右节点的父亲节点,可能是祖父节点的左节点。
如下:3不仅仅要大于2,还要小于5。
在这里插入图片描述

public boolean isValidBST(TreeNode root) { 
	// 对于根节点上下界为空    
    return recursion(root,null,null); 
}
public boolean recursion(TreeNode root,Integer low,Integer up){
    if(root == null){
        return true;
    }
    // 当前节点值必须大于下界,小于上界,才能满足二叉搜索树
    if(low != null && low >= root.val){
        return false;
    }
    if(up!=null && up <= root.val){
        return false;
    }
    // 对于左子树,上界就是根节点。
    if(!recursion(root.left,low,root.val)){
        return false;
    }
    // 对于右子树,下界就是根节点。
    if(!recursion(root.right,root.val,up)){ 
        return false;
    }
    return true;
}

方法二:中序遍历
解题思路:由于中序遍历的顺序为 左 根 右,对于二叉搜索树而已,前一个元素的值必定小于当前元素。只需要在中序遍历是,根据这个条件判断即可。

public boolean isValidBST(TreeNode root) {
    //中序遍历 左 根 右
    Stack<TreeNode> stack = new Stack<>();
    Integer val = null;
    while(root != null || !stack.empty()){
        while(root != null){
            stack.push(root);
            // 先到最左边。
            root = root.left;
        }
        // 根
        root = stack.pop();
        // 判断是否大于前一个元素。
        if(val != null && root.val <= val){
            return false;
        }
        val = root.val;
        // 右
        root = root.right;
    }
    return true;
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值