LeetCode 剑指 Offer 38. 字符串的排列 / 31. 下一个排列 / 第 246 场周赛

剑指 Offer 38. 字符串的排列

2021.6.22每日一题

题目描述
输入一个字符串,打印出该字符串中字符的所有排列。


你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。


示例:

输入:s = "abc"
输出:["abc","acb","bac","bca","cab","cba"]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

排列问题,且不能有重复的,排列问题的降重是降低树层部分的重复
当然,去除树枝部分的重复也能得到最后的结果,但是效率低

class Solution {
    //重复卡住了,想想怎么去重来着,先排序
    List<String> list = new ArrayList<>();
    boolean[] uesd;
    public String[] permutation(String s) {
        //排列问题,经典回溯问题
        char[] ss = s.toCharArray();
        Arrays.sort(ss);
        uesd = new boolean[ss.length];
        StringBuilder sb = new StringBuilder();
        backtracking(ss, sb);
        String[] ret = new String[list.size()];
        for(int i = 0; i < list.size(); i++){
            ret[i] = list.get(i);
        }
        return ret;

    }

    public void backtracking(char[] ss, StringBuilder sb){
        if(sb.length() == ss.length){
            list.add(sb.toString());
            return;
        }
        for(int i = 0; i < ss.length; i++){
            if(!uesd[i]){
                //上一个字符和这个字符相同并且没有被使用过,跳过
                if(i > 0 && !uesd[i - 1] && ss[i] == ss[i - 1])
                    continue;
                sb.append(ss[i]);
                uesd[i] = true;
                backtracking(ss, sb);
                sb.deleteCharAt(sb.length() - 1);
                uesd[i] = false;
            }
        }
    }
}

官解中看到,下一个排列的方法,就去看了一下那个题,写在后面
做过那个题以后呢,就可以有另一个思路,就是先将整个字符串排序,然后不断找下一个排列,这样找的好处是不会有重复

class Solution {
    //用下一个排列的思路
    public String[] permutation(String s) {
        List<String> ret = new ArrayList<String>();
        char[] arr = s.toCharArray();
        //先排序
        Arrays.sort(arr);
        
        ret.add(new String(arr));
        while(nextPermutation(arr))
            ret.add(new String(arr));
        
        /*
        do {
            ret.add(new String(arr));
        } while (nextPermutation(arr));
        */

        int size = ret.size();
        String[] retArr = new String[size];
        for (int i = 0; i < size; i++) {
            retArr[i] = ret.get(i);
        }
        return retArr;
    }
    //找下一个排列
    public boolean nextPermutation(char[] arr) {
        int i = arr.length - 2;
        //找到第一个左边小于右边的位置
        while (i >= 0 && arr[i] >= arr[i + 1]) {
            i--;
        }
        //如果整个数组已经是从大到小了,就找不到下一个排列了,返回false
        if (i < 0) {
            return false;
        }
        //找到大于arr[i] 的最小的元素,并交换位置
        int j = arr.length - 1;
        while (j >= 0 && arr[i] >= arr[j]) {
            j--;
        }
        swap(arr, i, j);
        //将后面的子数组翻转
        reverse(arr, i + 1);
        return true;
    }

    public void swap(char[] arr, int i, int j) {
        char temp = arr[i];
        arr[i] = arr[j];
        arr[j] = temp;
    }

    public void reverse(char[] arr, int start) {
        int left = start, right = arr.length - 1;
        while (left < right) {
            swap(arr, left, right);
            left++;
            right--;
        }
    }
}

31. 下一个排列

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

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

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


示例 1:

输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:

输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:

输入:nums = [1,1,5]
输出:[1,5,1]
示例 4:

输入:nums = [1]
输出:[1]

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/next-permutation
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

从后向前遍历,如果有左边的数小于右边的数的情况,就停止,例如当前遍历到的数是nums[i]
然后在 i 的后面从后向前找第一个比它大的数nums[j],并交换它们的位置
然后现在位置 i 后面的数还是从大到小排列,需要翻转过来
这样就形成了下一个排列

