本系列使用IDEA+LEETCODE EDITOR插件,题目描述统一英文。
题目链接
一、题目描述:
//Given an integer array nums of unique elements, return all possible subsets (t
//he power set).
//
// The solution set must not contain duplicate subsets. Return the solution in a
//ny order.
//
//
// Example 1:
//
//
//Input: nums = [1,2,3]
//Output: [[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
//
//
// Example 2:
//
//
//Input: nums = [0]
//Output: [[],[0]]
//
//
//
// Constraints:
//
//
// 1 <= nums.length <= 10
// -10 <= nums[i] <= 10
// All the numbers of nums are unique.
二、思路分析:
首先读题,看到求子集,首先想到的就是回溯算法。但是如果贸然去写会写出不是那么优秀的算法。
首先看劣等思路:
排序参数(题目只是给的例子是有序的,人家可没说给你的参数一定是有序的)。
每次回溯从参数下标开始往临时结果添加当前数字,因为每次都是从参数下标开始循环到数组结尾,会出现后面的重复添加的情况,以及先存了大数后存了小数导致最后的结果多余的情况。
需要在添加到结果集时增加判断。
代码可以AC但是性能很差:
解答成功:
执行耗时:2 ms,击败了11.32% 的Java用户
内存消耗:38.7 MB,击败了60.93% 的Java用户
思考为什么性能这么差,发现劣等写法有排序,有循环。那么
1. 为什么每次回溯都是一个循环?
为了每次可以添加index到nums.length的多个可能结果,减少递归次数。
2. 一定要排序么?
因为一次回溯想要添加多个结果才需要的排序。
所以一次就加一个数字,递归多几层应该就能提高性能。注意优化版和劣质版添加返回结果的时机不同。优化版是在每次index已经等于数组长度的时候添加到返回。
如何找到劣质代码的优化点,这个才是这期的重点内容。
三、AC 代码:
劣质版AC代码
class Solution {
public List<List<Integer>> subsets(int[] nums) {
// 很关键,题目描述没有说是有序的只是写了例子是有序的迷惑你,这里不判断回溯里会出问题的
Arrays.sort(nums);
List<List<Integer>> result = new ArrayList<>();
backtrace(result,new ArrayList<>(),nums,0);
result.add(new ArrayList<Integer>());
return result;
}
void backtrace(List<List<Integer>> result,List<Integer> tmp,int[] nums,int index){
if (index >= nums.length) {
return;
};
for (int i = index; i < nums.length; i++) {
// 关键点,不是求全排列,是有顺序的
if( tmp.size()==0 || (!tmp.contains(nums[i]) && tmp.get(tmp.size()-1) < nums[i])){
tmp.add(nums[i]);
result.add(new ArrayList<>(tmp));
backtrace(result,tmp,nums,index + 1);
tmp.remove(tmp.size() - 1);
}
}
}
}
解答成功:
执行耗时:2 ms,击败了11.32% 的Java用户
内存消耗:38.9 MB,击败了10.80% 的Java用户
优化后AC代码,耗时已经打败了100%:
class Solution {
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
backtrace(result, new ArrayList<>(), nums, 0);
return result;
}
void backtrace(List<List<Integer>> result, List<Integer> tmp, int[] nums, int index) {
// 临时结果集的数字个数跟当前层次匹配,则加入返回结果
if (index == nums.length) {
result.add(new ArrayList<>(tmp));
return;
}
// 不选当前数
backtrace(result, tmp, nums, index + 1);
// 选了当前数
tmp.add(nums[index]);
backtrace(result, tmp, nums, index + 1);
tmp.remove(tmp.size() - 1);
}
}
解答成功:
执行耗时:0 ms,击败了100.00% 的Java用户
内存消耗:38.7 MB,击败了58.42% 的Java用户
四、总结:
- 排序,重复计算是性能问题的常见点。
- 当发现代码量上去了且逻辑判断的次数多了时,就要思考代码是否有问题了。
本文正在参与「掘金 2021 春招闯关活动」, 点击查看 活动详情