【数据结构与算法】优秀的竞赛题

形成两个异或相等数组的三元组数目

LeetCode:形成两个异或相等数组的三元组数目

题目描述:

给你一个整数数组 arr 。

现需要从数组中取三个下标 i、j 和 k ,其中 (0 <= i < j <= k < arr.length) 。

a 和 b 定义如下:

a = arr[i] ^ arr[i + 1] ^ ... ^ arr[j - 1]
b = arr[j] ^ arr[j + 1] ^ ... ^ arr[k]
注意:^ 表示 按位异或 操作。

请返回能够令 a == b 成立的三元组 (i, j , k) 的数目。

示例:

输入:arr = [2,3,1,6,7]
输出:4
解释:满足题意的三元组分别是 (0,1,2), (0,2,2), (2,3,4) 以及 (2,4,4)

思想:

理解这一点:“a==b”相当于“a ^ b == 0”。问题就变得简单了。遍历所有(i,k)区间即可。

关键是“a^b==0”这点太难想到了

代码:

class Solution {
    public int countTriplets(int[] arr) {
        int count = 0;
        for(int i=0;i<arr.length-1;++i){
            int m = arr[i];
            for(int j=i+1;j<arr.length;++j){
                m^=arr[j];
                if(m==0) count+=j-i;
            }
        }
        return count;
    }
}

制作m束花所需的最小天数

LeetCode:制作m束花所需的最小天数

题目描述:

给你一个整数数组 bloomDay,以及两个整数 m 和 k 。

现需要制作 m 束花。制作花束时,需要使用花园中 相邻的 k 朵花 。

花园中有 n 朵花,第 i 朵花会在 bloomDay[i] 时盛开,恰好 可以用于 一束 花中。

请你返回从花园中摘 m 束花需要等待的最少的天数。如果不能摘到 m 束花则返回 -1 。

示例:

输入:bloomDay = [1,10,3,10,2], m = 3, k = 1
输出:3
解释:让我们一起观察这三天的花开过程,x 表示花开,而 _ 表示花还未开。
现在需要制作 3 束花,每束只需要 1 朵。
1 天后:[x, _, _, _, _]   // 只能制作 1 束花
2 天后:[x, _, _, _, x]   // 只能制作 2 束花
3 天后:[x, _, x, _, x]   // 可以制作 3 束花,答案为 3

思想:

这题的思想就是二分查找法。
思路很凌乱,需要把问题分解:

  • 单独写一个函数来完成此操作:每次取到一个mid时,需要判断在该天数下能不能完成花束制作。
  • 二分查找就按照标准来写。

代码:

class Solution {
    private int m;
    private int k;
    public int minDays(int[] bloomDay, int m, int k) {
        this.m = m;
        this.k=k;
        int max=0;
        for(int item : bloomDay){
            max = Math.max(max, item);
        }
        if(!success(bloomDay,max)) return -1;
        return binarySearch(bloomDay,0,max);
    }
    private int binarySearch(int[] bloomDay, int low, int high){
        if(high<low) return low;
        int mid = low + (high - low)/2;
        if(success(bloomDay,mid)){
            return binarySearch(bloomDay, low, mid-1);
        }else{
            return binarySearch(bloomDay, mid + 1, high);
        }
    }
    private boolean success(int[] bloomDay, int days){
        int res=0;
        int cnum=0;
        for(int item : bloomDay){
            if(item<=days){
                ++cnum;
                if(cnum == k) res++;
                else continue;
            }
            cnum = 0;
        }
        return res>=m;
    }
}

最小体力消耗路径

LeetCode:最小体力消耗路径

题目描述:

示例:

输入:heights = [[1,2,2],[3,8,2],[5,3,5]]
输出:2
解释:路径 [1,3,5,3,5] 连续格子的差值绝对值最大为 2 。
这条路径比路径 [1,2,2,2,5] 更优,因为另一条路劲差值最大值为 3 。

思想:

方法一:二分查找+BFS
主要思想:看完题目,我的思路是如何通过遍历一遍得到最小“体力耗费值”,这种思路很难。换个方向去想,如果给定“体力耗费值”S,判断当前S下能否到达目的地,就简单多了,使用BFS搜索+记忆化来完成。外层遍历这个S可使用二分查找。

编程技巧:
1.将BFS搜索的方法独立出来,更清晰;
2.boolean[] seen = new boolean[len*wid]可记忆已被遍历过的节点(无需设置成二维);
3.每个节点取子节点有四个方向,可用一个数组存这四个方向:static int[][] dirs = new int[][]{{0,1},{1,0},{-1,0},{0,-1}};
4.注意,更新seen数组时机,应该在取到子节点时候,而不是节点出队之后,不然会超时。