class Solution {
    public void nextPermutation(int[] nums) {
        //咋做,主要是空间限制了
        //找到最后一组从左边元素小于右边的组合,然后交换,如果找不到,就把整个数组翻转
        //不行,如果左边比右边小了,得在右边递减的序列中,找到第一个大于这个这个数字的数,然后将大于的这个数字放在开头
        //后面从小到大排列
        int l = nums.length;
        if(l == 1)
            return;
        boolean flag = false;
        for(int i = l - 2; i >= 0; i--){
            if(nums[i] < nums[i + 1]){
                flag = true;
                //在i后面找到比nums[i]大的第一个数,交换
                for(int j = l - 1; j > i; j--){
                    if(nums[j] > nums[i]){
                        swap(nums, i, j);
                        break;
                    }
                }
                //将后面翻转
                for(int j = 1; j <= (l - i) / 2; j++)
                    swap(nums, i + j, l - j);
                break;
            }
        }
        if(!flag){
            for(int i = 0; i < l / 2; i++){
                swap(nums, i, l - 1 - i);
            }
        }
    }

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

第 246 场周赛

这周不知道算不算有所突破,说有突破吧,过了三道题,几次周赛以来的第一次
但是一看最后一题,还是个中等题,瞬间感觉,这次是本来题就简单,排名还是一千多点,所以也不算有突破吧,哭辽

1903. 字符串中的最大奇数

题目描述
给你一个字符串 num ,表示一个大整数。请你在字符串 num 的所有 非空子字符串 中找出 值最大的奇数 ,并以字符串形式返回。如果不存在奇数,则返回一个空字符串 "" 。

子字符串 是字符串中的一个连续的字符序列。


示例 1:

输入:num = "52"
输出:"5"
解释:非空子字符串仅有 "5"、"2" 和 "52" 。"5" 是其中唯一的奇数。
示例 2:

输入:num = "4206"
输出:""
解释:在 "4206" 中不存在奇数。
示例 3:

输入:num = "35427"
输出:"35427"
解释:"35427" 本身就是一个奇数。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/largest-odd-number-in-string
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

就是找最后一个是奇数的字符位置,然后把前面的数输出
这里我从前向后遍历的,麻烦了,直接从后向前找第一个是奇数的位置就可以了

class Solution {
    public String largestOddNumber(String num) {
        int l = num.length();
        StringBuffer sb = new StringBuffer();
        int pre = 0;
        for(int i = 0; i < l; i++){
            char c = num.charAt(i);
            int t = c - '0';
            if(t % 2 == 1){
                sb.append(num.substring(pre, i + 1));
                pre = i + 1;
            }
        }
        return sb.toString();
    }
}

1904. 你完成的完整对局数

题目描述
一款新的在线电子游戏在近期发布,在该电子游戏中,以 刻钟 为周期规划若干时长为 15 分钟 的游戏对局。这意味着,在 HH:00、HH:15、HH:30 和 HH:45 ,将会开始一个新的对局,其中 HH 用一个从 00 到 23 的整数表示。游戏中使用 24 小时制的时钟 ,所以一天中最早的时间是 00:00 ,最晚的时间是 23:59 。

给你两个字符串 startTime 和 finishTime ,均符合 "HH:MM" 格式,分别表示你 进入 和 退出 游戏的确切时间,请计算在整个游戏会话期间,你完成的 完整对局的对局数 。

例如,如果 startTime = "05:20" 且 finishTime = "05:59" ,这意味着你仅仅完成从 05:30 到 05:45 这一个完整对局。而你没有完成从 05:15 到 05:30 的完整对局,因为你是在对局开始后进入的游戏;同时,你也没有完成从 05:45 到 06:00 的完整对局,因为你是在对局结束前退出的游戏。
如果 finishTime 早于 startTime ,这表示你玩了个通宵(也就是从 startTime 到午夜,再从午夜到 finishTime)。

假设你是从 startTime 进入游戏,并在 finishTime 退出游戏,请计算并返回你完成的 完整对局的对局数 。

 

示例 1:

输入:startTime = "12:01", finishTime = "12:44"
输出:1
解释:你完成了从 12:15 到 12:30 的一个完整对局。
你没有完成从 12:00 到 12:15 的完整对局,因为你是在对局开始后的 12:01 进入的游戏。
你没有完成从 12:30 到 12:45 的完整对局,因为你是在对局结束前的 12:44 退出的游戏。
示例 2:

输入:startTime = "20:00", finishTime = "06:00"
输出:40
解释:你完成了从 20:00 到 00:00 的 16 个完整的对局,以及从 00:00 到 06:00 的 24 个完整的对局。
16 + 24 = 40
示例 3:

输入:startTime = "00:00", finishTime = "23:59"
输出:95
解释:除最后一个小时你只完成了 3 个完整对局外,其余每个小时均完成了 4 场完整对局。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/the-number-of-full-rounds-you-have-played
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

这个题看似很简单,但是很多细节吧,写了老半天,感觉应该有更容易的写法
我是先将时间分成小时和分钟分别计算,然后分成开始时间大于结束时间,或者小于结束时间两种情况

class Solution {
    public int numberOfRounds(String startTime, String finishTime) {
        //先取小时和分钟
        int starth = (startTime.charAt(0) - '0') * 10 + (startTime.charAt(1) - '0');
        int startm = (startTime.charAt(3) - '0') * 10 + (startTime.charAt(4) - '0');
        int finishh = (finishTime.charAt(0) - '0') * 10 + (finishTime.charAt(1) - '0');
        int finishm = (finishTime.charAt(3) - '0') * 10 + (finishTime.charAt(4) - '0');
        if(starth <= finishh && startm <= finishm){
            int h = finishh - starth;
            int t1 = check(startm);
            int t2 = check2(finishm);
            return h * 4 + t2 - t1;
        }else if(starth < finishh && startm > finishm){
            int h = finishh - starth - 1;
            int t1 = check(startm);
            int t2 = check2(finishm);
            return h * 4 + 4 - t1 + t2 - 0;
        }else{
            int h = 23 - starth + finishh - 0;
            int t1 = check(startm);
            int t2 = check2(finishm);
            return h * 4 + 4 - t1 + t2 - 0;
        }
        
    }
    
