41. 缺失的第一个正数

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

示例 1:

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

示例 2:

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

示例 3:

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

提示:

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

哈希表法

// 时间复杂度 O(N) 空间复杂度 O(N)
    public int firstMissingPositive(int[] nums) {
        int n = nums.length;
        Set<Integer> set = new HashSet<>();        for(int num : nums) set.add(num);
        for(int i = 1;  i <= n; i ++){
            if(!set.contains(i)) return i;
        }        return n + 1;
    }

排序法

// 时间复杂度 O(Nlog(N)) 空
    public int firstMissingPositive(int[] nums) {
        // 先排序 Nlog(N)
        Arrays.sort(nums);        int pre = 0;
        for(int i = 0; i < nums.length; i ++){
            // 跳过非正整数和重复值
            if(nums[i] <= 0 || nums[i] == pre) continue;
            // 找到第一个突变的元素
            else if(nums[i] > pre + 1) break;
            pre ++;        }        return pre + 1;
    }

原地哈希法

 

// 时间复杂度 O(N) 空间复杂度 O(1)
    // 原地哈希: 自定义哈希函数 将数值 i 映射到 i - 1的位置上
    public int firstMissingPositive(int[] nums) {
        int len = nums.length;
        for(int i = 0; i < len; i ++){
            // 在指定范围内, 且没有放在正确位置上, 交换
            while(nums[i] > 0 && nums[i] <= len && nums[nums[i] - 1] != nums[i]){
                swap(nums, nums[i] - 1, i);
            }        }        // 找到不符合的位置        for(int i = 0; i < len; i ++){
            if(nums[i] != i + 1) return i + 1;
        }        // 都正确则返回数组长度 + 1
        return len + 1;
    }    void swap(int[] nums, int i, int j){
        int temp = nums[i];
        nums[i] = nums[j];        nums[j] = temp;    }

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

暴力求解

// 时间复杂度 O(N^2)
	public int trap(int[] height) {
        int res = 0;
        // 遍历每一列柱子
        for(int i = 1; i < height.length - 1; i ++){
            // 分别记录当前柱子向左 向右的最大高度
            int leftMax = 0, rightMax = 0;
            for(int j = 0; j <= i; j ++) 
                leftMax = Math.max(leftMax, height[j]);            for(int j = i; j < height.length; j ++) 
                rightMax = Math.max(rightMax, height[j]);			// 当前位置 water[i] = min(max(h[0->i], h(i->len-1))) - h[i]
            res += Math.min(leftMax, rightMax) - height[i];        }        return res;
    }

动态规划

// 时间复杂度 优化至 O(N) , 但空间复杂度为 O(N)
	public int trap(int[] height) {
        if(height.length == 0) return 0;
        int n = height.length;
        // 优化方案: 创建两个备忘录,分别记录leftMax 和 rightMax
        int[] leftMax = new int[n];
        int[] rightMax = new int[n];
        int res = 0;
        // 初始化
        leftMax[0] = height[0];
        rightMax[n - 1] = height[n - 1];
        for(int i = 1; i < n; i ++) 
            leftMax[i] = Math.max(height[i], leftMax[i - 1]);
        for(int i = n - 2; i >= 0; i --) 
            rightMax[i] = Math.max(height[i] , rightMax[i + 1]);
        for(int i = 1; i < n - 1; i ++) 
            res += Math.min(leftMax[i], rightMax[i]) - height[i];        return res;
    }

双指针

// 时间复杂度 O(N) 空间 O(1)
	// 省去创建备忘录的空间, 边走变算    public int trap(int[] height) {
        if(height.length == 0) return 0;
        int n = height.length;
        int l = 0, r = height.length - 1, res = 0;
        int leftMax = height[0], rightMax = height[n - 1];
        while(l <= r){
            leftMax = Math.max(leftMax, height[l]);            rightMax = Math.max(rightMax, height[r]);            if(leftMax < rightMax) 
                res += leftMax - height[l ++];            else 
                res += rightMax - height[r --];        }        return res;
    }

43. 字符串相乘

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

示例 1:

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

示例 2:

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

说明:

  1. num1 和 num2 的长度小于110。
  2. num1 和 num2 只包含数字 0-9。
  3. num1 和 num2 均不以零开头,除非是数字 0 本身。
  4. 不能使用任何标准库的大数类型(比如 BigInteger)直接将输入转换为整数来处理
