LeetCode刷题笔记(Java)---第381-400题

文章目录

      • 前言
      • 笔记导航
      • 381.O(1) 时间插入、删除和获取随机元素 - 允许重复
      • 382. 链表随机节点
      • 383.赎金信
      • 384. 打乱数组
      • 385.迷你语法分析器
      • 386. 字典序排数
      • 387. 字符串中的第一个唯一字符
      • 388. 文件的最长绝对路径
      • 389. 找不同
      • 390. 消除游戏
      • 391. 完美矩形
      • 392. 判断子序列
      • 393. UTF-8 编码验证
      • 394. 字符串解码
      • 395. 至少有K个重复字符的最长子串
      • 396. 旋转函数
      • 397. 整数替换
      • 398. 随机数索引
      • 399.除法求值
      • 400. 第N个数字

前言

需要开通vip的题目暂时跳过

笔记导航

点击链接可跳转到所有刷题笔记的导航链接

381.O(1) 时间插入、删除和获取随机元素 - 允许重复

设计一个支持在平均 时间复杂度 O(1) 下, 执行以下操作的数据结构。

注意: 允许出现重复元素。

  • insert(val):向集合中插入元素 val。

  • remove(val):当 val 存在时,从集合中移除一个 val。

  • getRandom:从现有集合中随机获取一个元素。每个元素被返回的概率应该与其在集合中的数量呈线性相关。

  • 解答

    class RandomizedCollection {
        HashMap<Integer, HashSet<Integer>> map;
        ArrayList<Integer> list;
        /** Initialize your data structure here. */
        public RandomizedCollection() {
            map = new HashMap<>();
            list = new ArrayList<>();
        }
    
        /** Inserts a value to the collection. Returns true if the collection did not already contain the specified element. */
        public boolean insert(int val) {
            if(!map.containsKey(val)){
                list.add(val);
                HashSet<Integer> set = new HashSet<>();
                set.add(list.size()-1);
                map.put(val,set);
                return true;
            }else{
                HashSet<Integer> set = map.get(val);
                list.add(val);
                set.add(list.size()-1);
                map.put(val,set);
                return false;
            }
        }
    
        /** Removes a value from the collection. Returns true if the collection contained the specified element. */
        public boolean remove(int val) {
            if(map.containsKey(val)){
                HashSet<Integer> set = map.get(val);
                Integer index = set.iterator().next();//删掉这个索引位置的val
                set.remove(index);//删除这个索引
                if(set.size() == 0) map.remove(val);
                if(index != list.size()-1) {
                    int lastNumber = list.get(list.size()-1);
                    HashSet<Integer> set1 = map.get(lastNumber);
                    set1.remove(list.size() - 1);//最后一个数字的索引删除
                    list.set(index, lastNumber);//最后一个数字替换要删除的位置
                    set1.add(index);//新的索引加入。
                }
                list.remove(list.size() - 1);
                return true;
            }else return false;
        }
    
        /** Get a random element from the collection. */
        public int getRandom() {
            Random random = new Random();
            int index = random.nextInt(list.size());
            return list.get(index);
        }
    }
    
  • 分析

    1. 和380题目很像,只是这道题目可以重复,所以hashMap 记录的value不能是一个索引值

      而应该是一个索引集合,为了查找的速度快 所以 索引集合使用HashSet

    2. 增加和删除的过程和上一题类似

  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第1张图片

382. 链表随机节点

给定一个单链表,随机选择链表的一个节点,并返回相应的节点值。保证每个节点被选的概率一样。

进阶:
如果链表十分大且长度未知,如何解决这个问题?你能否使用常数级空间复杂度实现?

LeetCode刷题笔记(Java)---第381-400题_第2张图片

  • 解答

    		ArrayList<Integer> list = new ArrayList<>();
        /** @param head The linked list's head.
            Note that the head is guaranteed to be not null, so it contains at least one node. */
        public Solution(ListNode head) {
            ListNode p = head;
            while(p!=null){
                list.add(p.val);
                p = p.next;
            }
        }
        
        /** Returns a random node's value. */
        public int getRandom() {
            Random random = new Random();
            int index  = random.nextInt(list.size());
            return list.get(index);
        }
    		
    		//方法二
    		private ListNode head;
        public Solution(ListNode head) {
           this.head = head;
        }
     
        public int getRandom() {
            int res = head.val;
            ListNode no = head.next;
            int i = 2;
            Random random = new Random();
            while(no!=null){
                if(random.nextInt(i) == 0){
                    res = no.val;
                }
                i++;
                no = no.next;
            }
            return res;
        }
    
  • 分析

    1. 将链表里的数字存在数组中,然后根据随机数生成一个索引,返回索引对应的值。

    2. 方法二是使用常数级空间复杂度 蓄水池出样算法

    3. random.nextInt(i)==0这行代码决定了最后输出的res,即所有满足该条件且i最大的no.val会被输出。那么我们不妨以i的值降序来看这里的while循环:

      • 设链表长n,则i==n时执行res=no.val的概率为1/n,即最终选择第n的点的概率是1/n;
      • 而最终选择第n-1个点的情况,就要求第n个点不被选,且i==n-1时执行了res=no.val,即(1-1/n)*(1/(n-1))=1/n…

      以此类推,所有n个点被选的概率都是1/n。

  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第3张图片
    方法二LeetCode刷题笔记(Java)---第381-400题_第4张图片

383.赎金信

给定一个赎金信 (ransom) 字符串和一个杂志(magazine)字符串,判断第一个字符串 ransom 能不能由第二个字符串 magazines 里面的字符构成。如果可以构成,返回 true ;否则返回 false。

