先简单说一下本题思路:
数据结构:栈
算法:递归
使用深度遍历,得到全排列。要使用一个标记数组来标记某个数在本次递归中是否被使用,然后在下次递归前要取消标记。
java
class Solution {
private int[] flag;
private List<List<Integer>> v = new ArrayList<>();
private List<Integer> vv = new ArrayList<>();
public List<List<Integer>> permute(int[] nums) {
flag = new int[nums.length];
dfs(nums);
return v;
}
private void dfs(int [] nums){
if(vv.size() == nums.length){
// v.add(vv);
v.add(new ArrayList<>(vv));
return;
}
for(int i = 0; i < nums.length; i++){
if(flag[i] == 0){
flag[i] = 1;
vv.add(nums[i]);
dfs(nums);
vv.remove(vv.size()-1);
flag[i] = 0;
}
}
}
}
重点看我注解的这段代码,之前用c++写程序习惯了,但是换到JAVA导致结果一直是空。仔细思考和查了下资料:
通过执行 v.add(vv);,实际上添加的是对列表 vv 的引用,而不是当前内容的快照。因为在整个 DFS 过程中 vv 不断被修改,所以在 v 中的所有条目最终都会成为同一个列表的引用------而一旦递归完成,这个列表就会是空的。
改成v.add(new ArrayList<>(vv))确保了存储在 v 中的每个排列在被添加到列表时都准确反映了当时的实际状态,而不是最后都指向同一个(最终为空的)列表 vv。
在Java中,对象(包括那些实现了List接口的对象,如ArrayList)是通过引用来操作的。将vv添加到v中时,若是直接添加vv,实际上添加的是vv这个列表的引用,而不是它此刻的内容拷贝。这意味着,随后对vv的所有修改(添加元素、删除元素等操作),都会反映在v中所保存的这个引用所指向的同一个列表对象上。
再向下引申一点,在c++中为什么直接用v.push_bcak(vv)就可以呢?
这是因为C++在这种情境下执行的是对象的复制(copy),而不是引用(或指针)的复制。对于C++的std::vector(或大多数标准容器),push_back方法会创建一个参数的副本,并将这个副本存入容器中。因此,在C++中不需要显式地从vv创建一个新的std::vector对象就可以直接使用push_back(vv),因为push_back便已自动完成了这一复制工作。
这种差异归根结底是由于Java和C++对对象和容器的操作语义不同。Java中的对象赋值通常是引用赋值,而C++中的赋值(包括容器中元素的插入)则是创建对象副本的复制操作。这也反映了Java和C++在内存管理和对象模型方面的根本区别。