Java实现LeetCode 题号:41 - 50

208 阅读3分钟

这是我参与11月更文挑战的第10天,活动详情查看:2021最后一次更文挑战

LeetCode习题集 有些题可能直接略过了,整理一下之前刷leetcode

41. 缺失的第一个正数

给定一个未排序的整数数组,找出其中没有出现的最小的正整数。

示例 1:

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

输入: [3,4,-1,1] 输出: 2 示例 3:

输入: [7,8,9,11,12] 输出: 1 说明:

你的算法的时间复杂度应为O(n),并且只能使用常数级别的空间。

通过下标记录,
    如果存在的话,那么就把当前下标的值改为1
    操作完,之间找第一个下标不为0的
class Solution {
      public int firstMissingPositive(int[] nums) {
        int[] m = new int[nums.length+1];
        for(int i = 0 ; i < nums.length; i++)
            if(nums[i]>0&&nums[i] <= nums.length)
                m[nums[i]] = 1;
        for(int i = 1 ; i < m.length; i++)
            if(m[i] == 0) return i;            
        return  m.length;
    }
}

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 感谢 Marcos 贡献此图。

示例:

输入: [0,1,0,2,1,0,1,3,2,1,2,1] 输出: 6

class Solution {
    public int trap(int[] height) {
         int n = height.length, sum = 0;
        if(n == 0) return 0;
        int[] left = new int[n];
        int[] right = new int[n];
        left[0] = height[0];
        right[n-1] = height[n-1];
        /*思路:动态规划求解
         * 计算出每一列中左边最高, 右边最高的高度
         * 最后每一列中包含水滴的个数即为两边最低的高度减去当前高度
        */
        for(int i = 1; i < n; i ++) {
        	left[i] = Math.max(height[i], left[i-1]);
        }
        for(int i = n-2; i >= 0; i --) {
        	right[i] = Math.max(height[i], right[i+1]);
        }
        //计算每一列中水滴的数量
        for(int i = 0; i < n; i ++) {
        	sum += Math.min(left[i], right[i])-height[i];
        //	System.out.println(Math.min(left[i], right[i])-height[i]);
        }
        return sum;
    }
}

43. 字符串相乘

给定两个以字符串形式表示的非负整数 num1 和 num2,返回 num1 和 num2 的乘积,它们的乘积也表示为字符串形式。

示例 1:

输入: num1 = "2", num2 = "3" 输出: "6" 示例 2:

输入: num1 = "123", num2 = "456" 输出: "56088" 说明:

num1 和 num2 的长度小于110。 num1 和 num2 只包含数字 0-9。 num1 和 num2 均不以零开头,除非是数字 0 本身。 不能使用任何标准库的大数类型(比如 BigInteger)或直接将输入转换为整数来处理。

PS:
   num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
        例: 123 * 45,  123的第1245的第04 乘积 08 存放在结果的第[1, 2]位中
          index:    0 1 2 3 4  
              
                        1 2 3
                    *     4 5
                    ---------
                          1 5
                        1 0
                      0 5
                    ---------
                      0 6 1 5
                        1 2
                      0 8
                    0 4
                    ---------
                    0 5 5 3 5
        这样我们就可以单独都对每一位进行相乘计算把结果存入相应的index中        
 
class Solution {
     public String multiply(String num1, String num2) {
        int n1 = num1.length()-1;
        int n2 = num2.length()-1;
        if(n1 < 0 || n2 < 0) return "";
        int[] mul = new int[n1+n2+2];
        
        for(int i = n1; i >= 0; --i) {
            for(int j = n2; j >= 0; --j) {
                int bitmul = (num1.charAt(i)-'0') * (num2.charAt(j)-'0');      
                bitmul += mul[i+j+1]; // 先加低位判断是否有新的进位
                
                mul[i+j] += bitmul / 10;
                mul[i+j+1] = bitmul % 10;
            }
        }
        
        StringBuilder sb = new StringBuilder();
        int i = 0;
        // 去掉前导0
        while(i < mul.length-1 && mul[i] == 0) 
            i++;
        for(; i < mul.length; ++i)
            sb.append(mul[i]);
        return sb.toString();
    }
}

44. 通配符匹配

给定一个字符串 (s) 和一个字符模式 (p) ,实现一个支持 '?' 和 '*' 的通配符匹配。

'?' 可以匹配任何单个字符。 '*' 可以匹配任意字符串(包括空字符串)。 两个字符串完全匹配才算匹配成功。

说明:

s 可能为空,且只包含从 a-z 的小写字母。 p 可能为空,且只包含从 a-z 的小写字母,以及字符 ? 和 *。 示例 1:

输入: s = "aa" p = "a" 输出: false 解释: "a" 无法匹配 "aa" 整个字符串。 示例 2:

输入: s = "aa" p = "" 输出: true 解释: '' 可以匹配任意字符串。 示例 3:

输入: s = "cb" p = "?a" 输出: false 解释: '?' 可以匹配 'c', 但第二个 'a' 无法匹配 'b'。 示例 4:

输入: s = "adceb" p = "ab" 输出: true 解释: 第一个 '' 可以匹配空字符串, 第二个 '' 可以匹配字符串 "dce". 示例 5:

输入: s = "acdcb" p = "a*c?b" 输入: false

class Solution {
      public boolean isMatch(String s, String p) {
          
        boolean[][] value = new boolean[p.length()+1][s.length()+1];
        value[0][0] = true;
        for(int i = 1;i <= s.length();i++){
            value[0][i] = false;
        }
        //value[i][j]就是p的第j个字符是不是匹配和s的第i个字符
        for(int i = 1;i <= p.length(); i++){
            if(p.charAt(i-1) == '*'){
                value[i][0] = value[i-1][0];
                for(int j = 1;j <= s.length(); j++){
                                    //*代表n个字符      *代表0个字符
                    value[i][j] = (value[i][j-1] || value[i-1][j]);
                }
            }else if(p.charAt(i-1) == '?'){
                value[i][0] = false;
                for(int j = 1;j <= s.length(); j++){
                    //?只能代表上一个
                    value[i][j] = value[i-1][j-1];
                }
            }else {
                value[i][0] = false;
                for(int j = 1;j <= s.length(); j++){
                    //其他时刻  必须是完全相等的时候才可以
                    value[i][j] = s.charAt(j-1) == p.charAt(i-1) && value[i-1][j-1];
                }
            }

        }
        return value[p.length()][s.length()];

    }
}

45. 跳跃游戏 II

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

输入: [2,3,1,1,4] 输出: 2 解释: 跳到最后一个位置的最小跳跃数是 2。 从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。 说明:

假设你总是可以到达数组的最后一个位置。


鉴于题目已经给了前提,那就是肯定能到达最后一个元素,
那么只要考虑每一跳所能达到的最远位置就行了,也就是每次都选择最远可达的点,

    reach是当前需要进行跳跃的右界限,
    nextReach是下一次跳跃的右界限,
    nextReach的值一直动态更新,
    
    以保证每次跳跃都是最远的
class Solution {
     public int jump(int[] nums) {
        if(nums.length == 1) return 0;
        int reach = 0;
        int nextreach = nums[0];
        int step = 0;
        for(int i = 0;i<nums.length;i++){
            nextreach = Math.max(i+nums[i],nextreach);
            if(nextreach >= nums.length-1) return (step+1);
            if(i == reach){
                step++;
                reach = nextreach;
            }
        }
        return step;
    }
}


46. 全排列

给定一个没有重复数字的序列,返回其所有可能的全排列。

示例:

输入: [1,2,3] 输出: [ [1,2,3], [1,3,2], [2,1,3], [2,3,1], [3,1,2], [3,2,1] ]

经典DFS深度搜索问题
class Solution {
   public List<List<Integer>> permute(int[] nums) {

        List<List<Integer>> res = new ArrayList<>();
        int[] visited = new int[nums.length];
        backtrack(res, nums, new ArrayList<Integer>(), visited);
        return res;

    }

    private void backtrack(List<List<Integer>> res, int[] nums, ArrayList<Integer> tmp, int[] visited) {
        if (tmp.size() == nums.length) {
            res.add(new ArrayList<>(tmp));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (visited[i] == 1) continue;
            visited[i] = 1;
            tmp.add(nums[i]);
            backtrack(res, nums, tmp, visited);
            visited[i] = 0;
            tmp.remove(tmp.size() - 1);
        }
    }
}

47. 全排列 II

给定一个可包含重复数字的序列,返回所有不重复的全排列。

示例:

输入: [1,1,2] 输出: [ [1,1,2], [1,2,1], [2,1,1] ]

这里包含了重复的元素,不能在和上面一样用boolean数组记录了,
每次都交换数组中两个值的位置,也可以做到全排列,但是要记得想一下,每个值都交换,那么肯定会多换一次的
怎么做到不重复的交换呢,
    单方向的交换
        交换的时候看看交换的两个位置之间有没有和结束位置相等的值,如果有那么就不交换
        这里就简称单方向的交换,为什么叫做单方向,这里是判断的是交换区间内的值不能与结束位置相等
        那么可以存在区间内的值和起始位置的值相等,也就是单方向的交换

class Solution {
    List<List<Integer>> ans = new ArrayList<>();
    
    public List<List<Integer>> permuteUnique(int[] nums) {
        dfs(nums,0);
        return ans;
    }
    
    private void dfs(int[] nums,int cur) {
        if (cur == nums.length) {
            List<Integer> line = new ArrayList<>();
            for (int i : nums) {
                line.add(i);
            }
            ans.add(line);
        } else {
            for (int i = cur;i < nums.length;i++) {
                if (canSwap(nums,cur,i)) {
                    swap(nums,cur,i);
                    dfs(nums,cur + 1);
                    swap(nums,cur,i);
                }
            }
        }
    }
    
    private boolean canSwap(int nums[],int begin,int end) {
        for (int i = begin;i < end;i++) {
            if (nums[i] == nums[end]) {
                return false;
            }
        }
        
        return true;
    }
    
