Leetcode初体验生存向——Day4

31. Next Permutation(Middle)

Question:

Implement next permutation, which rearranges numbers into the lexicographically next greater permutation of numbers.

If such arrangement is not possible, it must rearrange it as the lowest possible order (ie, sorted in ascending order).

The replacement must be in-place and use only constant extra memory.

Here are some examples. Inputs are in the left-hand column and its corresponding outputs are in the right-hand column.

1,2,3 → 1,3,2
3,2,1 → 1,2,3
1,1,5 → 1,5,1

Solution:

/**
 * 双指针法,主要依靠数学分析:
 * 有一个规律,把数组[1, 2, ..., i, i+1, ..., k, k+1, ...]从后往前找
 * 当某一位数字i小于它的后一位数字i+1的时候就说明要动它
 * 再次从后往前找数组,当找到某一位数字k大于i而k+1小于i的时候
 * 将k与i进行调换,就是要得到的结果
 * 如果i已经到了数组的第一位,说明该数组序列已经是最大的了,直接整个序列反向调换
 */
class Solution {
    public void nextPermutation(int[] nums) {
        // 初始化i,因为要与i+1作比较,这里从倒数第二个开始
        int i = nums.length - 2;
        while (i >= 0 && nums[i] >= nums[i+1]) {
            i--; // 如果i大于等于i+1,就一直往前挪
        }
        // 指针走到第一位或者第一位之后出现i<i+1了
        if (i >= 0) {
            //初始化k
            int k = nums.length - 1;
            while (k >= 0 && nums[k] <= nums[i]) {
                k--; // 如果k小于等于i,往前挪动k
            }
            // 反转i与k
            swap(nums, i, k);
        }
        // i指针走到-1,没有出现i小于i+1,说明整个序列都是最大的,直接反转
        reverse(nums, i + 1);
    }

    // 调转两个数
    private void swap(int[] nums, int i, int k) {
        int temp = nums[i];
        nums[i] = nums[k];
        nums[k] = temp;
    }

    // 反转整个序列
    private void reverse(int[] nums, int start) {
        int end = nums.length - 1;
        // 这里注意判断的是start与end指针,而不是指针指的数值
        while (start < end) {
            swap(nums, start, end);
            start++;
            end--;
        }
    }
}

链接:https://leetcode-cn.com/problems/next-permutation

32. Longest Valid Parentheses(Hard)

Question:

Given a string containing just the characters ‘(’ and ‘)’, find the length of the longest valid (well-formed) parentheses substring.

Example:

Input: “(()”
Output: 2
Explanation: The longest valid parentheses substring is “()”

Example:

Input: “)()())”
Output: 4
Explanation: The longest valid parentheses substring is “()()”

Solution:

/**
 * 利用栈的思想,为了避免因为括号不匹配导致的空栈问题,要在最左侧添加一个参照物
 * 具体参考:https://leetcode-cn.com/problems/longest-valid-parentheses/solution/shou-hua-tu-jie-zhan-de-xiang-xi-si-lu-by-hyj8/
 * 如 ( ) ( ( ) ) ) ( ) )
 *    0 1 2 3 4 5 6 7 8 9
 * 要在该序列的最左边加一个参照物-1
 * 将参照物入栈,左括号入栈,遇到右括号就出栈
 * 那么计算有效括号长度的方式为:当前索引-出栈后新的栈顶索引
 * 如入栈后栈内元素应为-1 0,但是遇到了右括号要出栈,栈内元素就变为了-1,当前索引是1,括号长度为1-(-1)=2
 * 接下来再入栈,栈内元素为-1 2 3,遇到右括号栈顶元素2次出栈,栈内元素变为-1,当前索引是5,括号长度为5-(-1)=6
 * 但这时又遇到了一个右括号6,栈内索引-1出栈,此时栈空了,让6入栈作为参照物,计算下一段有效长度
 */
