502. IPO
题意:给定k,w,profits数组和capital数组。k表示最多可完成的任务数。w是初始资本。profits是各个任务的收益。capital是开始此任务需要的初始资金。每次完成一个任务后,将收益加到资本中。问最多可以达到多少资本。
题解:贪心。用优先队列。每次选取可以开始的任务中收益最大的。
class Solution {
public:
struct pc{
int profits;
int capital;
pc(int u,int v):profits(u),capital(v){}
bool operator < (const Solution::pc &b) const {
return profits < b.profits;
}
};
struct cmp{
bool operator()(struct pc &a,struct pc &b)const {
if(a.capital != b.capital) return a.capital < b.capital;
return a.profits > b.profits;
}
};
int findMaximizedCapital(int k, int W, vector<int>& Profits, vector<int>& Capital) {
vector<pc> v;
for(int i = 0;i < Profits.size(); ++i){
v.push_back(pc(Profits[i],Capital[i]));
}
sort(v.begin(),v.end(),cmp());
priority_queue<pc> Q;
int pos = 0;
while(pos < v.size() && v[pos].capital <= W){
Q.push(v[pos++]);
}
while(!Q.empty() && k--){
pc u = Q.top(); Q.pop();
W += u.profits;
while(pos < v.size() && v[pos].capital <= W){
Q.push(v[pos++]);
}
}
return W;
}
};
514. Freedom Trail
题意:给定一个密码环,环上有一些小写字母。通过转动环,然后按按钮输入正确密码才能将门打开。密码是一个字符串。环上的字符以一个字符串给出,按顺时针排列。可以沿着逆时针和顺时针转动,转动一格计一步。按按钮就将位于位置0的字符输入,并计一步。问最少多少步可以将密码输入。
题解:动态规划。设dp[i][j]表示key从i到结尾,环上字符现在位于j时,要输完密码至少得步数。那么要转动到key[i]字符所在位置k,则dp[i][j] = min{dp[i + 1][k]} + 1。而k只要枚举当前位置最接近的左右两个和key[i]相等的位置即可(由于是小写字母,这个可以在O(32 * ring.length())的时间预先计算出来。接着,由于dp[i]只用到dp[i + 1]的信息,所以可以滚动数组,空间复杂度降为O(Ring.size() + Key.size())
class Solution {
public:
int findRotateSteps(string ring, string key) {
int n = ring.size();
int m = key.size();
if(m == 0) return 0;
vector<vector<int> > left(n,vector<int>(26)),right(n,vector<int>(26));
vector<int> leftpos(26,-1),rightpos(26,-1);
for(int i = 0;i < n; ++i){
leftpos[ring[i] - 'a'] = i;
rightpos[ring[n - i - 1] - 'a'] = n - i - 1;
}
for(int i = 0;i < n; ++i){
leftpos[ring[i] - 'a'] = i;
rightpos[ring[n - i - 1] - 'a'] = n - i - 1;
for(int j = 0;j < 26; ++j){
left[i][j] = leftpos[j];
right[n - i - 1][j] = rightpos[j];
}
}
vector<vector<int> > dp(2,vector<int>(n,0));
for(int i = m - 1;i >= 0; --i){
int c = key[i] - 'a';
for(int j = 0;j < n; ++j){
int L = left[j][c];
int R = right[j][c];
dp[i & 1][j] = min((j - L + n) % n + dp[1 - i & 1][L],(R - j + n) % n + dp[1 - i & 1][R]) + 1;
}
}
return dp[0][0];
}
};
517. Super Washing Machines
题意:给定N堆硬币(可能为空),每次可以任意选择m堆,从每一堆中那走一个,放到隔壁堆中取。问至少多少次能使所有堆得硬币一样多。
题解:dp[i]表示前0到i堆至少多少次能把多余的硬币放到右边去,并且除最后一个,都达到了目标值。dp[i + 1] = dp[i] + L +R,其中L和R分别是要从i+1堆向左边和右边拿走的硬币数量。
class Solution {
public:
int findMinMoves(vector<int>& machines) {
int n = machines.size();
if(n <= 1) return 0;
int tot = 0,ans = 0;
for(int i = 0;i < n; ++i) tot += machines[i];
if(tot % n) return -1;
int ave = tot / n;
machines.push_back(ave);
tot = 0;
tot = machines[0] - ave;
for(int i = 0;i < n; ++i){
if(tot >= 0)
ans = max(tot,ans);
else{
ans = max(ans,max(0,machines[i + 1] + tot - ave) - tot);
}
tot += machines[i + 1] - ave;
machines[i + 1] += tot;
}
return ans;
}
};
546. Remove Boxes
题意:给定一个整数序列,每个数字表示颜色。每次可以取走若干个连续的同颜色的一段(长度为k),获得k*k的分数。问怎么取可以使得当序列取完获得的分数最大。
题解:动态规划。因为连续的肯定是要一起取走的,所以先合并。设dp[i][j][k]表示从位置i到位置j,前面剩下有k个和i相同颜色的位置,那么要么i和前面的合并掉取走,要么一起留下来,和下一个一样的合并。答案是dp[0][n - 1][0]。
//vector会超内存
class Solution {
public:
void merge(vector<int>& boxes,int n,vector<int>&color,vector<int>&length){
boxes.push_back(-1);
int curLength = 0;
for(int i = 0;i < n; ++i){
++curLength;
if(boxes[i] == boxes[i + 1]) continue;
length.push_back(curLength);
color.push_back(boxes[i]);
curLength = 0;
}
}
int removeBoxes(vector<int>& boxes) {
int n = boxes.size();
if(n <= 1) return n;
vector<int> color,length;
merge(boxes,n,color,length);
n = color.size();
if(n == 1) return length[0] * length[0];
//vector<vector<vector<int>>> dp(n,vector<vector<int>>(n,vector<int>(boxes.size(),0)));
int dp[100][100][100] = {0};
return dfs(dp,color,length,0,n - 1,0);
}
int dfs(int dp[100][100][100],const vector<int>& color,vector<int>&length,int i,int j,int k){
if(i > j) return 0;
if(i == j) return (k + length[i]) * (k + length[i]);
int &res = dp[i][j][k];
if(res) return res;
res = dfs(dp,color,length,i + 1,j,0) + (k + length[i]) * (k + length[i]);
for(int p = i + 1;p <= j; ++p){
if(color[p] != color[i]) continue;
res = max(res, dfs(dp, color,length, i + 1, p - 1, 0) + dfs(dp, color,length, p, j, k + length[i]));
}
return res;
}
};
552. Student Attendance Record II
题意:给定三种字符(A,P,L)给定一个数字n,问长度n由这三种字符组成且的串的共有多少个。限制是其中一种只能有一个(A),还有一种最多两个连续(L),剩下一个没有限制(P)。
题解:dp[i][j][k]表示长度为i,A是否出现过,结尾连续L的个数为k的串的个数。然后进行转移即可。由于dp[i]只用到dp[i - 1]的信息,故可以用滚动数组。
class Solution {
public:
const long long MOD = 1E9 + 7;
int checkRecord(int n) {
long long dp[2][2][3] = {0LL};
dp[0][0][0] = 1LL;
for(int i = 1;i <= n; ++i){
for(int k = 0;k < 3; ++k){
if(k > 0){
dp[i&1][0][k] = dp[1 - i&1][0][k - 1];
dp[i&1][1][k] = dp[1 - i&1][1][k - 1];
}
else{
dp[i&1][0][k] = dp[1 - i&1][0][0] + dp[1 - i&1][0][1] + dp[1 - i&1][0][2];
dp[i&1][1][k] = dp[1 - i&1][1][0] + dp[1 - i&1][1][1] + dp[1 - i&1][1][2] +
dp[i&1][0][k];
}
dp[i&1][0][k] %= MOD;
dp[i&1][1][k] %= MOD;
}
}
return (dp[n&1][0][0] + dp[n&1][0][1] + dp[n&1][0][2] + dp[n&1][1][0] + dp[n&1][1][1] + dp[n&1][1][2]) % MOD;
}
};
564. Find the Closest Palindrome
题意:给定一个数字字符串,确定和它离最近的那个回文串,如果有两个一样近,输出小的那个。
题解:取这个字符串的前半段,如果它本身不是回文串,那么取前半段然后镜像构造一个回文串,这个肯定是距离最小的。如果它本身是回文串,将前半段减1,加1两种情况构造回文串。最近那个肯定是这两个中的一个,这个很显然,接着就是细节的问题了。
class Solution {
public:
bool g(string a,string b){ //a > b? true: false;
if(a == "inf") return true;
if(b == "inf") return false;
if(a.length() > b.length()) return true;
if(a.length() < b.length()) return false;
for(int i = 0;i < a.length(); ++i){
if(a[i] > b[i]) return true;
if(a[i] < b[i]) return false;
}
return false;
}
string dis(string a,string b){
if(a == b) return "inf";
if(g(a,b)) swap(a,b);
a = string(b.length() - a.length(),'0') + a;
char tmp = 0;
string s = "";
int i = 0;
for(int i = a.length() - 1;i >= 0; --i){
char t = b[i] - a[i] - tmp;
if(t < 0) tmp = 1, t += 10;
else tmp = 0;
s = char('0' + t) + s;
}
int pos = 0 ;
while(pos < s.length() && s[pos] == '0') ++pos;
s = s.substr(pos);
return s == string("0") ? "inf":s;
}
string minor(string s){
int mid = (s.length() + 1) / 2;
for(int i = mid; i < s.length() ; ++i)
s[i] = s[s.length() - 1 - i];
return s;
}
string nearestPalindromic(string s) {
int pos = 0;
while(pos < s.length() - 1 && s[pos] == '0') ++pos;
s = s.substr(pos);
int n = s.size();
if(n <= 1){
cout<<"true"<<endl;
if(s == "") return "0";
if(s == "0") return "1";
cout<<"noew"<<(s[0] - 1)<<endl;
return string("") + char(s[0] - 1);
}
string s1 = minor(s);
string diff1 = dis(s,s1);
int mid = (s.length() - 1) / 2;
string s2 = s;
while(mid >= 0 && s2[mid] == '0'){
s2[mid] = '9';
--mid;
}
if(mid == 0 && *(s2.begin()) == '1'){
s2.erase(s2.begin());
s2[(s2.length() - 1) / 2] = '9'; //this is because it's the closer one
}
else
s2[mid] = s2[mid] - 1;
s2 = minor(s2);
string diff2 = dis(s,s2);
mid = (s.length() - 1) / 2;
string s3 = s;
while(mid >= 0 && s3[mid] == '9'){
s3[mid] = '0';
--mid;
}
if(mid < 0)
s3 = "1" + s3;
else
s3[mid] = s3[mid] + 1;
s3 = minor(s3);
string diff3 = dis(s,s3);
if(!g(diff2,diff1) && !g(diff2,diff3)) return s2;
if(!g(diff1,diff2) && !g(diff1,diff3)) return s1;
return s3;
}
};
587. Erect the Fence
题意:就是求凸包,都是整点,可能有重复,也可以退化成一直线。从最左边开始,逆时针输出。
题解:Graham扫描
/**
* Definition for a point.
* struct Point {
* int x;
* int y;
* Point() : x(0), y(0) {}
* Point(int a, int b) : x(a), y(b) {}
* };
*/
Point operator-(Point &a, Point &b) {
int x = a.x - b.x;
int y = a.y - b.y;
return Point(x,y);
}
class Solution {
public:
struct cmp{
bool operator()(const Point &a, const Point &b){
if(a.x == b.x) return a.y < b.y;
return a.x < b.x;
}
};
bool eq(const Point &a, const Point &b){
return a.x == b.x && a.y == b.y;
}
int cross(Point &a,Point &b){
return a.x * b.y - a.y * b.x;
}
vector<int> convexHull(vector<Point>& points){
int n = points.size();
vector<int> S(n,0);
int end = 0;
for(int i = 1;i < n; ++i){
if(end == 0){
S[++end] = i;
continue;
}
int top = S[end];
int pre = S[end - 1];
Point v1 = points[i] - points[pre];
Point v2 = points[top] - points[pre];
if(cross(v1,v2) > 0){
--end;
--i;
}else{
S[++end] = i;
}
}
vector<int> down(S.begin(),S.begin() + end + 1);
if(down.size() == points.size()) return down;
S[0] = n - 1; end = 0;
for(int i = n - 2;i >= 0; --i){
if(end == 0){
S[++end] = i;
continue;
}
int top = S[end];
int pre = S[end - 1];
Point v1 = points[i] - points[pre];
Point v2 = points[top] - points[pre];
if(cross(v1,v2) > 0){
--end;
++i;
}else{
S[++end] = i;
}
}
vector<int>up(S.begin() + 1,S.begin() + end);
down.insert(down.end(),up.begin(),up.end());
return down;
}
/*
int gcd(int a,int b){
return b == 0 ? a : gcd(b, a % b);
}
bool inaLine(vector<Point>& points){
int pos = 1;
while(pos < points.size() && points[pos].x == points[0].x && points[pos].y == points[0].y) ++pos;
if(pos == points.size()) return true;
Point tmp = points[pos] - points[0];
int x = tmp.x,y = tmp.y;
int d = gcd(x,y);
x /= d; y /= d;
for(int i = 1;i < points.size(); ++i){
int px = points[i].x - points[0].x, py = points[i].y - points[0].y;
if(px == 0 && py == 0) continue;
d = gcd(px,py);
px /= d; py /= d;
if(x != px || y != py) return false;
}
return true;
}
*/
vector<Point> outerTrees(vector<Point>& points) {
if(points.size() <= 1) return points;
sort(points.begin(),points.end(),cmp()); //left to right ,down to up
//if(inaLine(points)) return points;
vector<int> convex = convexHull(points);
vector<Point> ans;
for(auto pos:convex) ans.push_back(points[pos]);
return ans;
}
};
600. Non-negative Integers without Consecutive Ones
题意:给定一个数字n,问比它小的,且二进制没有连续1的非负数有多少个。
题解:注意到,二进制长度为k的没有连续1的非负数的是Fibonacci数。接着,对于一个n,我们分别计算前缀和它一样的那些数有多少,加起来即可。如果它本身也是,加上它本身。另外一种做法是动态规划。dp[i][j]表示前i位,最后一位为j且不大于num前缀的数目。
class Solution {
public:
int findIntegers(int num) {
int fib[31];
fib[0] = 1;
fib[1] = 2;
for (int i = 2; i < 32; ++i)
fib[i] = fib[i - 1] + fib[i - 2];
int ans = 0, k = 30, pre_bit = 0;
for(int k = 30;k >= 0; --k) {
if (num & (1 << k)) {
ans += fib[k];
if (pre_bit) return ans;
pre_bit = 1;
}else
pre_bit = 0;
}
return ans + 1;
}
};
动态规划解法,压缩空间:
public class Solution {
public int findIntegers(int num) {
//one:all bit before cur is less than num and no continues 1 and cur bit is at one;
//zero:all bit before cur is less than num and no continues 1 and cur bit is at zero;
int one=0,zero=0,temp;
boolean isNum=true;
for(int i=31;i>=0;i--){
temp=zero;
zero+=one;
one=temp;
if(isNum&&((num>>i)&1)==1){
zero+=1;
}
if(((num>>i)&1)==1&&((num>>i+1)&1)==1){
isNum=false;
}
}
return one+zero+(isNum?1:0);
}
}
629. K Inverse Pairs Array
题意:给定n,k。问由1 ~n组成的序列中逆序对为k的共有多少种情况
题解:动态规划。设dp[i][j]为,前面有1~i + 1的序列,逆序对为j的有多少种。那么dp[i][j]
可以由i + 1所在位置进行递推。dp[i][j] = dp[i - 1][j] + dp[i - 1][j - 1] + ... dp[i - 1][max(0,j - i)]
和每次预先计算,故时间复杂度为O(nk)
class Solution {
public:
const int MOD = 1E9 + 7;
int getsum(int i,int j, vector<int> &sum)const {
int pre = max(j - i,0) - 1;
if(pre < 0) return sum[j];
else return sum[j] - sum[pre];
}
int kInversePairs(int n, int k) {
if(k == 0) return 1;
vector<int> dp(k + 1,0);
vector<int> sum(k + 1,1);
dp[0] = 1;
for(int i= 0;i < n; ++i){
for(int j = 1;j <= k; ++j){
dp[j] = (getsum(i,j,sum) % MOD + MOD) % MOD;
}
for(int j = 1;j <= k; ++j) sum[j] = (sum[j - 1] + dp[j]) % MOD;
}
return dp[k];
}
};
630. Course Schedule III
题意:给定一些课程,以(需要的时间,最迟结束时间)给出。课程之间不能重叠。问最多可以安排多少个课程。
题解:贪心。先对课程,按最迟结束时间从小到大排序。然后一个个安排。如果可以安排下,那么就放进去。如果安排不下,那么去掉前面(包括自己)一个耗时最多的。这样子肯定是最优的。因为对于前i个,如果这样做事最优的,那么对于第i+1个,这样做也肯定是最优的,不然得出矛盾。
class Solution {
public:
struct cmp{
bool operator ()(vector<int> &a, vector<int>&b){
return a[1] < b[1];
}
};
struct cmp2{
bool operator ()(pair<int,int> &a,pair<int,int> &b){
return a.first < b.first;
}
};
int scheduleCourse(vector<vector<int>>& courses) {
sort(courses.begin(),courses.end(),cmp());
priority_queue<pair<int,int> ,vector<pair<int,int>>, cmp2>Q;
int endTime = 0;
for(int i = 0;i < courses.size(); ++i){
Q.push(make_pair(courses[i][0],courses[i][1]));
endTime += courses[i][0];
if(endTime > courses[i][1]){
endTime -= Q.top().first;
Q.pop();
}
}
return Q.size();
}
};
632. Smallest Range
题意:给定k个list,每个list是一个有序int数组。求一个最小的区间,使得这k个数组中每一个都至少存在一个数在这个区间中。
题解:先合并成一个pair数组(值和所属的list)。然后双指针遍历即可。
class Solution {
public:
static bool cmp(pair<int,int> &a,pair<int,int> &b){
return a.first > b.first;
}
vector<int> smallestRange(vector<vector<int>>& nums) {
vector<pair<int,int>> numpair;
int k = nums.size();
for(int i = 0;i < k; ++i){
vector<pair<int,int>> tmp;
for(auto &num:nums[i]){
tmp.push_back(make_pair(num,i));
}
numpair.resize(numpair.size() + tmp.size());
merge(numpair.rbegin() + tmp.size(),numpair.rend(),tmp.rbegin(),tmp.rend(),numpair.rbegin(),cmp);
}
vector<int> counts(k);
int visn = 0;
int a = -1,b = -1,minimumS = INT_MAX;
int pre = 0;
for(int i = 0;i < numpair.size() ; ++i){
if(++counts[numpair[i].second] == 1) ++visn;
if(visn == k){
while(counts[numpair[pre].second] > 1){
--counts[numpair[pre].second];
++pre;
}
if(numpair[i].first - numpair[pre].first < minimumS){
minimumS = numpair[i].first - numpair[pre].first;
a = numpair[pre].first;
b = numpair[i].first;
}
}
}
return vector<int>{a,b};
}
};
639. Decode Ways II
题意:字符A-Z能编码成1到26,给定一串编码,问可以解码成多少种情况。其中编码;*;可以代表1-9中任意字符。
题解:动态规划。dp[i]表示编码串前i个可以解码的种数。然后按当前字符和前一个字符的类别进行状态转移。
class Solution {
public:
long long M = 1000000007;
int numDecodings(string s) {
int n = s.size();
if(n == 0) return 1;
vector<long long> dp(n + 1,0);
dp[0] = 1;
dp[1] = s[0] == '*'? 9 : s[0] != '0';
if(dp[1] == 0) return 0;
for(int i = 2; i <= n; ++i){
if(s[i - 2] == '1'){
if(s[i - 1] == '0') dp[i] = dp[i - 2];
else if(s[i - 1] == '*') dp[i] = 9 * dp[i - 2] + dp[i - 1] * 9;
else dp[i] = dp[i - 2] + dp[i - 1];
}else if(s[i - 2] == '2'){
if(s[i - 1] == '0') dp[i] = dp[i - 2];
else if(s[i - 1] == '*') dp[i] = 6 * dp[i - 2] + dp[i - 1] * 9;
else if(s[i - 1] <= '6' && s[i - 1] >='1') dp[i] = dp[i - 2] + dp[i - 1];
else dp[i] = dp[i - 1];
}else if(s[i - 2] == '*'){
if(s[i - 1] == '0') dp[i] = 2 * dp[i - 2];
else if(s[i - 1] == '*') dp[i] = 15 * dp[i - 2] + dp[i - 1] * 9;
else if(s[i - 1] <= '6' && s[i - 1] >= '1') dp[i] = 2 * dp[i - 2] + dp[i - 1];
else dp[i] = dp[i - 2] + dp[i - 1];
}else if(s[i - 2] > '2'){ //s[i - 2] greater than 2
if(s[i - 1] == '0') dp[i] = 0;
else if(s[i - 1] == '*')
dp[i] = 9 * dp[i - 1];
else
dp[i] = dp[i - 1];
}else{ //0
if(s[i - 1] == '0') dp[i] = 0;
else dp[i] = s[i - 1] == '*'? 9 * dp[i - 1] : dp[i - 1];
}
dp[i] = dp[i] % M;
}
return dp[n];
}
};
644. Maximum Average Subarray II
题意:给定一个数组和一个数字k。问长度至少为k的连续段的最大平均值为多少。
题解:一个简单的做法就是二分答案,然后check。
(nums[i]+nums[i+1]+...+nums[j])/(j-i+1)>=x
=>nums[i]+nums[i+1]+...+nums[j]>=x*(j-i+1)
=>(nums[i]-x)+(nums[i+1]-x)+...+(nums[j]-x)>=0
相当于检查有没有连续段长度至少为k且和非负。也就是最大k子段和,用单调队列动态规划解决。
算法复杂度为O(nlogn)。但这不是最优的,利用计算几何的方法,可以达到O(n)。
首先,计算前缀和P[i]。然后i到j的平均值为(P[j] - P[i - 1]) / (j - i + 1)。
我们把它看成两个点(i - 1,P[i - 1]),(j,P[j]),显然平均值就是他们两个点的线段斜率。
接着。我们从左至右,维护一个下凸包,每次就是找凸包的下切线。具体图解见:
http://www.csie.ntnu.edu.tw/~u91029/MaximumSubarray.html
该问题的一个增强版本是加上限宽度。也就是在单调队列里面加一个操作即可
class Solution {
public:
/*
bool check(vector<int>& nums, int k,double sum){
double cur = 0,pre = 0;
for(int i = 0;i < nums.size(); ++i){
cur += nums[i] - sum;
if(i >= k) pre += nums[i - k] - sum;
if(pre < 0) cur -= pre, pre = 0;
if(i >= k - 1 && cur >= 0) return true;
}
return cur >= 0;
}
double binarySearchSolution(vector<int>& nums, int k){
double eps = 1e-6;
double left = INT_MIN,right = INT_MAX,mid;
while(right - left > eps){
mid = (left + right) / 2;
if(check(nums,k,mid)) left = mid;
else right = mid;
}
return right;
}
*/
double slope(int x,int y, vector<int> &presum){ // the slope of the x,y
return double(presum[y + 1] - presum[x]) / (y + 1 - x);
}
double convexHullSolution(vector<int>& nums, int k){
deque<int> Q;
vector<int> presum(nums.size() + 1);
presum[0] = 0;
double ans = INT_MIN;
for(int i = 0;i < nums.size(); ++i) presum[i + 1] = presum[i] + nums[i];
for(int i = k - 1;i < nums.size(); ++i){
while(Q.size() > 1 && slope(*(Q.end() - 2),Q.back() - 1,presum) >= slope(*(Q.end() - 2),i - k,presum)) Q.pop_back(); //update the convex
Q.push_back(i - k + 1);
while(Q.size() > 1 && slope(Q.front(),Q.at(1) - 1,presum) <= slope(Q.front(),i,presum)) Q.pop_front();
ans = max(ans,slope(Q.front(),i,presum));
}
return ans;
}
double findMaxAverage(vector<int>& nums, int k) {
//return binarySearchSolution(nums,k);
return convexHullSolution(nums,k);
}
};
668. Kth Smallest Number in Multiplication Table
题意:给定一个m *n的乘法表,求里面第k大的数
题解:二分答案mid,然后判断小于等于mid的有多少对。这个通过枚举第一个乘数就知道了。假设乘数为i,则其中小于等于mid的数为min(n, mid / i)个。(用双指针可能更快点,因为除法比较耗时,而++运算--运算是很快的)
class Solution {
public:
int findKthNumber(int m, int n, int k) {
int left = 1, right = n * m;
while(left <= right){
int mid = (left + right) >> 1;
int num = (mid / n) * n;
for(int i = mid / n + 1; i <= min(m, mid); ++i){
num += mid / i;
}
if(num < k){
left = mid + 1;
}else{
right = mid - 1;
}
}
return right + 1;
}
};
719. Find K-th Smallest Pair Distance
题意:给定一个正整数数组,每两个数之间有一个距离(差值),问第k小的是多少?
题解:二分答案bound。然后判断距离小于等于bound的对有多少,如果大于k那么说明还可以更小,小于等于k说明答案要更大。
如何计算小于等于bound的对有多少,我们逐个计算,先排序,然后枚举小的数,nums[i],则我们只要知道i + 1到结尾中有多少个小于等于nums[i] + bound。
这个可通过二分得到,只要知道排最前的那个大于nums[i] + bound的数即可。
但是有更好的方法,注意到nums[i]是递增的,如果我们知道nums[i] + bound <= nums[j]那么肯定有nums[i + 1] + bound <= nums[j]。所以每次我们只需要从上一个的结果继续往后找。
class Solution {
bool check_less_than_k(vector<int> &nums,const int bound, int k){
for(int i = 0, prev_idx = 1; i < nums.size() - 1; ++i){
while(prev_idx < nums.size() && nums[prev_idx] - nums[i] <= bound) ++prev_idx;
//prev_idx = upper_bound(nums.begin() + prev_idx, nums.end(), nums[i] + bound) - nums.begin();
k -= prev_idx - i - 1;
}
return k > 0;
}
public:
int smallestDistancePair(vector<int>& nums, int k) {
sort(nums.begin(), nums.end());
int left = 0, right = *nums.rbegin() - *nums.begin();
while(left <= right){
int mid = (right + left) >> 1;
if(check_less_than_k(nums, mid, k)){
left = mid + 1;
}else{
right = mid - 1;
}
}
return right + 1;
}
};
780. Reaching Points
题意:给定sx,sy,tx,ty。每次变换可以令sx = sx + sy或者sy = sx + sy。问能不能从点(sx,sy)变换到(tx,ty)
题解:我们不从sx,sy变换到tx,ty。反过来想,相当于每次tx = tx - ty 或者 ty = ty - tx。也就是大的数减去小的那个。这不就是更相减损术吗。模拟过程加一点优化
class Solution {
public:
//更相减损术
bool reachingPoints(int sx, int sy, int tx, int ty) {
//这句使得tx > ty。而结果不变
tx += ty;
while(tx >= sx && ty >= sy) {
//我们假设ty > tx
swap(tx, ty);
swap(sx, sy);
//相减为0,此时如果4个相等为true,否则为false
//ty == sy,则ty不能再减小了。所以答案就是tx == sx
if(tx == ty || ty == sy) return (tx == sx && ty == sy);
//ty只能一直减tx直到相等或者小于
if(tx == sx) return (ty - sy) % tx == 0;
//到这里说明tx > sx 。 tx还需要减小,所以ty必须一直减tx直到小于tx,也就是模
ty %= tx;
}
return false;
}
};
803. Bricks Falling When Hit
题意:给定一个n*m的grid,位置为1表示有一块砖头,0表示没有。一块砖头不会掉落,当且仅当其和顶部直接连接或者上下左右四个方向有不会掉落的砖块(也就是往四个方向,有到顶部的路径存在)。给定一个数组hits,表示每次击碎其中一个位置,如果有砖头则砖头消失。然后后有些砖块会因此掉落。问每次掉落几个砖块?
题解:直接做很难,没什么思路。反向则简单得多。考虑从后往前,变成每次加砖块,会把多少个砖头连接到顶部去。我们可以用并查集来做这个事。注意只有当砖头在i时刻击碎,才可以加砖头,所以代码中采用--grid[i][j],这样每次加回1,当其为1时,说明是当前击碎的。
class Solution {
int find_pa(int x, vector<int> &pa){
return x == pa[x]? x : pa[x] = find_pa(pa[x], pa);
}
public:
vector<int> hitBricks(vector<vector<int>>& grid, vector<vector<int>>& hits) {
int n = grid.size(), m = grid[0].size();
vector<int> pa(n * m);
vector<int> nums(n * m, 1);
for(int i = 0;i < n * m; ++i) pa[i] = i;
//删点
for(int i = 0;i < hits.size(); ++i){
--grid[hits[i][0]][hits[i][1]];
}
int dir_x[] = {1, 0, -1, 0};
int dir_y[] = {0, 1, 0, -1};
//初始化并查集
for(int i = 0;i < n; ++i){
for(int j = 0;j < m; ++j){
if(grid[i][j] != 1) continue;
int pa1 = find_pa(i * m + j, pa);
for(int d = 0;d < 4; ++d){
int x = i + dir_x[d];
int y = j + dir_y[d];
if(x < 0 || x >= n || y < 0 || y >= m || grid[x][y] != 1) continue;
int pa2 = find_pa(x * m + y, pa);
if(pa1 > pa2) swap(pa1, pa2);
pa[pa2] = pa1;
if(pa1 != pa2) nums[pa1] += nums[pa2];
}
}
}
//加点,合并
vector<int> ans;
for(int i = hits.size() - 1; i >= 0; --i){
int x = hits[i][0], y = hits[i][1];
if(++grid[x][y] != 1){
ans.push_back(0);
continue;
}
int pa1 = x * m + y;
int drop_num = 0;
for(int d = 0;d < 4; ++d){
int n_x = x + dir_x[d];
int n_y = y + dir_y[d];
if(n_x < 0 || n_x >= n || n_y < 0 || n_y >= m || grid[n_x][n_y] != 1) continue;
int pa2 = find_pa(n_x * m + n_y, pa);
if(pa1 != pa2){
if(pa2 >= m)
drop_num += nums[pa2];
if(pa1 > pa2) swap(pa1, pa2);
pa[pa2] = pa1;
nums[pa1] += nums[pa2];
}
}
if(pa1 >= m) drop_num = 0;
ans.push_back(drop_num);
}
return vector<int>(ans.rbegin(), ans.rend());
}
};
805. Split Array With Same Average
题意:给定一个数组A,长度最长30。数字0到10000之间。把A分成两个数组B和C。问能不能使得B和C的平均值一样
题解:
假设A,B,C的长度分别为len_a,len_b,len_c,和分别为sum_a,sum_b,sum_c
则首先有len_a = len_b + len_c, sum_a = sum_b + sum_c
我们要让sum_b / len_b = sum_c / len_c 则有sum_b * len_c = sum_c * len_b
将len_c,sum_c替换成sum_a,sum_b,len_a,len_b得
sum_b * (len_a - len_b) = (sum_a - sum_b) * len_b,化简得到sum_b = sum_a * len_b / len_a
所以我们可以枚举len_b,对于每个len_b,我们就得到了sum_b。
问题就成了,在A中取len_b个数求和能不能得到sum_b。
这个可以通过动态规划得到设dp[i][s]表示在A中取i个数是否能得到和s。
假设B是那个比较短的数组,则len_b <= len_a / 2,sum_b = sum_a * len_b / len_a <= sum_a / 2 <= 150000
dp数组也可以用bitset,不过用bool也是可以的。
class Solution {
bool dp[16][150001] = {0};
public:
bool splitArraySameAverage(vector<int>& A) {
int sum_a = 0;
size_t len_a = A.size();
for(size_t i = 0;i < len_a; ++i) sum_a += A[i];
dp[0][0] = true;
for(size_t i = 0;i < len_a; ++i){
for(int k = (len_a >> 1) - 1;k >= 0; --k){
for(int s = (sum_a >> 1) - A[i]; s >= 0; --s){
if(!dp[k][s]) continue;
dp[k + 1][s + A[i]] = true;
}
}
}
for(size_t len_b = 1; len_b <= len_a >> 1; ++len_b){
if(sum_a * len_b % len_a) continue;
int sum_b = sum_a * len_b / len_a;
if(dp[len_b][sum_b]) return true;
}
return false;
}
};
862. Shortest Subarray with Sum at Least K
题意:给定一个数组(可能有负数),求和超多K>0的最短的子段长度
题解:容易想到的是二分长度,假设长度为t,则所有数减K/t,然后check有没有长度大于等于t且和大于等于0的。也就是问题转化为最大k子段和。渐进时间复杂度为O(nlogn)
另一种解是利用单调队列。首先我们求一个前缀和pre_sum,则我们是求pre_sum[i] - pre_sum[j] >= K中i - j最小的。对于一个k>j,如果pre_sum[k] <= pre_sum[j]那么显然pre_sum[j]在k后就不会再被用到了,因为位置k显然比j好。所以我们维护一个单调队列。对于当前位置i,我们要在单调队列里面找pre_sum[i] >= pre_sum[j] + K的最大的i,这个可以用二分得到。
但是我们注意到,如果有pre_sum[i] >= pre_sum[j] + K,那么如果有k > i,pre_sum[k] >= pre_sum[j] + K, 那么k这个位置并没有i好。所以j也不用再留在队列中。
这样时间复杂度就是O(n)
class Solution {
public:
int shortestSubarray(vector<int>& A, int K) {
deque<pair<int,int> > Q;
Q.emplace_back(0, -1);
int pre_sum = 0, res = INT_MAX;
for(int i = 0; i < A.size(); ++ i){
pre_sum += A[i];
while(!Q.empty() && pre_sum - Q.front().first >= K){
res = min(res, i - Q.front().second);
Q.pop_front();
}
while(!Q.empty() && Q.back().first >= pre_sum)
Q.pop_back();
Q.emplace_back(pre_sum, i);
}
return res == INT_MAX ? -1 : res;
}
};
864. Shortest Path to Get All Keys
题意:给定一个2维的矩阵。每个位置有一个字符。@是起始位置,#是墙表示不能通过的点。.是空格子。'a'-'f'是钥匙,对应的锁是'A'-'F'。要通过锁必须先拿到对应钥匙(大小写对应)。问最少多少步可以拿到所有钥匙。
题解:有多种解法,因为钥匙比较少。BFS是最好的。可以给普通的BFS加一个状态,表示拿到的钥匙即可。可以再进一步状态压缩,不过没必要了,因为数目都很小。
class Solution {
bool is_lock(char c){
return 'A' <= c && c <= 'F';
}
bool is_key(char c){
return 'a' <= c && c <= 'f';
}
struct state{
int r;
int c;
int k;
int step;
state(){};
state(int row, int col, int key, int s):r(row),c(col),k(key),step(s){}
};
int get_keys_num(vector<string>& grid){
int key_num = 0;
for(int i = 0;i < grid.size(); ++i)
for(int j = 0;j < grid[i].length(); ++j){
if(is_key(grid[i][j])) ++key_num;
}
return key_num;
}
pair<int,int> get_start(vector<string>& grid){
for(int i = 0;i < grid.size(); ++i)
for(int j = 0;j < grid[i].length(); ++j){
if(grid[i][j] == '@') return pair<int,int>(i, j);
}
return pair<int,int>(-1,-1);
}
public:
int shortestPathAllKeys(vector<string>& grid) {
int row = grid.size();
int col = grid[0].length();
int ans = INT_MAX;
bool vis[row][col][64];
queue<state> Q;
int key_num = get_keys_num(grid);
memset(vis, 0, sizeof(vis));
pair<int,int> start = get_start(grid);
Q.push(state(start.first, start.second, 0, 0));
int four_dir_r[] = {1, -1, 0, 0};
int four_dir_c[] = {0, 0, 1, -1};
while(!Q.empty()){
state cur = Q.front(); Q.pop();
if(__builtin_popcount(cur.k) == key_num) return cur.step;
int cur_r = cur.r;
int cur_c = cur.c;
int next_step = cur.step + 1;
int cur_k = cur.k;
if(vis[cur_r][cur_c][cur_k]) continue;
vis[cur_r][cur_c][cur_k] = true;
for(int i = 0;i < 4; ++i){
state next_state;
int next_r = cur_r + four_dir_r[i];
int next_c = cur_c + four_dir_c[i];
next_state.r = next_r;
next_state.c = next_c;
next_state.step = next_step;
next_state.k = cur_k;
if(next_r < 0 || next_c < 0 || next_r >= row || next_c >= col) continue;
if(grid[next_r][next_c] == '#') continue;
if(grid[next_r][next_c] == '.' || grid[next_r][next_c] == '@'){
Q.push(next_state);
}else if(is_key(grid[next_r][next_c])){
next_state.k = cur_k | (1 << (grid[next_r][next_c] - 'a'));
Q.push(next_state);
}else if(is_lock(grid[next_r][next_c])){
//return (grid[next_r][next_c] - 'A');
if(cur_k & (1 << (grid[next_r][next_c] - 'A')))
Q.push(next_state);
}
}
}
return -1;
}
};
871. Minimum Number of Refueling Stops
题意:从位置0开始开车,往一个方向走,起始有startFuel的汽油。期间一些位置有一些加油站,stations[i][0],stations[i][1]分别表示第i个加油站的位置和油量。汽车汽油可以装任意多。问至少加多少次油可以到达目的地。目的地是target。
题解:动态规划,将目的地加到加油站的后面去。dp[i][j]表示到达第i个加油站加j次油剩余的最大油量。如果小于0说明不可达。答案就是dp[n][j] >=0 的最小的j。没有这样的j,答案就是-1
class Solution {
public:
int minRefuelStops(int target, int startFuel, vector<vector<int>>& stations) {
long long dp[502][502] = {0};
memset(dp, -1, sizeof(dp));
dp[0][0] = startFuel;
vector<int> v;
v.push_back(target); v.push_back(1);
stations.push_back(v);
for(int i = stations.size() - 1; i > 0; --i) stations[i][0] -= stations[i - 1][0];
for(int i = 1; i <= stations.size(); ++i){
//if(i < stations.size()) stations[i][0] -= stations[i - 1][0];
for(int j = 0;j <= i; ++j){
if(dp[i - 1][j] >= stations[i - 1][0])
dp[i][j] = dp[i - 1][j];
if(j > 0 && dp[i - 1][j - 1] >= stations[i - 1][0])
dp[i][j] = max(dp[i][j], stations[i - 1][1] + dp[i - 1][j - 1]);
dp[i][j] -= stations[i - 1][0];
}
}
for(int i = 0;i <= stations.size(); ++i)
if(dp[stations.size()][i] >= 0) return i;
return -1;
}
};
878. Nth Magical Number
题意:给定两个正整数a,b(<=40000),一个数称为magical number,如果它能被a或者b整除。给定一个n(<=1e9),求第n个magical number。
题解:每一个lcm(a,b)是一个循环节。每个lcm(a,b)中,magical number的数目是num1 = a /lcm(a,b) + b/lcm(a,b) - 1。我们只需要计算n包含了多少个lcm(a,b),数目为n / num1。剩余的数n % num1就在一个lcm(a,b)中,可以通过二分得到
class Solution {
public:
const long long MOD = 1e9 + 7;
long long gcd(long long a, long long b){
if(!b) return a;
return gcd(b, a%b);
}
long long lcm(long long a, long long b){
return a / gcd(a,b) * b;
}
int nthMagicalNumber(int n, int a, int b) {
long long ab_lcm = lcm(a, b);
long long num_per_lcm = ab_lcm / a + ab_lcm / b - 1;
long long num_lcm = n / num_per_lcm;
long long num_rm = n % num_per_lcm;
//cout << ab_lcm<<num_lcm<<num_rm<<endl;
long long left = 0, right = ab_lcm - 1;
while(left <= right){
long long mid = (right - left) / 2 + left;
if(mid / a + mid / b < num_rm)
left = mid + 1;
else
right = mid - 1;
}
//cout << right <<endl;
return (num_lcm * (ab_lcm % MOD) + right + 1) % MOD;
}
};
879. Profitable Schemes
题意:给定一个背包,容量G,和一些物品,占用空间为group[i],价值为profit[i]。在这些物品中选择一部分放进背包里,价值不小于P的方案数是多少?
题解:典型背包问题。动态规划。dp[i][j]表示容量用掉i,获利为j的方案数,dp[i][P]是获利大于等于P的方案数。初始化dp[0][0] = 1。我们先计算出前k个物品的各种价值方案数。然后从这些方案中,再加入第k+1个物品得到前k+1个物品的各种价值的方案数。
class Solution {
public:
int profitableSchemes(int G, int P, vector<int>& group, vector<int>& profit) {
const long long MOD = 1e9 + 7;
//dp[i][j]表示用i个人,获利j的方案数.dp[i][P]获利至少位P的方案数
long long dp[101][102];
memset(dp, 0, sizeof(dp));
dp[0][0] = 1;
for(int k = 0; k < group.size(); ++k){
for(int i = G - group[k]; i >= 0; --i){
for(int j = 0; j <= P; ++j){
if(!dp[i][j]) continue;
dp[i][j] %= MOD;
dp[i + group[k]][min(P, j + profit[k])] += dp[i][j];
}
}
}
long long ans = 0;
for(int i = 0;i <= G; ++i) ans += dp[i][P];
ans %= MOD;
return int(ans);
}
};
891. Sum of Subsequence Widths
题意:给定一个数组,长度1到20000,每个数也是1到20000。对于一个子序列,它的宽度就是子序列中最大和最小数字的差。问所有子序列的宽度和。答案模1e9+7
题解:我们只要求出每个数字A[i]有多少个子序列将其作为最小值,以及多少个子序列将其作为最大值就可以了。我们先将数组排序。则一个元素A[i]要作为最小值,那么其余元素肯定在它的后面(都比他大),所以它作为最小值的序列数就是2^{比他大的数},即每个数取或者不取构成的序列。
class Solution {
public:
int sumSubseqWidths(vector<int>& A) {
sort(A.begin(), A.end());
const long long MOD = 1e9 + 7;
long long left = 0, right = 0;
vector<long long> two_pow(A.size() + 1,0);
two_pow[0] = 1;
for(int i = 1;i < two_pow.size(); ++i){
two_pow[i] = (two_pow[i - 1] << 1) % MOD;
}
for(int i = 0;i < A.size(); ++i){
left += A[i] * two_pow[A.size() - i - 1];
right += A[i] * two_pow[i];
left %= MOD;
right %= MOD;
}
return ((right - left) % MOD + MOD) % MOD;
}
};
895. Maximum Frequency Stack
题意:实现一个数据结构。push的时候往里面放数据。pop的时候返回频最高的的数字。如果有频率一样的,输出最后放入的那个。
题解:利用多个栈实现。每个栈存每个频率的数字。然后用一个map来存数字出现的频率。
class FreqStack {
stack<int> S[10001];
map<int, int> freq;
int max_freq;
public:
FreqStack() {
max_freq = 0;
}
void push(int x) {
int x_freq = ++freq[x];
S[x_freq].push(x);
max_freq = max(max_freq, x_freq);
}
int pop() {
int x = S[max_freq].top();
S[max_freq].pop();
if(S[max_freq].empty()) --max_freq;
--freq[x];
return x;
}
};
/**
* Your FreqStack object will be instantiated and called as such:
* FreqStack* obj = new FreqStack();
* obj->push(x);
* int param_2 = obj->pop();
*/
903. Valid Permutations for DI Sequence
题意:给定一个字符串S,长度为n,包含两种字符,D和I("decreasing" and "increasing")。一个0到n的合法排列是满足位置i的数字a和位置i+1的数字b的关系为a>b(如果S[i] == 'D'),a<b(如果S[i]=='I')。求合法的排列数
题解:动态规划,dp[i][j]表示0~i这i+1个数字的排列最后一个数字为j的合法排列数(对应S的前缀i个字符)。则答案就是
最后数字为j,前面是0到i中除j的剩下i个数。我们要求他们的合法排列数。注意到对于大于j的数,我们统统减去一得到0到i - 1的排列,它和0到i除去j的排列是一一对应的,合法性也是一致的。所以可以由0到i-1的合法排列得到0到i的合法排列。所以只要枚举j的前一个字符,构造转移方程即可,得到
如果S[i] == 'D'
如果S[i] == 'I'
每次求dp[i][j]有一个求和操作,我们可以通过预先求前缀和得到,这样时间复杂度就是O(n^2)
另外可以通过滚动数组进一步减少空间的使用
class Solution {
public:
int numPermsDISequence(string S) {
int n = S.length();
const long long MOD = 1e9 + 7;
vector<vector<long long> > dp(n + 1, vector<long long>(n + 1, 0));
dp[0][0] = 1;
for(int i = 1;i <= n; ++i){
for(int j = 0;j <= i ; ++j){
if(S[i - 1] == 'D'){
for(int k = j; k < i; ++k)
dp[i][j] += dp[i - 1][k];
}else{//S[i - 1] == 'I'
for(int k = 0; k < j; ++k)
dp[i][j] += dp[i - 1][k];
}
dp[i][j] %= MOD;
}
}
long long ans = 0;
for(int i = 0;i <= n; ++i) ans += dp[n][i];
return ans % MOD;
}
};
————————————————————————————————————————————————————————————————————————————————
class Solution {
public:
int numPermsDISequence(string S) {
int n = S.length();
const long long MOD = 1e9 + 7;
vector<vector<long long> > dp(2, vector<long long>(n + 1, 0));
vector<vector<long long> > sum(2, vector<long long>(n + 2, 0));
dp[0][0] = 1;
sum[0][0] = 0;
sum[0][1] = 1;
for(int i = 1;i <= n; ++i){
for(int j = 0;j <= i ; ++j){
if(S[i - 1] == 'D'){
dp[i&1][j] = sum[1 - i&1][i] - sum[1 - i&1][j];
}else{//S[i - 1] == 'I'
dp[i&1][j] = sum[1 - i&1][j];
}
dp[i&1][j] %= MOD;
sum[i&1][j + 1] = dp[i&1][j] + sum[i&1][j];
}
}
return sum[n&1][n + 1] % MOD;
}
};
906. Super Palindromes
题意:给定两个数L,R(1<=L,R<=1e18以字符串的形式)。问区间[L,R]的数字中是超级回文的有多少个。
超级回文指的是一个数字首先它是回文串,另外它是某个回文数字的平方。
题解:假设超级回文数字为a^2,那么a,a^2都是回文串,a<=1e9。我们可以枚举a的一半即可得到所有回文串。
另外一种解法就是直接打表,因为这个条件很苛刻,满足的数不会很多,打表发现算上0,共71个
打表程序
#include<bits/stdc++.h>
using namespace std;
bool is_palindrome(long long n){
long long m = 0;
long long tmp = n;
while(tmp){
m = m * 10 + tmp % 10 ;
tmp /= 10;
}
return m == n;
}
int main()
{
freopen("output.txt", "w", stdout);
vector<long long> a;
a.push_back(0);
a.push_back(1);
for(long long i = 2; i < 1e9; ++i){
if(i % 10 > 3 || i % 10 == 0) continue;
if(is_palindrome(i) && is_palindrome(i * i))
a.push_back(i * i);
}
cout<<'{';
for(int i = 0;i < a.size() - 1; ++i) cout<<a[i]<<',';
cout<<a[a.size() - 1]<<"};";
cout<<a.size()<<endl;
return 0;
}
———————————————————————————————————————————————————————————————————————————————————————
class Solution {
public:
int superpalindromesInRange(string L, string R) {
long long super_palindrome[] ={0,1,4,9,121,484,10201,12321,14641,40804,44944,1002001,1234321,4008004,100020001,102030201,104060401,121242121,123454321,125686521,400080004,404090404,10000200001,10221412201,12102420121,12345654321,40000800004,1000002000001,1002003002001,1004006004001,1020304030201,1022325232201,1024348434201,1210024200121,1212225222121,1214428244121,1232346432321,1234567654321,4000008000004,4004009004004,100000020000001,100220141022001,102012040210201,102234363432201,121000242000121,121242363242121,123212464212321,123456787654321,400000080000004,10000000200000001,10002000300020001,10004000600040001,10020210401202001,10022212521222001,10024214841242001,10201020402010201,10203040504030201,10205060806050201,10221432623412201,10223454745432201,12100002420000121,12102202520220121,12104402820440121,12122232623222121,12124434743442121,12321024642012321,12323244744232321,12343456865434321,12345678987654321,40000000800000004,40004000900040004};
int len = 71;
long long l = stoll(L.c_str());
long long r = stoll(R.c_str());
return upper_bound(super_palindrome, super_palindrome + len ,r) - lower_bound(super_palindrome, super_palindrome + len, l);
}
};
920 Number of Music Playlists
题意:给定三个数N,L,K <=100。给长度为L的数组填入1到N的数,每个数至少出现一次,且长度为K的连续子段不能出现重复。求填数的方案数
题解:动态规划。设dp[i][j]表示填完长度为i的数组用到j个数且连续K子段不出现重复的方案数。转移方程易得,见代码。
class Solution {
public:
static const int maxn = 101;
long long dp[maxn][maxn];
//long long C[maxn][maxn];
long long MOD = 1e9 + 7;
int numMusicPlaylists(int N, int L, int K) {
memset(dp, 0, sizeof(dp));
//dp[i][j] 长度为i的用了j个字符。每K个里面没有重复的方案数
dp[0][0] = 1;
for(int i = 1;i <= L; ++i){
for(int j = 1; j <= min(i, N); ++j){
dp[i][j] = (dp[i - 1][j] * max(j - K, 0) + dp[i - 1][j - 1] * max(0, N - j + 1)) % MOD;
}
}
return dp[L][N];
}
};
952. Largest Component Size by Common Factor
题意:给定n个数,表示n个节点。如果两个节点值不是互素的,则节点间有一条边。问最大连通分量有多少个点 题解:并查集。 可以对每个数先求素因子。如果两个数有公因子,最小那个一定是素因子。我们把每个素数当一个桶,放置有因子为它的数。当有一个数,具有因子a和因子b,那么两个桶的数合并(他们是同一个连通分量的)。这个过程用并查集完成。
class Solution {
// //欧拉筛
// vector<int> get_primer_euler(int maxn){
// vector<int> prime(maxn, 1);
// int prime_num = 0;
// for(size_t i = 2;i < maxn; ++i){
// if(prime[i])
// prime[prime_num++]=i;
// for(size_t j = 0;j < prime_num and prime[j] * i < maxn; ++j){
// prime[prime[j] * i] = false;
// if(i % prime[j] == 0) //保证每个合数只会被它的最小质因数筛去,因此每个数只会被标记一次
// break;
// }
// }
// //cout<<prime_num<<endl;
// prime.resize(prime_num);
// return prime;
// }
int find_pa(int x, unordered_map<int, int> &pa){
if(pa.count(x) == 0) return pa[x] = x;
return x == pa[x]? x : pa[x] = find_pa(pa[x], pa);
}
public:
int largestComponentSize(vector<int>& A) {
//int n = 100001;
unordered_map<int,int> nums;
unordered_map<int, int> pa;
int ans = 1;
//求素因子
for(int i = 0;i < A.size(); ++i){
if(A[i] == 1) continue;
int prev_fa = -1, cur_fa;
for(int j = 2;j <= sqrt(A[i]); ++j){
if(A[i] % j) continue;
while(A[i] % j == 0) A[i] /= j;
cur_fa = find_pa(j, pa);
if(prev_fa == -1) {
++nums[prev_fa = cur_fa];
continue;
}
if(cur_fa == prev_fa) continue;
pa[cur_fa] = prev_fa;
nums[prev_fa] += nums[cur_fa];
}
if(A[i] > 1) {
cur_fa = find_pa(A[i], pa);
if(prev_fa == -1) {
++nums[prev_fa = cur_fa];
}
if(cur_fa != prev_fa){
pa[cur_fa] = prev_fa;
nums[prev_fa] += nums[cur_fa];
}
}
ans = max(ans, nums[prev_fa]);
}
return ans;
}
};
还有两个不知道哪一题的
题意:给一颗二叉树,求它的后序遍历。要求非递归
题解:Morris traversal 后序遍历。代码是 抄过来的。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void reverseNodes(TreeNode* start, TreeNode* end) {
if (start == end) return;
TreeNode* x = start;
TreeNode* y = start -> right;
TreeNode* z;
while (x != end) {
z = y -> right;
y -> right = x;
x = y;
y = z;
}
}
void print(TreeNode* start, TreeNode* end, vector<int>& nodes) {
reverseNodes(start, end);
TreeNode* node = end;
while (true) {
nodes.push_back(node -> val);
if (node == start) break;
node = node -> right;
}
reverseNodes(end, start);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> nodes;
TreeNode* dump = new TreeNode(0);
dump -> left = root;
TreeNode* cur = dump;
while (cur) {
if (cur -> left) {
TreeNode* pre = cur -> left;
while (pre -> right && pre -> right != cur)
pre = pre -> right;
if (!(pre -> right)) {
pre -> right = cur;
cur = cur -> left;
}else {
print(cur -> left, pre, nodes);
pre -> right = NULL;
cur = cur -> right;
}
}
else cur = cur -> right;
}
return nodes;
}
};
题意:跟24一样,只不过是k个连续的node
题解:差不多
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode* l1, *l2, *tmp = new ListNode(0), *p, *nextP;
tmp->next = head;
head = tmp;
while(tmp){
p = tmp->next;
bool flag = false;
for(int i = 0; i < k; ++i){
if(!p){flag = true;break;}
p = p->next;
}
if(flag) break;
l1 = tmp->next;
nextP = l1;
for(int i = 0;i < k; ++i){
l2 = l1->next;
l1->next = p;
p = l1;
l1 = l2;
}
tmp->next = p;
tmp = nextP;
}
tmp = head->next;
delete head;
return tmp;
}
};