Leetcode 第377,378,406,413,416,417,437,445,451,452题(Java解法)

第377题 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。
题目数据保证答案符合 32 位整数范围。

示例 1:

输入输出
nums = [1,2,3], target = 47

解释:
所有可能的组合为:
(1, 1, 1, 1)
(1, 1, 2)
(1, 2, 1)
(1, 3)
(2, 1, 1)
(2, 2)
(3, 1)
请注意,顺序不同的序列被视作不同的组合。

示例 2:

输入输出
nums = [9], target = 30

解题思路

本题使用动态规划,通过计算从1到目标值中所有的组合数,最后推算出目标值的组合数。

代码

// 组合总和 Ⅳ:
class Solution {
    public int combinationSum4(int[] nums, int target) {
        int[] dp = new int[target + 1];
        dp[0] = 1;
        for (int i = 1; i <= target; i++) {//计算从1到目标值每个数的组合数
            for (int num : nums) {//遍历数组
                if (num <= i) {
                    dp[i] += dp[i - num];
                }
            }
        }
        return dp[target];
    }
}

时间复杂度为O(target×n),n为数组长度,target为目标值
空间复杂度为O(target

第378题 有序矩阵中第 K 小的元素

给你一个 n x n 矩阵 matrix ,其中每行和每列元素均按升序排序,找到矩阵中第 k 小的元素。
请注意,它是 排序后 的第 k 小元素,而不是第 k 个 不同 的元素。

示例 1:

输入输出
matrix = [[1,5,9],[10,11,13],[12,13,15]], k = 813

解释:矩阵中的元素为 [1,5,9,10,11,12,13,13,15],第 8 小元素是 13

示例 2:

输入输出
matrix = [[-5]], k = 1-5

解题思路

本题使用二分法求解,先判断中间数的位置与k的关系,然后不断缩小范围找到第k个位置。

代码

// 有序矩阵中第 K 小的元素:二分查找
class Solution {
    public int kthSmallest(int[][] matrix, int k) {//二分查找
        int n = matrix.length;
        int left = matrix[0][0];
        int right = matrix[n - 1][n - 1];
        while (left < right) {
            int mid = left + ((right - left) >> 1);
            if (check(matrix, mid, k, n)) {//判断函数,如果中间数的位置大于等于k,则将right放在中间位,否则将left放在中间位加1上
                right = mid;
            } else {
                left = mid + 1;
            }
        }
        return left;
    }
    public boolean check(int[][] matrix, int mid, int k, int n) {//查找中间数属于第几个位置,然后比较与k的大小
        int i = n - 1;
        int j = 0;
        int num = 0;
        while (i >= 0 && j < n) {
            if (matrix[i][j] <= mid) {
                num += i + 1;
                j++;
            } else {
                i--;
            }
        }
        return num >= k;
    }
}

时间复杂度为O(nlog(r−l)),n为数组长度,log(r-l)为二分法的时间复杂度
空间复杂度为O(1)

第406题 根据身高重建队列

假设有打乱顺序的一群人站成一个队列,数组 people 表示队列中一些人的属性(不一定按顺序)。每个 people[i] = [hi, ki] 表示第 i 个人的身高为 hi ,前面 正好 有 ki 个身高大于或等于 hi 的人。
请你重新构造并返回输入数组 people 所表示的队列。返回的队列应该格式化为数组 queue ,其中 queue[j] = [hj, kj] 是队列中第 j 个人的属性(queue[0] 是排在队列前面的人)。

示例 1:

输入输出
people = [[7,0],[4,4],[7,1],[5,0],[6,1],[5,2]][[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

解释:
编号为 0 的人身高为 5 ,没有身高更高或者相同的人排在他前面。
编号为 1 的人身高为 7 ,没有身高更高或者相同的人排在他前面。
编号为 2 的人身高为 5 ,有 2 个身高更高或者相同的人排在他前面,即编号为 0 和 1 的人。
编号为 3 的人身高为 6 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
编号为 4 的人身高为 4 ,有 4 个身高更高或者相同的人排在他前面,即编号为 0、1、2、3 的人。
编号为 5 的人身高为 7 ,有 1 个身高更高或者相同的人排在他前面,即编号为 1 的人。
因此 [[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]] 是重新构造后的队列。

示例 2:

输入输出
people = [[6,0],[5,0],[4,0],[3,2],[2,2],[1,4]][[4,0],[5,0],[2,2],[3,2],[1,4],[6,0]]

解题思路

本题使用贪心算法,首先比较数组中各数第一个位置,如果相等,则按从小到大排,如果不相等,则比较第二个数,按从小到大排。

代码

// 根据身高重建队列:贪心
class Solution {
    public int[][] reconstructQueue(int[][] people) {
        Arrays.sort(people, new Comparator<int[]>() {
            public int compare(int[] person1, int[] person2) {
                if (person1[0] != person2[0]) {//如果前后数值不相等
                    return person2[0] - person1[0];//则按数值从小到大排
                } else {
                    return person1[1] - person2[1];//如果前后数相等,则按第二个数从小到大排
                }
            }
        });
        List<int[]> ans = new ArrayList<int[]>();
        for (int[] person : people) {//遍历加入结果
            ans.add(person[1], person);
        }
        return ans.toArray(new int[ans.size()][]);
    }
}

时间复杂度为O( n 2 n^2 n2),n为数组长度
空间复杂度为O(logn)

第413题 等差数列划分

如果一个数列至少有三个元素,并且任意两个相邻元素之差相同,则称该数列为等差数列。
数组 A 包含 N 个数,且索引从0开始。数组 A 的一个子数组划分为数组 (P, Q),P 与 Q 是整数且满足 0<=P<Q<N 。
如果满足以下条件,则称子数组(P, Q)为等差数组:
元素 A[P], A[p + 1], …, A[Q - 1], A[Q] 是等差的。并且 P + 1 < Q 。
函数要返回数组 A 中所有为等差数组的子数组个数。

示例 1:

输入输出
1, 3, 5, 7, 93

示例 2:

输入输出
1, 1, 2, 5, 70

解题思路

使用动态规划不断判断每三个数之间是否为等差数列

代码

// 等差数列划分:动态规划
public class Solution {
    public int numberOfArithmeticSlices(int[] A) {
        int dp = 0;
        int sum = 0;
        for (int i = 2; i < A.length; i++) {
            if (A[i] - A[i - 1] == A[i - 1] - A[i - 2]) {//判断3个数,两两之间差值是否一样,然后不断遍历
                dp = 1 + dp;//dp统计当前是否为等差数列
                sum += dp;//sum统计整个包括多少个等差数列
            } else
                dp = 0;
        }
        return sum;
    }
}

时间复杂度为O(n),n为数组长度
空间复杂度为O(1)

第416题 分割等和子集

给你一个 只包含正整数 的 非空 数组 nums 。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。

示例 1:

输入输出
nums = [1,5,11,5]true

解释:数组可以分割成 [1, 5, 5] 和 [11] 。

示例 2:

输入输出
nums = [1,2,3,5]false

解释:数组不能分割成两个元素和相等的子集。

解题思路

本题使用动态规划,计算数组各数的总和,然后判断数组中是否有几个数相加等于数组总和的一半。

代码

// 分割等和子集:动态规划
class Solution {
    public boolean canPartition(int[] nums) {
        int n = nums.length;
        if (n < 2) {
            return false;
        }
        int sum = 0, maxNum = 0;
        for (int num : nums) {//遍历数组计算数组各数总和以及最大数
            sum += num;
            maxNum = Math.max(maxNum, num);
        }
        if (sum % 2 != 0) {//总和为奇数,不符合
            return false;
        }
        int target = sum / 2;
        if (maxNum > target) {//最大数超过总和一半,不符合
            return false;
        }
        boolean[] dp = new boolean[target + 1];
        dp[0] = true;
        for (int i = 0; i < n; i++) {
            int num = nums[i];
            for (int j = target; j >= num; --j) {//从总和的一半开始找是否有符合的数
                dp[j] |= dp[j - num];
            }
        }
        return dp[target];
    }
}

时间复杂度为O(n×target),n为数组长度,target为数组各数总和的一半
空间复杂度为O(target)

第417题 太平洋大西洋水流问题

给定一个 m x n 的非负整数矩阵来表示一片大陆上各个单元格的高度。“太平洋”处于大陆的左边界和上边界,而“大西洋”处于大陆的右边界和下边界。
规定水流只能按照上、下、左、右四个方向流动,且只能从高到低或者在同等高度上流动。
请找出那些水流既可以流动到“太平洋”,又能流动到“大西洋”的陆地单元的坐标。

示例 1:

输入输出
[[1,2,2,3,5],[3,2,3,4,4],[2,4,5,3,1],[6,7,1,4,5],[5,1,1,2,4]][[0, 4], [1, 3], [1, 4], [2, 2], [3, 0], [3, 1], [4, 0]]

解题思路

先将第一行和第一列的位置标记为可流向太平洋,将最后一行和最后一列标记为可流向大西洋,然后在递归判断各行各列是否比前一个值大,如果前一个值标记为1,且当前遍历的值比前一个值大,则也标记为1.最后判断流向太平洋和大西洋是否都为1.

代码

// 太平洋大西洋水流问题:DFS
    public List<List<Integer>> pacificAtlantic(int[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return new ArrayList<>();
        }
        int m = matrix.length;
        int n = matrix[0].length;
        int[][] pacific = new int[m][n];
        int[][] atlantic = new int[m][n];
        //从海洋边界开始
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (i == 0 || j == 0) {//使用DFS判断第一行和第一列是否能流到太平洋
                    dfs(matrix, pacific, i, j, matrix[i][j]);
                }
                if (i == m - 1 || j == n - 1) {//判断最后一行和最后一列是否能流到大西洋
                    dfs(matrix, atlantic, i, j, matrix[i][j]);
                }
            }
        }
        List<List<Integer>> res = new ArrayList<>();//将可行的解加入结果中
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (pacific[i][j] == 1 && atlantic[i][j] == 1) {
                    res.add(Arrays.asList(i, j));
                }
            }
        }
        return res;
    }
    private void dfs(int[][] matrix, int[][] aux, int i, int j, int pre) {
        //判断边界
        if (i < 0 || j < 0 || i > matrix.length - 1 || j > matrix[0].length - 1
                //已经流到过了
                || aux[i][j] == 1
                //不能流动
                || matrix[i][j] < pre) {
            return;
        }
        aux[i][j] = 1;
        dfs(matrix, aux, i - 1, j, matrix[i][j]);//递归四个方向
        dfs(matrix, aux, i + 1, j, matrix[i][j]);
        dfs(matrix, aux, i, j - 1, matrix[i][j]);
        dfs(matrix, aux, i, j + 1, matrix[i][j]);
    }

