【LeetCode】周赛纪录(七)第195场周赛20200628 判断路径是否相交 检查数组对是否可以被 k 整除-取模+哈希表 满足条件的子序列数目-二分+快速幂 满足不等式的最大值-双端队列

周赛第195场20200628

1496. 判断路径是否相交

题目描述1

给你一个字符串 path,其中 path[i] 的值可以是 'N''S''E' 或者 'W',分别表示向北、向南、向东、向西移动一个单位。

机器人从二维平面上的原点 (0, 0) 处开始出发,按 path 所指示的路径行走。

如果路径在任何位置上出现相交的情况,也就是走到之前已经走过的位置,请返回 True ;否则,返回 False

示例 1:

输入:path = "NES"
输出:false 
解释:该路径没有在任何位置相交。

示例 2:

输入:path = "NESWW"
输出:true
解释:该路径经过原点两次。

提示:

  • 1 <= path.length <= 10^4
  • path 仅由 {'N', 'S', 'E', 'W} 中的字符组成

Solution1

  1. 用HashSet记录是否走到过,每次将走过的坐标存入,借助Pair存坐标
class Solution {
    public boolean isPathCrossing(String path) {
        int n = path.length();
        Set<Pair<Integer, Integer>> set = new HashSet<>();
        int i = 0;
        int j = 0;
        Pair<Integer, Integer> pair = new Pair<>(i, j);
        set.add(pair);
        for(int k = 0; k < n; k++){
            char c = path.charAt(k);
            switch(c){
                case 'N' : i--; break;
                case 'S' : i++; break;
                case 'W' : j--; break;
                case 'E' : j++; break;
            }
            pair = new Pair<>(i, j);
            if(set.contains(pair)){
                return true;
            }else{
                set.add(pair);
            }
        }
        return false;
    }
}
  1. 自己实现哈希映射函数,比如令哈希函数 f(x, y) = x * 20001 + y。因为因为 y的取值范围在[-10^4, 10^4]内,共有 20001种可能性
class Solution {
    public boolean isPathCrossing(String path) {
        int n = path.length();
        Set<Integer> set = new HashSet<>();
        int i = 0;
        int j = 0;
        set.add(getHash(i, j));
        for(int k = 0; k < n; k++){
            char c = path.charAt(k);
            switch(c){
                case 'N' : i--; break;
                case 'S' : i++; break;
                case 'W' : j--; break;
                case 'E' : j++; break;
            }
            int value = getHash(i, j);
            if(set.contains(value)){
                return true;
            }else{
                set.add(value);
            }
        }
        return false;
    }
    public int getHash(int x, int y) {
        return x * 20001 + y;
    }
}

1497. 检查数组对是否可以被 k 整除

题目描述2

给你一个整数数组 arr 和一个整数 k ,其中数组长度是偶数,值为 n 。

现在需要把数组恰好分成 n / 2 对,以使每对数字的和都能够被 k 整除。

如果存在这样的分法,请返回 True ;否则,返回 False 。

示例 1:

输入:arr = [1,2,3,4,5,10,6,7,8,9], k = 5
输出:true
解释:划分后的数字对为 (1,9),(2,8),(3,7),(4,6) 以及 (5,10) 。

示例 2:

输入:arr = [1,2,3,4,5,6], k = 7
输出:true
解释:划分后的数字对为 (1,6),(2,5) 以及 (3,4) 。

示例 3:

输入:arr = [1,2,3,4,5,6], k = 10
输出:false
解释:无法在将数组中的数字分为三对的同时满足每对数字和能够被 10 整除的条件。

示例 4

输入:arr = [-10,10], k = 2
输出:true

示例 5:

输入:arr = [-1,1,-2,2,-3,3,-4,4], k = 3
输出:true

提示:

arr.length == n
1 <= n <= 10^5
n 为偶数
-10^9 <= arr[i] <= 10^9
1 <= k <= 10^5

Solution2

我们将数组中的每个数 对 k 进行取模,并将得到的余数进行配对。

class Solution {
    public boolean canArrange(int[] arr, int k) {
        Map<Integer, Integer> mod = new HashMap<Integer, Integer>();
        // 统计每个余数出现的次数
        for(int num : arr){
            mod.put((num % k + k) % k, mod.getOrDefault((num % k + k) % k, 0) + 1);
        }
        for(Map.Entry<Integer, Integer> entry : mod.entrySet()){
            int t = entry.getKey();
            int occ = entry.getValue();
            //余数为t的次数必须和余数为k-t的次数相等,互相配对
            if(t > 0 && mod.getOrDefault(k - t, 0) != occ){
                return false;
            }
        }
        //余数为0的次数也要是偶数
        return mod.getOrDefault(0, 0) % 2 == 0;
    }
}

Tips:

  1. 注意,负数取模后是负数,要把它们换算到正数上来,通过下面的方法得到在[0, k-1]的范围内的余数。
xk = (x % k + k) % k
  1. 用Entry依次取哈希表的每个键值
for(Map.Entry<Integer, Integer> entry : map.entrySet()){
    int t = entry.getKey();
    int occ = entry.getValue();
    ...
}

1498. 满足条件的子序列数目

题目描述3

给你一个整数数组 nums 和一个整数 target 。

请你统计并返回 nums 中能满足其最小元素与最大元素的 小于或等于 target 的 非空 子序列的数目。

由于答案可能很大,请将结果对 10^9 + 7 取余后返回。

示例 1:

输入:nums = [3,5,6,7], target = 9
输出:4
解释:有 4 个子序列满足该条件。
[3] -> 最小元素 + 最大元素 <= target (3 + 3 <= 9)
[3,5] -> (3 + 5 <= 9)
[3,5,6] -> (3 + 6 <= 9)
[3,6] -> (3 + 6 <= 9)

示例 2:

输入:nums = [3,3,6,8], target = 10
输出:6
解释:有 6 个子序列满足该条件。(nums 中可以有重复数字)
[3] , [3] , [3,3], [3,6] , [3,6] , [3,3,6]

示例 3:

输入:nums = [2,3,3,4,6,7], target = 12
输出:61
解释:共有 63 个非空子序列,其中 2 个不满足条件([6,7], [7])
有效序列总数为(63 - 2 = 61)

示例 4

输入:nums = [5,2,4,1,7,6,8], target = 16
输出:127
解释:所有非空子序列都满足条件 (2^7 - 1) = 127

提示:

1 <= nums.length <= 10^5
1 <= nums[i] <= 10^6
1 <= target <= 10^6

Solution3

二分+快速幂

注意子序列不要求连续。

  • 先排序,因为只关心这个子序列的最小值和最大值,而不关心元素的相对顺序。

  • 固定i,每次二分查找nums[i] + nums[r] <= target的最大r,则这个i对应的子序列的数目为2^(r-i),加到答案中。

    理解2^(r-i):从[i+1,r]的这几个数字,每个数字都有「出现在子序列中」和「不出现在子序列中」两种状态,2*2*2...*2一共r-i个2相乘。

    注意要把2的幂次的计算结果先取模存起来,因为会溢出。遇到取模的技巧:

( a + b ) m o d P = [ ( a m o d P ) + ( b m o d P ) ] m o d P , ( a × b ) m o d P = [ ( a m o d P ) × ( b m o d P ) ] m o d P (a+b)modP=[(amodP)+(bmodP)]modP , (a×b)modP=[(amodP)×(bmodP)]modP (a+b)modP=[(amodP)+(bmodP)]modP,(a×b)modP=[(amodP)×(bmodP)]modP

class Solution {
    static final int P = 1000000007;
    public int numSubseq(int[] nums, int target) {
        Arrays.sort(nums);
        long ans = 0;
        int n = nums.length;
        // 把2的幂次的计算结果先取模存起来,因为会溢出
        long[] pow = new long[n];
        pow[0] = 1;
        for(int i = 1; i < n; i++){
            pow[i] = (pow[i-1] << 1) % P;
        }
        for(int i = 0; i < n; i++){
            int l = i;
            int r = n-1;
            if(2 * nums[i] <= target){
            //二分 找nums[i] + nums[r] <= target的最大r
                while(l < r){
                    int mid = l + (r - l + 1) / 2;
                    if(nums[i] + nums[mid] > target){
                        r = mid - 1;
                    }else{
                        l = mid;
                    }
                }
                ans = (ans + pow[r-i]) % P;
            }
        }
        return (int)ans;
    }
}

1499. 满足不等式的最大值

题目描述4

给你一个数组 points 和一个整数 k 。数组中每个元素都表示二维平面上的点的坐标,并按照横坐标 x 的值从小到大排序。也就是说 points[i] = [xi, yi] ,并且在 1 <= i < j <= points.length 的前提下, xi < xj 总成立。

请你找出 yi + yj + |xi - xj| 的 最大值,其中 |xi - xj| <= k 且 1 <= i < j <= points.length。

题目测试数据保证至少存在一对能够满足 |xi - xj| <= k 的点。

示例 1:

输入:points = [[1,3],[2,0],[5,10],[6,-10]], k = 1
输出:4
解释:前两个点满足 |xi - xj| <= 1 ,带入方程计算,则得到值 3 + 0 + |1 - 2| = 4 。第三个和第四个点也满足条件,得到值 10 + -10 + |5 - 6| = 1 。
没有其他满足条件的点,所以返回 4 和 1 中最大的那个。

示例 2:

输入:points = [[0,0],[3,0],[9,2]], k = 3
输出:3
解释:只有前两个点满足 |xi - xj| <= 3 ,带入方程后得到值 0 + 0 + |0 - 3| = 3 。

提示:

2 <= points.length <= 10^5
points[i].length == 2
-10^8 <= points [i][0], points[i][1] <= 10^8
0 <= k <= 2 * 10^8
对于所有的1 <= i < j <= points.lengthpoints[i][0] < points [j][0] 都成 立。也就是说,xi 是严格递增的。

Solution4

双端队列。

化成yi-xi+yj+xj,固定j的值后,问题就变成了寻找区间内的最大值

从大到小遍历j,那么区间就是一个滑动窗口。可参考239. 滑动窗口最大值中用双端队列求解的方法。
参考题解:https://leetcode-cn.com/problems/max-value-of-equation/solution/java-on-shuang-xiang-dui-lie-by-qi-ge-tai-yang/

class Solution {
    public int findMaxValueOfEquation(int[][] points, int k) {
        Deque<Integer> queue = new ArrayDeque();
        int re = Integer.MIN_VALUE;
        for(int j = 0; j < points.length; j++){
            // 每次根据对首元素更新答案,更新前要判断是否满足xj-xi<=k
            // 从队列首部移除不满足不等式限制的项
            while(queue.size() > 0){
                int t = queue.getFirst();
                if(points[j][0] - points[t][0] > k){
                    queue.removeFirst();
                }else{
                    break;
                }
            }
            //更新答案
            if(queue.size() > 0){
                re = Math.max(re, points[j][0] + points[j][1] + points[queue.getFirst()][1] - points[queue.getFirst()][0]);
            }
            while(queue.size() > 0){
                int t = queue.getLast();
                // if(points[j][0] - points[t][0] <= points[j][1] - points[t][1]){
                // 新来一个点xj, yj,队列尾部为xt,yt,如果yj-xj>=yt-xt,也就是说存在更大的,移除队列尾部元素
                if(points[j][1] - points[j][0] >= points[t][1] - points[t][0]){
                    queue.removeLast();
                }else{
                    break;
                }
            }
            queue.addLast(j);
        }
        return re;
    }
}

欢迎关注公众号:GitKid,暂时每日分享LeetCode题解,在不断地学习中,公众号内容也在不断充实,欢迎扫码关注

你可能感兴趣的:(数据结构与算法,LeetCode)