(题目说明:为了不暴露赎金信字迹,要从杂志上搜索各个需要的字母,组成单词来表达意思。杂志字符串中的每个字符只能在赎金信字符串中使用一次。)
LeetCode刷题笔记(Java)---第381-400题_第5张图片

  • 解答

    //方法一
    public boolean canConstruct(String ransomNote, String magazine) {
            char[] chars = ransomNote.toCharArray();
            char[] chars2 = magazine.toCharArray();
            Arrays.sort(chars);
            Arrays.sort(chars2);
            int index = 0;
            int index2 = 0;
            while(index < chars.length && index2 < chars2.length){
                if(chars[index] == chars2[index2]){
                    index++;
                    index2++;
                }else if(chars[index] > chars2[index2]){
                    index2++;
                }else {
                    return false;
                }
            }
            return index == chars.length;
    		}
    //方法二
    public boolean canConstruct(String ransomNote, String magazine) {
            HashMap<Character,Integer> map2 = new HashMap<>();
            char[] chars = magazine.toCharArray();
            for(char c : chars){
                int sum = map2.getOrDefault(c,0);
                sum++;
                map2.put(c,sum);
            }
            char[] chars2 = ransomNote.toCharArray();
            for(char c : chars2){
                if(!map2.containsKey(c)){
                    return false;
                }else{
                    int sum = map2.get(c);
                    sum--;
                    if(sum == 0)map2.remove(c);
                    else map2.put(c,sum);
                }
            }
            return true;
        }
    // 方法三
    public boolean canConstruct(String ransomNote, String magazine) {
            char[] chars = new char[26];
            for(int i = 0;i < magazine.length();i++){
                chars[magazine.charAt(i) - 'a']++; 
            }
            for(int i = 0;i < ransomNote.length();i++){
                if(chars[ransomNote.charAt(i)-'a'] == 0)return false;
                else chars[ransomNote.charAt(i)-'a']--;
            }
            return true;
        }
    
  • 分析

    1. 方法一
    2. 将两个字符串转换成数组后,根据字母排序。
    3. 然后双指针去遍历数组。两个字符匹配则两个指针后移动。
    4. 若赎金信的指针所对应的字母 大于杂志的指针所对应的字母,则杂志的指针后移
    5. 若赎金信的指针所对应的字母 小于杂志的指针所对应的字母,则说明杂志中的字符不够去匹配赎金信中的字符,返货false。
    6. 最后返回 index是否等于赎金信字符串的长度。
    7. 方法二
    8. 用HashMap来存储杂志中字符出现的次数。
    9. 然后遍历赎金信中的每一个字符,在map中找到匹配的,则数量减1,没找到则返回false。
    10. 方法三
    11. 对上面方法的改进
    12. 直接改用26大小的数组,来存储字符出现的次数即可
  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第6张图片

    方法二LeetCode刷题笔记(Java)---第381-400题_第7张图片

    方法三LeetCode刷题笔记(Java)---第381-400题_第8张图片

384. 打乱数组

打乱一个没有重复元素的数组。
LeetCode刷题笔记(Java)---第381-400题_第9张图片

  • 解答

    //方法一
        private int[] array;
        private int[] original;
    
        private Random rand = new Random();
    
        private List<Integer> getArrayCopy() {
            List<Integer> asList = new ArrayList<>();
            for (int i = 0; i < array.length; i++) {
                asList.add(array[i]);
            }
            return asList;
        }
    
        public Solution(int[] nums) {
            array = nums;
            original = nums.clone();
        }
        
        public int[] reset() {
            array = original;
            original = original.clone();
            return array;
        }
        
        public int[] shuffle() {
            List<Integer> aux = getArrayCopy();
    
            for (int i = 0; i < array.length; i++) {
                int removeIdx = rand.nextInt(aux.size());
                array[i] = aux.get(removeIdx);
                aux.remove(removeIdx);
            }
    
            return array;
        }
    //方法二
        private int[] array;
        private int[] original;
    
        Random rand = new Random();
    
        private int randRange(int min, int max) {
            return rand.nextInt(max - min) + min;
        }
    
        private void swapAt(int i, int j) {
            int temp = array[i];
            array[i] = array[j];
            array[j] = temp;
        }
    
        public Solution(int[] nums) {
            array = nums;
            original = nums.clone();
        }
        
        public int[] reset() {
            array = original;
            original = original.clone();
            return original;
        }
        
        public int[] shuffle() {
            for (int i = 0; i < array.length; i++) {
                swapAt(i, randRange(i, array.length));
            }
            return array;
        }
    
  • 分析

    1. 方法一
    2. original数组记录数组原始的模样
    3. nums数组记录当前的数组
    4. 随机返回一个数组的排列组合使用一个ArrayList,每次随机的从list中得到一个数字,然后添加到数组中,从list中删去这个数字。
    5. 方法二 Fisher-Yates 洗牌算法
    6. 遍历数组的同时,随机的和当前位置之后的其中一个数字交换,本身也可以,为了保证概率相同。
  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第10张图片
    方法二LeetCode刷题笔记(Java)---第381-400题_第11张图片

385.迷你语法分析器

给定一个用字符串表示的整数的嵌套列表,实现一个解析它的语法分析器。

列表中的每个元素只可能是整数或整数嵌套列表

提示:你可以假定这些字符串都是格式良好的:

  • 字符串非空
  • 字符串不包含空格
  • 字符串只包含数字0-9、[、-、,、]

