import java.util.*;

/**
 * NC221 集合的所有子集(二)
 * @author d3y1
 */
public class Solution {
    private ArrayList<Integer> list = new ArrayList<>();
    private ArrayList<ArrayList<Integer>> result = new ArrayList<>();
    private HashSet<ArrayList<Integer>> set = new HashSet<>();

    /**
     * 代码中的类名、方法名、参数名已经指定,请勿修改,直接返回方法规定的值即可
     *
     * 相似 -> NC27 集合的所有子集(一) [nowcoder]
     *
     * @param nums int整型一维数组
     * @return int整型ArrayList<ArrayList<>>
     */
    public ArrayList<ArrayList<Integer>> subsets (int[] nums) {
        return solution1(nums);
        // return solution2(nums);
        // return solution22(nums);
        // return solution222(nums);
        // return solution3(nums);
        // return solution33(nums);
    }

    /**
     * 位运算
     *
     * 考虑数组[1,2,2], 选择前两个数, 或者第一、三个数, 都会得到相同的子集.
     *
     * 也就是说, 对于当前选择的数x, 若前面有与其相同的数y, 且没有选择y, 此时包含x的子集, 必然会出现在包含y的所有子集中.
     *
     * 我们可以通过判断这种情况, 来避免生成重复的子集.
     * 代码实现时, 可以先将数组排序; 迭代时, 若发现没有选择上一个数, 且当前数与上一个数相同, 则可以跳过当前生成的子集.
     *
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution1(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);

        boolean flag;
        for(int mask=0; mask<(1<<n); mask++){
            list.clear();
            flag = true;
            for(int j=0; j<n; j++){
                if(((mask>>j)&1) == 1){
                    // 没有选择上一个数, 且当前数与上一个数相同
                    if(j>0 && (((mask>>(j-1))&1)==0) && S[j]==S[j-1]){
                        flag = false;
                        break;
                    }
                    list.add(S[j]);
                }
            }
            if(flag){
                result.add(new ArrayList<>(list));
            }
        }

        sort(result);

        return result;
    }

    /**
     * 回溯法
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution2(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);
        backtrack2(S, 0, new ArrayList<>());

        return result;
    }

    /**
     * 回溯: 递归遍历解空间
     * @param S
     * @param idx
     * @param list
     */
    private void backtrack2(int[] S, int idx, ArrayList<Integer> list){
        if(!set.contains(list)){
            result.add(list);
            set.add(list);
        }
        ArrayList<Integer> newList;
        for(int i=idx; i<S.length; i++){
            newList = new ArrayList<>(list);
            newList.add(S[i]);
            backtrack2(S, i+1, newList);
        }
    }

    /**
     * 回溯法
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution22(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);
        backtrack22(S, 0, new ArrayList<>());

        return result;
    }

    /**
     * 回溯: 递归遍历解空间
     * @param S
     * @param idx
     * @param list
     */
    private void backtrack22(int[] S, int idx, ArrayList<Integer> list){
        if(!set.contains(list)){
            result.add(new ArrayList<>(list));
            set.add(new ArrayList<>(list));
        }
        for(int i=idx; i<S.length; i++){
            list.add(S[i]);
            backtrack22(S, i+1, list);
            list.remove(list.size()-1);
        }
    }

    /**
     * 回溯法
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution222(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);
        backtrack222(S, 0);

        return result;
    }

    /**
     * 回溯: 递归遍历解空间
     * @param S
     * @param idx
     */
    private void backtrack222(int[] S, int idx){
        result.add(new ArrayList<>(list));
        for(int i=idx; i<S.length; i++){
            // 没有选择上一个数, 且当前数与上一个数相同
            if(i>idx && S[i]==S[i-1]){
                continue;
            }
            list.add(S[i]);
            backtrack222(S, i+1);
            list.remove(list.size()-1);
        }
    }

    /**
     * 回溯法
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution3(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);
        backtrack3(S, 0);
        sort(result);

        return result;
    }

    /**
     * 回溯: 递归遍历解空间
     * @param S
     * @param idx
     */
    private void backtrack3(int[] S, int idx){
        if(idx == S.length){
            result.add(new ArrayList<Integer>(list));
            return;
        }

        list.add(S[idx]);
        backtrack3(S, idx+1);
        list.remove(list.size()-1);
        // 没有选择当前数(上面remove()), 且下一个数与当前数相同
        while(idx<S.length-1 && S[idx]==S[idx+1]){
            idx++;
        }
        backtrack3(S, idx+1);
    }

    /**
     * 回溯法
     * @param S
     * @return
     */
    private ArrayList<ArrayList<Integer>> solution33(int[] S){
        int n = S.length;
        if(n == 0){
            return new ArrayList<>();
        }

        Arrays.sort(S);
        backtrack33(S, 0, false);
        sort(result);

        return result;
    }

    /**
     * 回溯: 递归遍历解空间
     * @param S
     * @param idx
     * @param choosePre
     */
    private void backtrack33(int[] S, int idx, boolean choosePre){
        if(idx == S.length){
            result.add(new ArrayList<Integer>(list));
            return;
        }

        backtrack33(S, idx+1, false);
        // 没有选择上一个数, 且当前数与上一个数相同
        if(!choosePre && idx>0 && S[idx]==S[idx-1]){
            return;
        }
        list.add(S[idx]);
        backtrack33(S, idx+1, true);
        list.remove(list.size()-1);
    }

    /**
     * 排序结果集: 按字典序
     * @param result
     */
    private void sort(ArrayList<ArrayList<Integer>> result){
        // Collections.sort(result, new Comparator<ArrayList<Integer>>(){
        //     @Override
        //     public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2){
        //         int len = Math.min(o1.size(), o2.size());
        //         for(int i=0; i<len; i++){
        //             if(o1.get(i) != o2.get(i)){
        //                 return o1.get(i)-o2.get(i);
        //             }
        //         }
        //         return o1.size()-o2.size();
        //     }
        // });

        Collections.sort(result, (o1, o2) -> {
            int len = Math.min(o1.size(), o2.size());
            for(int i=0; i<len; i++){
                if(!o1.get(i).equals(o2.get(i))){
                    return o1.get(i)-o2.get(i);
                }
            }
            return o1.size()-o2.size();
        });
    }
}