class Solution {
    public int longestValidParentheses(String s) {
        Stack<Integer> stack = new Stack<Integer>(); // 初始化栈
        stack.push(-1); // 先给栈放入一个索引元素
        int len = 0; // 初始化最终有效括号长度
        for (int i = 0 ; i < s.length() ; i++) { // 从左到右遍历字符串,i为索引
            if ('(' == s.charAt(i)) {
                stack.push(i); // 如果是左括号就入栈
            }
            if (')' == s.charAt(i)) {
                stack.pop(); // 如果是右括号就出栈
                if (stack.isEmpty()) {
                    stack.push(i); // 如果栈空了,就把当前索引放进去
                }
                len = Math.max(len, i-stack.peek());
            }
        }
        return len;
    }
}

链接:https://leetcode-cn.com/problems/longest-valid-parentheses

33. Search in Rotated Sorted Array(Middle)

Question:

You are given an integer array nums sorted in ascending order, and an integer target.

Suppose that nums is rotated at some pivot unknown to you beforehand (i.e., [0,1,2,4,5,6,7] might become [4,5,6,7,0,1,2]).

If target is found in the array return its index, otherwise, return -1.

Example 1:

Input: nums = [4,5,6,7,0,1,2], target = 0
Output: 4

Example 2:

Input: nums = [4,5,6,7,0,1,2], target = 3
Output: -1

Example 3:

Input: nums = [1], target = 0
Output: -1

Constraints:

  • 1 <= nums.length <= 5000
  • -104 <= nums[i] <= 104
  • All values of nums are unique.
  • nums is guranteed to be rotated at some pivot.
  • -104 <= target <= 104

Solution:

/**
 * 看到时间复杂度O(log n)就知道是二分法
 * 但数组排列不是按顺序的
 * 升序排序的数组在预先未知的某个点进行了旋转
 * 此时在数组中选一个中点,可以发现无论数组是怎样排列的,有一边都会是升序排列
 * 如两组序列:
 * 3 4 5 6 7 8 9 1 2
 * 8 9 1 2 3 4 5 6 7
 * 第一组的mid是7,左侧按升序排列,第二组的mid是3,右侧按升序排列
 * 此时就可以分3种情况,来进行2分
 * 第一种:
 * 左边是升序排列,如果targe = 4,nums[0]<target<nums[mid],那么直接在左边2分即可
 * 第二种:
 * 左边不是升序排列,如果target=2,target<nums[mid] && nums[mid]<nums[0],那么直接在左边2分
 * 第三种:
 * 左边不是升序排列,如果target=9,target>nums[0] && nums[mid]<nums[0],那么直接在左边2分
 * 其余没提到的情况都将挪动指针,重新循环2分
 * 如:target > nums[mid] && nums[0]<nums[mid],那么就将左指针l改到mid位置上重新二分循环查找
 */
class Solution {
    public int search(int[] nums, int target) {
        // 输入数组为空,直接返回-1
        if (nums == null || nums.length == 0) return -1;
        int a0 = nums[0]; // 数组第一位
        int l = 0; // 左指针
        int r = nums.length - 1; // 右指针
        // 排除一种简单的可能性
        if (a0 == target) return 0;
        // 开始二分循环
        while (l < r) {
            int mid = (l + r) / 2; // 初始化中位数
            // 排除一种简单可能性
            if (target == nums[mid]) return mid;
            // 开始三种情况的判断
            if ((a0 < target && target < nums[mid]) || 
                (target < nums[mid] && nums[mid] < a0) || 
                (nums[mid] < a0 && a0 < target)) {
                    r = mid;
            } else {
                l = mid + 1;
            }
        }
        return l == r && target == nums[l] ? l : -1;
    }
}

链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array

34. Find First and Last Position of Element in Sorted Array(Middle)

Question:

Given an array of integers nums sorted in ascending order, find the starting and ending position of a given target value.

Your algorithm’s runtime complexity must be in the order of O(log n).

If the target is not found in the array, return [-1, -1].

Example 1:

Input: nums = [5,7,7,8,8,10], target = 8
Output: [3,4]

Example 2:

Input: nums = [5,7,7,8,8,10], target = 6
Output: [-1,-1]

Constraints:

  • 0 <= nums.length <= 10^5
  • -10^9 <= nums[i] <= 10^9
  • nums is a non decreasing array.
  • -10^9 <= target <= 10^9