LeetCode刷题笔记(Java)---第381-400题_第12张图片

  • 解答

    		//递归函数通过字符数组和cur下标确定要处理的位置
        char[] chars;
        int cur = 0;
        public NestedInteger deserialize(String s) {
            chars = s.toCharArray();
            //本身不是一个集合而是一个整数的情况
            if(chars[0]!='[') return new NestedInteger(Integer.valueOf(s));
            //调用递归函数返回根集合
            return getNest();
        }
        public NestedInteger getNest(){
            NestedInteger nest = new NestedInteger();
            int num = 0;//num用于缓存用逗号分割的整数类型的值
            int sign = 1;//当前记录的整数的符号,1代表正数,-1代表负数
            while(cur!=chars.length-1){
                cur ++;
                if(chars[cur]==',') continue;
                if(chars[cur]=='[') nest.add(getNest());//遇到[递归获取子集合
                else if(chars[cur]==']') return nest;
                else if(chars[cur]=='-') sign = -1;
                else{//是数字的情况
                    num = 10*num + sign * (chars[cur]-'0');
                    //如果下一个字符是,或者]说明当前数字已经记录完了,需要加入集合中
                    if(chars[cur+1]==','||chars[cur+1]==']'){
                        nest.add(new NestedInteger(num));
                        num = 0;
                        sign = 1;
                    }
                }
            }
            return null;
        }
    
  • 分析

    1. 如果第一个字符不是’[’ 说明只有一个整数
    2. 否则递归的去构造
    3. 当遇到 ‘[’ 递归获取子集合
    4. 遇到’]’ 返回这一层的结果
    5. 遇到’-’ 设置标记为-1 表示负数
    6. 数字的处理很简单,就已有的数字扩大10倍 加当前遍历到的数字。
    7. 如果下一个字符是’,’ 或者 ‘]’ 表示当前数字找完了,需要加入到集合当中。
  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第13张图片

386. 字典序排数

给定一个整数 n, 返回从 1 到 n 的字典顺序。

例如,

给定 n =1 3,返回 [1,10,11,12,13,2,3,4,5,6,7,8,9] 。

请尽可能的优化算法的时间复杂度和空间复杂度。 输入的数据 n 小于等于 5,000,000。

  • 解答

    // 方法一
    	public List<Integer> lexicalOrder(int n) {
            PriorityQueue<String> queue = new PriorityQueue<>(new Comparator<String>(){
                public int compare(String o1,String o2){
                    return o1.compareTo(o2);
                }
            });
            for(int i = 1;i<=n;i++){
                queue.add(String.valueOf(i));
            } 
            ArrayList<Integer> list = new ArrayList<>();
            while(!queue.isEmpty()){
                list.add(Integer.valueOf(queue.poll()));
            }
            return list;
        }
    
    // 方法二
    	List<Integer> list = new ArrayList<>();
        public List<Integer> lexicalOrder(int n) {
            dfs(0,0,n);
            return list;
        }
        public void dfs(int start,int num,int n){
            if(num > n) return;
            if(num > 0)list.add(num);
            for(int i = start > 0 ? 0 : 1;i <= 9;i++){
                dfs(start + 1,10 * num + i,n);
            }
        }
    
  • 分析

    1. 方法一,暴力解法 利用java中的优先级队列,构建小顶堆。将1-n的数字全部放入。然后再每次从堆顶拿出。
    2. 方法二,回溯 第一层是1-9的数字 之后的每层都是0-9的数字。所以start表示的就是当前有多少个数字。一开始的时候 是0.说明是第一层。那么就从1-9遍历之后的start>0 就从0-9遍历。
  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第14张图片

    方法二LeetCode刷题笔记(Java)---第381-400题_第15张图片

387. 字符串中的第一个唯一字符

给定一个字符串,找到它的第一个不重复的字符,并返回它的索引。如果不存在,则返回 -1。

LeetCode刷题笔记(Java)---第381-400题_第16张图片

  • 解答

    	//方法一
    	public int firstUniqChar(String s) {
            HashMap<Character, Integer> count = new HashMap<>();
            for (int i = 0; i < s.length(); i++) {
                char c = s.charAt(i);
                count.put(c, count.getOrDefault(c, 0) + 1);
            }
            
            for (int i = 0; i < s.length(); i++) {
                if (count.get(s.charAt(i)) == 1) 
                    return i;
            }
            return -1;
        }
        //方法二
    	public int firstUniqChar(String s) {
            int[] count = new int[26];
            int n = s.length();
            for (int i = 0; i < n; i++) {
                count[s.charAt(i) - 'a']++;
            }
            
            for (int i = 0; i < n; i++) {
                if (count[s.charAt(i) - 'a'] == 1) 
                    return i;
            }
            return -1;
        }
    
  • 分析

    1. 方法一 使用散列表记录字符出现的次数。然后再遍历一遍,若这个字符仅出现一次,则返回它的索引
    2. 方法二 使用数组代替散列表
  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第17张图片
    方法二LeetCode刷题笔记(Java)---第381-400题_第18张图片

388. 文件的最长绝对路径

假设我们以下述方式将我们的文件系统抽象成一个字符串:

字符串 “dir\n\tsubdir1\n\tsubdir2\n\t\tfile.ext” 表示:
LeetCode刷题笔记(Java)---第381-400题_第19张图片

目录 dir 包含一个空的子目录 subdir1 和一个包含一个文件 file.ext 的子目录 subdir2 。

字符串 “dir\n\tsubdir1\n\t\tfile1.ext\n\t\tsubsubdir1\n\tsubdir2\n\t\tsubsubdir2\n\t\t\tfile2.ext” 表示:

LeetCode刷题笔记(Java)---第381-400题_第20张图片

目录 dir 包含两个子目录 subdir1 和 subdir2。 subdir1 包含一个文件 file1.ext 和一个空的二级子目录 subsubdir1。subdir2 包含一个二级子目录 subsubdir2 ,其中包含一个文件 file2.ext。

