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

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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:

在这里插入图片描述

输入: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:

在这里插入图片描述

输入: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 以上是关于LeetCode 剑指 Offer 38. 字符串的排列 / 31. 下一个排列 / 第 246 场周赛的主要内容,如果未能解决你的问题,请参考以下文章

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

[LeetCode]剑指 Offer 38. 字符串的排列

[LeetCode]剑指 Offer 38. 字符串的排列

LeetCode 剑指Offer 38[回溯 递归] 字符串的排列 HERODING的LeetCode之路

LeetCode1269. 停在原地的方案数 / 剑指 Offer 38. 字符串的排列 / 216. 组合总和 III / 剑指 Offer 39. 数组中出现次数超过一半的数字/229. 求众数(

剑指offer38(Java)-字符串的排列(中等)