时间复杂度为O(mn),m,n为矩阵的行和列
空间复杂度为O(mn)

第437题 路径总和 III

给定一个二叉树,它的每个结点都存放着一个整数值。
找出路径和等于给定数值的路径总数。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的(只能从父节点到子节点)。
二叉树不超过1000个节点,且节点数值范围是 [-1000000,1000000] 的整数。

示例 1:

输入输出
root = [10,5,-3,3,2,null,11,3,-2,null,1], sum = 83

解释:和等于 8 的路径有:1. 5 -> 3
2. 5 -> 2 -> 1
3. -3 -> 11

解题思路

本题使用递归来求解,先建立二叉树,存放前缀和以及出现次数,然后不断递归每一个前缀和加上左右子节点的值是否满足目标值,每有一个满足的,则计算前缀和的次数

代码

// 路径总和 III:递归
class Solution {
    public int pathSum(TreeNode root, int sum) {
        Map<Integer, Integer> prefixSumCount = new HashMap<>();//key是前缀和, value是大小为key的前缀和出现的次数
        prefixSumCount.put(0, 1);
        return recursionPathSum(root, prefixSumCount, sum, 0);//递归
    }
    private int recursionPathSum(TreeNode node, Map<Integer, Integer> prefixSumCount, int target, int currSum) {
        if (node == null) {
            return 0;
        }
        int res = 0;
        currSum += node.val;//当前路径上的和
        res += prefixSumCount.getOrDefault(currSum - target, 0);
        // 更新路径上当前节点前缀和的个数
        prefixSumCount.put(currSum, prefixSumCount.getOrDefault(currSum, 0) + 1);
        res += recursionPathSum(node.left, prefixSumCount, target, currSum);//递归判断左右节点是否有满足目标值得节点
        res += recursionPathSum(node.right, prefixSumCount, target, currSum);
        prefixSumCount.put(currSum, prefixSumCount.get(currSum) - 1);//剪枝
        return res;
    }
}

