【编码题】字符串S由小写字母构成,长度为n。定义一种操作,每次都可以挑选字符串中任意的两个相邻字母进行交换。询问在至多交换m次之后,字符串中最多有多少个连续的位置上的字母相同?
【编码题】字符串S由小写字母构成,长度为n。定义一种操作,每次都可以挑选字符串中任意的两个相邻字母进行交换。询问在至多交换m次之后,字符串中最多有多少个连续的位置上的字母相同?
第一行为一个字符串S与一个非负整数m。(1 <= |S| <= 1000, 1 <= m <= 1000000)
一个非负整数,表示操作之后,连续最长的相同字母数量。
abcbaa 2
2
使2个字母a连续出现,至少需要3次操作。即把第1个位置上的a移动到第4个位置。 所以在至多操作2次的情况下,最多只能使2个b或2个a连续出现。
import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String[] strs = sc.nextLine().split(" ");
String s = strs[0];
int count = Integer.parseInt(strs[1]);
int len = s.length();
int[][] record = new int[len][26]; //记录字符串s的每个位置和26个字母的关系
int[] length = new int[26]; //记录每个字母在规定交换次数内最长的相同字母数量
for(int i = 0; i < len; i++){
record[i][s.charAt(i)-'a'] = 1;
}
for(int i = 0; i < 26; i++){
int[] position = new int[len]; //对于每个字母,记录字符串s中出现该字母的位置
int k = 0; //字符串s中出现该字母k次
for(int j = 0; j < len; j++){
if(record[j][i] == 1){
position[k] = j;
k += 1;
}
}
if(k == 0){
length[i] = 0;
}else if(k == 1){
length[i] = 1;
}else{
int answer = Integer.MIN_VALUE;
for (int p = 0; p < k; p++){
for (int q = p+1; q < k; q++){
int res = dfsSwichCharacter(p, q, position);
if(res <= count){ //在规定交换次数内,更新连续的某个相同字母数量
answer = Math.max(answer, q-p+1);
}
}
}
length[i] = answer;
}
}
int result = Integer.MIN_VALUE;
for(int i : length){
result = Math.max(result, i);
}
System.out.println(result);
}
public static int dfsSwichCharacter(int i, int j, int[] position){
if(i == j){
return 0;
}else if(i+1 == j){ //说明字符串s中position[i]和position[j]之间不可能再有该字母,所以移动次数就是坐标之差减一
return position[j] - position[i] - 1;
}else{ //移动次数相当于是把position[j]的字母移到position[i]隔壁的次数减去这两个位置之间该字母的个数
return dfsSwichCharacter(i+1, j-1, position) + position[j]-position[i]-1 - (j-i-1);
}
}
} #include <iostream>
#include <string>
using namespace std;
int main(void)
{
string str;
int step_max = 0;
cin >> str >> step_max;
int i = 0;
int lo = 0, hi = 0;
int step = 0;
int size = str.length();
int result = 0 , max_result = 0;
bool flag1 = true;
bool flag2 = true;
char target;
int step_lo = 0, step_hi = 0;
int ss = 0;
int s = 0;
int base_lo = 0;
int base_hi = 0;
for(i=0; i<size; i++)
{
step = step_max;
lo = hi = i;
target = str[i];
flag1 = true;
flag2 = true;
base_lo = 0;
base_hi = 0;
while(lo > 0 && str[lo-1] == str[lo])
lo--;
while(hi < size-1 && str[hi+1] == str[hi])
hi++;
s = hi - lo + 1;
ss = 0;
while(step > 0)
{
step_lo = step;
step_hi = step;
int temp_lo = lo;
int temp_hi = hi;
while(flag1 == true && step_lo > 0 && lo > 0 && str[lo-1] != target)lo--,step_lo--;
if(flag1 == true && str[lo-1] != target)
flag1 = false;
while(flag2 == true && step_hi > 0 && hi < size-1 && str[hi+1] != target)hi++,step_hi--;
if(flag2 == true && str[hi+1] != target)
flag2 = false;
base_lo += (step - step_lo);
base_hi += (step - step_hi);
if(step - base_lo < 0)
flag1 = false;
if(step - base_hi < 0)
flag2 = false;
if(flag1 == false && flag2 == false)
{
break;
}
if(flag1 == true && step_lo > step_hi)
{
hi = temp_hi;
lo--;
base_hi -= (step - step_hi);
step = step - base_lo;
ss++;
}
else if(flag2 == false)
{
hi = temp_hi;
lo--;
base_hi -= (step - step_hi);
step = step - base_lo;
ss++;
}
else if(flag2 == true && step_lo <= step_hi)
{
lo = temp_lo;
hi++;
base_lo -= (step - step_lo);
step = step - base_hi;
ss++;
}
else if(flag1 == false)
{
lo = temp_lo;
hi++;
ss++;
base_lo -= (step - step_lo);
step = step - base_hi;
}
}
result = s + ss;
if(result > max_result)
max_result = result;
}
cout << max_result;
return 0;
}
暴力方法一个
/*
先是上面那位老哥的分析:
提取出相同字符的位置,比如ababa中a的位置为(0,2,4),b的位置为(1,3)。对每个位置向量用动态规划求解。
字符a的位置数组为arr,动态规划过程:
dp(i,j)表示字符a第i个位置到第j个位置的字符要全部相邻,至少要用需要多少次交换。
1. i==j时,表示一个字符,不用交换,dp(i,j)为0;
2. i+1==j时,表示两个字符,位置相差arr[j]-arr[i];
3.其他情况,dp(i,j) = dp(i+1,j-1) + arr[j]-arr[i] - (j - i);
思路:
首先思考下第3种转移。因为[i+1,j-1]之间已经成了一个连续的段,所以左右两边都是往中间靠的,不管
怎么靠,交换的次数肯定都一样。然后就非常简单了
*/
#include<bits/stdc++.h>
using namespace std;
#define N 1005
int dp[N][N];//dp[i][j]表示第i个某种字符和相同的第j个字符成为一个连续段的花费
char str[2005];//我开了1005竟然说我越界,怀疑数据有问题
int main()
{
int m, len, ans=1;
scanf("%s %d", str, &m);
len=strlen(str);
for(int ch=0; ch<26; ch++)
{
int siz=0;
vector<int> v;
memset(dp, 63, sizeof(dp));
for(int i=0; i<len; i++)
if(str[i]==(ch+'a'))
{
v.push_back(i);
siz++;
dp[siz][siz]=0;
}
for(int i=siz-1; i>=0; i--)
{
for(int j=i+1; j<siz; j++)
{
int dis=v[j]-v[i]-(j-i);
if(i+1!=j)
dis+=dp[i+1][j-1];
dp[i][j]=min(dp[i][j], dis);
if(dp[i][j]<=m) ans=max(ans, j-i+1);
}
}
}
cout<<ans<<endl;
return 0;
}
``` c++
/*
这题是可以做到O(n)的。
首先考虑不同的字符
如样例abcbaa
a的位置是(0,4,5)
假设第一个位置在D,我们需要最小化
|0 - D| + |4 - D - 1| + |5 - D - 2|
变成|0 - D| + |3 - D| + |3 - D|
D取中位数时最优。证明可以考虑把这些数放在数轴上,你取一个D作为点,你发现你每次靠近中位数的时候,差值会不断减少
(打acm的应该很熟悉这个。。)
所以考虑某个字符的位置数组为pos[]
pos[i] = pos[i] - i
这样的话,任意取一个子区间(l,r)都能确定答案就是
|pos[l] - D| + |pos[l + 1] - D| + ... + |pos[r] - D|
处理前缀和,就可以快速求出这一段的最小花费,具体就是知道了D,那就知道绝对值拆开来是怎样。
所以对每个字符进行尺取就行了。
*/
#include<bits/stdc++.h>
using namespace std;
const int N = 2e6 + 15;
vector<int>C[27];
int pre[N];
inline int check(int l, int r) {
int mid = (r + l) >> 1;
int pos = pre[mid] - pre[mid - 1];
return pos * (mid - l) - pre[mid - 1] + pre[l - 1] + pre[r] - pre[mid] - pos * (r - mid);
}
int main() {
#ifdef local
freopen("input", "r", stdin);
// freopen("out.txt","w",stdout);
#endif
ios::sync_with_stdio(0);
cin.tie(0);cout.tie(0);
string s;
int m;
cin >> s >> m;
for(int i = 0; i < (int) s.size(); i++) {
C[s[i] - 'a'].push_back(i);
}
int ans = 0;
for(int id = 0; id < 26; id++) {
int sz = C[id].size();
for(int i = 0; i < sz; i++) {
pre[i + 1] = pre[i] + C[id][i] - i;
}
for(int l = 1, r = 1; l <= sz; l++) {
while(r + 1 <= sz && check(l, r + 1) <= m)++r;
ans = max(ans, r - l + 1);
}
}
cout << ans << endl;
}
``` #include<bits/stdc++.h>
using namespace std;
int min_step(vector<int>& array, int l, int r){
int mid = (l + r) / 2;
int res = 0;
for(int i = l; i <= r; i++){
res += abs(array[i] - array[mid]) - abs(i - mid);
}
return res;
}
int main(){
string s;
int m;
cin >> s >> m;
unordered_map<char, vector<int>> array;
for(int i = 0; i < s.size(); i++){
array[s[i]].push_back(i);
}
int res = 1;
for(auto i: array){
for(int l = 0, r = 1; r < i.second.size(); r++){
int step = min_step(i.second, l, r);
if(step <= m)
res = max(res, r - l + 1);
else
l++;
}
}
cout << res;
}
/*动态规划虽然很强,但是动态规划还是有一定难度的,其实对于每个字母,
分别记录下他们在的位置,然后分别遍历每一个点,让其左边的点后右边的点同时向它靠拢,
然后记录下最长的链的长度,就可以解决这个问题了。*/
#include<iostream>
#include<vector>
#include<map>
using namespace std;
int main()
{
string s;
int m;
cin>>s>>m;
map<char,vector<int> > loc;
map<char,int> res;
for(int i=0;i<s.size();i++)
{
loc[s[i]].push_back(i);
}
int result=0;
for(map<char,vector<int> >::iterator iter=loc.begin();iter!=loc.end();iter++)
{
char ch=iter->first;
vector<int> v=iter->second;
int k;
int stepl,stepr;
for(int i=0;i<v.size();i++)
{
k=m;
int left=i-1,right=i+1;
while(true)
{
if(left<0&&right>=v.size()) break;
stepl=m+1;
stepr=m+1;
if(left>=0)
{
stepl=(v[i]-v[left])-(i-left);
}
if(right<v.size())
{
stepr=(v[right]-v[i])-(right-i);
}
if(min(stepl,stepr)>k) break;
if(stepl<stepr)
{
k-=stepl;
left--;
}else if(stepr<stepl)
{
k-=stepr;
right++;
}
else
{
k-=stepl;
left--;
}
}
res[ch]=max(res[ch],right-left-1);
}
result=max(result,res[ch]);
}
cout<<result<<endl;
return 0;
}
dp[i][j] =dp[i + 1][j - 1] + ls.get(j) - ls.get(i) - 1 - (j - i - 1);递推式的意思是:从i+1~j-1已经连续好了,再把 i 和 j 也变成连续的,需要这两个位置的差(ls.get(j) - ls.get(i) - 1),减去里面已经有的(i - j - 1)。
import java.util.*;
// 注意类名必须为 Main, 不要有任何 package xxx 信息
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
String s = in.next();
int m = in.nextInt();
Map<Character, List<Integer>> map = new HashMap<>();
for (int i = 0; i < s.length(); ++i) {
map.computeIfAbsent(s.charAt(i), k -> new ArrayList<>()).add(i);
}
int ans = 0;
for (List<Integer> ls: map.values()) {
int n = ls.size();
int[][] dp = new int[n][n];
for (int i = n - 1; i >= 0; --i) {
for (int j = i + 1; j < n; ++j) {
dp[i][j] =dp[i + 1][j - 1] + ls.get(j) - ls.get(i) - 1 - (j - i - 1);
if (dp[i][j] <= m) ans = Math.max(ans, j - i + 1);
}
}
}
System.out.println(ans);
}
} #include <iostream>
#include <string>
#include <vector>
using namespace std;
/*
复杂度O(N)
具体思路:
1、提取出相同字符的位置,比如ababa中a的位置为(0,2,4),b的位置为(1,3)。
然后对每个字符的位置向量单独扫描处理。
2、显然将左右两边向中间靠拢代价最低。
3、当已经知道将[l,r]区间内元素合并的代价时,可以O(1)时间内知道[l,r+1]和[l+1,r]区间内的合并代价。
(参考下方的rpush,lpop函数)
*/
string S;
int N,M;
void rpush(vector<int> &arr,int &l,int &r,int &cost){
r++;
int mid=(l+r)/2;
cost+=arr[r]-arr[mid]-(r-mid);
}
void lpop(vector<int> &arr,int &l,int &r,int &cost){
int mid=(l+1+r)/2;
cost-=arr[mid]-arr[l]-(mid-l);
l++;
}
int check(vector<int> &arr){
if(arr.size()<=1)
return arr.size();
int l=0,r=0;
int cost=0;
int ans=1;
while(r+1<arr.size()){
if(cost>M)
lpop(arr,l,r,cost);
else{
rpush(arr,l,r,cost);
if(cost<=M)
ans = max(ans,r-l+1);
}
}
return ans;
}
int main(){
cin>>S>>M;
N = S.size();
vector< vector<int> > idx(26);
for(int i=0;i<N;++i){
int ch = S[i]-'a';
idx[ch].push_back(i);
}
int ans=1;
for(int i=0;i<26;++i)
ans = max(ans,check(idx[i]));
cout<<ans<<endl;
return 0;
} 二分查找加滑动窗口
#include<bits/stdc++.h>
using namespace std;
int xun(vector<int> v, int l, int mm) {
int n = v.size();
int m = (l + 1) / 2;
int r = 0;
int le = 0;
int re = -1;
for (int i = 0; i < l; i++) {
if (i < m - 1)
r += abs(v[i] - v[m - 1])-abs(m-1-i);
else
le += abs(v[i] - v[m - 1])-abs(m-1-i);
}
re = le + r;
if (le + r <= mm) return le + r;
else {
for (int i = 1; i < n - l + 1; i++) {
m++;
r = r + (v[m-1] - v[m - 2])*((l + 1) / 2) - (v[m-1] - v[i - 1]-(m-i));
le = le - (v[m-1] - v[m - 2])*(l - (l + 1) / 2) + (v[i + l - 1] - v[m-1]-(i+l-m));
re = le + r;
if (le + r <= mm) return re;
}
}
return -1;
}
int main() {
string s;
int m;
cin >> s >> m;
int l = s.length();
if (l == 0) {
cout << 0;
return 0;
}
vector<vector<int>> v(26);
for (int i = 0; i < l; i++) {
v[s[i] - 'a'].push_back(i);
}
int re = 0;
for (int i = 0; i < 26; i++) {
int ll = v[i].size();
if (ll >= 1) {
int ri = ll+1;
int le = 1;
while (le < ri) {
int mi = (ri + le) / 2;
if (xun(v[i], mi, m) == -1) {
ri = mi;
}
else le = mi + 1;
}
if (xun(v[i], le - 1, m) != -1) {
re = max(le - 1, re);
}
}
}
cout << re;
return 0;
} import java.util.*;
/**
* 记录每个字母出现的下标。
* 对每一个字母出现的所有位置,进行滑窗计算移动代价。
* 窗口从一个元素开始扩大,计算代价超过要求,缩小窗口,否则扩大窗口。
*
* 由经验可得,要把滑窗内所有位置移动到连续位置,两边元素靠中间元素移动的代价较小。
* 比如情况[11,13,15,17,19|21,23,25,30,31],选取中间靠左的(19, 选中间的任意都行)
* 需要分别计算左右移动到中间元素的代价是多少,左边移动为[15,16,17,18,19]计算得到代价10,
* 右边移动为[19,20,21,22,23,24]代价为21。
* 判断代价是否超过要求。
* 循环所有的字母情况。
*/
public class Main {
static Map<Character, List<Integer>> map = new HashMap<>();
//每个字母出现的下标前缀和,计算代价时就不用再遍历
static Map<Character, List<Integer>> mapPreSum = new HashMap<>();
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.nextLine();
String[] split = s.split(" ");
String str = split[0];
int m = Integer.parseInt(split[1]);
for (int i = 0; i < str.length(); i++) {
List<Integer> idxes = map.getOrDefault(str.charAt(i), new ArrayList<>());
List<Integer> preSum = mapPreSum.getOrDefault(str.charAt(i), new ArrayList<>());
idxes.add(i);
preSum.add(preSum.size() == 0 ? i : preSum.get(preSum.size() - 1) + i);
map.put(str.charAt(i), idxes);
mapPreSum.put(str.charAt(i), preSum);
}
int res = 0;
for (Character c : map.keySet()) {
res = Math.max(res, cost(map.get(c), mapPreSum.get(c), m));
}
System.out.println(res);
}
static int cost(List<Integer> position, List<Integer> preSum, int m) {
int res = 0;
int left = 0, right = 0;
while (right < position.size()) {
int mid = left + (right - left + 1) / 2;
int sumA = cost(position, preSum, left, mid, true);
int sumB = cost(position, preSum, mid, right, false);
int cost = sumB - sumA;
if (cost <= m) {
res = Math.max(res, right - left + 1);
right++;
} else left++;
}
return res;
}
static int cost(List<Integer> pos, List<Integer> preSum, int left, int right, boolean isLeft) {
int count = right - left + 1;
int sum = left == 0 ? preSum.get(right) : preSum.get(right) - preSum.get(left - 1);
int move;
if (isLeft) {
int rightPos = pos.get(right);
move = (rightPos + (rightPos - count + 1)) * count / 2;
} else {
int leftPos = pos.get(left);
move = (leftPos + (leftPos + count - 1)) * count / 2;
}
return sum - move;
}
}
import java.util.*;
public class Main{
public static void main (String[] args) {
Scanner sc = new Scanner(System.in);
String str = sc.next();
int m = sc.nextInt();
HashMap<Character, ArrayList<Integer>> table = new HashMap<>();
for(int i=0;i<str.length();i++){
if(!table.containsKey(str.charAt(i))){
int finalI = i;
table.put(str.charAt(i),new ArrayList<Integer>(){{add(finalI);}});
}else{
table.get(str.charAt(i)).add(i);
}
}
PriorityQueue<Integer> res = new PriorityQueue<Integer>(Comparator.reverseOrder());
for(Character x: table.keySet()){
PriorityQueue<Integer> temp = new PriorityQueue<Integer>(Comparator.reverseOrder());
for(int i = 0;i<table.get(x).size();i++){
int time = m;
int lcount = 1,rcount = 1;
PriorityQueue<Integer> dis = new PriorityQueue<Integer>(new Comparator<Integer>(){
@Override
public int compare(Integer j, Integer k) {
if (Math.abs(j)<Math.abs(k)){
return -1;
}else{
return 1;
}
}
});
for(int j=0;j<table.get(x).size();j++){
dis.add(table.get(x).get(j)-table.get(x).get(i));
}
while (!dis.isEmpty()){
int test = dis.poll();
if(test <0 && Math.abs(test)<=time){
int cost = (test+lcount);
lcount ++;
time += cost;
}else if(test>0 && test <=time){
int cost = (test-rcount);
rcount ++;
time -= cost;
}
}
temp.add(lcount+rcount-1);
}
res.add(temp.poll());
}
System.out.println(res.poll());
}
} import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
String s = sc.next();
int m = sc.nextInt();
Map<Character, List> map = new HashMap<>(26); // key为字符串中的每个字母,value记录该字母在字符串中出现的位置
for (int i = 0; i < s.length(); i++) {
char c = s.charAt(i);
List list = map.get(c);
if (list == null)
map.put(c, list = new ArrayList<Integer>(100));
list.add(i);
}
int maxLen = 1; // 记录连续最长的相同字母数量
// 遍历map
for (Map.Entry<Character, List> entry : map.entrySet()) {
List<Integer> arrayList = entry.getValue();
// 遍历字母在字符串中出现的位置
for (int i = 0; i < arrayList.size(); i++) {
int ctr = arrayList.get(i); // 以当前遍历的元素位置为中心,计算其他相同元素到到该中心需移动的步数
int[] move = new int[arrayList.size()];
// 获取 move[],表示每个相同字母到中心点 ctr 需要移动的最少步数
for (int j = 0; j < arrayList.size(); j++)
move[j] = (Math.abs(arrayList.get(j) - ctr) -
Math.abs(i - j));
// 排序后,选择移动代价最少的前 k + 1 个
Arrays.sort(move);
int sum = 0; // 记录移动步数之和
for (int k = 0; k < move.length; k++) {
sum += move[k];
if (sum > m)
break;
// 每有一个字母移动到中心点,该字母的连续相同数量就增加1
if (k + 1 > maxLen)
maxLen = k + 1;
}
}
}
System.out.println(maxLen);
}
} import sys line=sys.stdin.readline().strip() #nlikedic={} s,n=line.split() n=int(n) se=set(s)res=1 sdict={} for i in se: sdict[i]=[] for j in range(len(s)): sdict[s[j]].append(j) for c in se: dp=[]*len(sdict[c]) for i in range(len(sdict[c])): dp.append([0]*len(sdict[c])) #for i in range(len(sdict[c])): #dp[i][i]=0 for i in range(0,len(sdict[c])-1): #print(i) for j in range(i+1,len(sdict[c])): rr=sdict[c][j]-sdict[c][i]-(j-i) if i+1!=j: rr+=dp[i+1][j-1] dp[i][j]=rr if dp[i][j]<=n: #print('change',i,j,dp[i][j],res,j-i+1) res=max(res,j-i+1)print(res)
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scan=new Scanner(System.in);
String s=scan.next();
int m=scan.nextInt();
char[] chars=s.toCharArray();
Map<Character,ArrayList<Integer>> map=new HashMap<Character,ArrayList<Integer>>();
for(int i=0;i<chars.length;i++)
{
char key=chars[i];
if(map.containsKey(key))
{
ArrayList<Integer> list=map.get(key);
list.add(i);
}
else
{
ArrayList<Integer> list=new ArrayList<Integer>();
list.add(i);
map.put(key,list);
}
}
int count=0;
for(int i=0;i<26;i++)
{
char key=(char) ('a'+i);
ArrayList<Integer> list=map.get(key);
if(list!=null)
{
//j代表新坐标系下标,list.get(j)为第下标为j的字母在原始数组中的下标
//dp为 新坐标系中两个字母对应的原始坐标区间合成连续字母串所需步数
//第一个下标已确定,连续长度len已确定,则第二个下标不可能超过list.size()
//j+1到j所需操作步数(即空格数)为list.get(j+1)-list.get(j)-1
//j+len-2所在字母经操作后已移到原始坐标list.get(j+1)+(len-2-1)处
//j+len-1到j+len-2所需操作步数(即空格数)为list.get(j+len-1)-[list.get(j+1)+(len-2-1)]-1
//list.get(j+1)-list.get(j)-1 + list.get(j+len-1)-[list.get(j+1)+(len-2-1)]-1
//=list.get(j+len-1) - list.get(j) + 1 -len
//或者直接计算空格数,有多少空格,两端字母就移动几次:j+len-1,j区间共有len个字母(包括两端)
//则空格数为: list.get(j+len-1) - list.get(j) + 1 -len;
int[][] dp = new int[list.size()][list.size()];
for(int len=2;len<=list.size();len++) {
for (int j = 0; j + len - 1 < list.size(); j ++) {
dp[j][j+len-1] = dp[j+1][j+len-2] + list.get(j+len-1) - list.get(j) + 1 -len;
if (dp[j][j+len-1] <= m && count < len)
count = len;
}
}
}
}
System.out.println(count);
}
}