我们致力于寻找我们文件系统中文件的最长 (按字符的数量统计) 绝对路径。例如,在上述的第二个例子中,最长路径为 “dir/subdir2/subsubdir2/file2.ext”,其长度为 32 (不包含双引号)。

给定一个以上述格式表示文件系统的字符串,返回文件系统中文件的最长绝对路径的长度。 如果系统中没有文件,返回 0。

说明:

文件名至少存在一个 . 和一个扩展名。
目录或者子目录的名字不能包含 .。
要求时间复杂度为 O(n) ,其中 n 是输入字符串的大小。

请注意,如果存在路径 aaaaaaaaaaaaaaaaaaaaa/sth.png 的话,那么 a/aa/aaa/file1.txt 就不是一个最长的路径。

  • 解答

    public int lengthLongestPath(String input) {
            if (input.length() == 0) {
                return 0;
            }
            int res = 0;
            int[] sum = new int[input.length() + 1];    
            for (String s : input.split("\n")) {
                int level = s.lastIndexOf('\t') + 2;    
                int len = s.length() - (level - 1);    
                if (s.contains(".")) {
                    res = Math.max(res, sum[level - 1] + len);
                } else {
                    sum[level] = sum[level - 1] + len + 1;
                }
            }
            return res;
        }
    
  • 分析

    1. 数组sum 表示第几层目录的长度
    2. 将字符串input。根据“\n“ 分割
    3. 然后遍历分割 好的部分。
    4. 首先判断是否有‘\t’ 没有的话表示第一层
    5. 然后计算当前文件或文件夹的名字长度。带转义字符的字符串长度,实际是不算上" \ "的。
    6. 判断当前是否表示的是文件,如果是的话res = Math.max(res, sum[level - 1] + len);
    7. 否则更新当前第level层的目录长度 = 上一层的目录长度+当前文件或文件夹名字的长度。再加1是因为要加上“/‘
  • 提交结果LeetCode刷题笔记(Java)---第381-400题_第21张图片

389. 找不同

给定两个字符串 s 和 t,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

LeetCode刷题笔记(Java)---第381-400题_第22张图片
LeetCode刷题笔记(Java)---第381-400题_第23张图片

  • 解答

        //方法一
    	public char findTheDifference(String s, String t) {
            int[] counts = new int[26];
            char[] schar = s.toCharArray();
            char[] tchar = t.toCharArray();
            for(int i = 0;i<schar.length;i++){
                counts[schar[i]-'a']++;
            }
            for(int i = 0;i<tchar.length;i++){
                counts[tchar[i] - 'a']--;
                if(counts[tchar[i] - 'a'] < 0)return tchar[i];
            }
            return 'a';
        }
       //方法二
       public char findTheDifference(String s, String t) {
            char res = t.charAt(t.length()-1);
            char[] schar = s.toCharArray();
            char[] tchar = t.toCharArray();
            for(int i = 0;i<schar.length;i++){
                res ^= schar[i];
                res ^= tchar[i];
            }
            return res;
        }
    
  • 分析

    1. 方法一,计数

    2. 方法二,异或

      两个相同的字符异或为0

      一个字符异或0等于本身。

      所以异或可以找出唯一一个字符个数是奇数的字符。

  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第24张图片

    方法二LeetCode刷题笔记(Java)---第381-400题_第25张图片

390. 消除游戏

给定一个从1 到 n 排序的整数列表。
首先,从左到右,从第一个数字开始,每隔一个数字进行删除,直到列表的末尾。
第二步,在剩下的数字中,从右到左,从倒数第一个数字开始,每隔一个数字进行删除,直到列表开头。
我们不断重复这两步,从左到右和从右到左交替进行,直到只剩下一个数字。
返回长度为 n 的列表中,最后剩下的数字。

LeetCode刷题笔记(Java)---第381-400题_第26张图片

  • 解答

    public int lastRemaining(int n) {
            return n == 1 ? 1 : 2 * (n/2 + 1 - lastRemaining(n/2));
        }
    
  • 分析

    1. 官方题解

      LeetCode刷题笔记(Java)---第381-400题_第27张图片

    2. 假设n = 2k 第一次消除之后,剩下绿色的部分。

    3. 第二次是从后面往前消除,不妨从后往前编号 1 - k

    4. 第一次可以表示为f(2n) 表示2n个数中最后剩下的数字

    5. 第二次可以表示为f(n) 表示n个数中最后剩下的数字。

    6. 因为第二次的结果是蓝色的部分的数字,那么如何将第二次的结果映射回绿色的部分呢?

    7. 可以发现绿色和蓝色之间是有关系的。

    8. f(2k) = 2(k + 1 - f(k))

    9. 上面分析的是偶数的情况。如果当n为奇数呢?

    10. 可以发现 在第一次删除的时候,就把最后一个奇数给删掉了。

    11. 所以f(2k + 1) = 2(k + 1 - f(k)) 式子和偶数的时候一样。

    12. 所以可以统一写为

    13. f(n) = 2(n/2 +1 - f(n/2)) 这里除法向下取整

  • 提交结果LeetCode刷题笔记(Java)---第381-400题_第28张图片

391. 完美矩形

我们有 N 个与坐标轴对齐的矩形, 其中 N > 0, 判断它们是否能精确地覆盖一个矩形区域。

每个矩形用左下角的点和右上角的点的坐标来表示。例如, 一个单位正方形可以表示为 [1,1,2,2]。 ( 左下角的点的坐标为 (1, 1) 以及右上角的点的坐标为 (2, 2) )。