时间复杂度为O(n),n为二叉树节点数
空间复杂度为O(n)

第445题 两数相加 II

给你两个 非空 链表来代表两个非负整数。数字最高位位于链表开始位置。它们的每个节点只存储一位数字。将这两数相加会返回一个新的链表。
你可以假设除了数字 0 之外,这两个数字都不会以零开头。

示例 1:

输入输出
(7 -> 2 -> 4 -> 3) + (5 -> 6 -> 4)7 -> 8 -> 0 -> 7

解题思路

使用栈的特性,后进先出,利用两个数,一个数存储两链表出栈后的数加上进位的结果,然后对10去余,另一个是进位,相加结果除以10,直到遍历完两个链表。

代码

// 两数相加 II:栈
class Solution {
    public ListNode addTwoNumbers(ListNode l1, ListNode l2) {
        Deque<Integer> stack1 = new LinkedList<Integer>();
        Deque<Integer> stack2 = new LinkedList<Integer>();
        while (l1 != null) {
            stack1.push(l1.val);
            l1 = l1.next;
        }
        while (l2 != null) {
            stack2.push(l2.val);
            l2 = l2.next;
        }
        int carry = 0;
        ListNode ans = null;
        while (!stack1.isEmpty() || !stack2.isEmpty() || carry != 0) {
            int a = stack1.isEmpty() ? 0 : stack1.pop();
            int b = stack2.isEmpty() ? 0 : stack2.pop();
            int cur = a + b + carry;
            carry = cur / 10;
            cur %= 10;
            ListNode curnode = new ListNode(cur);
            curnode.next = ans;
            ans = curnode;
        }
        return ans;
    }
}

