LeetCode 438. 找到字符串中所有字母异位词 / 786. 第 K 个最小的素数分数 / 400. 第 N 位数字(优先队列,二分+双指针)

Posted Zephyr丶J

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了LeetCode 438. 找到字符串中所有字母异位词 / 786. 第 K 个最小的素数分数 / 400. 第 N 位数字(优先队列,二分+双指针)相关的知识,希望对你有一定的参考价值。

438. 找到字符串中所有字母异位词

2021.11.28 每日一题

题目描述

给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

异位词 指由相同字母重排列形成的字符串(包括相同的字符串)。

示例 1:

输入: s = “cbaebabacd”, p = “abc”
输出: [0,6]
解释:
起始索引等于 0 的子串是 “cba”, 它是 “abc” 的异位词。
起始索引等于 6 的子串是 “bac”, 它是 “abc” 的异位词。

示例 2:

输入: s = “abab”, p = “ab”
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 “ab”, 它是 “ab” 的异位词。
起始索引等于 1 的子串是 “ba”, 它是 “ab” 的异位词。
起始索引等于 2 的子串是 “ab”, 它是 “ab” 的异位词。

提示:

1 <= s.length, p.length <= 3 * 10^4
s 和 p 仅包含小写字母

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

思路

一个简单的滑动窗口

class Solution 
    public List<Integer> findAnagrams(String s, String p) 
        //滑动窗口,想想能避免比较26个字母吗,弄个字符串表示吗
        int[] ss = new int[26];
        for(char c : p.toCharArray())
            ss[c - 'a']++;
        
        List<Integer> res = new ArrayList<>();
        int[] temp = new int[26];
        for(int i = 0; i < s.length(); i++)
            char c = s.charAt(i);
            temp[c - 'a']++;
            if(i >= p.length() - 1)
                for(int j = 0; j < 26; j++)
                    if(temp[j] != ss[j])
                        break;
                    if(j == 25)
                        res.add(i - p.length() + 1);
                
                temp[s.charAt(i - p.length() + 1) - 'a']--;
            
        
        return res;
    

用一个表示几个位置不同的变量differ来进行O1的判断
或者可以用变长的滑动窗口

class Solution 
    public List<Integer> findAnagrams(String s, String p) 
        //滑动窗口,想想能避免比较26个字母吗
        //用一个变量表示区别
        List<Integer> res = new ArrayList<>();
        if(s.length() < p.length())
            return res;
        int[] ss = new int[26];
        for(int i = 0; i < p.length(); i++)
            ss[s.charAt(i) - 'a']++;
            ss[p.charAt(i) - 'a']--;
        
        int differ = 0;
        for(int i = 0; i < 26; i++)
            if(ss[i] != 0)
                differ++;
        
        if(differ == 0)
            res.add(0);
        for(int i = p.length(); i < s.length(); i++)
            char a = s.charAt(i - p.length());
            char b = s.charAt(i);
            //到这里想想,如果向右移动窗口,那么就是增加一个字母,应该是加
            //而从左边移出去的字母,因为前面是加,所以这里是减
            ss[a - 'a']--;
            if(ss[a - 'a'] == 0)
                differ--;
            else if(ss[a - 'a'] == -1)
                differ++;
            ss[b - 'a']++;
            if(ss[b - 'a'] == 0)
                differ--;
            else if(ss[b - 'a'] == 1)
                differ++;
            if(differ == 0) 
                res.add(i - p.length() + 1);
        
        return res;
    

786. 第 K 个最小的素数分数

2021.11.29 每日一题

题目描述

给你一个按递增顺序排序的数组 arr 和一个整数 k 。数组 arr 由 1 和若干 素数 组成,且其中所有整数互不相同。

对于每对满足 0 < i < j < arr.length 的 i 和 j ,可以得到分数 arr[i] / arr[j] 。

那么第 k 个最小的分数是多少呢? 以长度为 2 的整数数组返回你的答案, 这里 answer[0] == arr[i] 且 answer[1] == arr[j] 。

示例 1:

输入:arr = [1,2,3,5], k = 3
输出:[2,5]
解释:已构造好的分数,排序后如下所示:
1/5, 1/3, 2/5, 1/2, 3/5, 2/3
很明显第三个最小的分数是 2/5

示例 2:

输入:arr = [1,7], k = 1
输出:[1,7]

提示:

2 <= arr.length <= 1000
1 <= arr[i] <= 3 * 10^4
arr[0] == 1
arr[i] 是一个 素数 ,i > 0
arr 中的所有数字 互不相同 ,且按 严格递增 排序
1 <= k <= arr.length * (arr.length - 1) / 2

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

思路

暴力解法,优先队列,要是这样就结束了也不配困难题了

class Solution 
    public int[] kthSmallestPrimeFraction(int[] arr, int k) 
        int n = arr.length;
        PriorityQueue<double[]> pq = new PriorityQueue<>((a, b) -> 
            if(b[0] >= a[0])
                return 1;
            else
                return -1;
        );
        for(int i = 0; i < n; i++)
            for(int j = i + 1; j < n; j++)
                pq.offer(new double[](double)arr[i] / arr[j], arr[i], arr[j]);
                if(pq.size() > k)
                    pq.poll();
            
        
        return new int[](int)pq.peek()[1], (int)pq.peek()[2];
    

将每个分母对应的所有数字看成一个链表,然后相当于合并n个有序链表
每次用优先队列取出所有链表的最小值,取到第k个就是答案

class Solution 
    public int[] kthSmallestPrimeFraction(int[] arr, int k) 
        //暴力很显然不符合要求
        //看了答案感觉自己为啥没有想到呢
        //对于每一个数组中的数,除了1,都可以作为分母,然后其他比它小的数都可以作为该分母的分子
        //那么,每一个分母最小的数肯定是 arr[0] / arr[i];每一个分母对应一个序列
        //那么问题转化成了找到这些序列中第k大的数

        //首先将每个序列的头部,也就是这n - 1个数放入优先队列中排序
        //将其中最小的数取出,然后放入该序列的下一个数,也就是arr[j + 1] / arr[i]
        //然后当取出k个以后,就是第k个数
        //这个可以保证是最小吗,因为每次取出的肯定是整个序列的最小值,所以可以保证

        int n = arr.length;
        PriorityQueue<int[]> pq = new PriorityQueue<>((a, b) -> (arr[a[0]] * arr[b[1]] - arr[a[1]] * arr[b[0]]));
        //首先放入所以列表的第一位数
        for(int i = 1; i < n; i++)
            pq.offer(new int[]0, i);
        
        //然后每次取出所有数的最小值,然后放入该列表的第二个数
        for(int i = 1; i < k; i++)
            int[] top = pq.poll();
            int a = top[0];
            int b = top[1];
            //System.out.println(a + "," + b);
            //如果该列表有下一个数
            if(a + 1 < b)
                pq.offer(new int[]a + 1, b);
            
        
        int[] top = pq.poll();
        return new int[]arr[top[0]], arr[top[1]];
       

二分+双指针,这个挺难想的
首先得想到用双指针可以计算出小于一个数的分数个数
然后才能自然而然想到二分
而双指针行的通的原因是,当分母增大时,分数肯定减小,所以之前分母小于mid的分子个数对于当前分母依然满足条件,因而可以继续增大分子

class Solution 
    public int[] kthSmallestPrimeFraction(int[] arr, int k) 
        //二分加双指针也写一下
        //思路就是在0到1中找一个数x,使得小于等于x的数恰好有k个
        int n = arr.length;
        double left = 0.0;
        double right = 1.0;
        while(true)
            double mid = (right + left) / 2;

            //计算比mid小的分数个数,用双指针的方法
            int i = -1;  //左边指针
            int count = 0;
            int x = 0;
            int y = 1;
            for(int j = 1; j < n; j++)
                while((double)arr[i + 1] / arr[j] < mid)
                    i++;
                
                //找到比mid小的最大数
                if(i != -1 && arr[i] * y > arr[j] * x)
                    x = arr[i];
                    y = arr[j];
                
                count += i + 1;
            
            if(count == k)
                return new int[]x, y;

            if(count < k)
                left = mid;
            else
                right = mid;
            
        
    

400. 第 N 位数字

2021.11.30 每日一题,又是一个月的徽章,11月也过去了

题目描述

给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, …] 中找出并返回第 n 位数字。

示例 1:

输入:n = 3
输出:3

示例 2:

输入:n = 11
输出:0
解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, … 里是 0 ,它是 10 的一部分。

提示:

1 <= n <= 2^31 - 1

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

思路

先找n在当前哪个数字范围内,然后精确找到对应的数字,在找对应的位
因为所有数字都共用一个范围,所以用打表的方式把这个范围进行预处理

class Solution 
    static List<long[]> list;
    static
        list = new ArrayList<>();
        int t = Integer.MAX_VALUE;
        long range = 10;
        long count = 9;  //当前数量
        int single = 2;
        list.add(new long[]0, 1);
        list.add(new long[]count, single);
        while(t >= count)
            single++;
            count += (range * 10 - range) * (single - 1);
            list.add(new long[]count, single);
            range *= 10;
        
    
    public int findNthDigit(int n) 
        //就查在1 10 100 1000...哪个范围内,然后再查在这个范围内的哪个数
        //然后取出这个数的第几位

        //在list中查找n的位置
        //for(int i = 0; i < list.size(); i++)
        //    System.out.println(list.get(i)[0] + "," + list.get(i)[1]);
        //
        
        int left = 0;
        int right = list.size() - 1;
        while(left < right)
            int mid = (right - left + 1) / 2 + left;
            //要找的是小于等于n的第一个数
            //所以如果小于等于n,那么就留下
            if(list.get(mid)[0] <= n)
                left = mid;
            else
                right = mid - 1;
            
        
        //找到了left
        int count = (int)list.get(left)[0];
        int wei = (int)list.get(left)[1];
        int differ = n - count;     //总共差了多少位
        int idx = differ / wei;     //在第几个数
        int remainer = differ % wei;
        int base = (int)Math.pow(10, wei - 1);
        if(remainer == 0)
            int num = base + idx - 1;
            return num % 10;
        else
            int num = base + idx;
            differ = differ - idx * (wei);
            //在num中找第differ位
            int cha = wei - differ;
            return num / (int)Math.pow(10, cha) % 10;
        
    

以上是关于LeetCode 438. 找到字符串中所有字母异位词 / 786. 第 K 个最小的素数分数 / 400. 第 N 位数字(优先队列,二分+双指针)的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode #438 找到字符串中所有字母异位词

leetcode中等438找到字符串中所有字母异位词

LeetCode 438 找到字符串中所有字母异位词[数组 滑动窗口] HERODING的LeetCode之路

leetcode 438. 找到字符串中所有字母异位词(Find All Anagrams in a String)

LeetCode 438. 找到字符串中所有字母异位词 / 786. 第 K 个最小的素数分数 / 400. 第 N 位数字(优先队列,二分+双指针)

滑动窗口同数组的结合(LeetCode 438.找到字符串中所有字母异位词&567.字符串的排列)