LeetCode刷题笔记(Java)---第381-400题_第29张图片

LeetCode刷题笔记(Java)---第381-400题_第30张图片
LeetCode刷题笔记(Java)---第381-400题_第31张图片
LeetCode刷题笔记(Java)---第381-400题_第32张图片

  • 解答

    public boolean isRectangleCover(int[][] rectangles) {
            int left = Integer.MAX_VALUE;
            int right = Integer.MIN_VALUE;
            int top = Integer.MIN_VALUE;
            int bottom = Integer.MAX_VALUE;
            
            int n = rectangles.length;
    
            Set<String> set = new HashSet<>();
            int sumArea = 0;
    
            for (int i = 0; i < n; i++) {
                //获取四个点的坐标
                left = Math.min(left, rectangles[i][0]);
                bottom = Math.min(bottom, rectangles[i][1]);
                right = Math.max(right, rectangles[i][2]);
                top = Math.max(top, rectangles[i][3]);
                //计算总小矩形的面积
                sumArea += (rectangles[i][3] - rectangles[i][1]) * (rectangles[i][2] - rectangles[i][0]);
                //分别记录小矩形的坐标
                String lt = rectangles[i][0] + " " + rectangles[i][3];
                String lb = rectangles[i][0] + " " + rectangles[i][1];
                String rt = rectangles[i][2] + " " + rectangles[i][3];
                String rb = rectangles[i][2] + " " + rectangles[i][1];
                //如果有就移除 没有就加入
                if (!set.contains(lt)) set.add(lt);
                else set.remove(lt);
                if (!set.contains(lb)) set.add(lb);
                else set.remove(lb);
                if (!set.contains(rt)) set.add(rt);
                else set.remove(rt);
                if (!set.contains(rb)) set.add(rb);
                else set.remove(rb);
            }
    
            //最后只剩4个大矩形坐标且面积相等即为完美矩形
            if (set.size() == 4 && set.contains(left + " " + top) && set.contains(left + " " + bottom) && set.contains(right + " " + bottom) && set.contains(right + " " + top)) {
                return sumArea == (right - left) * (top - bottom);
            }
            return false;
        }
    
  • 分析

    1. 完美矩阵有两个特点,1 是 所有小矩阵的点,除了最左上,最左下,最右上,最右下,其他的点都出现了两次。2 是 小矩阵的面积和等于最外围矩阵的面积。
    2. 遍历小矩阵,计算最左上,最左下,最右上,最右下的点。
    3. 同时计算小矩阵的面积和
    4. 然后将小矩阵的4个点放入到set中,如果已经存在,说明是第二次出现,则不放入set,把set中原有的删除。
    5. 最后遍历完之后。set中留下的就是最左上,最左下,最右上,最右下四个点的坐标。并且得到了所有小矩阵的面积和。
    6. 如果set中留下的坐标点不止4个说明不是完美矩阵。
    7. 如果是4个的话,就计算这4个点构成的面积是否等于之前遍历出来小矩阵的面积和。
  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第33张图片

392. 判断子序列

给定字符串 s 和 t ,判断 s 是否为 t 的子序列。

你可以认为 s 和 t 中仅包含英文小写字母。字符串 t 可能会很长(长度 ~= 500,000),而 s 是个短字符串(长度 <=100)。

字符串的一个子序列是原始字符串删除一些(也可以不删除)字符而不改变剩余字符相对位置形成的新字符串。(例如,"ace"是"abcde"的一个子序列,而"aec"不是)。

LeetCode刷题笔记(Java)---第381-400题_第34张图片

  • 解答

    public boolean isSubsequence(String s, String t) {
            int s_index = 0;
            int t_index = 0;
            while(s_index < s.length() && t_index < t.length()){
                if(s.charAt(s_index) == t.charAt(t_index)){
                    s_index++;
                    t_index++;
                }else t_index++;
            }
            return s_index == s.length();
        }
    
  • 分析

    1. 双指针
    2. 若当前两个指针指向相同的字符,则两个指针都后移动
    3. 否则t指针后移
    4. 最后判断s_index 是否等于s的长度
  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第35张图片

393. UTF-8 编码验证

UTF-8 中的一个字符可能的长度为 1 到 4 字节,遵循以下的规则:

  1. 对于 1 字节的字符,字节的第一位设为0,后面7位为这个符号的unicode码。

  2. 对于 n 字节的字符 (n > 1),第一个字节的前 n 位都设为1,第 n+1 位设为0,后面字节的前两位一律设为10。剩下的没有提及的二进制位,全部为这个符号的unicode码。
    这是 UTF-8 编码的工作方式:

    LeetCode刷题笔记(Java)---第381-400题_第36张图片

给定一个表示数据的整数数组,返回它是否为有效的 utf-8 编码。

  • 注意:
    输入是整数数组。只有每个整数的最低 8 个有效位用来存储数据。这意味着每个整数只表示 1 字节的数据。