Solution:

/**
 * 时间复杂度O(log n)说明使用二分法
 * 引用一位大佬的神方法:
 * 写一个二分方法查找符合输入字符的第一个元素
 * 这个时候只需要找到等于target的第一个元素和target+1的第一个元素
 * 因为数组是升序排列的,target+1的第一个元素的索引再减1,就是等于target的最后一个元素
 */
class Solution {
    public int[] searchRange(int[] nums, int target) {
        int a = search(nums, target); // 查找第一个target元素
        int b = search(nums, target + 1); // 查找比target大的第一个元素

        // 数组中不存在target,直接返回[-1,1]
        if(a == nums.length || nums[a] != target) {
            return new int[]{-1,-1};
        }
        // 数组中存在target,则b-1是最后一个target的元素索引
        return new int[]{a, b-1};
    }
    
    // 二分查找nums中第一个>=t的元素位置
    int search(int[] nums, int t) {
        int l = 0, r = nums.length; // 初始化指针
        while (l < r) {
            int mid = (l + r)>>>1; // >>>是无符号右移,相当于(l+r)/(2^1)
            // 二分
            if(nums[mid] < t)
                l = mid + 1;
            else
                r = mid;
        }
        return l;
    }
}

链接:https://leetcode-cn.com/problems/find-first-and-last-position-of-element-in-sorted-array

35. Search Insert Position(Easy)

Question:

Given a sorted array and a target value, return the index if the target is found. If not, return the index where it would be if it were inserted in order.

You may assume no duplicates in the array.

Example 1:

Input: [1,3,5,6], 5
Output: 2

Example 2:

Input: [1,3,5,6], 2
Output: 1

Example 3:

Input: [1,3,5,6], 7
Output: 4

Example 4:

Input: [1,3,5,6], 0
Output: 0

Solution:

/**
 * 二分法解决
 */
class Solution {
    public int searchInsert(int[] nums, int target) {
        int len = nums.length;
        int l = 0, r = len;
        while (l < r) {
            int mid = (l + r) >>> 1;
            if (nums[mid] < target) {
                l = mid + 1;
            }
            else {
                r = mid;
            }
        }
        return l;
    }
}

链接:https://leetcode-cn.com/problems/search-insert-position

36. Valid Sudoku(Middle)

Question:

Determine if a 9x9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:

  1. Each row must contain the digits 1-9 without repetition.
  2. Each column must contain the digits 1-9 without repetition.
  3. Each of the 9 3x3 sub-boxes of the grid must contain the digits 1-9 without repetition.


A partially filled sudoku which is valid.

The Sudoku board could be partially filled, where empty cells are filled with the character ‘.’.

Example 1:

