private void helper(final int[] candidates, final int start, final int target, final ArrayList<Integer> item, final ArrayList<ArrayList<Integer>> res) {

        // has to check this first. because even start>=candidates.length, we can still return
        if (target == 0) {
            res.add(new ArrayList<Integer>(item));
            return;
        }

        if (target < 0 || start >= candidates.length) {
            return;
        }

        for (int i = start; i < candidates.length; i++) {
            // here i>start but not i>0 is because we still have to consider repeated elements
            // i>0 will remove any repeated elements
            // i> start will happen when a combination try failed, the recursion returned
            // at this point we already considered the repeated elements in the last recursion
            // so if we meet any repeated elements again, we need to skip it
            // in order not to have duplicate combinations
            if (i > start && candidates[i] == candidates[i - 1]) {
                continue;
            }
            item.add(candidates[i]);
            // i increase 1 is because we can only use one number once
            helper(candidates, i + 1, target - candidates[i], item, res);
            item.remove(item.size() - 1);
        }

    }