代码:

class Solution {
    class Node{
        int x;
        int y;
        Node(int x,int y){
            this.x = x;
            this.y = y;
        }
    }

    public int minimumEffortPath(int[][] heights) {
        int min=0,max=999999;
        for(int[] item : heights){
            for(int i=0;i<item.length;++i){
                min = Math.min(min,item[i]);
                max = Math.max(max,item[i]);
            }
        }
        int low = 0,high=max-min;

        while(low<=high){
            int mid = (low + high)/2;
            if(canReach(heights,mid)){
                high = mid-1;
            }else{
                low = mid+1;
            }
        }
        return low;

    }
    static int[][] dirs = new int[][]{{0,1},{1,0},{-1,0},{0,-1}};
    private boolean canReach(int[][] heights, int S){
        int len = heights.length;
        int wid = heights[0].length;
        Queue<Node> queue = new LinkedList<>();
        queue.add(new Node(0,0));
        boolean[] seen = new boolean[len*wid];
        seen[0] = true;

        while(!queue.isEmpty()){
            Node node = queue.poll();
            int x = node.x;
            int y = node.y;
            for(int i=0;i<4;++i){
                int nx = x+dirs[i][0];
                int ny = y+dirs[i][1];
                if(!inRange(nx,ny,len,wid)) continue;//超出数组范围
                if(seen[nx*wid+ny]) continue;//已经遍历过的,无需遍历
                if(Math.abs(heights[x][y]-heights[nx][ny])<=S){
                    queue.offer(new Node(nx,ny));//满足要求,加入队列
                    seen[nx*wid + ny] = true;//子节点全部标记为“已遍历”
                }
            }
        }
        return seen[len*wid-1];
    }
    private boolean inRange(int nx,int ny,int len,int wid){
        if(nx<len&&nx>=0&&ny<wid&&ny>=0) return true;
        return false;
    }
}

将x减到0的最小操作数

LeetCode:将 x 减到 0 的最小操作数

题目描述:

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例:

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

思想:

思想:外部最小即找中间最大。用滑动窗口找最长的片段使得sum(片段)=sum(nums)-x

代码:

class Solution {
    public int minOperations(int[] nums, int x) {
        int len = nums.length;
        int sum = 0;
        for(int num : nums){
            sum+=num;
        }
        int total = sum - x;
        int current = nums[0];
        int low=0,high=1;
        int res = len+1;
        while(low<=high&&high<len){
            if(current<total){
                current+=nums[high++];
            }else if(current>total){
                current-=nums[low++];
            }
            if(current==total){
                res = Math.min(res,len-high+low);
                if(high<len) current+=nums[high++];
            }
        }
        return res==(len+1)?-1:res;
    }
}

找出最具竞争力的子序列

LeetCode:找出最具竞争力的子序列

题目描述:

给你一个整数数组 nums 和一个正整数 k ,返回长度为 k 且最具 竞争力 的 nums 子序列。

数组的子序列是从数组中删除一些元素(可能不删除元素)得到的序列。

在子序列 a 和子序列 b 第一个不相同的位置上,如果 a 中的数字小于 b 中对应的数字,那么我们称子序列 a 比子序列 b(相同长度下)更具 竞争力 。 例如,[1,3,4] 比 [1,3,5] 更具竞争力,在第一个不相同的位置,也就是最后一个位置上, 4 小于 5 。

示例:

输入:nums = [3,5,2,6], k = 2
输出:[2,6]
解释:在所有可能的子序列集合 {[3,5], [3,2], [3,6], [5,2], [5,6], [2,6]} 中,[2,6] 最具竞争力。

思想:

在一个无序数组中找一个有序序列,考虑使用单调栈来记忆。

直接通过k来确定栈中元素大小,这样不合适,因为栈中元素是动态变化的。但是使用逆向思维,通过k可以确定 n-k,即可以确定出栈元素数量,控制出栈数量为n-k,剩下来的就是目标序列了。

代码:

class Solution {
    public int[] mostCompetitive(int[] nums, int k) {
        Stack<Integer> stack = new Stack<>();
        int popNum = nums.length-k;
        for(int item : nums) {
            while(popNum>0&&!stack.empty()&&stack.peek()>item){
                stack.pop();
                popNum--;
            }
            stack.push(item);
        }
        while(popNum>0){
            stack.pop();
            popNum--;
        }
        int[] res = new int[k];
        for(int i=k-1;i>=0;--i){
            res[i] = stack.pop();
        }
        return res;
    }
}
posted @ 2020-04-06 12:31  数小钱钱的种花兔  阅读(418)  评论(0编辑  收藏  举报