Input:
[
[“5”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
Output: true

Example 2:

Input:
[
[“8”,“3”,".",".",“7”,".",".",".","."],
[“6”,".",".",“1”,“9”,“5”,".",".","."],
[".",“9”,“8”,".",".",".",".",“6”,"."],
[“8”,".",".",".",“6”,".",".",".",“3”],
[“4”,".",".",“8”,".",“3”,".",".",“1”],
[“7”,".",".",".",“2”,".",".",".",“6”],
[".",“6”,".",".",".",".",“2”,“8”,"."],
[".",".",".",“4”,“1”,“9”,".",".",“5”],
[".",".",".",".",“8”,".",".",“7”,“9”]
]
Output: false
Explanation: Same as Example 1, except with the 5 in the top left corner being
modified to 8. Since there are two 8’s in the top left 3x3 sub-box, it is invalid.

Note:

  • A Sudoku board (partially filled) could be valid but is not necessarily solvable.
  • Only the filled cells need to be validated according to the mentioned rules.
  • The given board contain only digits 1-9 and the character ‘.’.
  • The given board size is always 9x9.

Solution:

/**
 * 难点在于数学计算
 * 按照三个规则,可以考虑三种情况(假设当前遍历的是board[i][j]):
 * 1. board[i][j]在第i行是否出现过
 * 2. board[i][j]在第j行是否出现过
 * 3. board[i][j]在第j/3+(i/3)*3个box中是否出现过
 * 前两种情况好理解,第三种情况可以将数度分开
 * 如一个3*9的矩阵,被分为3个3*3的box,每个数所在的位置就和横坐标无关了,因为3个box在一排
 * 而纵坐标0-2属于box[0],3-5属于box[1],6-8属于box[2],这样看直接j/3就可以了
 * 但是如果在下面再加两派,变成3排的3个3*3的box,就需要加上3的倍数
 * 0*3表示第一排的,1*3表示第二排的,2*3表示第三排的,然后再加上纵轴的位置,就可以得出是哪一个box
 * 因此最后的box就是j/3+(i/3)*3
 * 看不懂这里可以画个图,或者参考:https://leetcode-cn.com/problems/valid-sudoku/solution/36-jiu-an-zhao-cong-zuo-wang-you-cong-shang-wang-x/
 * 在判断时,存储的是状态表,如果出现了就将状态变为1,没有出现就是0
 * 当再次出现时发现这个位置是1,直接返回false就行
 */
class Solution {
    public boolean isValidSudoku(char[][] board) {
        // 初始化行、列、box状态表
        // 第一位就是第几行或者第几列,或者第几个box,第二位是board中要判断的那个数字,即board[i][j]
        // 因此要配合board[i][j]取值是1-9,这里将它的取值设置为0-9,即给10位
        // 初始化后默认值均为0
        int [][]row = new int[9][10];
        int [][]col = new int[9][10];
        int [][]box = new int[9][10];

        // 开始循环判断board中的每一位数字
        for (int i = 0 ; i < 9 ; i++) {
            for (int j = 0 ; j < 9 ; j++) {
                // 如果是空位,直接下次循环,无需判断
                if (board[i][j] == '.') continue;
                // 有数字的情况
                int num = board[i][j] - '0'; // 把char类型的board数字改为int类型
                // 判断行,等于1说明有这个数
                if (row[i][num] == 1) return false;
                // 判断列
                if (col[j][num] == 1) return false;
                // 判断box
                if (box[j/3 + (i/3) * 3][num] == 1) return false;

                // 如果之前没出现,现在出现了,设置为1
                row[i][num] = 1;
                col[j][num] = 1;
                box[j/3 + (i/3) * 3][num] = 1;
            }
        }
        return true;
    }
}

链接:https://leetcode-cn.com/problems/valid-sudoku

37. Sudoku Solver(Hard)

Question:

Write a program to solve a Sudoku puzzle by filling the empty cells.

A sudoku solution must satisfy all of the following rules:

  1. Each of the digits 1-9 must occur exactly once in each row.
  2. Each of the digits 1-9 must occur exactly once in each column.
  3. Each of the the digits 1-9 must occur exactly once in each of the 9 3x3 sub-boxes of the grid.

Empty cells are indicated by the character ‘.’.


A sudoku puzzle…


…and its solution numbers marked in red.

Note:

  • The given board contain only digits 1-9 and the character ‘.’.
  • You may assume that the given Sudoku puzzle will have a single unique solution.
  • The given board size is always 9x9.

Solution:

/**
 * 通过回溯法解数独
 * 这里仍然利用上一题的思想,设置三个状态表,分别判断行、列、九宫格内是否有重复数字
 * 从左上角开始,即row = 0, col = 0,直到第一个空方格,开始填写数字
 * 如果3种情况都没有重复数字就填写,并记录下三种情况下的状态
 * 继续填写下一个空方格
 * 如果此时已经是最后一个格子了,即row = 8, col = 8
 * 意味着数独已解决,否则放置接下来的数字
 * 如果出现了重复数字就将当前数字改为'.',更改状态表,回溯尝试下一个数字
 *
 * 这道题比较难,代码从solveSudoku开始看,涉及一层层迭代又回溯
 */
class Solution {
    int n = 3; // 九宫格box大小
    int N = n * n; // 数独的长和宽

    // 初始化状态表
    int [][] rows = new int [N][N+1];
    int [][] cols = new int [N][N+1];
    int [][] boxes = new int [N][N+1];

    char [][] board; // 设置全局数组,把输入保存进来

    boolean sudokuSolved = false; // 判断数独有没有解决

    /**
     * 判断当前填入数字是否符合游戏条件
     */
    private boolean couldPlace (int d, int row, int col) {
        int boxIndex = (row / n) * n + col / n; // 计算一下输入的数字位于哪个box,具体思路参考上一题
        // 看一下3个状态是不是都等于0,如果有1个是1(数字出现过),就不等于0,返回false
        return rows[row][d] + cols[col][d] + boxes[boxIndex][d] == 0;
    }

    /**
     * 在数独中填入数字+改状态
     */
    public void placeNumber (int d, int row, int col) {
        int boxIndex = (row / n) * n + col / n;

        // 三个状态表改状态
        rows[row][d]++;
        cols[col][d]++;
        boxes[boxIndex][d]++;

        board[row][col] = (char)(d + '0'); // board是字符类型数组,要把d转换为char
    }

    /**
     * 填下一个数字(用到迭代)
     */
    public void placeNextNumbers(int row, int col) {
        // 如果结束了,改判断,改了判断之后就不会走回溯的removeNumber方法
        if ((col == N - 1) && (row == N - 1)) {
            sudokuSolved = true;
        }
        // 没结束的话判断是不是到最后一列了
        // 到最后一列就转下一行,没有的话走下一列
        else {
            if (col == N - 1) backtrack(row + 1, 0);
            else backtrack(row, col + 1);
        }
    }

    /**
     * 移除数字
     */
    public void removeNumber(int d, int row, int col) {
        int boxIndex = (row / n) * n + col / n;

        // 把状态都移除
        rows[row][d]--;
        cols[col][d]--;
        boxes[boxIndex][d]--;

        // 数组里的数字改成'.'
        board[row][col] = '.';
    }

    /**
     * 回溯
     * 感觉就是迭代之后失败了就移除
     */
    private void backtrack(int row, int col) {
        // 如果当前格子是空的,就填数字
        if (board[row][col] == '.') {
            // 从1开始试,试到9
            for (int d = 1 ; d < 10 ; d++) {
                // 判断一下当前符合游戏条件吗(行、列、九宫格都不能有重复)
                // 不符合继续试下一个数,符合填数字,改状态
                if (couldPlace(d, row, col)) {
                    // 把数字填到当前位置
                    placeNumber(d, row ,col);
                    // 填下一个数字(这里用了迭代)
                    placeNextNumbers(row, col);
                    // 如果试了所有数字都不符合条件,当前迭代a中的backtrack的for语句会结束,回到上一迭代中
                    // 到了上一迭代a-1,就会走下面的语句,即移除刚才那个试不出来的数字的前一个数字
                    // 移除后会再走迭代a-1中的for循环,试d++,然后再次迭代,这样循环往复
                    // 这里主要体现了回溯的思想
                    // 如果sodukuSolved状态改了,就不会走这一句,迭代一层层退出,回溯算法结束
                    if (!sudokuSolved) removeNumber(d, row, col);
                }
            }
        }
        // 如果当前格子有设置好的数字,填下一个数字
        else {
            placeNextNumbers(row, col);
        }
    }

    /**
     * 解数独
     */
    public void solveSudoku(char[][] board) {
        this.board = board; // 把数组设成全局的,省的在其它方法里面一遍一遍写

        // 把三个状态表都初始化一下
        for (int i = 0 ; i < N ; i++) {
            for (int j = 0 ; j < N ; j++) {
                char numChar = board[i][j];
                // 有初始数字的地方填一下状态表,变为1,没有的地方就保持是0
                if (numChar != '.') {
                    // 把字符转为数字
                    int num = Character.getNumericValue(numChar);
                    placeNumber(num, i, j);
                }
            }
        }

        // 开始回溯
        backtrack(0, 0);
    }
}

链接:https://leetcode-cn.com/problems/sudoku-solver

38. Count and Say(Easy)

Question:

The count-and-say sequence is the sequence of integers with the first five terms as following:

1.     1
2.     11
3.     21
4.     1211
5.     111221

1 is read off as “one 1” or 11.
11 is read off as “two 1s” or 21.
21 is read off as “one 2, then one 1” or 1211.

Given an integer n where 1 ≤ n ≤ 30, generate the nth term of the count-and-say sequence. You can do so recursively, in other words from the previous member read off the digits, counting the number of digits in groups of the same digit.

Note: Each term of the sequence of integers will be represented as a string.

Example 1:

Input: 1
Output: “1”
Explanation: This is the base case.

Example 2:

Input: 4
Output: “1211”
Explanation: For n = 3 the term was “21” in which we have two groups “2” and “1”, “2” can be read as “12” which means frequency = 1 and value = 2, the same way “1” is read as “11”, so the answer is the concatenation of “12” and “11” which is “1211”.

Solution:

/**
 * 使用双指针,递归与StringBuffer
 * 如n=6
 * 通过递归得到上一层字符串str="111221"
 * 将start指向下标0,从下标1开始遍历,到"2"的下标3时,因为"1"与"2"不等
 * 此时sb拼上(3-0=3)个1即sb.append(3).appnd(1)
 * 将指针指向下标3,重复操作,直到str到最后一位,sb直接拼上即可
 */
class Solution {
    public String countAndSay(int n) {

        // 递归终止条件
        if (n == 1) return "1";

        // 初始化StringBuffer
        StringBuffer sb = new StringBuffer();

        // 拿到上一层的字符串,这里用到了递归
        // 当迭代到n==1时会直接返回"1",结束递归,然后回到上一轮迭代,即继续推测下一层n==2
        // 说白了就是倒着往回找,然后从最开始往下写
        String str = countAndSay(n - 1);

        int length = str.length(); // 字符串长度
        int start = 0; // 初始化起始指针
        for (int end = 1 ; end < length + 1 ; end++) {
            // 当字符串到达最后一位,直接拼接
            if (end == length) {
                sb.append(end - start).append(str.charAt(start));
            }
            // 找到数字不同的,拼接后更新start
            else if (str.charAt(end) != str.charAt(start)) {
                sb.append(end - start).append(str.charAt(start));
                start = end;
            }
        }
        return sb.toString();
    }
}

链接:https://leetcode-cn.com/problems/count-and-say

39. Combination Sum(Middle)

Question:

Given a set of candidate numbers (candidates) (without duplicates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

The same repeated number may be chosen from candidates unlimited number of times.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [2,3,6,7], target = 7,
A solution set is:
[
[7],
[2,2,3]
]

Example 2:

Input: candidates = [2,3,5], target = 8,
A solution set is:
[
[2,2,2,2],
[2,3,3],
[3,5]
]

Constraints:

  • 1 <= candidates.length <= 30
  • 1 <= candidates[i] <= 200
  • Each element of candidate is unique.
  • 1 <= target <= 500

Solution:

/**
 * 使用回溯
 * 这里加上排序,减少回溯次数
 * 题目中提到可以重复选取数字,因此想到回溯
 */
class Solution {

    // 初始化最终结果
    private List<List<Integer>> res = new ArrayList<>();

    // 回溯
    private void backtrack(List<Integer> prob, int[] candidates, int target, int sum, int begin) {

        // 如果符合条件,就把当前序列加到结果集中并返回,结束当前回溯分支
        if (sum == target) {
            // res嵌套了一个ArrayList,最开始初始化最终结果只初始化了最外层的,别忘了把中间的也初始化一下
            res.add(new ArrayList<>(prob));
            return;
        }

        for (int i = begin ; i < candidates.length ; i++) {
            int rs = candidates[i] + sum; // 临时保存当前加法总和
            // 总和比目标值小或者相等,就继续迭代,再下一次迭代中会判断是否已经符合条件
            if (rs <= target) {
                prob.add(candidates[i]);
                // 继续迭代
                // 因为题目说可以重复,再次迭代时int begin传入i的值即可
                // 如果不能重复,int begin的位置只能传入i+1,即指针指向下一位进行查找
                backtrack(prob, candidates, target, rs, i);
                // 迭代完一个分支,就会进行回溯,删除最后一个元素,进入for循环尝试其他可能性
                prob.remove(prob.size()-1);
            }
            // 总和比目标值大说明当前序列肯定达不到目标,直接结束
            // 这里就利用排序实现了剪枝操作,不需要每个序列都尝试到最后一个,只要出现不符合就结束
            else {
                break;
            }
        }
    }

    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        // 初始化最终结果中的一个可能
        List<Integer> prob = new ArrayList<>();
        // 排序
        Arrays.sort(candidates);
        // 回溯
        backtrack(prob, candidates, target, 0, 0);
        // 全部回溯完毕返回最终结果
        return res;
    }
}

链接:https://leetcode-cn.com/problems/combination-sum

40. Combination Sum II(Middle)

Question:

Given a collection of candidate numbers (candidates) and a target number (target), find all unique combinations in candidates where the candidate numbers sums to target.

Each number in candidates may only be used once in the combination.

Note:

  • All numbers (including target) will be positive integers.
  • The solution set must not contain duplicate combinations.

Example 1:

Input: candidates = [10,1,2,7,6,1,5], target = 8,
A solution set is:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]

Example 2:

Input: candidates = [2,5,2,1,2], target = 5,
A solution set is:
[
[1,2,2],
[5]
]

Solution:

/**
 * 使用回溯
 * 加上排序,减少回溯次数,同时也方便剪枝
 * 与上一题不同,这里需要特别注意,题目允许不同层重复,但不允许同层重复
 * 如candidates = [10,1,2,7,6,1,5], target = 8
 * 进行排序后candidates = [1,1,2,5,6,7,10]
 * 可以出现[1,1,6],但不能出现两次[1,2,5]
 * 这就要求在回溯的时候需要增加一个判断来剪枝,即回溯时此时的数字不能与candidates(不是当前序列prob,而是candidates)的上一个数字相同
 * 如果相同就代表回溯前即上一次迭代的时候已经找过该数字的所有可能
 * 排序也是为了可以更好的剪枝,如上
 */
class Solution {

    // 初始化最终结果
    private List<List<Integer>> res = new ArrayList<>();

    // 回溯
    private void backtrack(List<Integer> prob, int[] candidates, int target, int sum, int begin) {

        // 如果符合条件,就把当前序列加到结果集中并返回,结束当前回溯分支
        if (sum == target) {
            // res嵌套了一个ArrayList,最开始初始化最终结果只初始化了最外层的,别忘了把中间的也初始化一下
            res.add(new ArrayList<>(prob));
            return;
        }

        for (int i = begin ; i < candidates.length ; i++) {
            int rs = candidates[i] + sum; // 临时保存当前加法总和

            // 剪枝,如果candidates此时的值与上一个相同,说明会出现同层重复,去掉
            if (i > begin && candidates[i-1] == candidates[i]) continue;

            // 总和比目标值小或者相等,就继续迭代,再下一次迭代中会判断是否已经符合条件
            if (rs <= target) {
                prob.add(candidates[i]);
                // 继续迭代
                // 与上一题目不同,题目说不能同层重复,int begin的位置只能传入i+1,即指针指向下一位进行查找
                backtrack(prob, candidates, target, rs, i+1);
                // 迭代完一个分支,就会进行回溯,删除最后一个元素,进入for循环尝试其他可能性
                prob.remove(prob.size()-1);
            }
            // 总和比目标值大说明当前序列肯定达不到目标,直接结束
            // 这里就利用排序实现了剪枝操作,不需要每个序列都尝试到最后一个,只要出现不符合就结束
            else {
                break;
            }
        }
    }

    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        // 初始化最终结果中的一个可能
        List<Integer> prob = new ArrayList<>();
        // 排序
        Arrays.sort(candidates);
        // 回溯
        backtrack(prob, candidates, target, 0, 0);
        // 全部回溯完毕返回最终结果
        return res;
    }
}

链接:https://leetcode-cn.com/problems/combination-sum-ii

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值