    public int check(int t){
        if(t == 0)
            return 0;
        else if(t <= 15)
            return 1;
        else if(t > 15 && t <= 30)
            return 2;
        else if(t > 30 && t <= 45)
            return 3;
        else if(t > 45 && t <= 59)
            return 4;
        return 0;
    }
    public int check2(int t){
        if(t < 15)
            return 0;
        else if(t >= 15 && t < 30)
            return 1;
        else if(t >= 30 && t < 45)
            return 2;
        else if(t >= 45 && t <= 59)
            return 3;
        return 0;
    }
}

更好的思路,并且代码也更简洁的思路
转换为分钟,同时将结束时间变成当前能结束对局的时间或者将开始时间转换为能开始当前对局的时间
然后计算开始到结束能完成的对局数,也就是时间差除以15

class Solution {
    public int numberOfRounds(String startTime, String finishTime) {
        //转换为分钟
        int start = 60 * Integer.valueOf(startTime.substring(0, 2)) + Integer.valueOf(startTime.substring(3, 5));
        int finish = 60 * Integer.valueOf(finishTime.substring(0, 2)) + Integer.valueOf(finishTime.substring(3, 5));
        if(start > finish)
            finish += 24 * 60;
        //将开始时间转换
        start = (start + 14) / 15 * 15; 
        return Math.max(0, (finish - start) / 15);
    }
}

1905. 统计子岛屿

题目描述
给你两个 m x n 的二进制矩阵 grid1 和 grid2 ,它们只包含 0 (表示水域)和 1 (表示陆地)。一个 岛屿 是由 四个方向 (水平或者竖直)上相邻的 1 组成的区域。任何矩阵以外的区域都视为水域。

如果 grid2 的一个岛屿,被 grid1 的一个岛屿 完全 包含,也就是说 grid2 中该岛屿的每一个格子都被 grid1 中同一个岛屿完全包含,那么我们称 grid2 中的这个岛屿为 子岛屿 。

请你返回 grid2 中 子岛屿 的 数目 。


示例 1:

LeetCode 剑指 Offer 38. 字符串的排列 / 31. 下一个排列 / 第 246 场周赛_第1张图片

输入:grid1 = [[1,1,1,0,0],[0,1,1,1,1],[0,0,0,0,0],[1,0,0,0,0],[1,1,0,1,1]], grid2 = [[1,1,1,0,0],[0,0,1,1,1],[0,1,0,0,0],[1,0,1,1,0],[0,1,0,1,0]]
输出:3
解释:如上图所示,左边为 grid1 ,右边为 grid2 。
grid2 中标红的 1 区域是子岛屿,总共有 3 个子岛屿。
示例 2:

LeetCode 剑指 Offer 38. 字符串的排列 / 31. 下一个排列 / 第 246 场周赛_第2张图片

输入:grid1 = [[1,0,1,0,1],[1,1,1,1,1],[0,0,0,0,0],[1,1,1,1,1],[1,0,1,0,1]], grid2 = [[0,0,0,0,0],[1,1,1,1,1],[0,1,0,1,0],[0,1,0,1,0],[1,0,0,0,1]]
输出:2 
解释:如上图所示,左边为 grid1 ,右边为 grid2 。
grid2 中标红的 1 区域是子岛屿,总共有 2 个子岛屿。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/count-sub-islands
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

简单的思路,在第二个图中深搜找到岛屿,然后判断这个岛屿在第一个图中是否存在

class Solution {
    int[][] grid2;
    boolean[][] used;
    int m;
    int n;
    int[][] direction = {{0,1},{0,-1},{1,0},{-1,0}};
    public int countSubIslands(int[][] grid1, int[][] grid2) {
        //就是在grid2中找岛屿呗,找到以后判断grid1中是否都是1
        this.grid2 = grid2;
        this.m = grid1.length;
        this.n = grid1[0].length;
        int res = 0;
        used = new boolean[m][n];
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(grid2[i][j] == 0 || used[i][j])
                    continue;
                List<int[]> list = new ArrayList<>();

                dfs(i, j, list);
                //找到一个岛屿
                boolean flag = true;
                for(int[] n1 : list){
                    if(grid1[n1[0]][n1[1]] == 0)
                        flag = false;
                }
                if(flag)
                    res++;
            }
        }
        return res;
    }
    
    public void dfs(int i, int j, List<int[]> list){
        if(i < 0 || i >= m || j < 0 || j >= n || used[i][j] || grid2[i][j] == 0)
            return;
        list.add(new int[]{i,j});
        used[i][j] = true;
        for(int k = 0; k < 4; k++){
            int newi = i + direction[k][0];
            int newj = j + direction[k][1];
            dfs(newi, newj, list);
        }
    }
}

