校招题

NC258932

题目

其实就是一个三分答案的模板题,可以看出这是一个单谷函数。

借助这篇 博客 复习一下。

#include <bits/stdc++.h>
using namespace std;
using db = long double;
db eps = 1e-6;

void solve() {

	db v0,x,y;
	cin >> v0 >> x >> y;

	auto f = [&] (db t) {
		return t + y / (v0 + t * x);
	};

	db l = 0 , r = 1E12;
	while (fabs(l - r) > eps) {
		db md = (l + r) / 2;
		db fl = f(md - eps) , fr = f(md + eps);
		if (fl < fr) {
			r = md;
		} else {
			l = md;
		}
	}

	cout << fixed << setprecision(10) << f(l) << "\n";

}

int main() {

	// ios::sync_with_stdio(false);
	// cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

NC257435

题目

抓住性质:一个数末尾 0 的个数 = 该数分解质因数后 2 的个数与 5 的个数的最小值。

  • 10 = 2 × 5 → 1 个 0
  • 100 = 2² × 5² → 2 个 0
  • 20 = 2² × 5¹ → min(2,1) = 1 个 0

然后针对每个数为左端点,固定 \(2\) 合法范围(双指针),用桶统计 \(5\) 的个数。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;

void solve() {
	
	int n,x;
	cin >> n >> x;
	
	vector<pair<int,int>> a(n);
	for (int i = 0 ; i < n ; ++i) {
		int num;
		cin >> num;
		while (num % 2 == 0) num /= 2 , a[i].first++;  
		while (num % 5 == 0) num /= 5 , a[i].second++;
	}

	sort(a.begin() , a.end());

	LL ans = 0;
	int r = n - 1;
	vector<int> cnt5(31);
	for (int i = 0 ; i < n ; ++i) {
		if (i == r + 1) {
			cnt5[a[i].second] -= 1;
			r += 1;
		}
		while (r > i && a[i].first + a[r].first >= x) {
			cnt5[a[r].second] += 1;
			r--;
		}
		if (r + 1 >= n) continue;
		int del = max(0 , x - a[i].second);
		for (int j = del ; j <= 30 ; ++j) {
			ans += cnt5[j];
		}
	}

	cout << ans << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

NC260766

题目

感觉像那种一步步优化的题目,挺有意思的

首先自然想到的是 \(dp\) 计数。
dp[i][j][k]表示到 \(i\) 位为止,末位两位是字符 \(j\),\(k\) 的字符串的价值总和。
dp1[i][j][k]表示到 \(i\) 位为止,末位两位是字符 \(j\),\(k\) 的字符串的个数。
自然有了下述代码的转移。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 1E9 + 7 , N = 1010;

LL dp[N][26][26],dp1[N][26][26];

void solve() {

	LL n;
	cin >> n;

	if (n <= 2) {
		cout << 0 << "\n";
		return;
	}

	for (int i = 0 ; i < 26 ; ++i) {
		for (int j = 0 ; j < 26 ; ++j) {
			dp[2][i][j] = 0;
			dp1[2][i][j] = 1;
		}
	}

	for (int i = 3 ; i <= n ; ++i) {
		for (int j = 0 ; j < 26 ; ++j) {
			for (int k = 0 ; k < 26 ; ++k) {
				for (int w = 0 ; w < 26 ; ++w) {
					dp[i][k][w] = (dp[i][k][w] % mod + dp[i - 1][j][k] % mod + 1LL * (j == w) * dp1[i - 1][j][k] % mod) % mod;
					(dp1[i][k][w] += dp1[i - 1][j][k]) %= mod;
				}
			}
		}
	}

	int ans = 0;
	for (int j = 0 ; j < 26 ; ++j) {
		for (int w = 0 ; w < 26 ; ++w) {
			(ans += dp[n][j][w]) %= mod;
			// cout << dp[n][j][w] << " ";
		}
		// cout << "\n";
		// cout << "\n";
	}

	cout << ans << "\n";
	// cout << (27 * 26 + 1) * 26 << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

\(dp\)\(dp1\) 肯定都有高度的对称性 (观察转移方程)

打表 \(3\),\(4\),\(5\)

\(dp[n][i][j]\) 分别为 \(1\) , \(52\) , \(2028\)
\(dp1[n][i][j]\) 分别为 \(26\) , \(676\) , \(17576\)

观察得到:1 * 26 + 26 = 52 , 52 * 26 + 676 = 2028

由于 \(n\)\(1E12\) , 启示我们用矩阵快速幂解决这个问题。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 1E9 + 7;

struct Mat {
	int a[2][2];
};
Mat operator * (Mat l, Mat r) {
	
	Mat t;
	t.a[0][0] = t.a[0][1] = t.a[1][0] = t.a[1][1] = 0;
	for (int i = 0 ; i < 2 ; ++i) {
		for (int j = 0 ; j < 2 ; ++j) {
			for (int k = 0 ; k < 2 ; ++k) {
				t.a[i][j] = (t.a[i][j] % mod +  1LL * l.a[i][k] * r.a[k][j] % mod) % mod;
			}
		}
	}
	return t;
}

void solve() {

	LL n;
	cin >> n;

	if (n <= 2) {
		cout << 0 << "\n";
		return;
	}


	/*
		[1 , 26]  , [26 , 0] --> [27 , 26] -> []
		[0 , 0]  , [1 ,  26]	[0 , 0]
	*/
	Mat l,r,base;
	l.a[0][0] = 1 , l.a[0][1] = 26, l.a[1][0] = 0 , l.a[1][1] = 0;
	r.a[0][0] = 26 , r.a[0][1] = 0 , r.a[1][0] = 1 , r.a[1][1] = 26;
	base.a[0][0] = 1 , base.a[0][1] = 0 , base.a[1][0] = 0 , base.a[1][1] = 1;

	n -= 3;
	while (n) {
		if (n & 1) base = base * r;
		r = r * r;
		n >>= 1;
	}

	l = l * base;
	cout << 1LL * l.a[0][0] * 26 % mod * 26 % mod << "\n";
	
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

NC261663

题目

首先 \(k\) 个数肯定又要用上,贪心地想,肯定是排序后的前后缀。
经典做法,长度固定,枚举前缀/后缀即可。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
using db = long double;

void solve() {

	int n,k;
	cin >> n >> k;

	vector<db> a(n);
	for (int i = 0 ; i < n ; ++i) {
		cin >> a[i];
	}
	sort(a.begin() , a.end());

	if (k == 1) {
		cout << *max_element(a.begin() , a.end()) - *min_element(a.begin() , a.end()) << "\n";
		return;
	}
	if (k == n) {
		cout << 0 << "\n";
		return;
	}

	vector<db> pre(n);
	for (int i = 0 ; i < n ; ++i) {
		pre[i] = a[i] + (i == 0 ? 0 : pre[i - 1]);
	}

	auto get_sum = [&] (int l,int r) {
		if (l > r) return (db)0;
		return pre[r] - (l == 0 ? 0 : pre[l - 1]);
	};

	db ans = 1E18;
	for (int i = 0 ; i < n && n - (k - i) >= i && i <= k; ++i) {
		db l = get_sum(0 , i - 1) , r = get_sum(n - (k - i) , n - 1);
		db avg = (l + r) / k;
		db mn = min({avg , a[i] , a[n - (k - i) - 1] }) , mx = max({avg , a[i] , a[n - (k - i) - 1] });
		ans = min(ans , mx - mn);		
	} 

	cout << fixed << setprecision(10) << ans << "\n";
}	

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

NC256071

题目

贪心,路程一定要短,那么菊花图最合适。
\(2\),\(3\)数量较多的那个作为根即可,剩下的就是计数。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int mod = 1E9 + 7;

void solve() {
	
	int n;
	vector<int> cnt(2);
	cin >> n >> cnt[0];
	
	cnt[1] = n - cnt[0];
	int rt = (cnt[0] > cnt[1]) ? 0 : 1;

	auto qsm = [&] (int a,LL b,int mod) {
		int ans = 1;
		while (b) {
			if (b & 1) ans = 1LL * ans * a % mod;
			a = 1LL * a * a % mod;
			b >>= 1;
		}
		return ans;
	};

	auto mul = [&] (int x,int y) {
		int z = 1LL * x * y % mod;
		return z;
	};

	int ans = 1;
	
	//菊花图
	//rt
	ans = mul(ans , qsm(3 , 1LL * (cnt[rt] - 1) , mod)); // rt ^ 2;
	ans = mul(ans , qsm(4 , 1LL * cnt[rt ^ 1] , mod)); //6
	//rt类
	LL num = 1LL * (cnt[rt] - 1) * (cnt[rt] - 2) / 2;
	ans = mul(ans , qsm(4 , num , mod)); //rt ^ 3
	
	//(rt ^ 1) 类
	num = 1LL * cnt[rt ^ 1] * (cnt[rt ^ 1] - 1) / 2;
	ans = mul(ans , qsm(6 , num , mod)); //rt * (rt ^ 1) * (rt ^ 1); //6

	num = 1LL * (cnt[rt] - 1) * cnt[rt ^ 1];
	ans = mul(ans , qsm(6 , num , mod)); //rt * rt * (rt ^ 1)

	cout << ans << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

NC258420

题目

众数的数量不是 \(n\) 就是 \(n - 1\)

\(n\) 的情况就是总和是 \(n\) 的倍数,\(n - 1\) 就是不是倍数,拿出一个数出来调节。

怎么算代价呢?大于和小于\(avg\)两边调整代价的最大值。

\(n - 1\) 的话,把最大/小作为调节的数。

		#include <bits/stdc++.h>
		using namespace std;
		using LL = long long;

		void solve() {
			int n;
			cin >> n;

			LL sum = 0;
			vector<int> a(n);
			for (int i = 0 ; i < n ; ++i) {
				cin >> a[i];
				sum += a[i];
			}
			sort(a.begin() , a.end());

			auto cal = [&] (int avg,int idx) {
				LL ans = 0;
				for (int i = 0 ; i < n ; ++i) {
					if (i == idx) continue;
					if (a[i] > avg) ans += a[i] - avg;
				}
				return ans;
			};

			if (sum % n == 0) {
				cout << cal(sum / n , -1) << "\n";
				return;
			}


			LL ans = 1E18;
			//sum - a[0] = (n - 1) * avg + k;
			//分配一下有 (n - 1 - k) 个 avg , k 个 avg + 1;
			//如果全部变成 avg 的话, 算大于 avg 贡献即可, 多出来的算到 a[0]
			//如果全部变成 avg + 1 的话,其实算小于 avg 的贡献,但如果算大于 avg 贡献的话,则要补偿 n - 1 - k
			//其实两边变化取 max
			int avg = (sum - a[0]) / (n - 1) , k = (sum - a[0]) % (n - 1);
			ans = min({ans , cal(avg , 0) , cal(avg + 1 , 0) + n - 1 - k}); 
			
			avg = (sum - a[n - 1]) / (n - 1) , k = (sum - a[n - 1]) % (n - 1);
			ans = min({ans , cal(avg , n - 1) , cal(avg + 1 , n - 1) + n - 1 - k});

			cout << ans << "\n";	
		}

		int main() {

			ios::sync_with_stdio(false);
			cin.tie(nullptr);

			int t = 1;
			while (t--) {
				solve();
			}

			return 0;

		}

华为机试编程模拟题4 第三题

题目

其实很简单,但我思考惯性了,把 \(k\) 加入的 \(dp\) 维度,导致空间炸了。

其实就是常规的二维 \(max \ dp\) , 但我们只取最后一行,离中间点距离在 \(k\) 之内的点。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;
const int N = 305;

void solve() {
	
	int n,K;
	cin >> n >> K;
	
	vector<vector<int>> a(n);
	for (int i = 0 ; i < n ; ++i) {
		a[i].resize(2 * (i + 1) - 1);
		for (int j = 0 ; j < 2 * (i + 1) - 1 ; ++j) {
			cin >> a[i][j];
		} 
	}

	const LL INF = 1E18;
	vector<vector<LL>> dp(n);
	for (int i = 0 ; i < n ; ++i) {
		dp[i].resize(2 * (i + 1) - 1);
		for (int j = 0 ; j < 2 * (i + 1) - 1 ; ++j) {
			dp[i][j] = -INF;
		}
	}
	
	auto check = [&] (int i,int j) {
		return (0 <= j && j < 2 * (i + 1) - 1);
	};

	
	dp[0][0] = a[0][0];
	for (int i = 1 ; i < n ; ++i) {
		for (int j = 0 ; j < 2 * (i + 1) - 1 ; ++j) {
			if (check(i - 1 , j)) {
				dp[i][j] = max(dp[i][j], dp[i - 1][j] + a[i][j]);
			}
			if (j - 1 >= 0 && check(i - 1 , j - 1)) {
				dp[i][j]= max(dp[i][j], dp[i - 1][j - 1] + a[i][j]);
			}
			if (j - 2 >= 0 && check(i - 1 , j - 2)) {
				dp[i][j] = max(dp[i][j] , dp[i - 1][j - 2] + a[i][j]);
			}
		}
	}

	
	LL ans = -INF;
	//n - 1 , n - 1 - k , n - 1 + k
	for (int j = max(0 , n - 1 - K) ; j <= min(2 * n - 2 , n - 1 + K) ; ++j) {
		ans = max(ans , dp[n - 1][j]);
	}

	cout << ans << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;
}

华为机试模拟 8

补充:

  • 将数字加入字符串 s += (1 + '0');

  • stl 查询 t 是否是 s 的子串

    s.find(t) != string::npos; //t是s的子串
    
  • 求最长不下降子序列 o(nlogn) (非数据结构)

相较于传统的想法维护 dp[i]i 结尾的最长满足序列,我们反过来维护长度序列 d

d[x] 代表在 dp 更新中,长度为 x 的序列中,末尾最小值为 d[x]

d[x] 肯定越小越好,仅需找出最后一个大于等于 a[i]d[x] , 更新 d[x + 1]a[i] 即可,发现这样恰恰能维护 d[x] 非降序。

#include <bits/stdc++.h>
using LL = long long;
using namespace std;

void solve() {
	
	int n;
	cin >> n;

	vector<pair<int,int>> car(n);
	for (int i = 0 ; i < n ; ++i) {
		cin >> car[i].first >> car[i].second;
	}
	sort(car.begin() , car.end());

	//最长不下降子序列
	const int inf = 2E9;
	vector<int> dp(n + 1 , inf);
	dp[0] = -inf;
	
	int ans = 0;
	for (int i = 0 ; i < n ; ++i) {
		auto it = upper_bound(dp.begin() , dp.end() , car[i].second);
		if (it == dp.begin()) {
			continue;
		}
		int x = prev(it) - dp.begin();
		ans = max(ans , x + 1);
		dp[x + 1] = min(dp[x + 1] , car[i].second);
	}

	cout << n - ans << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

	return 0;

}

力扣 寻找两个正序数组的中位数


题目

简单描述:给定两个有序数组 (长度分别为 mn),如何在 O(log(n + m)) 的时间复杂度内,找到两个数组的中位数。

  1. 设总长度为 total = n + m , 如果 total 为奇数的话,则是 total / 2 的位置,否则就是 total / 2total / 2 + 1 的平均值。

  2. 其实抽象一下,如何在 log(k) 的复杂度内,找到合并后数组的第 k 位呢?

  3. 对比 a[k / 2 - 1]b[k / 2 - 1] 的大小 (坐标从 0 开始)。

    • 如果 a[k / 2 - 1] < b[k / 2 - 1] , 则 a[k / 2 - 1] 最多都只有 k / 2 - 2 比它小 , 那么从 a[0]a[k / 2 - 1] 都可以舍去,k 也已经减去 k / 2 个数。
    • 反之亦然,并且=的情况放在>= 或者 <= 均可。
    • 边界问题直接看代码即可。
class Solution {
public:
    int find_k(vector<int>& a,vector<int>& b,int k) {
        int n = a.size() , m = b.size();
        int idx_a = 0 , idx_b = 0;
        while (true) {
            if (idx_a == n) return b[idx_b + k - 1];
            if (idx_b == m) return a[idx_a + k - 1];
            if (k == 1) return min(a[idx_a] , b[idx_b]);
            int now_idx_a = min(n - 1 , idx_a + k / 2 - 1);
            int now_idx_b = min(m - 1 , idx_b + k / 2 - 1);
            if (a[now_idx_a] < b[now_idx_b]) {
                k -= now_idx_a - idx_a + 1;
                idx_a = now_idx_a + 1;
            } else {
                k -= now_idx_b - idx_b + 1;
                idx_b = now_idx_b + 1;
            }
        }
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int n = nums1.size() , m = nums2.size();
        int total = n + m;
        if (total % 2) {
            return 1.0 * find_k(nums1 , nums2 , total / 2 + 1);
        } else {
            return (find_k(nums1 , nums2 , total / 2) + find_k(nums1 , nums2 , total / 2 + 1)) / 2.0;
        }
    }
};

接雨水

题目

对于位置 \(i\) 能接的雨水等于左右边最大高度的最小值减去当前高度。

class Solution {
public:
    int trap(vector<int>& height) {
        int n = height.size();
        vector<int> l(n) , r(n);
        for (int i = 0 ; i < n ; ++i) {
            l[i] = i == 0 ? height[i] : max(height[i] , l[i - 1]);
        }
        for (int i = n - 1 ; i >= 0 ; --i) {
            r[i] = i == n - 1 ? height[i] : max(height[i] , r[i + 1]);
        }
        int ans = 0;
        for (int i = 0 ; i < n ; ++i) {
            ans += min(l[i] , r[i]) - height[i];
        }
        return ans;
    }
};

NC256063

题目

双指针维护,注意边界产生的贡献即可。

#include <bits/stdc++.h>
using namespace std;
using LL = long long;

void solve() {

	LL n;
	int m1,m2;
	cin >> n >> m1 >> m2;

	vector<pair<int,LL>> line1(m1),line2(m2);
	for (int i = 0 ; i < m1 ; ++i) {
		cin >> line1[i].first >> line1[i].second;
	}
	for (int i = 0 ; i < m2 ; ++i) {
		cin >> line2[i].first >> line2[i].second;
	}

	int idx1 = 0 , idx2 = 0;
	LL l1 = 0 , l2 = 0 , ans = 0;
	while (idx1 < m1 && idx2 < m2) {
		
		LL r1 = l1 + line1[idx1].second - 1;
		LL r2 = l2 + line2[idx2].second - 1;
		LL l = max(l1 , l2) , r = min(r1 , r2);

		//算边界的贡献
		if (line1[idx1].first == line2[idx2].first) {
			if (l1 < l2 || l2 < l1) {
				++ans;
			} else if (l1 == l2 && idx1 > 0 && idx2 > 0 && line1[idx1 - 1] == line2[idx2 - 1]) {
				++ans;
			}
		} else {
			//交接贡献
			if (l <= r) {
				ans += r - l;
			}
			//算边界贡献
			if (l1 < l && idx2 > 0 && line2[idx2 - 1].first == line1[idx1].first) {
				++ans;
			}
			if (l2 < l && idx1 > 0 && line1[idx1 - 1].first == line2[idx2].first) {
				++ans;
			}
			if (l1 == l2 && idx1 > 0 && idx2 > 0 && line1[idx1 - 1].first == line2[idx2].first
				&& line2[idx2 - 1].first == line1[idx1].first) {
				++ans;
			}
		}

		if (r1 < r2) {
			l1 = r1 + 1;
			idx1 += 1;
		} else if (r2 < r1) {
			l2 = r2 + 1;
			idx2 += 1;
		} else {
			l1 = r1 + 1 , l2 = r2 + 1;
			idx1 += 1 , idx2 += 1;
		}
	}

	cout << ans << "\n";
}

int main() {

	ios::sync_with_stdio(false);
	cin.tie(nullptr);

	int t = 1;
	while (t--) {
		solve();
	}

}

动态中位数

题目

对顶堆维护

class MedianFinder {
public:

    priority_queue<int> Min;
    priority_queue<int , vector<int>  , greater<int>> Max;

    MedianFinder() {}
    
    void addNum(int num) {
        if (Min.empty() || num <= Min.top()) {
            Min.push(num);
            if (Max.size() + 1 < Min.size()) {
                Max.push(Min.top());
                Min.pop();
            }
        } else {
            Max.push(num);
            if (Max.size() > Min.size()) {
                Min.push(Max.top());
                Max.pop();
            }
        }
    }
    
    double findMedian() {
        if (Min.size() > Max.size()) {
            return Min.top();
        } else {
            return (Min.top() + Max.top()) / 2.0;
        }
    }
};

力扣 最长有效括号

题目

alt text

  • 思路一

    dp[i] 为以 i 为结尾的最长合法序列,显然只有 ) 结尾的有效。

    如果 s[i - 1] == '(' , 那么 dp[i] = dp[i - 2] + 2

    如果 s[i - 1] == ') , 那么 dp[i] = dp[i - 1] + dp[i - dp[i - 1] - 2] + 2 (仅在 s[i - dp[i - 1] - 1] == '(' 有效)。

    class Solution {
    public:
    	int longestValidParentheses(string s) {
    		int n = s.size() , ans = 0;
    		vector<int> dp(n , 0);
    		for (int i = 1 ; i < n ; ++i) {
    			if (s[i] == ')') {
    				if (s[i - 1] == '(') dp[i] = (i >= 2 ? dp[i - 2] : 0) + 2;
    				else {
    					if (i - dp[i - 1] - 1 >= 0 && s[i - dp[i - 1] - 1] == '(') {
    						dp[i] = dp[i - 1] + (i - dp[i - 1] - 2 >= 0 ? dp[i - dp[i - 1] - 2] : 0) + 2;
    					}
    				}
    			}
    			ans = max(ans , dp[i]);
    		}
    		return ans;
    	}
    };
    
  • 思路二

    alt text

    class Solution {
    public:
    	int longestValidParentheses(string s) {
    		int n = s.size() , ans = 0;
    		vector<int> stk;
    		stk.push_back(-1);
    		for (int i = 0 ; i < n ; ++i)  {
    			if (s[i] == '(') {
    				stk.push_back(i);
    			} else {
    				stk.pop_back();
    				if (stk.empty()) {
    					stk.push_back(i);
    				} else {
    				ans = max(ans , i - stk.back()); 
    				}
    			}
    		}
    		return ans;
    	}
    };
    
  • 思路三

    alt text

    class Solution {
    public:
    	int longestValidParentheses(string s) {
    		
    		int ans = 0 , n = s.size();
    		
    		int left = 0 , right = 0;
    		for (int i = 0 ; i < n ; ++i) {
    			if (s[i] == '(') {
    				++left;
    			} else {
    				++right;
    			}
    			if (right > left) {
    				left = right = 0;
    			} else if (left == right) {
    				ans = max(ans , left * 2);
    			}
    		}
    
    		left = right = 0;
    		for (int i = n - 1 ; i >= 0 ; --i) {
    			if (s[i] == '(') {
    				++left;
    			} else {
    				++right;
    			}
    			if (left > right) {
    				left = right = 0;
    			} else if (left == right) {
    				ans = max(ans , left * 2);
    			}
    		}
    
    		return ans;
    	}
    };
    

力扣 最短回文串

题目

题目的本质就是找最长回文前缀

  • 思路一

直接利用 Manacher

class Solution {
public:
    string get(string t) {
        string s = "#";
        for (auto x : t) {
            s += x;
            s += "#";
        }
        return s;
    }
    vector<int> Manacher(string s) {
        int n = s.size();
        vector<int> f(n);
        for (int i = 0 , j = 0 ; i < n ; i++) {
            if (2 * j - i >= 0 && j + f[j] > i) {
                f[i] = min(f[2 * j - i] , j + f[j] - i);
            }
            while (i - f[i] >= 0 && i + f[i] < n && s[i - f[i]] == s[i + f[i]]) {
                f[i] += 1;
            }
            if (i + f[i] > j + f[j]) {
                j = i;
            }
        }
        return f;
    }
    string shortestPalindrome(string s) {
        
        int m = s.size();
        string t = get(s);
        auto f = Manacher(t);
        int n = t.size();

        int id = 0;
        for (int i = 0 ; i < n ; ++i) {
            if (i - f[i] + 1 == 0) {
                id = i;
            }
        }

        string ans = "";
        for (int i = n - 1 ; i > id + f[id] - 1 ; --i) {
            if (t[i] != '#') ans += t[i];
        }
        for (int i = 0 ; i < n ; ++i) {
            if (t[i] != '#') ans += t[i];
        }
        return ans;
    }
};
  • 思路二

    字符串 hash , 一个正着,一个反着,看相不相同。

    class Solution {
    public:
    	using uLL = unsigned long long;
    	string shortestPalindrome(string s) {
    		int n = s.size() , id = 0;
    		uLL base = 9999991 , mul = 1 , pre = 0 , suf = 0;
    		for (int i = 0 ; i < n ; ++i) {
    			pre = pre * base + (s[i] - 'a' + 1);
    			suf = suf + (s[i] - 'a' + 1) * mul;
    			mul = mul * base;
    			if (pre == suf) {
    				id = i;
    			} 
    		}
    		string ans = "";
    		for (int i = n - 1 ; i > id ; --i) {
    			ans += s[i];
    		}
    		return ans + s;
    	}
    };
    
  • 思路三

    • 看到前缀,立马想到 kmpborder 的定义。

    • 用正序匹配逆序,如果能匹配上,就是回文相等。

    class Solution {
    public:
    	vector<int> kmp(string s) {
    		int n = s.size();
    		vector<int> f(n + 1);
    		for (int i = 1 , j = 0; i < n ; ++i) {
    			while (j && s[i] != s[j]) {
    				j = f[j];
    			}
    			j += (s[i] == s[j]);
    			f[i + 1] = j;
    		}
    		return f;
    	}
    	string shortestPalindrome(string s) {
    		string t = s;
    		reverse(t.begin() , t.end());
    		auto f = kmp(s);
    		int n = s.size() , id = 0;
    		for (int i = 0 , j = 0 ; i < n ; ++i) {
    			while (j && (j == n || s[j] != t[i])) {
    				j = f[j];
    			}
    			j += (t[i] == s[j]);
    			id = j;
    		}
    		// cout << id ;
    		string tmp = "";
    		for (int i = n - 1 ; i >= id ; --i) {
    			tmp += s[i];
    		}
    		return tmp + s; 
    	}
    };
    
    • 注意遍历完 t 时的 j 值,因为要保证前缀回文中的 “前缀” 二字。

力扣 确实的第一个正数

题目

要求时间复杂度 o(n),空间为常数

  • 思路一

    关键在于要利用 nums 数组本身

    • <= 0 的数全赋值为 n + 1
    • 遍历数组,把 abs(nums[i]) - 1 位置的数取反
    • 再次遍历,找到第一个 >= 0 的位置。
    class Solution {
    public:
    	int firstMissingPositive(vector<int>& nums) {
    		int n = nums.size();
    		for (int i = 0 ; i < n ; ++i) {
    			if (nums[i] <= 0) nums[i] = n + 1;
    		}
    		for (int i = 0 ; i < n ; ++i) {
    		int val = abs(nums[i]);
    		if (1 <= val && val <= n) nums[val - 1] = -abs(nums[val - 1]);
    		}
    		for (int i = 0 ; i < n ; ++i) {
    			if (nums[i] >= 0) return i + 1;
    		}
    		return n + 1;
    	}
    };
    
  • 思路二

    类似于环形换位置

    class Solution {
    public:
    	int firstMissingPositive(vector<int>& nums) {
    		int n = nums.size();
    		for (int i = 0 ; i < n ; ++i) {
    			while (1 <= nums[i] && nums[i] <= n && nums[nums[i] - 1] != nums[i]) {
    				swap(nums[nums[i] - 1] , nums[i]);
    			}
    		}
    		for (int i = 0 ; i < n ; ++i) {
    			if (nums[i] != i + 1) {
    				return i + 1;
    			}
    		}
    		return n + 1;
    	}
    };
    

力扣 计算右侧小于当前元素的个数

题目

  • 思路一

    离散化树状数组

    class Solution {
    public:
    	vector<int> countSmaller(vector<int>& nums) {
    		int n = nums.size();
    		vector<int> P;
    		for (int i = 0 ; i < n ; ++i) {
    			P.push_back(nums[i]);
    		}
    		sort(P.begin() , P.end());
    		P.erase(unique(P.begin() , P.end()) , P.end());
    		auto find = [&] (int x) {
    			return lower_bound(P.begin() , P.end() , x) - P.begin() + 1;
    		};
    		auto lowbit = [&] (int x) {
    			return x & (-x);
    		};
    		int m = P.size();
    		vector<int> tr(m + 1);
    		auto add = [&] (int x) {
    			while (x <= m) {
    				tr[x] += 1;
    				x += lowbit(x);
    			}
    		};
    		auto getsum = [&] (int x) {
    			int ans = 0;
    			while (x) {
    				ans += tr[x];
    				x -= lowbit(x);
    			}
    			return ans;
    		};
    		vector<int> ans(n);
    		for (int i = n - 1 ; i >= 0 ; --i) {
    			int pos = find(nums[i]);
    			if (pos != 1) ans[i] = getsum(pos - 1);
    			add(pos);
    		}
    		return ans;
    	}
    };
    
  • 思路二

    归并排序

    class Solution {
    public:
    	vector<int> countSmaller(vector<int>& nums) {
    		int n = nums.size();
    		vector<pair<int,int>> nod(n);
    		for (int i = 0 ; i < n ; ++i) {
    			nod[i] = make_pair(nums[i] , i);
    		}
    		vector<int> ans(n);
    		function<void(int,int)> mergesort =[&] (int l,int r) {
    			if (l == r) return;
    			int md = (l + r) / 2;
    			mergesort(l , md) , mergesort(md + 1 , r);
    			int i = l , j = md + 1 , pos = 0;
    			vector<pair<int,int>> tmp(r - l + 1);
    			while (i <= md && j <= r) {
    				if (nod[j].first < nod[i].first) tmp[pos ++ ] = nod[j ++ ];
    				else {
    					ans[nod[i].second] += (j - md - 1);
    					tmp[pos ++] = nod[i ++];
    				}
    			}
    			while (i <= md) {
    				ans[nod[i].second] += (j - md - 1);
    				tmp[pos ++ ] = nod[i ++];
    			}
    			while (j <= r) {
    				tmp[pos ++] = nod[j ++];
    			}
    			for (int k = l; k <= r ; ++k) {
    				nod[k] = tmp[k - l];
    			}
    		};
    		mergesort(0 , n - 1);
    		return ans;
    	}
    };
    

力扣 网格图中最少访问的格子数

题目

  • 思路一

    其实最显然的想法是维护 \([i + 1 , i + grid[i][j]]\)\([j + 1 , j + grid[i][j]]\) 区间问题,这个东西树状数组和线段树都能做到。

  • 思路二

    \((i,j)\) 坐标拆开,考虑到往下走和往右走两者情况,维护每一行和每一列的优先队列。

  • 思路三

    看到转移的代价为 \(1\) , 其实队列就行了。

    但不可以在区间数一个个去试到底转移了没有,不然就\(o(n^3)\)

    由转移代价为 \(1\) 的贪心性质,如果转移过了,就不用再转移了。

    基于此,那么对于每一行或者每一列都维护一个并查集,直接加速就可以了。

    对于 \(grid[i][j]\),它又在 \(i\) 行上,又在 \(j\) 列上,那么只用看先用行遍历到它,还是先用列遍历到它,用个 \(used[i][j]\) 维护就好了。

    时间复杂度 \(O(n * m * log(n * m))\)

    class Solution {
    public:
    	const int inf = 1E9;
    	int minimumVisitedCells(vector<vector<int>>& grid) {
    		int n = grid.size() , m = grid[0].size();
    		vector<vector<int>> row(n , vector<int>(m + 1)) , col(m , vector<int>(n + 1));
    		for (int i = 0 ; i < n ; ++i) {
    			for (int j = 0 ; j <= m ; ++j) {
    				row[i][j] = j;
    			}
    		}
    		for (int j = 0 ; j < m ; ++j) {
    			for (int i = 0 ; i <= n ; ++i) {
    				col[j][i] = i;
    			}
    		}
    		function<int(int , int , int)> find = [&] (int x,int c,int ty) {
    			if (ty == 0) {
    				return col[c][x] == x ? x : col[c][x] = find(col[c][x] , c , 0);
    			} else {
    				return row[c][x] == x ? x : row[c][x] = find(row[c][x] , c , 1);
    			}
    		};
    		vector<vector<int>> used(n , vector<int>(m));
    		vector<vector<int>> dp(n , vector<int>(m , inf));
    		dp[0][0] = 1;
    		used[0][0] = 1;
    		queue<pair<int,int>> Q;
    		Q.push(make_pair(0 , 0));
    		while (!Q.empty()) {
    			auto [i , j] = Q.front();
    			Q.pop();
    			int ri = min(i + grid[i][j] , n - 1) , rj = min(j + grid[i][j] , m - 1);
    			int li = find(i + 1 , j , 0);
    			while (li <= ri) {
    				if (!used[li][j]) {
    					used[li][j] = 1;
    					dp[li][j] = dp[i][j] + 1;
    					Q.push(make_pair(li , j));
    				}
    				if (li + 1 <= n) col[j][li] = li + 1;
    				li = find(li + 1 , j , 0);
    			}
    			int lj = find(j + 1 , i , 1);
    			while (lj <= rj) {
    				if(!used[i][lj]) {
    					used[i][lj] = 1;
    					dp[i][lj] = dp[i][j] + 1;
    					Q.push(make_pair(i , lj));
    				}
    				if (lj + 1 <= m) row[i][lj] = lj + 1;
    				lj = find(lj + 1 , i , 1);
    			}
    		}
    		if (dp[n - 1][m - 1] == inf) dp[n - 1][m - 1] = -1;
    		return dp[n - 1][m - 1];
    	}
    };
    

posted @ 2025-10-07 09:38  xqy2003  阅读(3)  评论(0)    收藏  举报