LeetCode刷题笔记(Java)---第381-400题_第37张图片

  • 解答

    public boolean validUtf8(int[] data) {
    
            int numberOfBytesToProcess = 0;
    
            int mask1 = 1 << 7;//用于检查最高位是否是1
            int mask2 = 1 << 6;//用于检查第二高位是否是0
    
            for(int i = 0; i < data.length; i++) {
                if (numberOfBytesToProcess == 0) {
                    int mask = 1 << 7;
                     while ((mask & data[i]) != 0) {
                        numberOfBytesToProcess += 1;//记录高位开始有多少个1
                        mask = mask >> 1;
                     }
    
                    if (numberOfBytesToProcess == 0) {//如果高位为0 跳过
                        continue;
                    }
    
                    if (numberOfBytesToProcess > 4 || numberOfBytesToProcess == 1) {//1的个数大于1 或者等于1 不符合字符要求,返沪false
                        return false;
                    }
    
                } else {
    								//判断后续的有多少个10 开头的二进制,若不是10开头 则返回false
                    if (!((data[i] & mask1) != 0 && (mask2 & data[i]) == 0)) {
                        return false;
                    }
                }
    
                numberOfBytesToProcess -= 1;//计数-1
            }
    
            return numberOfBytesToProcess == 0;
        }
    
  • 分析

    1. 用位运算加快匹配的速度
    2. 计算当前二进制数高位有多少个1,标示字符的个数
    3. 若结果等于1 或者大于4 则不符合utf-8的要求
    4. 之后就判断是否是“10”开头的编码 是的话,则计数-1
  • 提交结果
    在这里插入图片描述

394. 字符串解码

给定一个经过编码的字符串,返回它解码后的字符串。

编码规则为: k[encoded_string],表示其中方括号内部的 encoded_string 正好重复 k 次。注意 k 保证为正整数。

你可以认为输入字符串总是有效的;输入字符串中没有额外的空格,且输入的方括号总是符合格式要求的。

此外,你可以认为原始数据不包含数字,所有的数字只表示重复的次数 k ,例如不会出现像 3a 或 2[4] 的输入。

LeetCode刷题笔记(Java)---第381-400题_第38张图片

  • 解答

    public String decodeString(String s){
            StringBuilder builder = new StringBuilder();//记录答案
            StringBuilder temp = new StringBuilder();//记录递归解析的字符串
            int depth = 0;//括号的深度
            int num = 0;//数字
            for(int i = 0;i < s.length();i++){
                char ch = s.charAt(i);
                if(ch >= '0' && ch <= '9'){
                  //如果depth为0.并且当前是数字,说明这个数字是用来计算这一层解析到的字符串 要在答案中出现的次数,计算这个num
                    if(depth == 0)
                        num = num * 10 + ch-'0';
                  //否则直接加入到准备递归解析的字符串序列中
                    else temp.append(ch);
                }
                else if(ch == '['){//遇到'[' 若depth > 0 说明当前的字符属于递归解析的字符串,则加入到temp中
                    if(depth > 0)
                        temp.append("[");
                    depth++; // depth++ 递归深度
                }
                else if(ch == ']'){//遇到']' 若dept > 1 说明当前的字符属于递归解析的字符串,则加入到temp中
                    if(depth > 1)
                        temp.append("]");
                    else if(depth == 1){//若等于 1 表示要递归的部分字符串已经找到。递归的解析temp
                        String str = decodeString(temp.toString());
                        if(num == 0)
                            num = 1;
                        for(int j = 0;j<num;j++){//根据数字。将解析后的结果重复的添加到答案中
                            builder.append(str);
                        }
                        num = 0;
                        temp = new StringBuilder();
                    }
                    depth--;
                }
                else if(depth > 0){//若大于0。加入到递归解析字符串中
                    temp.append(ch);
                }
                else if(depth == 0){//若等于0 加入到答案集合中。
                    builder.append(ch);
                }
            }
            return builder.toString();
    }
    
  • 分析

    1. 遍历,根据‘[‘ 出现的次数,代表要递归的深度,
    2. 根据递归的深度,来决定 当前遍历到的字符是要归于 答案中 还是要归于 准备递归解析的字符串中
    3. 当depth = 1 并且遇到了’]'说明找齐了要准备递归解析的字符串。进行递归的解析。
    4. 然后根据数字num 重复的添加到答案中,并重置 num 和temp
  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第39张图片

395. 至少有K个重复字符的最长子串

找到给定字符串(由小写字符组成)中的最长子串 T , 要求 T 中的每一字符出现次数都不少于 k 。输出 T 的长度。

LeetCode刷题笔记(Java)---第381-400题_第40张图片

  • 解答

    public int longestSubstring(String s, int k) {
            int len = s.length();
            if (len == 0 || k > len) return 0;
            if (k < 2) return len;
    
            return count(s.toCharArray(), k, 0, len - 1);
        }
    
        private static int count(char[] chars, int k, int p1, int p2) {
            if (p2 - p1 + 1 < k) return 0;
            int[] times = new int[26];  //  26个字母
            //  统计出现频次
            for (int i = p1; i <= p2; ++i) {
                ++times[chars[i] - 'a'];
            }
            // 去掉头部不符合的字符
            while (p2 - p1 + 1 >= k && times[chars[p1] - 'a'] < k) {
                ++p1;
            }
          // 去掉尾部不符合的字符
            while (p2 - p1 + 1 >= k && times[chars[p2] - 'a'] < k) {
                --p2;
            }
    
            if (p2 - p1 + 1 < k) return 0;
            for (int i = p1; i <= p2; ++i) {
                //  如果第i个不符合要求,从i处分为前半部分和后半部分。
                if (times[chars[i] - 'a'] < k) {
                  	// 对于两部分去递归的寻找答案。返回两个结果的最大值
                    return Math.max(count(chars, k, p1, i - 1), count(chars, k, i + 1, p2));
                }
            }
            return p2 - p1 + 1;
        }
    
  • 分析

    1. 先统计字符串中所有字符出现的次数。
    2. 若每个字符出现的次数小于k 那么它必定不能出现在结果当中。
    3. 双指针,去掉头部和尾部不符合条件二的字符
    4. 然后遍历剩余的字符。若当中出现了不符合的字符。那么就根据这个位置,将原本的字符串拆成两个子字符串。分别的去递归的判断结果。得到两个结果中的最大值。
    5. 返回处理后的字符串的长度。
  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第41张图片