1906. 查询差绝对值的最小值

题目描述
一个数组 a 的 差绝对值的最小值 定义为:0 <= i < j < a.length 且 a[i] != a[j] 的 |a[i] - a[j]| 的 最小值。如果 a 中所有元素都 相同 ,那么差绝对值的最小值为 -1 。

比方说,数组 [5,2,3,7,2] 差绝对值的最小值是 |2 - 3| = 1 。注意答案不为 0 ,因为 a[i] 和 a[j] 必须不相等。
给你一个整数数组 nums 和查询数组 queries ,其中 queries[i] = [li, ri] 。对于每个查询 i ,计算 子数组 nums[li...ri] 中 差绝对值的最小值 ,子数组 nums[li...ri] 包含 nums 数组(下标从 0 开始)中下标在 li 和 ri 之间的所有元素(包含 li 和 ri 在内)。

请你返回 ans 数组,其中 ans[i] 是第 i 个查询的答案。

子数组 是一个数组中连续的一段元素。

|x| 的值定义为:

如果 x >= 0 ,那么值为 x 。
如果 x < 0 ,那么值为 -x 。
 

示例 1:

输入:nums = [1,3,4,8], queries = [[0,1],[1,2],[2,3],[0,3]]
输出:[2,1,4,1]
解释:查询结果如下:
- queries[0] = [0,1]:子数组是 [1,3] ,差绝对值的最小值为 |1-3| = 2 。
- queries[1] = [1,2]:子数组是 [3,4] ,差绝对值的最小值为 |3-4| = 1 。
- queries[2] = [2,3]:子数组是 [4,8] ,差绝对值的最小值为 |4-8| = 4 。
- queries[3] = [0,3]:子数组是 [1,3,4,8] ,差的绝对值的最小值为 |3-4| = 1 。
示例 2:

输入:nums = [4,5,2,2,7,10], queries = [[2,3],[0,2],[0,5],[3,5]]
输出:[-1,1,1,3]
解释:查询结果如下:
- queries[0] = [2,3]:子数组是 [2,2] ,差绝对值的最小值为 -1 ,因为所有元素相等。
- queries[1] = [0,2]:子数组是 [4,5,2] ,差绝对值的最小值为 |4-5| = 1 。
- queries[2] = [0,5]:子数组是 [4,5,2,2,7,10] ,差绝对值的最小值为 |4-5| = 1 。
- queries[3] = [3,5]:子数组是 [2,7,10] ,差绝对值的最小值为 |7-10| = 3 。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/minimum-absolute-difference-queries
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路

当时没啥时间了,主要是第二个题我的思路太麻烦了,浪费时间了
这个题咋做呢,主要问题是找到一个范围内,两个最相近的数
那么怎么找呢,应该是排序以后,遍历两个数的差值,但是显然时间复杂度太高
看到最大数为100,想到可以在这个范围内查找是否有当前数
用一个二维数组存储到目前下标i之前存在哪些数
然后查询某个区间内是否有这个数,就可以用前缀和的思想把这个复杂度降为O(1)

class Solution {
    public int[] minDifference(int[] nums, int[][] queries) {
        //还是不知道怎么预处理啊啊啊
        //先想想暴力怎么算,感觉排序,然后查看两个数的差是最直观的方法,能更简单点吗,这样肯定超时
        //最大值为100,提示我们可以遍历1-100的所有值,查看这个范围内有没有这个值
        //那么如何判断这个范围内是否有这个数呢
        //可以用一个二维数组存储当前下标为i之前所有数字出现的情况
        //然后查询某个区间内是否有这个数,就可以用前缀和的思想把这个复杂度降为O(1)
        int n = nums.length;
        int[][] pre = new int[n + 1][101];
        
        //预处理前缀
        for(int i = 1; i <= n; i++){
            for(int j = 1; j <= 100; j++){
                pre[i][j] = pre[i - 1][j];
            }
            pre[i][nums[i - 1]]++;
        }
        
        //然后查询这个范围内的数
        int l = queries.length;
        int[] res = new int[l];
        Arrays.fill(res, Integer.MAX_VALUE);
        for(int i = 0; i < l; i++){
            int left = queries[i][0];
            int right = queries[i][1];
            int t = 0;  //上一个存在的数
            for(int j = 1; j < 101; j++){
                //如果这个范围内没有这个数
                if(pre[right + 1][j] - pre[left][j] <= 0)
                    continue;
                //如果有这个数
                if(t == 0)
                    t = j;
                else{
                    res[i] = Math.min(Math.abs(j - t), res[i]);
                    t = j;
                }
            }
            if(res[i] == Integer.MAX_VALUE)
                res[i] = -1;
        }

        return res;
    }
}
小结

这次周赛怎么说呢,最终感觉还是有点进步的,没有说排名,具体体会到的进步就是感觉自己写代码的能力提高了,也可能是做了几次周赛以后,写起代码来更加认真了,比如那个岛的问题,换做以前,就算知道思路,写代码估计也得错好多次,这次错了一次,错的那一次还是因为grid2,写成grid1了,一眼就能看出来的问题
反正提前批也来了,自己感觉也没怎么准备好,随便投几个先试试吧,继续加油了!!!

你可能感兴趣的:(LeetCode,周赛,leetcode,java)