    private void swap(int nums[],int i,int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

48. 旋转图像

给定一个 n × n 的二维矩阵表示一个图像。

将图像顺时针旋转 90 度。

说明:

你必须在原地旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要使用另一个矩阵来旋转图像。

示例 1:

给定 matrix = [ [1,2,3], [4,5,6], [7,8,9] ],

原地旋转输入矩阵,使其变为: [ [7,4,1], [8,5,2], [9,6,3] ] 示例 2:

给定 matrix = [ [ 5, 1, 9,11], [ 2, 4, 8,10], [13, 3, 6, 7], [15,14,12,16] ],

原地旋转输入矩阵,使其变为: [ [15,13, 2, 5], [14, 3, 4, 1], [12, 6, 8, 9], [16, 7,10,11] ]

    假设一个5*5的数组,然后我先旋转四个角的元素,这个很简单,简单排序算法中两个元素交换的延申而已。
    然后我尝试交换下一组元素,我交换完成外圈元素时,发现了很有规律的现象: 
        当我们把数组值的行列下标对应到坐标系中时,可以将各元素,看做一个点,那么整个坐标系中有四个点
        (这里不能贴图,建议在纸上画出来,方便理解)。
        四个点的规律如下:四个点绝对向一个方向移动,且有一个下标保持不变。

            左上角的点,绝对向右移动,
            右上角的点,绝对向下移动,
            右下角的点,绝对向左移动,
            左下角的点,绝对向上移动,
        归纳得到有两个固定不变的值,其对应在第一次旋转中,分别是0和matrix.length。
        剩余两个变化的值也有规律,分别是两个运动轨迹:从0->matrix.length和从matrix.length->0
        提取运动轨迹间的关系,就能通过循环,完成第一圈旋转
        开始内圈旋转的时候,变换固定值,约束内圈,返回到开始的思路,继续旋转
class Solution {
    public void rotate(int[][] matrix) {
          int abs1 = 0;
        int abs2 = matrix.length - 1;
        int times = 0;
        while (abs1 <= abs2) {
            int p1 = abs1;
            int p2 = abs2;
            while (p1 != abs2) {
                int temp = matrix[abs1][p1];        //左上
                matrix[abs1][p1] = matrix[p2][abs1];//左上 = 左下
                matrix[p2][abs1] = matrix[abs2][p2];//左下 = 右下
                matrix[abs2][p2] = matrix[p1][abs2];//右下 = 右上
                matrix[p1][abs2] = temp;            //右上 = 左上
                p1 += 1;
                p2 -= 1;
            }
            abs1 += 1;
            abs2 -= 1;
        }
    }
}

49. 字母异位词分组

给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。

示例:

输入: ["eat", "tea", "tan", "ate", "nat", "bat"], 输出: [ ["ate","eat","tea"], ["nat","tan"], ["bat"] ] 说明:

所有输入均为小写字母。 不考虑答案输出的顺序。

来源:力扣(LeetCode) 链接:leetcode-cn.com/problems/gr… 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

循环每个字符串:
    把当前字符串转成字符数组
    然后对字符数组按照ASCII值进行排序,如果是相同字母组成的话,肯定排序后的数组是一样的,
    只不过是字符的顺序不一样,排序后一定是一样的
    
    使用HashMap记录通过每个字符串排序后的数组,值为list<String>,把相同的都放在一起,
    如果不存在排序后的此数组,就创建一个新的list
class Solution {
     public List<List<String>> groupAnagrams(String[] strs) {
        HashMap<String,ArrayList<String>> map=new HashMap<>();
        for(String s:strs){
            char[] ch=s.toCharArray();
            Arrays.sort(ch);
            String key=String.valueOf(ch);
            if(!map.containsKey(key))    map.put(key,new ArrayList<>());
            map.get(key).add(s);
        }
        return new ArrayList(map.values());
    }
}

50. Pow(x, n)

实现 pow(x, n) ,即计算 x 的 n 次幂函数。

示例 1:

输入: 2.00000, 10 输出: 1024.00000 示例 2:

输入: 2.10000, 3 输出: 9.26100 示例 3:

输入: 2.00000, -2 输出: 0.25000 解释: 2-2 = 1/22 = 1/4 = 0.25 说明:

-100.0 < x < 100.0 n 是 32 位有符号整数,其数值范围是 [−231, 231 − 1] 。

	使用折半计算,每次把n缩小一半,这样n最终会缩小到0,任何数的0次方都为1,
        这时候我们再往回乘,
            如果此时n是偶数,直接把上次递归得到的值算个平方返回即可,
            如果是奇数,则还需要乘上个x的值。
        还有一点需要引起我们的注意的是n有可能为负数,
            对于n是负数的情况,我们可以先用其绝对值计算出一个结果再取其倒数即可。
        我们让i初始化为n,然后看i是否是2的倍数,
            是的话x乘以自己,
            否则res乘以x,
        i每次循环缩小一半,直到为0停止循环。
        最后看n的正负,如果为负,返回其倒数。
class Solution {
    public double myPow(double x, int n) {
         double res = 1.0;
        for(int i = n; i != 0; i /= 2){
            if(i % 2 != 0){
                res *= x;
            }
            x *= x;
        }
        return  n < 0 ? 1 / res : res;
    }
}