396. 旋转函数

给定一个长度为 n 的整数数组 A 。

假设 Bk 是数组 A 顺时针旋转 k 个位置后的数组,我们定义 A 的“旋转函数” F 为:

F(k) = 0 * Bk[0] + 1 * Bk[1] + … + (n-1) * Bk[n-1]。

计算F(0), F(1), …, F(n-1)中的最大值。

  • 注意:
    可以认为 n 的值小于 105。

LeetCode刷题笔记(Java)---第381-400题_第42张图片

  • 解答
//方法一
public int maxRotateFunction(int[] A) {
        int len = A.length;
        if(len == 0)return 0;
        int res = Integer.MIN_VALUE;
        int num = 0;
        for(int i = 0;i<len;i++){
            int temp = 0;
            for(int j = 0;j<len;j++){
                temp += ((num + j) % len) * A[j];
            }
            num++;
            res = Math.max(res,temp);
        }
        return res;
    }
//方法二
public int maxRotateFunction(int[] A) {
        int sum = 0;
  			int n =  A.length;
        int dp1 = 0, dp2 = 0;
        for (int i = 0; i < n; i++) {
            sum += A[i];
            dp1 += i * A[i];
        }

        int ans = dp1;
        for (int i = 1; i < A.length; i++) {
            dp2 = dp1 + sum - n * A[n - i];
            ans = Math.max(dp2, ans);
            dp1 = dp2;
        }

        return ans;
    }
  • 分析

    1. 方法一暴力解决
    2. 题目中说旋转数组,其实可以不用旋转数组,而是改变每一位的乘数即可。
    3. 例如A[0] = 4 在F[0]的时候是乘0的 F[1]的时候是乘1的,而F[n]的时候就是乘n-1
    4. 之后的每一项都是如此有规律的改变乘数,即可达到旋转数组的效果。
    5. 方法二dp
    6. f[n] = f[n-1] + sum(A) - n * bk[n-1]
    7. 所以需要先得到f[0] 和sum(A) 就可以知道后续的结果
    8. 第一个for 就是为了得到f[0]和sum(A)
    9. 第二个循环就是根据转移方程,得出结果 并保留大的作为答案。
  • 提交结果

    方法一LeetCode刷题笔记(Java)---第381-400题_第43张图片
    方法二LeetCode刷题笔记(Java)---第381-400题_第44张图片

397. 整数替换

给定一个正整数 n,你可以做如下操作:

  1. 如果 n 是偶数,则用 n / 2替换 n。
  2. 如果 n 是奇数,则可以用 n + 1或n - 1替换 n。
    n 变为 1 所需的最小替换次数是多少?

LeetCode刷题笔记(Java)---第381-400题_第45张图片

  • 解答

    public int integerReplacement(int n) {
            if (n == Integer.MAX_VALUE)
                return 32;
            if (n <= 3)
                return n - 1;
            if (n % 2 == 0)
                return integerReplacement(n / 2) + 1;
            else
                return (n & 2) == 0 ? integerReplacement(n - 1) + 1 : integerReplacement(n + 1) + 1;
        }
    
  • 分析

    1. 把题目中的数字转换成2进制来看

      例如 8 的二进制为 1000 , 15的二进制为 1111

    2. 当是偶数的时候,二进制直接右移动一位,记为1次。

    3. 当是偶数的时候,可以选择n - 1 或者n + 1. 记为1次。

    4. 对于15 而言 可以选择成为 10000 或者 1110

    5. 重点在于如何选择奇数变成偶数的策略

    6. 其实就是要使得这一次的转换,二进制当中1 出现的个数变少。

    7. 例如15 如果转换成10000 之后只需要一直除2 就好了。若转换成了1110 除2之后 得到111 还需要再进行奇数到偶数的转换。

    8. 所以当末尾两位是11的时候,就选择n+1的策略 若当末尾两位是01的时候,就选择n-1的策略。

    9. 也就是当n是奇数的时候 判断 n & 2 是否等于 0

    10. 3 是一种特殊情况。3 的二进制为 11 若根据上述的规则 应该采取n+1的策略,3->4->2>1 但是 3->2>1明显次数更少。所以另外的判断。

  • 提交结果
    LeetCode刷题笔记(Java)---第381-400题_第46张图片

398. 随机数索引

给定一个可能含有重复元素的整数数组,要求随机输出给定的数字的索引。 您可以假设给定的数字一定存在于数组中。

  • 注意:
    数组大小可能非常大。 使用太多额外空间的解决方案将不会通过测试。

LeetCode刷题笔记(Java)---第381-400题_第47张图片

  • 解答

    //方法1
    class Solution {
        HashMap<Integer,ArrayList<Integer>> map = new HashMap<>();
    
        public Solution(int[] nums) {
            for(int i = 0;i < nums.length;i++){
                ArrayList<Integer> list = map.getOrDefault(nums[i],new ArrayList<>());
                list.add(i);
                map.put(nums[i],list);
            }
        }
        
        public int pick(int target) {
            ArrayList<Integer> list = map.get(target);
            Random random = new Random();
            int index = random.nextInt(list.size());
            return list.get(index);        
        }
    }
    //方法2
    class Solution {
    		private int[] nums;
        public Solution(int[] nums) {
           this.nums = nums;
        }
        
        public int pick(int target) {
            Random r = new Random();
            int n = 0;
            int index = 0;
            for(int i = 0;i < nums.length;i++)
                if(nums[i] == target){
                //我们的目标对象中选取。
                    n++;
                    //我们以1/n的概率留下该数据
                    if(r.nextInt() % n == 0) index = i;
                }
            return index;
        }
    }
    
  • 分析

    1. 方法1
    2. 将数字作为value 索引作为key中的一部分,保存在hashmap当中
    3. 对于目标target 先得到所对应的索引链表
    4. 基于索引链表的长度 得到随机数。返回索引
    5. 方法2
    6. 蓄水池抽样算法
    7. 假设当前正要读取第n个数据,则我们以1/n的概率留下该数据,否则留下前n-1个数据中的一个。
    8. 第一次遇到target n = 1
    9. 此时必定能留下该数字。更新index
    10. 后续若再次遇到target 则以1/n的概率留下该数字,也就是更新index 否则保留之前的index
  • 提交结果

    方法1LeetCode刷题笔记(Java)---第381-400题_第48张图片
    方法2LeetCode刷题笔记(Java)---第381-400题_第49张图片

