月度归档: 2022年6月

LeetCode刷题【60】排列序列

给出集合 [1,2,3,...,n],其所有元素共有 n! 种排列。

按大小顺序列出所有排列情况,并一一标记,当 n = 3 时, 所有排列如下:

  1. "123"
  2. "132"
  3. "213"
  4. "231"
  5. "312"
  6. "321"

给定 n 和 k,返回第 k 个排列。

 

示例 1:

输入:n = 3, k = 3
输出:"213"

示例 2:

输入:n = 4, k = 9
输出:"2314"

示例 3:

输入:n = 3, k = 1
输出:"123"

 

提示:

  • 1 <= n <= 9
  • 1 <= k <= n!
Related Topics
  • 递归
  • 数学

  • 👍 682
  • 👎 0
  • 逆康托展开 与 康托展开【Cantor Expansion】
    关于本题原型可以参见下康托展开【Cantor Expansion】 ,就是原题了,具体内容不做更多赘述

    思路

    1. 已知现有数字n,那么按字典序排序组合后我们知道。初步的根据第1位的字符可以分成n段,每段长度为(n-1)!
    2. 继而我们这当中的某一段中,我又可以如上分成(n-1)段,因为前面已经用了一位数字了,而这(n-1)段的每段长度为(n-2)!
    3. 那么按照题意,我们可以按照从大区间到小区间逐步缩小范围的方法来处理
    4. 首先除以(n-1)!,得到第一位的数字,记下余数
    5. 用上一步的余数除以(n-2)!,知道了第二位数字,继续记下余数用来处理第三位数字
    6. 每次除以(n-?)!得到的结果是确定的未使用的数字的顺序,即在确定这个数字的时候需要跳过前面已经使用过的数字,见方法findNthNum(int nth)
    7. 最终还剩下一个数字,就是最后一位了

    容易掉的坑,k--,上来记得要减下,被这步坑了好久,减一之后直接除了便于取整取余划分区间

    代码

    class Solution {
    
        boolean[] usedNums;
    
        public String getPermutation(int n, int k) {
            k--;
            usedNums = new boolean[n+1];
            int[] factorial = new int[n];
            factorial[n-1] = 1;
            for (int i = n-2; i >= 0; i--) {
                factorial[i] = factorial[i+1] * (n-i);
            }
            char[] ans = new char[n];
            int idx = 0;
            while (++idx < n){
                int i = k / factorial[idx];
                k %= factorial[idx];
                int num = findNthNum(i+1);
                ans[idx-1] = (char)('0'+ num);
            }
            ans[idx-1] = (char)('0'+ findNthNum(1));
            return new String(ans);
        }
    
        int findNthNum(int nth){
            for (int i = 1; i < usedNums.length; i++) {
                if(!usedNums[i]){
                    nth--;
                    if (nth==0){
                        usedNums[i] = true;
                        return i;
                    }
                }
            }
            return -1;
        }
    }

    以上其实是逆康托展开的过程,根据第k的数值,确定这个数值是什么

    根据定义,与这一过程相对的是康托展开的过程,根据数值确定这个数值的排序

    那么相应思路为

    1. 从头开始确定当前数字在未使用数字中的排序i(从0下标开始或者你认为从正数1开始排序i'那么前面有i'-1组),那么前面有i * (n-1)!
    2. 第二位同样假设第二位数字在未使用数字中的排序i_1(下标),第二位则对应为i_1 * (n-2)!
    3. 后面依次叠加
    4. 最终得到的结果即为对应的排序

    LeetCode刷题【807】保持城市天际线

    给你一座由 n x n 个街区组成的城市,每个街区都包含一座立方体建筑。给你一个下标从 0 开始的 n x n 整数矩阵 grid ,其中 grid[r][c] 表示坐落于 rc 列的建筑物的 高度

    城市的 天际线 是从远处观察城市时,所有建筑物形成的外部轮廓。从东、南、西、北四个主要方向观测到的 天际线 可能不同。

    我们被允许为 任意数量的建筑物 的高度增加 任意增量(不同建筑物的增量可能不同) 。 高度为 0 的建筑物的高度也可以增加。然而,增加的建筑物高度 不能影响 从任何主要方向观察城市得到的 天际线

    不改变 从任何主要方向观测到的城市 天际线 的前提下,返回建筑物可以增加的 最大高度增量总和

     

    示例 1:

    输入:grid = [[3,0,8,4],[2,4,5,7],[9,2,6,3],[0,3,1,0]]
    输出:35
    解释:建筑物的高度如上图中心所示。
    用红色绘制从不同方向观看得到的天际线。
    在不影响天际线的情况下,增加建筑物的高度:
    gridNew = [ [8, 4, 8, 7],
                [7, 4, 7, 7],
                [9, 4, 8, 7],
                [3, 3, 3, 3] ]
    

    示例 2:

    输入:grid = [[0,0,0],[0,0,0],[0,0,0]]
    输出:0
    解释:增加任何建筑物的高度都会导致天际线的变化。
    

     

    提示:

    • n == grid.length
    • n == grid[r].length
    • 2 <= n <= 50
    • 0 <= grid[r][c] <= 100
    Related Topics
    • 贪心
    • 数组
    • 矩阵

  • 👍 229
  • 👎 0
  • 题面的示例已经给出了基本实现思路
    题面的示例已经给出了基本实现思路,顺着这个思路写代码就对了、

    代码

    class Solution {
        public int maxIncreaseKeepingSkyline(int[][] grid) {
            int[] rowMin = new int[grid.length];
            int[] colMin = new int[grid[0].length];
            for (int row = 0; row < grid.length; row++) {
                for (int col = 0; col < grid[row].length; col++) {
                    int height = grid[row][col];
                    rowMin[row] = Math.max(rowMin[row],height);
                    colMin[col] = Math.max(colMin[col],height);
                }
            }
            int ans = 0;
            for (int row = 0; row < grid.length; row++) {
                for (int col = 0; col < grid[row].length; col++) {
                    ans += Math.min(rowMin[row],colMin[col]) - grid[row][col];
                }
            }
            return ans;
        }
    }

    LeetCode刷题【637】二叉树的层平均值

    给定一个非空二叉树的根节点 root , 以数组的形式返回每一层节点的平均值。与实际答案相差 10-5 以内的答案可以被接受。

     

    示例 1:

    输入:root = [3,9,20,null,null,15,7]
    输出:[3.00000,14.50000,11.00000]
    解释:第 0 层的平均值为 3,第 1 层的平均值为 14.5,第 2 层的平均值为 11 。
    因此返回 [3, 14.5, 11] 。
    

    示例 2:

    输入:root = [3,9,20,15,7]
    输出:[3.00000,14.50000,11.00000]
    

     

    提示:

    • 树中节点数量在 [1, 104] 范围内
    • -231 <= Node.val <= 231 - 1
    Related Topics
    • 深度优先搜索
    • 广度优先搜索
    • 二叉树

  • 👍 354
  • 👎 0
  • bfs

    class Solution {
    
        public List<Double> averageOfLevels(TreeNode root) {
            List<Double> res = new ArrayList<>();
            if (null == root){
                return res;
            }
            Queue<TreeNode> queue = new LinkedList<>();
            queue.offer(root);
            while (!queue.isEmpty()){
                int size = queue.size();
                double sum = 0;
                int i=-1;
                while (++i < size){
                    TreeNode node = queue.poll();
                    sum += (double) node.val;
                    if (node.left!=null){
                        queue.offer(node.left);
                    }
                    if (node.right!=null){
                        queue.offer(node.right);
                    }
                }
                res.add(sum/size);
            }
            return res;
        }
    }

    LeetCode刷题【670】最大交换

    给定一个非负整数,你至多可以交换一次数字中的任意两位。返回你能得到的最大值。

    示例 1 :

    输入: 2736
    输出: 7236
    解释: 交换数字2和数字7。
    

    示例 2 :

    输入: 9973
    输出: 9973
    解释: 不需要交换。
    

    注意:

    1. 给定数字的范围是 [0, 108]
    Related Topics
    • 贪心
    • 数学

  • 👍 244
  • 👎 0
  • 凑合写了个费劲巴拉的方法
    排序之后跟原序列对比,如果不同则交换位置

    交换的新数字取最右边出现的位置

    class Solution {
    
    
    
        public int maximumSwap(int num) {
            int size = stringSize(num);
            if (size==1){
                return num;
            }
            int[] arr = new int[size];
            int[] map = new int[10];
            Arrays.fill(map,-1);
            int idx = size;
            while ( num != 0){
                arr[--idx] = num%10;
                num /= 10;
                if (map[arr[idx]]==-1){
                    map[arr[idx]]=idx;
                }
            }
            PriorityQueue<int[]> queue = new PriorityQueue<>(new Comparator<int[]>() {
                @Override
                public int compare(int[] o1, int[] o2) {
                    return o2[0]-o1[0];
                }
            });
    
            for (int i = arr.length-1; i >=0; i--) {
                queue.offer(new int[]{arr[i],map[arr[i]]});
            }
    
            int i = -1;
            int ans = 0;
            while (!queue.isEmpty() && queue.peek()[0] == arr[++i]){
                ans = ans * 10 + queue.poll()[0];
            }
            if (queue.isEmpty()){
                return ans;
            }
            ans = ans * 10 + queue.peek()[0];
            int[] swapIdx = new int[]{i,queue.peek()[1]};
            while (++i < arr.length){
                if (i == swapIdx[1]){
                    ans = ans * 10 + arr[swapIdx[0]];
                }else{
                    ans = ans * 10 + arr[i];
                }
    
            }
    
            return ans;
        }
    
    
    
    
        final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                99999999, 999999999, Integer.MAX_VALUE };
    
        // Requires positive x
        static int stringSize(int x) {
            for (int i=0; ; i++)
                if (x <= sizeTable[i])
                    return i+1;
        }
    }

    LeetCode刷题【748】最短补全词

    给你一个字符串 licensePlate 和一个字符串数组 words ,请你找出 words 中的 最短补全词

    补全词 是一个包含 licensePlate 中所有字母的单词。忽略 licensePlate 中的 数字和空格 不区分大小写。如果某个字母在 licensePlate 中出现不止一次,那么该字母在补全词中的出现次数应当一致或者更多。

    例如:licensePlate = "aBc 12c",那么它的补全词应当包含字母 'a''b' (忽略大写)和两个 'c' 。可能的 补全词"abccdef""caaacab" 以及 "cbca"

    请返回 words 中的 最短补全词 。题目数据保证一定存在一个最短补全词。当有多个单词都符合最短补全词的匹配条件时取 words第一个 出现的那个。

     

    示例 1:

    输入:licensePlate = "1s3 PSt", words = ["step", "steps", "stripe", "stepple"]
    输出:"steps"
    解释:最短补全词应该包括 "s"、"p"、"s"(忽略大小写) 以及 "t"。
    "step" 包含 "t"、"p",但只包含一个 "s",所以它不符合条件。
    "steps" 包含 "t"、"p" 和两个 "s"。
    "stripe" 缺一个 "s"。
    "stepple" 缺一个 "s"。
    因此,"steps" 是唯一一个包含所有字母的单词,也是本例的答案。

    示例 2:

    输入:licensePlate = "1s3 456", words = ["looks", "pest", "stew", "show"]
    输出:"pest"
    解释:licensePlate 只包含字母 "s" 。所有的单词都包含字母 "s" ,其中 "pest"、"stew"、和 "show" 三者最短。答案是 "pest" ,因为它是三个单词中在 words 里最靠前的那个。
    

     

    提示:

    • 1 <= licensePlate.length <= 7
    • licensePlate 由数字、大小写字母或空格 ' ' 组成
    • 1 <= words.length <= 1000
    • 1 <= words[i].length <= 15
    • words[i] 由小写英文字母组成
    Related Topics
    • 数组
    • 哈希表
    • 字符串

  • 👍 116
  • 👎 0
  • 哈希表,字符统计,简单,特纯粹的哈希表统计

    1. 统计licensePlate中字符出现次数
    2. 遍历words[] 分别统计每个word的字符出现次数
    3. 对比两者统计结果,需要满足
      • licensePlate中出现的字符,在word中出现的次数要大于等于在licensePlate中出现的次数
      • 判断需要返回的结果ans 和当前word的长度,取较小的一个,相等的情况不变,即长度相等取现出现的

    就…纯粹的哈希表统计

    代码

    class Solution {
        public String shortestCompletingWord(String licensePlate, String[] words) {
            int[] arr = charCnt(licensePlate);
            String ans = null;
            for (String word : words) {
                int[] wArr = charCnt(word);
                boolean flag = true;
                for (int i = 0; flag && i < arr.length; i++) {
                    if (arr[i]>0 && wArr[i] <arr[i]){
                        flag = false;
                    }
                }
                ans = flag && ( ans == null || word.length() < ans.length()) ? word : ans;
            }
            return ans;
        }
    
        private int[] charCnt(String str){
            int[] arr = new int[26];
            for (int i = 0; i < str.length(); i++) {
                char c = str.charAt(i);
                if (c >= 'a' && c <= 'z'){
                    arr[str.charAt(i)-'a']++;
                }
                if (c >= 'A' && c <= 'Z'){
                    arr[str.charAt(i)-'A']++;
                }
            }
            return arr;
        }
    }

    LeetCode刷题【剑指 Offer 15】二进制中1的个数

    编写一个函数,输入是一个无符号整数(以二进制串的形式),返回其二进制表达式中数字位数为 ‘1’ 的个数(也被称为 汉明重量).)。

     

    提示:

    • 请注意,在某些语言(如 Java)中,没有无符号整数类型。在这种情况下,输入和输出都将被指定为有符号整数类型,并且不应影响您的实现,因为无论整数是有符号的还是无符号的,其内部的二进制表示形式都是相同的。
    • 在 Java 中,编译器使用 二进制补码 记法来表示有符号整数。因此,在上面的 示例 3 中,输入表示有符号整数 -3

     

    示例 1:

    输入:n = 11 (控制台输入 00000000000000000000000000001011)
    输出:3
    解释:输入的二进制串 00000000000000000000000000001011 中,共有三位为 '1'。
    

    示例 2:

    输入:n = 128 (控制台输入 00000000000000000000000010000000)
    输出:1
    解释:输入的二进制串 00000000000000000000000010000000 中,共有一位为 '1'。
    

    示例 3:

    输入:n = 4294967293 (控制台输入 11111111111111111111111111111101,部分语言中 n = -3)
    输出:31
    解释:输入的二进制串 11111111111111111111111111111101 中,共有 31 位为 '1'。

     

    提示:

    • 输入必须是长度为 32二进制串

     

    注意:本题与主站 191 题相同:https://leetcode-cn.com/problems/number-of-1-bits/

    Related Topics
    • 位运算

  • 👍 255
  • 👎 0
  • 位运算
    简单
    1&1 = 1
    1&0 = 0
    0&0 = 0

    public class Solution {
        // you need to treat n as an unsigned value
        public int hammingWeight(int n) {
            int ans = 0;
            int p = 0;
            while (++p <= 32){
                ans += n & 1;
                n >>= 1;
            }
            return ans;
        }
    }

    LeetCode刷题【剑指 Offer 43】1~n 整数中 1 出现的次数

    输入一个整数 n ,求1~n这n个整数的十进制表示中1出现的次数。

    例如,输入12,1~12这些整数中包含1 的数字有1、10、11和12,1一共出现了5次。

     

    示例 1:

    输入:n = 12
    输出:5
    

    示例 2:

    输入:n = 13
    输出:6

     

    限制:

    • 1 <= n < 2^31

    注意:本题与主站 233 题相同:https://leetcode-cn.com/problems/number-of-digit-one/

    Related Topics
    • 递归
    • 数学
    • 动态规划

  • 👍 343
  • 👎 0
  • 按位计算统计,(【左侧数值】当前位1【右侧数值】)
    直接拿一个数字31261分析下,过程如下

        3      1      2      6      1
                                    3126 + 1
                             3130
                      3200
                3000 + 261 + 1
        10000
    1. 最低位当前数字是1,这个位置上1粗线的次数由他前面的数字决定,所以我们可以非常明白的知道,这个时候这一位一共出现了3127次字符1
      • 分别为有前缀的3126 次
      • 和无前缀的 当数字n = 1的时候的1次
    2. 倒数第二位数字6,这个数字大于1,根据上面算最低位的时候的情况分析依据,我们可以知道这个位置一共出现了3130次,这个结果可以也可以分为两部分
      • 有前缀的时候3120种情况,数字xxx1x的情况,当前位前面有312种情况,而又因为当前位大于1,后面一位的0-9的10种情况,即为3120种
      • 无前缀的时候1019的10种情况
      • 这样,我们暂时把这部分换为另外一种算法,即为( 312 + 1 ) * 10 种。
    3. 再往前一位数字2,同前面的数字6的情况,因为大于1所以结果为(31 + 1) * 100
    4. 再次往前一位,数字1,虽然前面为1的情况我们已经分析过了,但是那个是不完整的,在这里,我们将完整分析下为1的时候的情况
      • 因为后面还有其他数字,整个31xxx区段是还没完全结束的,所以我们不能直接得到结果为4000
      • 那么前面部分的情况为012的3000种情况,也可以认为如果左侧数字是k,那么现在就有k000
      • 又因为之前说了这个31xxx区段是还没完全结束,所以应当还有当前位后面部分的261种情况,外加如果后面都为0的情况
      • 那么这个位置数字1出现的次数就是3000 + 261 + 1 = 3262
    5. 最高位为3,大于1,可以直白的知道有10000次出现了字符1,那么按照前面的规律,我们假定最高位为0,按照之前的方法可以得到( 0 + 1 ) * 10000
    6. 举例数据中没有出现的当前位为0的情况,这个就比较简单了,直接就是左侧数据出现的次数了
      • 如 30261 中0的位置,
      • 就是数字1xxx,11xxx,21xxx的3000种情况

    计算中间的问题

    我们在计算中使用了一个辅助变量10,100,100010000,这个数值是比入参数字大一位的

    又因为题目给的入参条件1 <= n < 2^31

    所以中间计算过程我就偷了个懒给转成了long型数据

    另外,葱低往高取每一位的数字的时候可以直接用余10,之后再除10的方法,我这边一开始是准备转成数组直接根据数组计算左右侧数据影响的当前位置1出现次数的,最后就还是没改了

    代码

    class Solution {
        /*
        3      1      2      6      1
                                    3127
                             3130
                      3200
                3000 + 261 + 1
         10000
         */
    
    
        final static int [] sizeTable = { 9, 99, 999, 9999, 99999, 999999, 9999999,
                99999999, 999999999, Integer.MAX_VALUE };
    
        public int countDigitOne(int n) {
            long ans = 0;
            long num = (long) n;
            int size = numSize(n);
            int[] arr = new int[size];
            int idx = size-1;
            while (n != 0){
                arr[idx] = n%10;
                n /= 10;
                idx--;
            }
            idx = size-1;
            long tmp = 10;
            while (idx >= 0){
                long r = 0;
                if (arr[idx] == 0){
                    r = ( num / tmp ) * ( tmp / 10 );
                }
                if (arr[idx] == 1){
                    r = ( num / tmp ) * ( tmp / 10 ) + ( num % (tmp / 10) ) + 1;
                }
                if (arr[idx] > 1){
                    r = ( (num / tmp)+1 ) * ( tmp / 10 );
                }
                ans += r;
                tmp *= 10;
                idx--;
            }
    
            return (int)ans;
        }
    
    
    
        static int numSize(int x) {
            for (int i=0; ; i++)
                if (x <= sizeTable[i])
                    return i+1;
        }
    }