【数据结构与算法】优秀的竞赛题
形成两个异或相等数组的三元组数目
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;
}
}