/**
num1的第i位(高位从0开始)和num2的第j位相乘的结果在乘积中的位置是[i+j, i+j+1]
例: 123 * 45,  123的第1位 2 和45的第0位 4 乘积 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中        
**/
public String multiply(String num1, String num2) {
    int m = num1.length(), n = num2.length();
    // 记录每位数字的数组
    int[] arr = new int[m + n];
    // 从个位开始计算
    for(int i = m - 1; i >= 0; i --){
        for(int j = n - 1; j >= 0; j --){
            // i+j, i+j+1
            int mul = (num1.charAt(i) - '0') * (num2.charAt(j) - '0');
            int p1 = i + j, p2 = i + j + 1;
            // 本身乘积 + 原来的数
            int sum = mul + arr[p2];
            // 取个位
            arr[p2] = sum % 10;
            // 进位
            arr[p1] += sum / 10;
        }
    }
    // 移除前导零,只需记录第一个非0的位置即可
    int i = 0;
    while(i < arr.length && arr[i] == 0) i ++;
    if(i == arr.length) return "0";
    StringBuilder res = new StringBuilder();
    while(i < arr.length){
        res.append(arr[i++]);
    }
    return res.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 = "*a*b"
输出: true
解释: 第一个 '*' 可以匹配空字符串, 第二个 '*' 可以匹配字符串 "dce".

示例 5:

输入:
s = "acdcb"
p = "a*c?b"
输出: false
// f[i][j] = 标识 s的 0 - i 与 p的 0 - j 是否匹配
public boolean isMatch(String s, String p) {    int m = s.length(), n = p.length();    boolean[][] f = new boolean[m + 1][n + 1];
	// 空串匹配    f[0][0] = true;
	// s为空,只要p的开头是* 就代表匹配
    for(int j = 1; j <= n; j ++){
        if(p.charAt(j - 1) == '*') f[0][j] = true;
        else break;    }    for(int i = 1; i <= m; i ++){
        for(int j = 1; j <= n; j ++){
            // f[i][j - 1]的情况 标识 ab和ab*
            // f[i - 1][j]的情况 标识 abcd 和 ab*
            if(p.charAt(j - 1) == '*'){
                f[i][j] = f[i][j - 1] || f[i - 1][j];
            }else if(p.charAt(j - 1) == '?' || s.charAt(i - 1) == p.charAt(j - 1)){
                f[i][j] = f[i - 1][j - 1];
            }
        }
    }
    return f[m][n];
}

45. 跳跃游戏 II

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

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

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

示例:

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

说明:

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

 

贪心算法

public int jump(int[] nums) {
        int ans = 0, start = 0, end = 1;
        while(end < nums.length){
            int maxPos = 0;
            for(int i = l; i < end; i ++){
                //能跳到的最远的距离
                maxPos = Math.max(maxPos, i + nums[i]);
            }
            //下一次起跳点范围开始的格子
            start = end;
            //下一次起跳点范围结束的格子
            end = maxPos + 1;
            ans ++;
        }
        return ans;
    }

46. 全排列

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

示例:

输入: [1,2,3]
输出:[  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] used;    public List<List<Integer>> permute(int[] nums) {
        used = new boolean[nums.length];
        dfs(nums);        return res;
    }    void dfs(int[] nums){
        if(path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }        for(int i = 0; i < nums.length; i ++){
            if(!used[i]){
                used[i] = true;
                path.add(nums[i]);
                dfs(nums);                path.remove(path.size() - 1);
                used[i] = false;
            }        }    }}

47. 全排列 II

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

示例:

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

 

我们需要明确,在这个DFS的过程中,哪里出现了重复?在这个图中,已经十分明显地看到:

  • 这次搜索的起点和上次的起点相同 , visited[i - 1] == visited[i]。
  • 且上一次的数已经被撤销,visited[i-1] ==false。

为保证i-1不越界,加上i>0的条件:

if (i > 0 && nums[i] == nums[i - 1] && !visited[i - 1]) {
    continue;
}List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[] visited;
    public List<List<Integer>> permuteUnique(int[] nums) {
        visited = new boolean[nums.length];
        Arrays.sort(nums); //保证数组的有序性
        dfs(nums);
        return res;
    }
        void dfs(int[]nums){
        if(path.size() == nums.length) {
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = 0; i < nums.length ; i++){
            if( visited[i]) continue;
            if( i > 0 && nums[i] == nums[i - 1]  && !visited[i - 1]) continue; //此处剪枝
            visited[i] = true;
            path.add(nums[i]);
            dfs(nums);
            visited[i] = false;
            path.remove(path.size()-1);
        }
    }

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]
]public void rotate(int[][] matrix) {
        int n = matrix.length;        //转置        /*
         1 2 3  --> 1 4 7
         4 5 6  --> 2 5 8
         7 8 9  --> 3 6 9
        */
        for(int i = 0; i < n; i ++){
            for(int j = i; j < n; j ++){
                swap(matrix, i, j, j, i);
            }
        }
        /*
         1 4 7  --> 7 4 1
         2 5 8  --> 8 5 2         3 6 9  --> 9 6 3        */        for(int i = 0; i < n; i ++){
            for(int j = 0; j < n / 2; j ++){
                swap(matrix, i, j, i, n - j - 1);
            }
        }
    }
    void swap(int[][] m, int i1, int j1, int i2, int j2){
        int temp = m[i1][j1];
        m[i1][j1] = m[i2][j2];
        m[i2][j2] = temp;
    }

49. 字母异位词分组

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

示例:

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

说明:

  • 所有输入均为小写字母。
  • 不考虑答案输出的顺序。
public List<List<String>> groupAnagrams(String[] strs) {
        //O(N KlogK) N = strs.length  K = str.length()
        if(strs.length == 0) return new ArrayList<>();
        Map<String, List> hash = new HashMap<>();
        for(String str : strs){
            // 字符排序
            char[] chs = str.toCharArray();            Arrays.sort(chs);            String key = new String(chs);
            if(!hash.containsKey(key)) hash.put(s, new ArrayList<List>());
            hash.get(key).add(str);        }        return new ArrayList(hash.values());
    }

50. Pow(x, n)

实现 pow(xn) ,即计算 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] 。
public double myPow(double x, int n) {
        if(x == 0.0f) return 0.0d;
        long b = n;
        double res = 1.0;
        // 当n < 0时,转化为 n >= 0的情况
        if(b < 0){
            x = 1 / x;
            b = -b;
        }
        while(b > 0){
            if((b & 1) == 1) // b % 2 == 1
                res *= x;
            x *= x; // x = x ^ 2
            b >>= 1; // b = b // 2
        }
        return res;
    }

如果觉得本文对你有帮助,可以点赞关注支持一下,也可以关注我公众号,上面有更多技术干货文章以及相关资料共享,大家一起学习进步!