【LeetCode】 31 - 40题解

31. 下一个排列

实现获取下一个排列的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。

必须原地修改,只允许使用额外常数空间。

以下是一些例子,输入位于左侧列,其相应输出位于右侧列。
1,2,31,3,2
3,2,11,2,3
1,1,51,5,1

    public void nextPermutation(int[] nums) {
        int i = nums.length - 2;
        //找到第一个不再递增的位置
        while (i >= 0 && nums[i + 1] <= nums[i]) {
            i--;
        }
        //如果到了最左边,就直接倒置输出
        if (i < 0) {
            reverse(nums, 0);
            return;
        }
        //找到刚好大于 nums[i]的位置
        int j = nums.length - 1;
        while (j >= 0 && nums[j] <= nums[i]) {
            j--;
        }
        //交换
        swap(nums, i, j);
        //利用倒置进行排序
        reverse(nums, i + 1);

    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[j];
        nums[j] = nums[i];
        nums[i] = temp;
    }

    private void reverse(int[] nums, int start) {
        int i = start, j = nums.length - 1;
        while (i < j) {
            swap(nums, i, j);
            i++;
            j--;
        }
    }

32. 最长有效括号

给定一个只包含 '('')' 的字符串,找出最长的包含有效括号的子串的长度。

示例 1:

输入: "(()"
输出: 2
解释: 最长有效括号子串为 "()"

示例 2:

输入: ")()())"
输出: 4
解释: 最长有效括号子串为 "()()"
    //")()(" --> 1001
    public int longestValidParentheses(String s) {
        Stack<Integer> stack = new Stack<>();
        int n = s.length();
        char[] chs = s.toCharArray();
        int[] mark = new int[n];
        int left = 0, len = 0, ans = 0;
        for(int i = 0; i < n; i ++){
            if(chs[i] == '(') stack.push(i);
            else{
                //没有匹配的左括号的右括号,置1
                if(stack.isEmpty())mark[i] = 1;
                //如果有匹配的左括号,弹出
                else stack.pop();
            }
        }
        // System.out.println(Arrays.toString(mark));
        //多余的左括号,置1
        while(!stack.isEmpty()){
            mark[stack.pop()] = 1;
        }
        //找到最长连续0的长度
        for(int i = 0; i < n; i ++){
            if(mark[i] == 1){
                len = 0;
                continue;
            }
            len ++;
            ans = Math.max(ans , len);
        }
        return ans;
    }

33. 搜索旋转排序数组

假设按照升序排序的数组在预先未知的某个点上进行了旋转。

( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。

搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1

你可以假设数组中不存在重复的元素。

你的算法时间复杂度必须是 O(log n) 级别。

示例 1:

输入: nums = [4,5,6,7,0,1,2], target = 0
输出: 4

示例 2:

输入: nums = [4,5,6,7,0,1,2], target = 3
输出: -1
    public int search(int[] nums, int target) {
        if(nums.length == 0) return -1;
        int l = 0, r = nums.length -1;
        //找到最小值的索引位置
        while(l < r){
            int mid = l + r >> 1;
            if( nums[mid] <= nums[nums.length -1 ]) 
                r = mid;
            else 
                l = mid + 1;
        }//此时l == r  == k
        if( target <= nums[nums.length -1]) 
            //[k,len-1]
            r = nums.length -1; 
        else {
            //左边那段中寻找[0,k-1]
            l = 0;
            r --; //因为上面二分完之后找到了第二段的第一个索引,因此r = r-1
        }
        while(l < r){
            int mid = l + r >> 1;
            if( nums[mid] >= target) r = mid;
            else l = mid + 1;
        }
        //二分得出的是第一个>=target的值,需要判断一下是否就是寻找的那个数
        if( nums[l]  ==  target) return l;
        return -1;
    }

34. 在排序数组中查找元素的第一个和最后一个位置

给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。

你的算法时间复杂度必须是 O(log n) 级别。

如果数组中不存在目标值,返回 [-1, -1]

示例 1:

输入: nums = [5,7,7,8,8,10], target = 8
输出: [3,4]

示例 2:

输入: nums = [5,7,7,8,8,10], target = 6
输出: [-1,-1]
    public int[] searchRange(int[] nums, int t) {
        if(nums.length == 0) return new int[]{-1, -1};
        int l = 0, r = nums.length -1;
        //找第一个数
        while(l < r){
            int mid = l + r >>> 1;
            //假设mid位置为8,前面可能还有,r = mid
            if(nums[mid] >= t){
                r = mid;
            }else{
                l = mid + 1;
            }
        }
        //将会二分出大于等于t的第一个数如果不等于t 则不存在
        if(nums[r] != t) return new int[]{-1, -1};
        int start = r;

        l = 0;
        r = nums.length -1;
        while(l < r){
            //这里条件 l = mid, 注意这里向上取整
            int mid = l + r + 1 >>> 1;
            //找到第一个 <= target的数
            if( nums[mid] <= t){
                l = mid;
            }else{
                r = mid - 1;
            }
        }
        int end = r;
        return new int[]{start, end};
    } 

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

你可以假设数组中无重复元素。

示例 1:

输入: [1,3,5,6], 5
输出: 2

示例 2:

输入: [1,3,5,6], 2
输出: 1
    public int searchInsert(int[] nums, int target) {
        if(nums.length == 0 || nums[nums.length-1] < target) return nums.length;
        int l = 0, r = nums.length -1;
        //找到 >= target的第一个坐标
        while( l < r){
            int mid = l + r >> 1;
            if( nums[mid] >= target){
                r = mid;
            }else{
                l = mid + 1;
            }
        }
        return r;
    }

36. 有效的数独

判断一个 9x9 的数独是否有效。只需要根据以下规则,验证已经填入的数字是否有效即可。

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

img

上图是一个部分填充的有效的数独。

数独部分空格内已填入了数字,空白格用 '.' 表示。

示例 2:

输入:
[
  ["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"]
]
输出: false
解释: 除了第一行的第一个数字从 5 改为 8 以外,空格内其他数字均与 示例1 相同。
     但由于位于左上角的 3x3 宫内有两个 8 存在, 因此这个数独是无效的。

说明:

  • 一个有效的数独(部分已被填充)不一定是可解的。
  • 只需要根据以上规则,验证已经填入的数字是否有效即可。
  • 给定数独序列只包含数字 1-9 和字符 '.'
  • 给定数独永远是 9x9 形式的。
    public boolean isValidSudoku(char[][] board) {
        // 记录某行,某位数字是否已经被摆放
        boolean[][] row = new boolean[9][9];
        // 记录某列,某位数字是否已经被摆放
        boolean[][] col = new boolean[9][9];
        // 记录某 3x3 宫格内,某位数字是否已经被摆放
        boolean[][] block = new boolean[9][9];
        for(int i = 0; i < 9; i ++){
            for(int j = 0; j < 9; j ++){
                //.的情况不用考虑
                if(board[i][j] == '.') continue;
                //数字为1-9
                int num = board[i][j] - '1';
                //映射到子数独中
                int index = i / 3 * 3 + j / 3;
                if(row[i][num] || col[j][num] || block[index][num]) 
                    return false;
                else{
                    row[i][num] = true;
                    col[j][num] = true;
                    block[index][num] = true;
                }
            }
        }
        return true;
    }

37. 解数独

编写一个程序,通过填充空格来解决数独问题。

一个数独的解法需遵循如下规则

  1. 数字 1-9 在每一行只能出现一次。
  2. 数字 1-9 在每一列只能出现一次。
  3. 数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。

空白格用 '.' 表示。

img

一个数独。

img

答案被标成红色。

提示:

  • 给定的数独序列只包含数字 1-9 和字符 '.'
  • 你可以假设给定的数独只有唯一解。
  • 给定数独永远是 9x9 形式的。
    public void solveSudoku(char[][] board) {
        boolean flag = dfs(board, 0 ,0);
        return;
    }
    boolean dfs(char[][] board, int i , int j){
        if(i == 9) return true;
        //到最后一列,换下一行
        if(j == 9) return dfs(board, i + 1, 0); 
        if(board[i][j] != '.') return dfs(board, i , j + 1);
        //选择列表1 - 9
        for(char c = '1'; c <= '9'; c ++){ 
            if (!isValid(board, i ,j ,c)) continue; 
            //选择
            board[i][j] = c; 
            //下一层
            if (dfs(board, i, j + 1)) return true;
            //撤销选择
            board[i][j] = '.';
        }
        return false;
    }
    boolean isValid(char[][] board , int x , int y ,char n){
        for (int i = 0;i < 9; i ++){
            //列重复
            if (board[i][y] == n) return false;
            //行重复
            if (board[x][i] == n) return false;
        }
        //找到九宫格左上元素,依次遍历
        for (int i = x / 3 * 3; i < x / 3 * 3 + 3; i ++){ 
            for (int j = y / 3 * 3;j < y / 3 * 3 + 3; j ++){
                if (board[i][j] == n) return false;
            }
        }
        return true;
    }

38. 外观数列

给定一个正整数 n(1 ≤ n ≤ 30),输出外观数列的第 n 项。

注意:整数序列中的每一项将表示为一个字符串。

「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。前五项如下:

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

第一项是数字 1

描述前一项,这个数是 1 即 “一个 1 ”,记作 11

描述前一项,这个数是 11 即 “两个 1 ” ,记作 21

描述前一项,这个数是 21 即 “一个 2 一个 1 ” ,记作 1211

描述前一项,这个数是 1211 即 “一个 1 一个 2 两个 1 ” ,记作 111221

示例 1:

输入: 1
输出: "1"
解释:这是一个基本样例。

示例 2:

输入: 4
输出: "1211"
解释:当 n = 3 时,序列是 "21",其中我们有 "2" 和 "1" 两组,"2" 可以读作 "12",也就是出现频次 = 1 而 值 = 2;类似 "1" 可以读作 "11"。所以答案是 "12" 和 "11" 组合在一起,也就是 "1211"。
    public String countAndSay(int n) {
        String s = "1";
        //循环n - 1次
        for(int i = 0; i < n - 1 ; i ++){
            StringBuilder sb = new StringBuilder();
            //每一次记录相同一段的个数
            for(int j = 0; j < s.length(); j ++){
                int k = j;
                while(k < s.length() && s.charAt(k) == s.charAt(j)){
                    k++;
                }
                //个数
                String count = k - j + "";
                //个数 + 字符
                sb.append(count + s.charAt(j));
                j = k - 1;
            }
            s= sb.toString();
        }
        return s;
    }

39. 组合总和

给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的数字可以无限制重复被选取。

说明:

  • 所有数字(包括 target)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
  [7],
  [2,2,3]
]

