模板
i = 0
while(i<n){
start = i
while( i<n && check(args) ) {
i+=1
}
}
1. LC 3011 判断一个数组是否可以变为有序
这题我比赛时用的并查集。看灵神视频学了个分组循环的做法。
对于每个分组,如果可以交换,则扩展分组的窗口,直至达到尽头或者不能交换为止。这样这个分组里的数都是可以任意交换的,因此就可以对这个分组进行排序。对每个分组排序后如果能使得整个数组有序,那么就成功。
import java.util.Arrays;
class Solution {
public boolean canSortArray(int[] nums) {
int i = 0;
int start;
int n = nums.length;
while(i<n){
start = i;
while(i<n && check(nums[start],nums[i])){
i++;
}
Arrays.sort(nums,start,i);
}
return inOrder(nums);
}
private boolean check(int num1,int num2){
return Integer.bitCount(num1)==Integer.bitCount(num2);
}
private boolean inOrder(int[] nums){
for (int i = 1; i < nums.length; i++) {
if(nums[i-1]>nums[i]){
return false;
}
}
return true;
}
}
2. LC 1446 连续字符
入门题。分组记录每个连续字符子串长度,维护最大值。
class Solution {
public int maxPower(String s) {
char[] ch = s.toCharArray();
int i = 0;
int n = ch.length;
int max = 0;
while(i<n){
char c = ch[i];
int start = i;
while(i<n&&ch[i]==c){
i++;
}
max = Math.max(max,i-start);
}
return max;
}
}
3. LC 1869 哪种连续子字符串更长
入门题。分组记录0/1子串长度,维护最大值,最后比较。
class Solution {
public boolean checkZeroOnes(String s) {
int max0 = 0;
int max1 = 1;
char[] ch = s.toCharArray();
int i = 0;
int n = ch.length;
while(i< n){
int start = i;
char c = ch[i];
boolean which = c =='1';
while(i<n && c == ch[i]){
i++;
}
if(which){
max1 = Math.max(max1,i-start);
}else{
max0 = Math.max(max0,i-start);
}
}
return max1>max0;
}
}
4. LC 1957 删除字符使字符串变好
入门题。分组检查连续相同子串长度,超过2就缩减到2,拼到答案里即可。
class Solution {
public String makeFancyString(String s) {
char[] ch = s.toCharArray();
StringBuilder sb = new StringBuilder();
int i = 0;
int n = ch.length;
while(i<n){
int start = i;
while(i<n && ch[start]==ch[i]){
i++;
}
int cnt = Math.min(2,i-start);
sb.append(String.valueOf(ch[start]).repeat(cnt));
}
return sb.toString();
}
}
5. LC 2110 股票平滑下跌阶段的数目
入门题。分组查询每段平滑下跌阶段。贡献是(l+1)*l/2(等差数列),累加即可。
class Solution {
public long getDescentPeriods(int[] prices) {
int i = 0;
int start;
int n = prices.length;
long ans = 0;
while(i<n){
start = i;
while(i<n-1 && prices[i]==prices[i+1]+1 ){
i++;
}
int cnt = i-start+1;
ans += (long) (cnt + 1) *cnt/2;
i++;
}
return ans;
}
}
6. LC 2765 最长交替子数组
每日一题+入门题。分组查询交替子数组长度,维护最大值
class Solution {
public int alternatingSubarray(int[] nums) {
int n = nums.length;
int max = -1;
int i = 0;
int start;
while(i<n){
start = i;
int diff = 1;
while(i<n-1 && nums[i+1]-nums[i]==diff){
diff *= -1;
i++;
}
if(i>start){
max = Math.max(max,i-start+1);
}
if(!(i>start)){
i++;
}
}
return max;
}
}
7. LC 228 汇总区间
入门题。分组查询递增1的区间,记录下来整合成目标格式的字符串即可。
import java.util.ArrayList;
import java.util.List;
class Solution {
public List<String> summaryRanges(int[] nums) {
ArrayList<List<Integer>> l = new ArrayList<>();
int i = 0;
int n = nums.length;
while(i<n){
List<Integer> tmp = new ArrayList<>();
while(i<n-1 && nums[i]+1==nums[i+1]){
tmp.add(nums[i]);
i++;
}
tmp.add(nums[i]);
i++;
l.add(tmp);
}
return print(l);
}
private List<String> print(List<List<Integer>> l){
ArrayList<String> ans = new ArrayList<>();
for (List<Integer> is : l) {
StringBuilder sb = new StringBuilder();
if(is.size()==1){
sb.append(is.get(0));
}else{
sb.append(is.get(0)).append("->").append(is.get(is.size()-1));
}
ans.add(sb.toString());
}
return ans;
}
}
8. LC 2760 最长奇偶子数组
入门题。分组查询满足条件的区间,维护最大值即可。
class Solution {
public int longestAlternatingSubarray(int[] nums, int threshold) {
int max = 0;
int i = 0;
int start;
int n = nums.length;
while(i<n){
if(nums[i]%2!=0 || nums[i]>threshold){
i++;
continue;
}
start = i;
while(i<n-1 && nums[i]%2!=nums[i+1]%2 && nums[i+1]<=threshold){
i++;
}
max = Math.max(max,i-start+1);
if(i==start){
i++;
}
}
return max;
}
}
9. LC 1887 使数组元素相等的减少操作次数
这道题我没想出来分组循环的写法。就想了个最好情况下O(nlgn)的散列表写法。思路大致就是,每个数最终都要等于那个最小数。每一批相同的数都要往下逐级递减。举个例子:
[ 1 1 2 2 3 3 ]
两个3要都减少到2,然后4个2减少到1。维护一个有序列表模拟这个过程即可。
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
class Solution {
public int reductionOperations(int[] nums) {
int ops = 0;
// 最终都会减小到最小值
TreeMap<Integer, Integer> tm = new TreeMap<>();
for (int num : nums) {
mAdd(tm,num);
}
// 累加即可
int prefix = 0;
Integer[] arr = tm.keySet().toArray(Integer[]::new);
for(int i=arr.length-1;i>=0;i--){
prefix += tm.get(arr[i]);
ops+=prefix;
}
ops-=prefix;
return ops;
}
private void mAdd(Map<Integer,Integer> m,int num){
m.put(
num,m.getOrDefault(num,0)+1
);
}
}
后来我发现自己sb了,这个写法完全可以改成严格的O(nlgn)的,因为减少操作的时候,跟元素的索引根本没关系。
prefix相当于每个分组到最小元素的步长。每遍历到某个分组的元素,就加上一次这个步长。如果是下一个分组就给步长+1。
import java.util.Arrays;
class Solution {
public int reductionOperations(int[] nums) {
int ops = 0;
Arrays.sort(nums);
int prefix = 0; // 相当于步长
for(int i=1;i<nums.length;i++){
if(nums[i]!=nums[i-1]){
prefix++;
}
ops+=prefix;
}
return ops;
}
}
10. LC 2038 如果相邻两个颜色均相同则删除当前颜色
少见的博弈类型的题。这道题比较简单,只要统计双方的可以操作的次数就可以了。可能会疑惑,有没有可能A操作完给了B新的操作机会?或者反过来,B给A机会?这是不可能的,因为要求连续子数组的数量大于等于3,所以删到长度≤2的时候就不能删了,就不可能给对方新的机会,这个≤2的子串会一直卡着对面操作。
统计操作次数很简单。分组统计连续子数组长度,为自己的操作次数贡献Math.max(0,l-2)次机会。
class Solution {
public boolean winnerOfGame(String colors) {
// 统计分别可以删几次就行
int opA = 0;
int opB = 0;
char[] ch = colors.toCharArray();
int n = ch.length;
int i = 0;
int start;
while(i<n){
start = i;
while(i<n && ch[i]==ch[start]){
i++;
}
int acc = Math.max(0,i-start-2);
if(ch[start]=='A'){
opA += acc;
}else{
opB += acc;
}
}
return opA>0 && opA>opB;
}
}
11. LC 1759 统计同质子字符串的数目
入门题。查询连续相同子串的长度,计算贡献。1+2+…+l即可。
取模想不清楚?那就直接在能取模的地方全部取模,我愿称之为取模仙人。
class Solution {
int mod = (int)1e9+7;
public int countHomogenous(String s) {
char[] ch = s.toCharArray();
long ans = 0;
int i=0;
int start;
int n = ch.length;
while(i<n){
start = i;
while(i<n && ch[i]==ch[start]){
i++;
}
int cnt = i-start;
ans = (ans%mod + ((long) ((cnt + 1) % mod) *(cnt%mod)/2)%mod)%mod;
}
return (int) (ans%mod);
}
}
12. LC 1578 使绳子变成彩色的最短时间
入门题。查询连续相同的子串,记录需要最长时间删除的代价,当前分组总体时间减去该最长时间即为分组的最短时间,每个分组的最短时间加起来就是总体的最短时间。
class Solution {
public int minCost(String colors, int[] neededTime) {
char[] ch = colors.toCharArray();
int n = ch.length;
int i = 0;
int start;
int ans = 0;
while(i<n){
start = i;
int max = neededTime[start];
int tmp = 0;
while(i<n && ch[start]==ch[i]){
max = Math.max(max,neededTime[i]);
tmp+=neededTime[i];
i++;
}
if(i!=start+1){
ans += tmp - max;
}
}
return ans;
}
}
13. LC 1839 所有元音按顺序排布的最长子字符串
入门题。对于每个分组记录长度,维护当前最大的字符,要求后续的全部大于这个字符。同时记录所有出现过的字符,如果到最后5个元音字符全部出现过,那么维护最大值。
import java.util.Arrays;
import java.util.HashSet;
class Solution {
static char[] vowels = new char[]{'a','e','i','o','u'};
public int longestBeautifulSubstring(String word) {
char[] ch = word.toCharArray();
int n = ch.length;
int i = 0;
int max = 0;
while(i<n){
HashSet<Character> vs = new HashSet<>();
int start = i;
char last = ch[i];
while(i<n && ch[i]>=last){
last = ch[i];
vs.add(ch[i]);
i++;
}
if(check(vs)){
max = Math.max(max,i-start);
}
}
return max;
}
private boolean check(HashSet<Character> vs){
return vs.size()==5;
}
}
14. LC 826 安排工作以达到最大收益
首先可以看一个例子:
difficult = [85,47,57] profit = [24,66,99]
很明显第一个任务又难钱又少,这样的任务谁还选?肯定做那个难度低并且钱多的嘛。
所以我们的任务就是,让profit随着difficult的增加而增加,也就是类似于:
difficult = [2,4,6,8,10] profit = [10,20,30,40,50]
profit随着difficult单调增,很明显是吧difficult当作x轴了,所以我们按照difficult排序,同时如果difficult相同,把profit更大的排在前面(这样是为了分组循环时,让profit更大的收割后面又难钱又少的任务,或者说一样难但钱更少的任务)
分组循环
每一轮以当前遍历到的位置为基点,如果后面的某个位置难度≥当前位置的难度,但是钱≤当前位置的钱,就可以跳过他了。否则不能跳过
二分查找
分组循环后,profit随着difficult单调增。这个时候就变成了一个很trivial二分题目了。
具体实现上,分组循环时不需要额外开第二个数组,可以原地更新,这是因为遍历的速度一定快于或等于数组大小增长的速度。
import java.util.Arrays;
import java.util.Comparator;
class Solution {
public int maxProfitAssignment(int[] difficulty, int[] profit, int[] worker) {
int m,n;
m = worker.length;
n = difficulty.length;
int[][] rec = new int[n][2];
for (int i = 0; i < n; i++) {
rec[i][0] = difficulty[i];
rec[i][1] = profit[i];
}
Arrays.sort(rec, (o1, o2) -> {
if(o1[0]==o2[0]){
return -Integer.compare(o1[1],o2[1]);
}
return Integer.compare(o1[0],o2[0]);
});
int size = 0;
int i = 0;
while(i<n){
int j = i;
while(i<n){
if(rec[j][1]>=rec[i][1] && rec[j][0]<=rec[i][0]){
i++;
}else{
break;
}
}
rec[size++] = rec[j];
}
int ans = 0;
for (int j = 0; j < m; j++) {
ans += max_profit(rec,size,worker[j]);
}
return ans;
}
private int max_profit(int[][] rec,int size,int ability){
int l = 0;
int r = size;
int mid,ans;
ans = 0;
while(l<r){
mid = ((r-l)>>>1)+l;
if(rec[mid][0]<=ability){
ans = rec[mid][1];
l = mid+1;
}else{
r = mid;
}
}
return ans;
}
}
空间复杂度O(n),时间复杂度O((m+n)logn),瓶颈在排序。