时间复杂度为O(max(m,n)),m,n为两链表的长度
空间复杂度为O(m+n)

第451题 根据字符出现频率排序

给定一个字符串,请将字符串里的字符按照出现的频率降序排列。

示例 1:

输入输出
“tree”“eert”

示例 2:

输入输出
“cccaaa”“cccaaa”

示例 3:

输入输出
“Aabb”“bbAa”

解题思路

本题使用桶排序,先将各字母及其出现次数存入哈希表中,然后再按出现次数划分桶,最后将哈希表中的字母放入桶内,再从大到小输出到结果中。

代码

// 根据字符出现频率排序:桶排序
class Solution {
    public String frequencySort(String s) {
        Map<Character, Integer> map = new HashMap<Character, Integer>();
        int maxFreq = 0;
        int length = s.length();
        for (int i = 0; i < length; i++) {
            char c = s.charAt(i);
            int frequency = map.getOrDefault(c, 0) + 1;//用哈希表存储各字符及其出现次数
            map.put(c, frequency);
            maxFreq = Math.max(maxFreq, frequency);//计算出现次数最多的字符
        }
        StringBuffer[] buckets = new StringBuffer[maxFreq + 1];//建立桶
        for (int i = 0; i <= maxFreq; i++) {//按出现次数不同,建立桶
            buckets[i] = new StringBuffer();
        }
        for (Map.Entry<Character, Integer> entry : map.entrySet()) {
            char c = entry.getKey();
            int frequency = entry.getValue();
            buckets[frequency].append(c);//将哈希表中的字符存入桶中
        }
        StringBuffer sb = new StringBuffer();
        for (int i = maxFreq; i > 0; i--) {
            StringBuffer bucket = buckets[i];
            int size = bucket.length();
            for (int j = 0; j < size; j++) {
                for (int k = 0; k < i; k++) {
                    sb.append(bucket.charAt(j));//加入各桶中的字母加入到结果中
                }
            }
        }
        return sb.toString();
    }
}

时间复杂度为O(n+k),n为字符串长度,k为字符串包含的不同字符个数
空间复杂度为O(n+k)

第452题 用最少数量的箭引爆气球

在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。

示例 1:

输入输出
points = [[10,16],[2,8],[1,6],[7,12]]2

解释:对于该样例,x = 6 可以射爆 [2,8],[1,6] 两个气球,以及 x = 11 射爆另外两个气球

示例 2:

输入输出
points = [[1,2],[3,4],[5,6],[7,8]]4

示例 3:

输入输出
points = [[1,2],[2,3],[3,4],[4,5]]2

解题思路

代码

// 用最少数量的箭引爆气球:贪心
class Solution {
    public int findMinArrowShots(int[][] points) {
        if (points.length == 0) {
            return 0;
        }
        Arrays.sort(points, new Comparator<int[]>() {//排序
            public int compare(int[] point1, int[] point2) {
                if (point1[1] > point2[1]) {
                    return 1;
                } else if (point1[1] < point2[1]) {
                    return -1;
                } else {
                    return 0;
                }
            }
        });
        int pos = points[0][1];
        int ans = 1;
        for (int[] balloon: points) {//遍历数组
            if (balloon[0] > pos) {//如果下一个数的起始大于当前数的结尾,则将pos标记为下一数的结尾,然后将结果值自增,否则继续遍历
                pos = balloon[1];
                ++ans;
            }
        }
        return ans;
    }
}

时间复杂度为O(nlogn),n为数组长度
空间复杂度为O(logn)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值