2016-10-19 5 views
-1

Я написал программу для вычисления всех возможных подмножеств заданного массива.Правильное использование объекта списка java

class Solution { 
    /** 
    * @param S: A set of numbers. 
    * @return: A list of lists. All valid subsets. 
    */ 
    public ArrayList<ArrayList<Integer>> subsets(int[] nums) { 
     // write your code here 
     if(nums == null) return null; 

     ArrayList<ArrayList<Integer>> res = new ArrayList<>(); 
     ArrayList<Integer> list = new ArrayList<>(); 

     helper(res, list, nums, 0); 

     return res; 
    } 

    private void helper(ArrayList<ArrayList<Integer>> res, ArrayList<Integer> list, int [] nums, int start) { 

     //res.add(list); 
     res.add(new ArrayList<Integer>(list)); 
     //System.out.println(list); 
     for(int i = start; i < nums.length; i++) { 
      list.add(nums[i]); 
      //System.out.println(list); 
      helper(res, list, nums, i+1); 
      list.remove(list.size()-1); 
     } 
    } 
} 

Программа работает нормально. Однако, если я использую

res.add(list); 

вместо

res.add(new ArrayList<Integer>(list)); 

Тогда это даст неправильный результат. Например, когда я даю этот ввод

int [] nums = new int[]{0}; 

Ожидаемый выход: [[], [0]]. Однако я получаю [[], []].

Вопросы: 1.Потому что я пытаюсь добавить тот же объект списка к объекту res. Я предполагаю, что объект res ArrayList пытается проверить, находится ли объект списка в нем. Это верно?

2.Почему я получаю [[], []]? Поскольку объект списка будет обновлен до [0] в цикле, почему он не добавляется к объекту res?

+1

Вы понимаете разницу между помещением 'list' в' res' и затем изменением 'list'; и поместив копию 'list' в' res', а затем изменив исходный 'list'? – khelwood

+0

Я думаю, что понял. После того, как я добавлю список в res во второй раз, я сделал list.remove(), поэтому второе добавление также пуст. – drdot

ответ

1

Вы испытываете опасности изменчивости, и вы нашли общее решение: в защитной копии.

Когда вы res.add(list), вы ставите ссылку на list в res. Затем вы продолжите работу с тем же списком и измените его. Потому что вы работаете с тем же объектом, как вы помещаете в res, когда вы его изменяете (здесь, с remove()), затем распечатайте его через res, вы увидите изменения в обоих местах.

Когда вы делаете res.add(new ArrayList<Integer>(list)), конструктор создает новый объект ArrayList и копирует все элементы из list в него. Отныне list и новый объект не влияют друг на друга. Когда вы изменяете list, ваш новый объект не изменяется. Вы сделали защитную копию .

Оборонительное копирование - это хорошо зарекомендовавший себя способ решения этой проблемы. Другой способ, который многие считают лучше, заключается в том, чтобы избежать модификации объектов после их создания. Проделайте некоторое чтение на неизменяемых объектах.


Дополнительный совет: код выглядит аккуратнее и имеет другое «хорошее» свойство, если вы используете более короткое имя интерфейса, а не конкретный типа, где это возможно. Итак:

public List<List<Integer>> subsets(int[] nums) ... 

List<List<Integer>> res = new ArrayList<>();  
+0

Спасибо за объяснение стандартными терминологиями, которые у меня отсутствуют, и бонус-отзыв. Не могли бы вы также рассказать о хороших свойствах в бонусе? – drdot

+0

"Слишком широкий". Вы столкнетесь с этим, когда будете продолжать учиться. – slim

Смежные вопросы