399.除法求值

给出方程式 A / B = k, 其中 A 和 B 均为用字符串表示的变量, k 是一个浮点型数字。根据已知方程式求解问题,并返回计算结果。如果结果不存在,则返回 -1.0。

输入总是有效的。你可以假设除法运算中不会出现除数为 0 的情况,且不存在任何矛盾的结果。

LeetCode刷题笔记(Java)---第381-400题_第50张图片
LeetCode刷题笔记(Java)---第381-400题_第51张图片

  • 解答

    HashMap<String, List<Edge>> map = new HashMap<>();
        double[] res;
    
        public double[] calcEquation(List<List<String>> equations, double[] values, List<List<String>> queries) {
            init(equations,values);//初始化图
            res = new double[queries.size()];
            for(int i = 0;i < queries.size();i++){
                List<String> points = queries.get(i);
                String point1 = points.get(0);
                String point2 = points.get(1);
                ArrayList<String> visited = new ArrayList<>();
                visited.add(point1);
                res[i] = dfs(point1,point2,visited);
            }
            return res;
        }
    
        public void  init(List<List<String>> equations, double[] values){
            for(int i = 0;i < equations.size();i++){
                List<String> points = equations.get(i);
                double result = values[i];
                String point1 = points.get(0);
                String point2 = points.get(1);
    
                List<Edge> edges = map.get(point1);
                if(edges == null){
                    edges = new ArrayList<>();
                    map.put(point1,edges);
                }
                edges.add(new Edge(point1,point2,result));
    
                edges = map.get(point2);
                if(edges == null){
                    edges = new ArrayList<>();
                    map.put(point2,edges);
                }
                edges.add(new Edge(point2,point1,1/result));
            }
        }
    
        private double dfs(String start, String dest, List<String> visited) {
            // 验证是否存顶点
            if (!map.containsKey(start) || !map.containsKey(dest)) {
                return -1.0;
            }
            visited.add(start);
            if (start.equals(dest)) {
                return 1.0;
            }
            // 获取 start 顶点的边
            List<Edge> startEdges = map.get(start);
            if (startEdges == null || startEdges.isEmpty()) {
                return -1.0;
            }
            // 深度优先遍历集合
            for (Edge edge : startEdges) {
                if (visited.contains(edge.v2)) {
                    continue;
                }
                double res = dfs(edge.v2, dest, visited);
                if (res != -1.0) {
                    return res * edge.val;
                }
            }
            return -1.0;
        }
    
        class Edge{
            private String v1;
            private String v2;
            private double val;
            public Edge(String v1,String v2,double val){
                this.v1 = v1;
                this.v2 = v2;
                this.val = val;
            }
        }
    
  • 分析

    1. init 是初始化图的算法
    2. map 用来保存 结点和它包含的边的关系 key是点 value是边集合
    3. 边的元素包括了 两个结点,以及有向边权重
    4. 之后就是遍历queries
    5. 使用深度搜索 来得到答案。
  • 提交结果

    LeetCode刷题笔记(Java)---第381-400题_第52张图片

400. 第N个数字

在无限的整数序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …中找到第 n 个数字。

  • 注意:
    n 是正数且在32位整数范围内 ( n < 231)。

LeetCode刷题笔记(Java)---第381-400题_第53张图片

  • 解答

    public int findNthDigit(int n) {
            long bit = 1, p = 9;
            while(n - bit * p > 0){
                n -= p * bit;
                bit++;
                p *= 10;
            }
    
            int num = (int)(Math.pow(10, bit - 1) + (n-1) / bit);//第n个数字 所在的数
    
            int pos = (int)(n % bit);//第n个数所在的位置
            pos = pos == 0 ? (int)bit : pos;//当 pos等于0的时候 则pos应该是最后一位
    
            int t = (int) Math.pow(10, bit - pos);//需要去掉末尾多少位的数字
            return num / t % 10;//得到该位数字
        }
    
  • 分析

    1. 找规律
    2. 1-9 9个数字;10-99 20 * 9 个数字 ;100-999 300 * 9 个数字
    3. while循环 就是用来确定第n个数字 是属于哪一组中的数。例如n = 190
    4. 190-9 - 180 = 1
    5. 说明它属于100-900 中
    6. 然后通过Math.pow(10, bit - 1) + (n-1) / bit 来得到第n个数在哪个数字中
    7. 例如上面n = 190。属于数字 100
    8. pos是计算属于这个数字的第几位
    9. 例如n = 190 属于数字100的第1位 1
    10. 192 属于数字100的第3位 0
    11. t是需要去掉末尾多少位数字,然后% 10 就可以得到指定位置上的数字。
  • 提交结果LeetCode刷题笔记(Java)---第381-400题_第54张图片

你可能感兴趣的:(#,LeetCode刷题笔记,java,leetcode,算法)