https://www.cnblogs.com/summerday152/p/13615904.html

    public List<List<Integer>> combinationSum(int[] arr, int t) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        Arrays.sort(arr);//排序是剪枝的前提
        dfs(res,path,0,arr,t);
        return res;
    }
    
    void dfs(List<List<Integer>> res,List<Integer> path,int s,int[] arr, int t){
        if(t <= 0){
            if(t == 0){
                res.add(new ArrayList<>(path));
            }
            return;
        }
        for(int i = s; i < arr.length; i++){
            if(arr[i] > t){ //由于数组已经有序,当前这个数应该小于等于剩余数t
                break;
            }
            path.add(arr[i]);
            dfs(res,path,i,arr,t-arr[i]); //因为
            path.remove(path.size()-1);
        }  
    }

40. 组合总和 II

https://www.cnblogs.com/summerday152/p/13615904.html

给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次。

说明:

  • 所有数字(包括目标数)都是正整数。
  • 解集不能包含重复的组合。

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]
    public List<List<Integer>> combinationSum2(int[] arr, int t) {
        List<List<Integer>> res = new ArrayList<>();
        List<Integer> path = new ArrayList<>();
        Arrays.sort(arr);//排序是剪枝的前提
        dfs(res,path,0,arr,t);
        return res;
    }
	// s 可以看成层数, i可以看成这一层从第几个开始
    void dfs(List<List<Integer>> res,List<Integer> path,int s,int[] arr, int t){
        if(t <= 0){
            if(t == 0){
                res.add(new ArrayList<>(path));
            }
            return;
        }
        for(int i = s; i < arr.length; i++){ 
            
            if(arr[i] > t){ //由于数组已经有序,当前这个数应该小于等于剩余数t
                break;
            }
            //保证递归树的同一个层级不出现相同的元素
            if(i > s && arr[i] == arr[i - 1]) continue;
            path.add(arr[i]);
            dfs(res,path,i+1,arr,t-arr[i]); 
            path.remove(path.size()-1);
        }  
    }
posted @ 2020-10-05 20:40  天乔巴夏丶  阅读(196)  评论(0编辑  收藏  举报