目录
这道题的思路和算法如下:
首先,将二维数组 restaurants 转换为餐馆对象列表。每个餐馆对象包含 id、rating、veganFriendly、price 和 distance 五个属性。
使用过滤器条件对餐馆列表进行过滤:
对过滤后的餐馆列表进行排序:
返回排序后的餐馆id列表。
- import java.util.*;
- class Restaurant {
- int id;
- int rating;
- int veganFriendly;
- int price;
- int distance;
-
- public Restaurant(int id, int rating, int veganFriendly, int price, int distance) {
- this.id = id;
- this.rating = rating;
- this.veganFriendly = veganFriendly;
- this.price = price;
- this.distance = distance;
- }
- }
- class Solution {
- public List
filterRestaurants(int[][] restaurants, int veganFriendly, int maxPrice, int maxDistance) { - List
restaurantList = new ArrayList<>(); -
- // 将二维数组转换为餐馆对象列表
- for (int[] res : restaurants) {
- restaurantList.add(new Restaurant(res[0], res[1], res[2], res[3], res[4]));
- }
-
- // 使用过滤器条件进行过滤
- return restaurantList.stream()
- .filter(r -> veganFriendly == 0 || r.veganFriendly == veganFriendly)
- .filter(r -> r.price <= maxPrice && r.distance <= maxDistance)
- .sorted((r1, r2) -> {
- if (r1.rating == r2.rating) {
- return r2.id - r1.id;
- } else {
- return r2.rating - r1.rating;
- }
- })
- .map(r -> r.id)
- .collect(Collectors.toList());
- }
- }
复杂度分析:
因此,总的时间复杂度为 O(nlogn)。
空间复杂度方面,需要额外使用一个餐馆对象列表来存储转换后的餐馆信息,所以空间复杂度为 O(n),其中 n 是餐馆的数量。
综上所述,该算法的时间复杂度为 O(nlogn),空间复杂度为 O(n)。
LeetCode运行结果:
思路和算法如下:
- import java.util.ArrayList;
- import java.util.List;
-
- public class Solution {
- public List
filterRestaurants(int[][] restaurants, int veganFriendly, int maxPrice, int maxDistance) { - List<int[]> filtered = new ArrayList<>();
-
- // 过滤符合条件的整型数组
- for (int[] res : restaurants) {
- if ((veganFriendly == 1 && res[2] == 0) || res[3] > maxPrice || res[4] > maxDistance) {
- continue;
- }
- filtered.add(res);
- }
-
- // 根据评分和 id 进行排序
- filtered.sort((r1, r2) -> {
- if (r1[1] == r2[1]) {
- return r2[0] - r1[0];
- } else {
- return r2[1] - r1[1];
- }
- });
-
- // 提取餐馆 id 并返回
- List
result = new ArrayList<>(); - for (int[] res : filtered) {
- result.add(res[0]);
- }
-
- return result;
- }
- }
复杂度分析:
假设 restaurants 数组的长度为 n。
综上所述,该算法的时间复杂度为 O(nlogn)。
对于空间复杂度,主要取决于存储过程中的辅助数据结构的大小。在这种情况下,使用一个辅助数据结构(例如优先队列或链表)来存储过滤和排序后的餐馆信息。假设过滤后符合条件的餐馆数量为 m,则辅助数据结构的大小为 O(m)。因此,算法的空间复杂度为 O(m)。
LeetCode运行结果:
这个问题可以使用回溯算法来解决。回溯算法是一种通过不断尝试所有可能的选择,并在遇到不满足条件的情况时进行回退,继续尝试其他选择的算法。具体的思路如下:
这样,就可以找到所有满足条件的组合了。注意,为了避免重复的组合出现,我们在每次递归调用时,从当前位置开始选择元素,而不是从0开始选择。这样可以保证每个元素只能使用一次,避免重复的组合。另外,对于给定的输入,保证和为目标值的不同组合数少于150个,所以回溯算法是可行的,不会超时。
- import java.util.ArrayList;
- import java.util.List;
-
- class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- List
> result = new ArrayList<>();
- List
path = new ArrayList<>(); - backtrack(candidates, target, 0, path, result);
- return result;
- }
-
- private void backtrack(int[] candidates, int target, int start, List
path, List> result)
{ - if (target < 0) {
- return;
- }
- if (target == 0) {
- result.add(new ArrayList<>(path));
- return;
- }
- for (int i = start; i < candidates.length; i++) {
- path.add(candidates[i]);
- backtrack(candidates, target - candidates[i], i, path, result);
- path.remove(path.size() - 1);
- }
- }
- }
复杂度分析:
回溯算法的时间复杂度和空间复杂度通常是难以精确计算的,因为它涉及到了所有可能的组合情况。但可以从一个大致的角度进行分析。
设候选数组的长度为 n,目标值为 target。
时间复杂度:
空间复杂度:
综上所述,回溯算法的时间复杂度为 O(n^target),空间复杂度为 O(target)。在实际应用中,需要根据具体问题规模和约束条件综合考虑算法的效率和可用性。
LeetCode运行结果:
除了回溯算法,也可以使用动态规划来解决这个问题。
在动态规划方法中,我们使用一个二维数组dp来保存每个目标值对应的所有组合。dp[i]中存储的是目标值为i时的所有组合。
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
-
- class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- Arrays.sort(candidates); // 对数组进行排序
- List
>> dp = new ArrayList<>();
-
- // 初始化dp数组
- for (int i = 0; i <= target; i++) {
- dp.add(new ArrayList<>());
- }
-
- // 遍历每个目标值
- for (int i = 1; i <= target; i++) {
- List
> combinations = new ArrayList<>();
-
- // 遍历每个候选数字
- for (int j = 0; j < candidates.length && candidates[j] <= i; j++) {
- if (candidates[j] == i) {
- combinations.add(Arrays.asList(candidates[j]));
- } else {
- for (List
prev : dp.get(i - candidates[j])) { - if (candidates[j] <= prev.get(0)) {
- List
combination = new ArrayList<>(); - combination.add(candidates[j]);
- combination.addAll(prev);
- combinations.add(combination);
- }
- }
- }
- }
-
- dp.set(i, combinations);
- }
-
- return dp.get(target);
- }
- }
复杂度分析:
LeetCode运行结果:
除了回溯算法和动态规划之外,还可以使用DFS+剪枝的方法来解决这个问题。
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
-
- class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- Arrays.sort(candidates); // 对数组进行排序
- List
> combinations = new ArrayList<>();
- dfs(candidates, target, 0, new ArrayList<>(), combinations);
- return combinations;
- }
-
- /**
- * DFS+剪枝求解组合
- * @param candidates 候选数字数组
- * @param target 目标值
- * @param start 从候选数字数组的哪个位置开始搜索
- * @param combination 当前的组合
- * @param combinations 结果集
- */
- private void dfs(int[] candidates, int target, int start, List
combination, List> combinations)
{ - if (target == 0) { // 如果目标值为0,说明已经找到一个组合
- combinations.add(new ArrayList<>(combination));
- return;
- }
-
- for (int i = start; i < candidates.length && candidates[i] <= target; i++) {
- // 剪枝操作:如果当前候选数字已经比目标值大了,就没有继续搜索的必要了
- if (candidates[i] > target) {
- break;
- }
- combination.add(candidates[i]); // 将当前候选数字加入到组合中
- dfs(candidates, target - candidates[i], i, combination, combinations); // 继续搜索,从当前候选数字开始搜索
- combination.remove(combination.size() - 1); // 回溯操作:将当前候选数字移除组合
- }
- }
- }
在DFS+剪枝方法中,我们使用一个dfs函数来实现搜索。首先对候选数字进行排序,然后从0位置开始搜索。遍历候选数字数组,如果当前候选数字不大于目标值,将其加入到组合中。然后,将目标值减去当前候选数字,继续从当前位置开始搜索,直到目标值为0。如果目标值为0,说明已经找到一个组合,将其加入到combinations列表中。 回溯操作时,将当前候选数字从组合中移除,继续搜索下一个候选数字。
可以看出,与回溯算法相比,DFS+剪枝方法会在搜索过程中进行一些剪枝操作,从而减少了不必要的递归调用,提高了效率。
复杂度分析:
需要注意的是,虽然DFS+剪枝方法可以在搜索过程中减少不必要的递归调用,但在最坏情况下,仍然需要遍历所有可能的组合,因此其时间复杂度与回溯算法相同。空间复杂度方面,DFS+剪枝方法与回溯算法相比,由于没有维护二维数组dp,所以空间复杂度略低一些。
LeetCode运行结果:
使用Java的动态规划+状态压缩方法来解决组合总和问题。
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.List;
-
- public class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- Arrays.sort(candidates); // 对数组进行排序
- int[] dp = new int[target + 1]; // 动态规划数组
- List
> combinations = new ArrayList<>();
-
- dp[0] = 1; // 初始化dp[0]为1
-
- for (int candidate : candidates) {
- for (int i = candidate; i <= target; i++) {
- if (dp[i - candidate] > 0) {
- // 如果dp[i - candidate]不为0,说明可以通过当前候选数字得到目标值i
- dp[i] += dp[i - candidate]; // 更新dp[i]
- }
- }
- }
-
- if (dp[target] == 0) {
- // 如果dp[target]为0,说明无法得到目标值target,直接返回空列表
- return combinations;
- }
-
- dfs(candidates, target, 0, new ArrayList<>(), combinations, dp); // 使用DFS搜索符合条件的组合
-
- return combinations;
- }
-
- private void dfs(int[] candidates, int target, int start, List
combination, List> combinations, int[] dp)
{ - if (target == 0) { // 如果目标值为0,说明已经找到一个组合
- combinations.add(new ArrayList<>(combination));
- return;
- }
-
- for (int i = start; i < candidates.length && target >= candidates[i]; i++) {
- if (dp[target - candidates[i]] > 0) {
- combination.add(candidates[i]); // 将当前候选数字加入到组合中
- dfs(candidates, target - candidates[i], i, combination, combinations, dp); // 继续搜索,从当前候选数字开始搜索
- combination.remove(combination.size() - 1); // 回溯操作:将当前候选数字移除组合
- }
- }
- }
- }
该方法的核心思想是使用动态规划数组dp记录每个目标值对应的组合数。首先初始化dp[0]为1,表示目标值为0时有一种组合方式。然后,按照候选数字的顺序更新dp数组,遍历候选数字数组,并从候选数字开始更新dp数组中的对应位置。如果dp[i - candidate]不为0,则说明可以通过当前候选数字得到目标值i,因此更新dp[i] += dp[i - candidate]。
在DFS过程中,我们根据dp数组进行剪枝操作,只选择能够达到目标值的候选数字进行搜索。如果dp[target]为0,说明无法得到目标值target,直接返回空列表。否则,继续使用DFS搜索符合条件的组合。
复杂度分析:
时间复杂度:
空间复杂度:
综上所述,使用动态规划+状态压缩方法解决组合总和问题的时间复杂度为O(target * n),空间复杂度为O(target)。在实际应用中,如果目标值较大,则算法的效率会受到限制。因此,对于较大的目标值,可能需要考虑其他优化方法或策略来提高算法效率。
LeetCode运行结果:
使用Java的广度优先搜索(BFS)方法来解决组合总和问题。
- import java.util.ArrayList;
- import java.util.Arrays;
- import java.util.LinkedList;
- import java.util.List;
- import java.util.Queue;
-
- public class Solution {
- public List
> combinationSum(int[] candidates, int target) {
- Arrays.sort(candidates); // 对数组进行排序
- List
> combinations = new ArrayList<>();
- Queue
queue = new LinkedList<>(); // 使用队列保存搜索状态 -
- // 初始化队列,将初始状态(目标值、路径、起始位置)加入队列
- queue.offer(new State(target, new ArrayList<>(), 0));
-
- while (!queue.isEmpty()) {
- State state = queue.poll(); // 取出当前状态
-
- if (state.target == 0) { // 如果目标值为0,说明已经找到一个符合条件的组合
- combinations.add(state.path);
- continue;
- }
-
- for (int i = state.start; i < candidates.length && candidates[i] <= state.target; i++) {
- List
newPath = new ArrayList<>(state.path); - newPath.add(candidates[i]); // 将当前候选数字加入到路径中
- // 将更新后的状态(目标值、路径、起始位置)加入队列
- queue.offer(new State(state.target - candidates[i], newPath, i));
- }
- }
-
- return combinations;
- }
-
- // 定义状态类
- private class State {
- int target; // 目标值
- List
path; // 当前路径 - int start; // 起始位置
-
- public State(int target, List
path, int start) { - this.target = target;
- this.path = path;
- this.start = start;
- }
- }
- }
该方法使用广度优先搜索(BFS)进行搜索。使用队列保存搜索状态,每次从队列中取出一个状态进行拓展,直到队列为空。
在每一次拓展过程中,依次选择候选数字,并判断是否满足以下条件:
由于每个位置的数字可以重复使用,因此每次拓展时仍可以选择当前候选数字及之后的数字。这样可以保证得到所有可能的组合。
复杂度分析:
设 n 为候选数字数组 candidates 的长度,m 为目标值 target 的大小,k 为结果列表 combinations 的平均长度(即每个组合的平均元素个数)。
时间复杂度:
空间复杂度:
总结起来,使用广度优先搜索(BFS)的组合总和问题算法的时间复杂度为 O(nlogn + kn),空间复杂度为 O(n + m + k^2)。
LeetCode运行结果:
算法思路:
- class Solution {
- public List
> combinationSum2(int[] candidates, int target) {
- Arrays.sort(candidates); // 先排序
- List
> res = new ArrayList<>();
- backtrack(res, new ArrayList<>(), candidates, target, 0);
- return res;
- }
-
- private void backtrack(List
> res, List temp, int[] candidates, int target, int start)
{ - if (target == 0) {
- res.add(new ArrayList<>(temp));
- } else if (target > 0) {
- for (int i = start; i < candidates.length; i++) {
- // 如果当前数字和前一个数字相等,则跳过,避免重复
- if (i > start && candidates[i] == candidates[i - 1]) {
- continue;
- }
- temp.add(candidates[i]);
- backtrack(res, temp, candidates, target - candidates[i], i + 1);
- temp.remove(temp.size() - 1);
- }
- }
- }
- }
复杂度分析:
算法的时间复杂度取决于两个因素,候选数字数组的长度(n)和目标数 target 的大小(m)。
空间复杂度主要取决于递归过程中使用的栈空间和结果列表的空间。
需要注意的是,排序操作会占用一定的时间和空间,但由于排序只需要进行一次,所以对于整体的时间和空间复杂度影响较小。
LeetCode运行结果:
除了回溯算法,还可以使用记忆化搜索(Memoization Search)来解决这个问题。
记忆化搜索的思路类似于回溯算法。在递归过程中,为了避免重复计算某些值,我们可以使用一个记忆化数组 memo,记录每个状态的计算结果。在递归过程中,如果遇到一个已经计算过的状态,则直接返回其对应的计算结果,避免重复计算。
- class Solution {
- public List
> combinationSum2(int[] candidates, int target) {
- Arrays.sort(candidates);
- Map
>> memo = new HashMap<>(); - return search(candidates, target, 0, memo);
- }
-
- private List
> search(int[] candidates, int target, int start, Map>> memo) {
- String key = target + "," + start;
- if (memo.containsKey(key)) {
- return memo.get(key);
- }
-
- List
> res = new ArrayList<>();
- if (target == 0) {
- res.add(new ArrayList<>());
- } else if (target > 0) {
- for (int i = start; i < candidates.length && candidates[i] <= target; i++) {
- if (i > start && candidates[i] == candidates[i - 1]) {
- continue; // 避免重复计算相同的数字
- }
-
- List
> subRes = search(candidates, target - candidates[i], i + 1, memo);
- for (List
subList : subRes) { - List
tempList = new ArrayList<>(); - tempList.add(candidates[i]);
- tempList.addAll(subList);
- res.add(tempList);
- }
- }
- }
-
- memo.put(key, res);
- return res;
- }
- }
复杂度分析:
对于记忆化搜索的复杂度分析,我们可以将其分为两个部分来看:计算每个状态的时间复杂度和总状态数。
综上所述,记忆化搜索的时间复杂度为 O(n * 2^n),其中 n 是候选数字数组的长度。空间复杂度为 O(n * m),其中 n 是候选数字数组的长度,m 是目标数 target 的大小。记忆化数组 memo 的空间复杂度为 O(n * m)。这里的空间复杂度主要取决于 memo 数组的大小。同时,递归调用的栈空间复杂度也为 O(n),因为在最坏情况下,回溯的深度可能达到 n 级别。
LeetCode运行结果:
除了回溯算法和记忆化搜索,还可以使用递归加剪枝的方法来解决该问题。这种方法类似于回溯算法,但通过一些条件判断来减少搜索的分支。
在递归加剪枝的方法中,我们首先对候选数字数组 candidates 进行排序。然后,使用回溯的方式从候选数字数组中选择数字,并进行相应的剪枝操作。
在每一轮的选择中,我们从 start 位置开始遍历候选数字数组。如果当前数字和上一个数字相同且不是起始位置,则跳过,以避免重复组合的产生。
对于每个选择,我们有两个分支:
当 remain 等于 0 时,表示已经找到一个合法的组合,将其加入到结果列表 result 中。
该方法通过剪枝操作,减少了不必要的搜索分支,提高了效率。
- class Solution {
- public List
> combinationSum2(int[] candidates, int target) {
- Arrays.sort(candidates);
- List
> result = new ArrayList<>();
- List
temp = new ArrayList<>(); - backtrack(result, temp, candidates, target, 0);
- return result;
- }
-
- private void backtrack(List
> result, List temp, int[] candidates, int remain, int start)
{ - if (remain == 0) {
- result.add(new ArrayList<>(temp));
- return;
- }
-
- for (int i = start; i < candidates.length; i++) {
- if (i > start && candidates[i] == candidates[i - 1]) {
- continue;
- }
-
- if (candidates[i] <= remain) {
- temp.add(candidates[i]);
- backtrack(result, temp, candidates, remain - candidates[i], i + 1);
- temp.remove(temp.size() - 1);
- } else {
- break; // 剩余的数字已经超过目标值,不再搜索
- }
- }
- }
- }
复杂度分析:
时间复杂度和空间复杂度与回溯算法相同,为 O(n * 2^n) 和 O(n)。
LeetCode运行结果: