leetcode 題解 (500-1000題,持續更新,part 2)

part1(1-500)part3(1000-*)

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;
    }
};

591. Tag Validator

題意:驗證一個標籤是否合法。

標籤以<tagname>起始</tagname終止>,tagname必須是大寫字符,且長度不大於9。

形式如下<tagname>tagcontent </tagname>

tagcontent可能包含以下部分,

  1. 一般text(不包含左尖括號<)
  2. CDATA形式爲  <![CDATA[CDATA_CONTENT]]>,CDATA_CONTENT可以爲任意字符
  3. tag嵌套

題解:模擬題。直接模擬判斷過程。懶得寫,見代碼註釋

class Solution {
public:
    bool isValid(string code) {
		if(code.empty() || code[0] != '<')
			return false;
		int index = 0, N = code.length();
        stack<string> tag;
        
		while(index < N){
            //遇到左括號,三種情況,起始,終止標籤和CDATA
			if(code[index] == '<'){
				++index;
                //CDATA部分
				if(index < N && code[index]== '!'){
                    //不被包含在tag中,則invalid
                    if(tag.empty())
                        return false;
					++index;
                    //接下是[CDATA[
					if(code.substr(index, 7) != "[CDATA[")
						return false;
					index += 7;
                    //CDATA_CONTENT
                    while(true){
                        //找到一個可能的CDATA_CONTENT結尾
						while(index < N && code[index] != ']') ++index;
						if(index < N - 3){
							if(code.substr(index,3) == "]]>")  break;
						}else
							return false;
						++index;
					}
				}else if(index < N && code[index]== '/'){ //tag結尾
					if(tag.empty())
						return false;
					++index;
					int i = index, len;
                    //標籤,len爲標籤長度
					while(i < N && code[i] != '>'){
						len = i - index + 1;
						if(len > (int)tag.top().length() || code[i] != tag.top()[len - 1])
							return false;
						i++;
					}
					len = i - index;
					if(len != (int)tag.top().length())
						return false;
                    
                    
					tag.pop();
					index = ++i;
                    //外層必須是一個tag,而不是兩個tag並排
                    if(tag.empty() && index != N)
                        return false;
				}else{ //起始標籤
					int i = index, len;
                    //tag
					while(i < N && code[i] != '>'){
						len = i - index + 1;
    
						if(code[i] > 'Z' || code[i] < 'A')
							return false;
						i++;
						if(len > 9)
							return false;
					}
					len = i - index;
					if(i == N || len < 1 || len > 9)
						return false;
					tag.push(code.substr(index,len));
					index = ++i;
				}
			}else //其他內容。直接略
				++index;
		}
		return tag.empty();
    }
};

 

 

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);
    }
};

664. Strange Printer

題意:有一個stange printer他能做以下的事

  1. 每次只能從一個位置到另一個位置打印同一個字符
  2. 在每一輪的打印中,新打印的會覆蓋舊打印的字符

給定一個字符串,問至少需要多少次打印才能得到。比如aba需要兩次。先打印aaa,再打印b

題解:動態規劃,設dp[i][j]表示i到j子串需要多少次打印。則有以下的情況

首先注意到,第一個字符一定可以在第一輪打印。如果它單獨打印顯然可以,否則,它和到另外的一個位置k的子串打印,那麼顯然也可以放在第一次,因爲如果不是第一次,那麼會覆蓋掉前面的打印,把前面的打印的起始位置移到k以後並不會產生差別。所以我們可以分兩種情況

  1. i位置單獨打印,則次數爲dp[i + 1][j] + 1
  2. i和k一起打印(s[i] == s[k]),那麼k這個位置就不用管了(相當於和i合一起了),並且將串分成兩部分i  ~ k , k + 1~ j。i~k的打印次數爲dp[i][k - 1],k+1~j的打印次數dp[k + 1][j]。總次數爲dp[i][k - 1] + dp[k + 1][j]
class Solution {
    int dp[101][101];
    
    int dfs(int i, int j, string &str){
        if(i > j) return 0;
        if(i == j) return 1;
        if(dp[i][j]) return dp[i][j];
        
        dp[i][j] = dfs(i + 1, j, str) + 1;
        for(int k = i + 1; k < str.length(); ++k){
            if(str[k] == str[i]) dp[i][j] = min(dp[i][j], dfs(i, k - 1, str) + dfs(k + 1, j, str));
        }
            
        return dp[i][j];
    }
    
public:
    int strangePrinter(string s) {
        string str = "";
        for(int i = 0;i < s.length(); ++i){
            str += s[i];
            while(i + 1 < s.length() && s[i + 1] == s[i]) ++i;
        }
        return dfs(0, str.length() - 1, str);
    }
};

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;
    }
};

675. Cut Off Trees for Golf Event

題意:給以個網格,表示一個森林,其中0表示障礙物不能通過,其他都能通過,1表示平地,高於1表示有一棵樹。從(0,0)位置開始,每次砍最低的樹,最少需要多少步

題解:按樹高排序,就是從當前位置走到下個樹的高度位置的最少步數,bfs可解決。一開始我看成樹是不能通過的,只有砍掉才能通過,這樣也可以做,稍微修改即可(將排序部分改成優先隊列)。

class Solution {
    typedef pair<int, pair<int,int>> height_pos, step_pos;
    //放在外面會快很多
    bool vis[50][50];
    queue<step_pos> Q;
    
    int get_step(const pair<int,int> &s, const pair<int,int> &e, const vector<vector<int>>& forest){
        while(not Q.empty()) Q.pop();
        Q.push(make_pair(0, s));
        //vector<vector<bool>> vis(forest.size(), vector<bool>(forest[0].size(), false));
        memset(vis, 0, sizeof(vis));
        
        int dir_x[] = {1, 0, -1, 0};
        int dir_y[] = {0, 1, 0, -1};
        vis[s.first][s.second] = true;
        while(not Q.empty()){
            step_pos cur = Q.front(); Q.pop();
            int x = cur.second.first, y = cur.second.second;
            if(cur.second == e) return cur.first;
            
            for(int d = 0; d < 4; ++d){
                int next_x = x + dir_x[d];
                int next_y = y + dir_y[d];
                if(next_x < 0 or next_y < 0 or next_x >= forest.size() or next_y >= forest[0].size() or vis[next_x][next_y] or forest[next_x][next_y] == 0) continue;
                vis[next_x][next_y] = true;
                Q.push(make_pair(cur.first + 1, make_pair(next_x, next_y)));
            }            
        }
        return -1;   
    }
    
public:
    
    int cutOffTree(vector<vector<int>>& forest) {
        vector<height_pos> tree_pos;
        // priority_queue<height_pos> Q;
        for(int i = 0;i < forest.size(); ++i){
            for(int j = 0;j < forest[i].size(); ++j){
                if(forest[i][j] > 1){
                    //優先隊列是大頂堆,所以加個負讓小的先出
                    //Q.push(make_pair(-forest[i][j], make_pair(i, j)));
                    tree_pos.push_back(make_pair(forest[i][j], make_pair(i, j)));
                }
            }
        }
        
        sort(tree_pos.begin(), tree_pos.end());
        int ans = 0;
        
        pair<int, int> prev(0, 0);
        
        for(int i = 0;i < tree_pos.size(); ++i){
            pair<int, int> &cur = tree_pos[i].second;
            forest[cur.first][cur.second] = 1;
            int step = get_step(prev, cur, forest);
            if(step == -1) return -1;
            ans += step;
            prev.swap(cur);
        }
        
        return ans;
    }
};

679. 24 Game

題意:24點。+-*/四種運算和括號。給定4個1到9的數字,問能不能得到24

題解:枚舉運算,遞歸。當然還有其他的方法

 

class Solution {

    int helper(vector<double>& nums){
        
        if(nums.size() == 1){
            return fabs(nums[0] - 24) < 1e-5;
        }
        
        for(int i = 0;i < nums.size(); ++i){
            for(int j = i + 1; j < nums.size(); ++j){
                vector<double> new_num;
                for(int k = 0; k < nums.size(); ++k){
                    if(k == i or k == j) continue;
                    new_num.push_back(nums[k]);
                }
                
                for(int k = 0; k < 6; ++k){
                    double res = 0;
                    if(k == 3 and nums[j] == 0) continue;
                    if(k == 5 and nums[i] == 0) continue;
                    switch(k){
                        case 0: res = nums[i] + nums[j]; break;
                        case 1: res = nums[i] - nums[j]; break;
                        case 2: res = nums[i] * nums[j]; break;
                        case 3: res = nums[i] / nums[j]; break;
                        case 4: res = nums[j] - nums[i]; break;
                        case 5: res = nums[j] / nums[i]; break;
                        default: assert(false);
                    }
                    
                    new_num.push_back(res);
                    if(helper(new_num)) return true;
                    new_num.pop_back();
                }
            }
        }
        return false;

    }
public:
    bool judgePoint24(vector<int>& nums) {
        vector<double> new_num;
        for(int i = 0;i < 4; ++i)
            new_num.push_back(nums[i]);


        return helper(new_num);
    }
};

685. Redundant Connection II

題意:給定一個有向圖,去掉一個邊以後它是一個有根樹,求這條邊,如果有多條邊滿足,則返回下標最大的那個。

題解:有兩種情況發生,某個節點的入度大於1,那麼刪除的邊肯定是以這個節點爲末端的。另外一種情況就是所有節點入度都是1,這時候這條邊是某個節點到根的邊。刪除的邊肯定是一個環裏面的一條邊(看成無向圖的環)。

如果是情況1,答案就是那條即以某個入度大於1的點位終點,且在環上的邊。

如果是情況2,則答案是環上最後一條邊。

將以某個點入度大於一爲結尾的邊移到所有邊的後面去。然後從頭到尾遍歷邊,利用並查集就可以判斷加進當前邊會不會產生環。如果產生環,則答案就是這條邊。

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> findRedundantDirectedConnection(vector<vector<int>>& edges) {
        int n = edges.size();
        vector<int> in_degree(n + 1, 0);
        vector<int> pa(n + 1);
        
        for(int i = 1;i <= n; ++i) pa[i] = i;
        
        int redundant_node = 0;
        
        for(int i = 0;i < n; ++i){
            if(++in_degree[edges[i][1]] > 1){
                redundant_node = edges[i][1];
            }
        }
        
        vector<int> candidate_edges;
        
        for(int i = 0;i < n; ++i){
            int x = edges[i][0], y = edges[i][1];
            int p1 = find_pa(x, pa), p2 = find_pa(y, pa);
            
            if(y == redundant_node){
                candidate_edges.push_back(i);
                continue;
            }
            
            if(p1 == p2) return edges[i];
            pa[p2] = pa[p1];
        }
        
        for(int i = 0;i < candidate_edges.size(); ++i){
            int x = edges[candidate_edges[i]][0], y =  edges[candidate_edges[i]][1];
            int p1 = find_pa(x, pa), p2 = find_pa(y, pa);
            if(p1 == p2) return edges[candidate_edges[i]];
            pa[p2] = pa[p1];
        }
        
        return {};
        
    }
};

689. Maximum Sum of 3 Non-Overlapping Subarrays

題意:給定一個正整數數組和一個整數k。在這個數組中選3個不相交的子數組,使得和最大。輸出是3個子數組的起始位置

題解:動態規劃。設dp[i][j]表示前i個數,取j+1個不相交子數組最大和時,最後一個元素的下標。

預處理sum_k[i]表示以i爲結束位置的k個數的和。sum[i][j]表示

dp[i][0] = \max_{k <= i} sum_k[k]

而dp[i][1]分兩種情況考慮,即要不要加入當前元素i,如果不加入,就是dp[i - 1][1].。如果加入,就是將以i結尾的k個元素去掉後,考慮前面最大的dp[k][0]。dp[i][2]也一樣

分成3個循環可能更快一些。

class Solution {
public:
    vector<int> maxSumOfThreeSubarrays(vector<int>& nums, int k) {
        int n = nums.size();
        int dp[n][3] = {0};
        int sum_k[n] = {0};
        //以i結尾的k個數的和
        for(int i = 0;i < n; ++i){
            if(i < k){
                sum_k[k - 1] += nums[i];
            }else{
                sum_k[i] = sum_k[i - 1] + nums[i] - nums[i - k];
                
            }
        }
        
        //dp[i][j] 表示在前i個取j + 1個不重疊k長子數組的最大和的結尾下標
        //int idx1 = -1, idx2 = -1, idx3 = -1;
        
        dp[k - 1][0] = k - 1; dp[2 * k - 1][1] = 2 * k - 1; dp[3 * k - 1][2] = 3 * k - 1;

        
        for(int i = k; i < n; ++i){

            dp[i][0] = dp[i - 1][0];
            if(sum_k[i] > sum_k[dp[i - 1][0]]){
                dp[i][0] = i;
            }
            
            if(i + 1 > 2 * k){
                int p = dp[i][1] = dp[i - 1][1];
                if(sum_k[i] + sum_k[dp[i - k][0]] > sum_k[p] + sum_k[dp[p - k][0]]){
                    dp[i][1] = i;
                }
            }
            
            if(i + 1 > 3 * k){
                int p = dp[i][2] = dp[i - 1][2];
                if(sum_k[i] + sum_k[dp[i - k][1]] + sum_k[dp[dp[i - k][1] - k][0]] > 
                            sum_k[p] + sum_k[dp[p - k][1]] + sum_k[dp[dp[p - k][1] - k][0]]){
                     dp[i][2] = i;
                }
            }
        }
        return {dp[dp[dp[n - 1][2] - k][1] - k][0] - k + 1, dp[dp[n - 1][2] - k][1] - k + 1, dp[n - 1][2] - k + 1};
    }
};

691. Stickers to Spell Word

題意:給定一些字符串和一個target字符串,從這些字符串中取出一些(可以重複)字符串的字母來構成target。問最少取多少個

題解:動態規劃。我們用字符串來組成target。相當於從target中減去一些字母。我一開始使用的是字母的計數,不過事實上不用,可以先把字符串排序,利用集合操作即可。我們設狀態state爲從target減去一些字符得到的狀態字符串。dp[state]表示狀態爲state到達空串需要多少個字符串。轉移到state的狀態可以通過枚舉字符串然後然後去掉相應字符得到。state用字符排序表示(也可以用其他)

class Solution {
public:
    
    unordered_map<string, int> dp;
    vector<string> a;
    int n;
    
    int minStickers(vector<string>& A, string t) {
        a = A, n = a.size();
        sort(t.begin(), t.end());
        for(auto& s : a) sort(s.begin(), s.end());
        dp[""] = 0;
        int ans = t.size() + 1;
        solve(t, 0, ans);
        return dp[t] == 1e8 ? -1 : dp[t];
    }
    
    int solve(string t, int depth, int &ans) {
        //剪枝可去掉,沒什麼影響
        if(depth > ans) return 1;
        if(t.length() == 0) {
            ans = depth;
            return 0;
        }
        if(dp.count(t)) return dp[t];
        int ret = 1e8;
        for(string s : a) {
            if(s.find(t[0]) == -1) continue; 
            string T;
            set_difference(t.begin(), t.end(), s.begin(), s.end(), back_inserter(T));
            ret = min(ret, solve(T, depth + 1, ans) + 1);
        }
        return dp[t] = ret;
    }
};

699. Falling Squares

題意:掉落方塊。和俄羅斯方塊一樣,只不過只有方形。輸入是給定一些方塊,起始位置和變長。一個個往下掉,碰到地面或者其他方塊的頂部就會停止掉落然後固定住。對每一個方塊,輸出當前的最高高度。

題解:線段樹模板題,區間最大值。也可以暴力求解

class segmentTree{
    int *val;
    bool *lazy;
    int n;
    
    void push_down(int node){
        if(lazy[node]){
            val[node * 2] = val[node * 2 + 1] = val[node];
            lazy[node * 2] = lazy[node * 2 + 1] = true;
            lazy[node] = false;
        }
        
    }
    void set_max(int left, int right, int l, int r, int node, int v){
        if(r < left || l > right) return;
        if(l == r){
            //cout<<"r == l"<<l<<endl;
            val[node] = v;
            return;
        }
        
        
        if(left <= l && r <= right){
            lazy[node] = true;
            //cout<<left<<right<<" "<<l<<" "<<r<<" lr"<<endl;
            val[node] = v;
            return;
        }
        push_down(node);
        int mid = (l + r) / 2;
        //cout<<"mid = "<<mid<<endl;
        set_max(left, right, l, mid, node * 2, v);
        set_max(left, right, mid + 1, r, node * 2 + 1, v);
        
        val[node] = max(val[node * 2], val[node * 2 + 1]);
        //lazy[node] = false;
    }
    
    int get_max(int left, int right, int l, int r, int node){
        if(l > r || r < left || l > right){
            return 0;
        }
        else if(l == r){
            return val[node];
        }
        push_down(node);
        if(left <= l && right >= r) return val[node];
        int mid = (l + r) / 2;
        
        return max(get_max(left, right, l, mid, node * 2) , get_max(left, right, mid + 1, r, node * 2 + 1) );
    }
    
public:
    segmentTree(int n): n(n){
        int size = n << 2;
        val = new int[size];
        lazy = new bool[size];
        memset(val, 0, size * sizeof(int));
        memset(lazy, 0, size * sizeof(bool));
    }
    
    ~segmentTree(){
         delete[] val;
         delete[] lazy;
    }
    
    int find(int left, int right){
        return get_max(left, right, 1, n, 1);
    }
    
    void set(int left, int right, int v){
        set_max(left, right, 1, n, 1, v);
    }
    
};

class Solution {
public:
    vector<int> fallingSquares(vector<vector<int>>& positions) {
        set<int> pos;
        unordered_map<int, int> re_idx;
        
        for(int i = 0;i < positions.size(); ++i){
            pos.insert(positions[i][0]);
            pos.insert(positions[i][0] + positions[i][1]);
        }
        
        int s = 0;
        //sort(pos.begin(), pos.end());
        for(set<int>::iterator iter = pos.begin(); iter != pos.end(); ++iter){
            re_idx[*iter] = ++s;
        }
        
        segmentTree seg_tree(s - 1);
        vector<int> ans;
        int max_height = 0;
        for(int i = 0;i < positions.size(); ++i){
            int left = re_idx[positions[i][0]];
            int right = re_idx[positions[i][0] + positions[i][1]];
            int h = seg_tree.find(left, right - 1);
            seg_tree.set(left, right - 1, h + positions[i][1]);
            
            ans.push_back(max(max_height, h + positions[i][1]));
            max_height = ans.back();
            
        }
        return ans;
    }
};

710. Random Pick with Blacklist

題意;給定一個N,和一個數組blacklist。實現一個函數pick,從[0,N)中且不再blacklist中的數按均勻分佈取數。

題解:可以採用二分法,數總共有N - blacklist.size()個,均勻取一個數rank \in [0,N)\backslash blacklist。然後二分計算實際排名爲rank是哪個數即可。

還可以用另一種方法,隨機取一個數,如果在B中,則重新取,判斷是否在B中可用hashset。這樣做的話,如果B的數比較密集,那麼重複的次數可能會很高。所以先保證B在N中比較稀疏,如果稀疏則按上面做法。如果密集的話,說明N比較小,可以先預處理出第rank個是哪個數,直接取。

class Solution {
    int n;
    vector<int> blacklist;
public:
    Solution(int N, vector<int>& blacklist) {
        n = N - blacklist.size();
        sort(blacklist.begin(), blacklist.end());
        this->blacklist.swap(blacklist);
    }
    
    int pick() {
        int rank = random() % n + 1;
        int left = 0, right = blacklist.size() - 1;
        while(left <= right){
            int mid = (left + right) >> 1;
            if(blacklist[mid] - mid >= rank){
                right = mid - 1;
            }else{
                left = mid + 1;
            }
        }
       return rank + right;
        
    }
};

/**
 * Your Solution object will be instantiated and called as such:
 * Solution* obj = new Solution(N, blacklist);
 * int param_1 = obj->pick();
 */

715. Range Module

題意:實現一個類,能夠進行區間查詢,增刪區間。

題解:區間範圍1到1e9

題解:如果數據範圍小,或者可以離線的話可以用線段樹做,但是數據範圍大且不能離線。所以只能考慮直接存儲線段以及進行線段的合併拆分操作了。線段存在一個有序的數據結構中,然後每次增加線段,把能合併的合併即可。而刪除就相反,該拆分的拆分,完全刪除的刪除。代碼很容易懂的,見代碼。

class RangeModule {
    set<pair<int,int> >  segments;
    typedef set<pair<int,int>>::iterator seg_iter;
    
public:
    RangeModule() {
        //插入(0, 0)方便操作, 簡化代碼
        segments.insert(make_pair(0, 0));
    }
    
    void addRange(int left, int right) {
        
        seg_iter iter = segments.lower_bound(make_pair(right, 0));
        if(iter == segments.end() or iter->first > right) --iter;
        
        right = max(right, iter->second);
        
        while(iter->second >= left){
            left = min(left, iter->first);
            segments.erase(iter--);
        }
        
        segments.insert(make_pair(left, right));
       
    }
    
    bool queryRange(int left, int right) {
        //找到最後一個左端點小於等於left的區間
        seg_iter iter = segments.lower_bound(make_pair(left, 0));
        if(iter == segments.end() or iter->first > left) --iter;
        return iter->second >= right;
    }
    
    void removeRange(int left, int right) {

        //找到第一個和當前區間相交的區間
        seg_iter left_iter = --segments.lower_bound(make_pair(left, 0));
        if(left_iter->second <= left) ++left_iter;
        if(left_iter->first >= right) return;
        
        //最後一個和當前區間相交的區間
        seg_iter right_iter = --segments.lower_bound(make_pair(right, 0));
        if(right_iter->second <= left) return;
        ++right_iter;
        
        for(seg_iter iter = left_iter; iter != right_iter; ){
            int left1 = min(left, iter->first);
            int right1 = left;
            int left2 = right;
            int right2 = max(right, iter->second);
            segments.erase(iter++);
            
            if(left1 < right1)
                segments.insert(make_pair(left1, right1));
            if(left2 < right2)
                segments.insert(make_pair(left2, right2));
        }
    }
};

/**
 * Your RangeModule object will be instantiated and called as such:
 * RangeModule* obj = new RangeModule();
 * obj->addRange(left,right);
 * bool param_2 = obj->queryRange(left,right);
 * obj->removeRange(left,right);
 */

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 {
    //long long dp[2][1000][4];
    const long long MOD = 1e9 + 7;
public:
    int countPalindromicSubsequences(string S) {
        int n = S.length();
        int cur = 0;
        long long dp[2][1000][4] = {0};
        
        for(int i = n - 1; i >= 0; --i){
            cur = i & 1;
            dp[cur][i][S[i] - 'a'] = 1;
            for(int j = i + 1; j < n; ++j){
                
                for(int k = 0; k < 4; ++k){
                    if(S[i] - 'a' != k) dp[cur][j][k] = dp[cur^1][j][k];
                    else dp[cur][j][k] = dp[cur][j - 1][k];
                }
                if(S[i] == S[j]){
                    dp[cur][j][S[i] - 'a'] = 2;
                    for(int k = 0;k < 4; ++k)
                        dp[cur][j][S[i] - 'a'] += dp[cur^1][j - 1][k];
                
                    dp[cur][j][S[i] - 'a'] %= MOD;
                }
            }
        }
        
        long long ans = 0;
        for(int k = 0; k < 4; ++k){
            ans += dp[cur][n - 1][k];
        }
        
        return ans % MOD;
    }
};

730. Count Different Palindromic Subsequences

題意:給定一個字符串,問有多少個不同個的迴文串

題解:動態規劃。以下兩種動態規劃思路。

設dp[i][j][k]表示子串i到j,起始字符串爲k時不同的迴文串個數。

情形1:     如果S[i] != k,則dp[i][j][k] = dp[i + 1][j][k] (因爲不是i爲起始的)

情形2: 如果s[j] != k 則dp[i][j][k] = dp[i+1][j][k] (不是以j結束的)

情形3: s[i] == s[j] == k,則dp[i][j][k] = sum(dp[i + 1][j - 1]) + 2 (以i位置爲起始的迴文串個數爲i+1後面任意字符開始到j-1的迴文串拼接上s[i]和s[j]然後再加上s[i]單個字符以及s[i]s[j]兩個字符兩種情況)

然後可以用滾動數組減少內存。

解法2:設dp[i][j]表示子串i到j的不同迴文串個數。則有以下情況

情形1:s[i] != s[j]那麼,dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1] (i到j的不同迴文串等於i到j-1的迴文串和i + 1到j的迴文串的並,所以減去重複計算的部分dp[i+1][j-1])

情形2:s[i] == s[j] 又分幾種子情況。具體分3種,就是i + 1到j - 1中有多少個字符和s[i]相同。 沒有相同的:那麼dp[i][j] = dp[i + 1][j - 1] * 2 + 2 (在原迴文串中加或者不加入這兩個字符,再加上s[i],s[i]s[j]兩種情況) 一個相同: dp[i][j] = dp[i + 1][j - 1] * 2 + 1 (原迴文串中加或者不加這兩個字符, 然後加上s[i]s[j]一種情況) 兩個及以上:dp[i][j] = dp[i + 1][j - 1] * 2 - dp[next + 1][prev - 1] (找到i的下一個和s[i]相同字符的位置next,以及j前一個和j相同字符的位置prev)。i到j的不同迴文串個數,考慮是不是以s[i]爲起始兩種情況。如果以s[i]起始那麼個數爲dp[i + 1][j - 1] - dp[next +1][prev - 1]。如果不以s[i]爲起始,那麼個數爲dp[i + 1][j - 1]

class Solution {
    int dp[2][1001][4];
    const int MOD = 1e9 + 7;
public:
    int countPalindromicSubsequences(string S) {
        //memset(dp, 0, sizeof(dp));
        int n = S.length();
        int cur = 0;
        
        for(int i = n - 1;i >= 0; --i){
            cur = i & 1;
            dp[cur][i][S[i] - 'a'] = 1;
            for(int j = i + 1; j < n; ++j){
                
                for(int k = 0; k < 4; ++k){
                    if(S[i] - 'a' != k) dp[cur][j][k] = dp[cur^1][j][k];
                    else dp[cur][j][k] = dp[cur][j - 1][k];
                }
                if(S[i] == S[j]){
                    dp[cur][j][S[i] - 'a'] = 2;
                    for(int k = 0;k < 4; ++k)
                        dp[cur][j][S[i] - 'a'] = (dp[cur][j][S[i] - 'a'] + dp[cur^1][j - 1][k]) % MOD;
                }
            }
        }
        
        int ans = 0;
        for(int k = 0; k < 4; ++k){
            ans = (ans + dp[cur][n - 1][k]) % MOD;
        }
        
        return ans % MOD;
    }
};
class Solution {
    int dp[1001][1001];
    const int mod = 1000000007;
public:
    int countPalindromicSubsequences(string S) {
        
        int n = S.length();
        
        vector<int> prev(n), next(n);
        vector<int> prev_pos(4, -1), next_pos(4, n);
        
        for(int i = 0;i < n; ++i){
            prev[i] =prev_pos[S[i] - 'a'];
            prev_pos[S[i] - 'a'] = i;
            
            next[n - i - 1] = next_pos[S[n - i - 1] - 'a'];
            next_pos[S[n - i - 1] - 'a'] = n - i - 1;
        }
        
        for (int i = n - 1;i >= 0; --i){
            dp[i][i] = 1;
            for (int j = i + 1; j < n; ++j){
                if (S[i] == S[j]){
                    dp[i][j] = dp[i + 1][j - 1] * 2;
                    if (next[i] == prev[j]){ //一個相同的
                        dp[i][j] += 1;
                    } else if (next[i] > prev[j]) { //沒有相同的
                        dp[i][j] += 2;
                    } else {//兩個及以上相同的
                        dp[i][j] -= dp[next[i] + 1][prev[j] - 1];
                    }   
                } else {
                    dp[i][j] = dp[i + 1][j] + dp[i][j - 1] - dp[i + 1][j - 1];
                }
                dp[i][j] = dp[i][j] < 0 ? dp[i][j] + mod : dp[i][j] % mod;
            }
        }
        return dp[0][n - 1];
    }
};

732. My Calendar III

題意:區間覆蓋。每次給定一個區間[start, end),返回被覆蓋最多的次數。

題解:可以用離散化線段樹。由於數目很少,用區間標記預處理前綴和。每次從頭到尾算一遍。

class MyCalendarThree {
public:
    map<int, int> seg;
    MyCalendarThree() {
        
    }
    
    int book(int start, int end) {
        seg[start] += 1;
        seg[end] -= 1;
        
        int res = 0, max_val = 0;
        for (auto item : seg) {
            res = max(res, max_val += item.second);
        }
        return res;
    }
};

/**
 * Your MyCalendarThree object will be instantiated and called as such:
 * MyCalendarThree* obj = new MyCalendarThree();
 * int param_1 = obj->book(start,end);
 */
const int MX = 1e5 + 5;
const int M = 1e9;
class MyCalendarThree {
public:
    int ls[MX], rs[MX], sum[MX], lz[MX];
    int cnt, root;
    MyCalendarThree() {
        ls[0] = rs[0] = sum[0] = lz[0] = 0;
        cnt = 0;
        root = ++cnt;
        init_node(root);
    }
    void init_node(int rt) {
        ls[rt] = rs[rt] = sum[rt] = lz[rt] = 0;
    }
    void PushUP(int rt) {
        int l = ls[rt], r = rs[rt];
        sum[rt] = max(sum[l] + lz[l], sum[r] + lz[r]);
    }
    void update(int L, int R, int l, int r, int &rt) {
        if(rt == 0) {
            rt = ++cnt;
            init_node(rt);
        }
        if(L <= l && R >= r) {
            lz[rt]++;
            return;
        }
        int m = (l + r) >> 1;
        if(L <= m) update(L, R, l, m, ls[rt]);
        if(R > m) update(L, R, m + 1, r, rs[rt]);
        PushUP(rt);
    }
    int book(int l, int r) {
        if(l < r) update(l, r - 1, 0, M, root);
        return sum[root] + lz[root];
    }
};

作者:zhrt
鏈接:https://leetcode-cn.com/problems/my-calendar-iii/solution/chi-san-hua-xian-duan-shu-by-zhrt/
來源:力扣(LeetCode)
著作權歸作者所有。商業轉載請聯繫作者獲得授權,非商業轉載請註明出處。

736. Parse Lisp Expression

題意:將Lisp表達式轉換成結果。有三種命令let, add,mul。

題解:將代碼來源中代碼修改了一下。

class Solution {
    unordered_map<string, stack<int>> var2val;
public:
    int evaluate(string expression) {
        //是一個數字
        if ((expression[0] == '-') || (expression[0] >= '0' && expression[0] <= '9')) {
            return stoi(expression);
        }
        else if (expression[0] != '(') {
            //是一個變量
            return var2val[expression].top();
        }
        //是一表達式
        //to get rid of the first '(' and the last ')'
        //去掉頭尾括號()
        int start = 0;
        
        string s = expression.substr(1, expression.size() - 2);
        string word = parse(s, start);
        if (word == "let") {
            vector<string> var;
            while (true) {
                string variable = parse(s, start);
                //if there is no more expression, simply evaluate the variable
                if (start > s.size()) {
                    int res = evaluate(variable);
                    for(string v : var){
                        var2val[v].pop();
                        //if(var2val[v].empty()) var2val.erase(v);
                    }
                    return res;
                }
                var.emplace_back(variable);
                var2val[variable].push(evaluate(parse(s, start)));                    
            }
        }
        else if (word == "add") {
            return evaluate(parse(s, start)) + evaluate(parse(s, start));
        }
        else  { //if (word == "mult")
            return evaluate(parse(s, start)) * evaluate(parse(s, start));
        } 
       // return -1;
    }
private:

    //得到一個表達式或者一個詞
    string parse(string &s, int &start) {    //function to seperate each expression
        int end = start + 1, temp = start, count = 1;
        //count:括號嵌套層數
        //end:表達式結尾
        
        if (s[start] == '(') {
            while (count) {
                if (s[end] == '(') {
                    ++count;
                }
                else if (s[end] == ')') {
                    --count;
                }
                ++end;
            }
        }
        else {
            while (end < s.size() && s[end] != ' ') {
                ++end;
            }
        }
        start = end + 1;
        return s.substr(temp, end - temp);
    }
};

741. Cherry Pickup

題意:給定一個N *N的網格。每個格子有一個數,0代表空,-1代表障礙物,1代表得分。從左上角0,0位置開始走只能往右和往下,不能通過障礙物,到達右下角後,再從右下角走到左上角只能延左或者上走,同樣不能通過障礙物。問最後得到的最大得分(兩次通過同一個只記一次)。如果沒有路徑從左上角到達右下角,則爲0。

題解:問題等價於從左下角,沿着兩條路走到右下角的最大得分。容易想到的是四維的動態規劃,dp[i1][j1][i2][j2]表示從左上角的兩條路到達(i1,j1),(i2,j2)兩個點的最大得分,答案就是dp[n - 1][n - 1][n - 1][n - 1]。然而可以通過將狀態改變一下得到O(n^3)的動態規劃。我們讓j2 = i1 + j1 - i2,也就是兩個路徑同時走一步,這樣就可以省去一個狀態。然後在最上方和左方補個圍牆,可以省去判斷邊界。進一步用滾動數組可以減少內存的使用。

class Solution {
    
    // bool check(int i1, int j1, int i2, int j2, const vector<vector<int>>& grid){
    //     bool check_first_point = (i1 >= 0) and (j1 >= 0) and (grid[i1][j1] != -1);
    //     bool check_second_point = (i2 >= 0) and (j2 >= 0) and (grid[i2][j2] != -1);
    //     return check_first_point and check_second_point;
    // }
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int n = grid.size();
        int dp[2][n + 1][n + 1];
        memset(dp, -1, sizeof(dp));
        
        dp[0][1][1] = grid[0][0];
        int cur = 1;
        
        for(int i1 = 1;i1 <= n; ++i1){
            cur ^= 1;
            if(i1 > 1)
                memset(dp[cur], -1, sizeof(dp[cur]));
            for(int j1 = 1;j1 <= n; ++j1){
                
                if(grid[i1 - 1][j1 - 1] == -1) continue;
                
                for(int i2 = max(1, i1 + j1 - n); i2 < i1 + j1 and i2 <= n; ++i2){
                    int j2 = i1 + j1 - i2;
                    if(grid[i2 - 1][j2 - 1] == -1) continue;
                    
                    // int max_prev_val = -1;
                    
                    //if(check(i1 - 1, j1, i2, j2 - 1, grid))
//                         max_prev_val = max(max_prev_val, dp[i1 - 1][j1][i2]);
                    
//                     //if(check(i1 - 1, j1, i2 - 1, j2, grid)) 
//                         max_prev_val = max(max_prev_val, dp[i1 - 1][j1][i2 - 1]);
                    
//                      //if(check(i1, j1 - 1, i2, j2 - 1, grid)) 
//                         max_prev_val = max(max_prev_val, dp[i1][j1 - 1][i2]);
                    
//                      //if(check(i1, j1 - 1, i2 - 1, j2, grid)) 
//                         max_prev_val = max(max_prev_val, dp[i1][j1 - 1][i2 - 1]);
                    int max_prev_val = max(max(dp[cur^1][j1][i2], dp[cur^1][j1][i2 - 1]), max(dp[cur][j1 - 1][i2], dp[cur][j1 - 1][i2 - 1]));
                    
                    if(max_prev_val != -1)
                        dp[cur][j1][i2] = max_prev_val + (i1 == i2 ? grid[i1 - 1][j1 - 1] : grid[i1 - 1][j1 - 1] + grid[i2 - 1][j2 - 1]);
                }
            }
        }
        
        
        
        return dp[cur][n][n] == -1? 0 : dp[cur][n][n];
    }
};

745. Prefix and Suffix Search

題意:給定一個word的字符串數組。需要實現一個函數f,兩個參數prefix和suffix,返回以prefix爲前綴,suffix爲後綴的字符串中最大的下標。如果沒有返回-1,字符串最長長度爲10。

題解:本題有多種做法。我用的是trie。每個前綴保存一個trie由所有這個前綴的字符串的逆序構成,節點值保存以當前後綴的最大下標。還有其他的做法。

class Trie{
    struct Node{
        int val;
        Node *next[26];
        Node(){
            memset(next, NULL, sizeof(next));
        }
    };
    
    //禁止賦值和禁止複製構造,以免引起double free
    Trie(Trie &trie);
    const Trie& operator = (Trie &trie);
    int tot;
    // share_prt<Node> root;
    Node *root;
    //淺拷貝trie tree導致double free
    vector<Node*> nodes;
    
public:
    Trie(): root(0){
        nodes.push_back(root = new Node());
    }
    ~Trie(){
        for(int i = 0;i < nodes.size(); ++i){
            delete nodes[i];
            nodes[i] = NULL;
        }
    }
    void insert(const string &str, int weight){
        Node *p = root;
        p->val = weight;
        for(int i = 0;i < str.length(); ++i){
            int id = str[i] - 'a';
            if(p->next[id] == NULL){
                nodes.push_back(p->next[id] = new Node());
            }
            p = p->next[id];
            p->val = weight;
        }
    }
    
    int find(string suffix){
        Node *p = root;
        for(int i = 0;i < suffix.length(); ++i){
            int id = suffix[i] - 'a';
            if(p->next[id] == NULL) return -1;
            p = p->next[id];
        }
        return p->val;
    }
};



class WordFilter {
    int n;
    unordered_map<string, Trie *> prefix2trie;
    vector<Trie*> tries;
public:
    WordFilter(vector<string>& words): n(words.size()) {
        
        for(int i = 0; i < n; ++i){
            string word = words[i];
            reverse(word.begin(), word.end());
            string prefix = "";
            for(int j = 0;j <= word.size(); ++j){
                if(!prefix2trie.count(prefix)) {
                    tries.push_back(prefix2trie[prefix] = new Trie());
                }
                prefix2trie[prefix]->insert(word, i);
                if(j < word.size())
                    prefix += words[i][j];
            }
        }
    }
    ~WordFilter(){
        for(int i = 0; i < tries.size(); ++i){
            delete tries[i];
            tries[i] = NULL;
        }
    }
    int f(string prefix, string suffix) {
         if(!prefix2trie.count(prefix)) return -1;
         reverse(suffix.begin(), suffix.end());
         return prefix2trie[prefix]->find(suffix);
        return 0;
    }
};

/**
 * Your WordFilter object will be instantiated and called as such:
 * WordFilter* obj = new WordFilter(words);
 * int param_1 = obj->f(prefix,suffix);
 */

749. Contain Virus

題意:給定一個grid,其中1代表受病毒感染區域,0表示未被感染。白天的時候,選擇可能影響最多塊未被感染地區的連通塊,將他們用牆隔開正常區域。牆建在感染和未感染鄰接點的中間。夜晚的時候,病毒開始向周圍擴散。問一直到最後病毒不再擴散這個過程建了多少牆。

題解:模擬題。先找出影響最多未感染點的連通塊,然後算出需要的牆數,將他們固定住。然後未被固定的進行擴散和合並(合併連通塊)。循環進行。

class Solution {
    int pa[2500];
    vector<int> dir_x, dir_y;
    int n, m;
    unordered_map<int, int> cnt, cnt2;
    int find_pa(int x){
        return x == pa[x] ? x: pa[x] = find_pa(pa[x]);
    }
    //是否有未封鎖區域還會繼續感染
    bool check_done(vector<vector<int>>& grid){
        for(int i = 0; i < grid.size(); ++i){
            for(int j = 0; j < grid[0].size(); ++j){
                if(grid[i][j] == 0) {
                    for(int d = 0; d < 4; ++d){
                        int nx = i + dir_x[d];
                        int ny = j + dir_y[d];
                        if(nx < 0 or ny < 0 or nx >= grid.size() or ny >= grid[i].size() or grid[nx][ny] != 1) continue;
                        return false;
                    }
                }
                
            }
        }
        return true;
    }
    //搜索可能感染最多區域的部分,以及需要的牆數
    pair<int,int> get_threathened_part(vector<vector<int>>& grid){
        cnt.clear(); cnt2.clear();
        for(int i = 0; i < grid.size(); ++i){
            for(int j = 0; j < grid[i].size(); ++j){
                if(grid[i][j] != 0) continue;

                vector<int> pars;
                for(int d = 0;  d < 4; ++d){
                    int nx = i + dir_x[d];
                    int ny = j + dir_y[d];
                    if(nx < 0 or ny < 0 or nx >= grid.size() or ny >= grid[i].size() or grid[nx][ny] != 1) continue;
                    int pa1 = find_pa(nx * grid[i].size() + ny);
                    pars.push_back(pa1);
                    ++cnt2[pa1];
                }
                sort(pars.begin(), pars.end());
                if(pars.size() == 0) continue;
                ++cnt[pars[0]];
                for(int k = 1;k < pars.size(); ++k){
                    if(pars[k] == pars[k - 1]) continue;
                    ++cnt[pars[k]];
                }

            }
        }
        int max_cnt = -1;
        int threathened_part = 0;
        for(unordered_map<int, int>::const_iterator iter = cnt.begin(); iter != cnt.end(); ++iter){
            if(iter->second > max_cnt){
                max_cnt = iter->second;
                threathened_part = iter->first;
            }
        }
        return {threathened_part, cnt2[threathened_part]};
    }

    //還沒封鎖的區域往周圍傳播
    void spread(vector<vector<int>>& grid){
        for(int i = 0; i < grid.size(); ++i){
            for(int j = 0; j < grid[i].size(); ++j){
                if(grid[i][j]) continue;

                //vector<int> pars;
                bool affected = false;
                for(int d = 0;  d < 4; ++d){
                    int nx = i + dir_x[d];
                    int ny = j + dir_y[d];
                    if(nx < 0 or ny < 0 or nx >= n or ny >= m or grid[nx][ny] != 1) continue;
                    affected = true;
                    break;
                }
                if(affected)
                    grid[i][j] = 3;
            }
        }

       for(int i = 0; i < grid.size(); ++i){
            for(int j = 0; j < grid[i].size(); ++j){
                if(grid[i][j] != 3) continue;
                vector<int> pars;
                for(int d = 0;  d < 4; ++d){
                    int nx = i + dir_x[d];
                    int ny = j + dir_y[d];
                    if(nx < 0 or ny < 0 or nx >= n or ny >= m or grid[nx][ny] != 1) continue;
                    int pa1 = find_pa(nx * grid[i].size() + ny);
                    pars.push_back(pa1);
                }
                grid[i][j] = 1;
                pa[i * m + j] = pars[0];
                for(int k = 1; k < pars.size(); ++k)
                    pa[pars[k]] = pars[0];
            }
       }

    }

public:
    int containVirus(vector<vector<int>>& grid) {
        dir_x = {0, 0, 1, -1};
        dir_y = {1, -1, 0, 0};

        n = grid.size();
        m = grid[0].size();
        //初始化並查集
        for(int i = 0;i < n * m; ++i) pa[i] = i;

        //先計算所屬連通集
        for(int i = 0; i < n; ++i){
            for(int j = 0; j < m; ++j){
                if(!grid[i][j]) continue;
                int pa1 = find_pa(i * m + j);
                if(j > 0 and grid[i][j - 1]){
                    int  pa2 = find_pa(i * m + j - 1);
                    pa[pa2] = pa1;
                }
                if(i > 0 and grid[i - 1][j]){
                    int pa2 = find_pa((i - 1) * m + j);
                    pa[pa2] = pa1;
                }
            }
        }
        int ans = 0;
        while(!check_done(grid)){
            pair<int,int> tp_cnt = get_threathened_part(grid);
            ans += tp_cnt.second;

            //填充爲2
            for(int i = 0;i < n; ++i){
                for(int j = 0;j < m; ++j){
                    if(grid[i][j] == 1 and find_pa(i * m + j) == tp_cnt.first)
                        grid[i][j] = 2;
                }
            }
            spread(grid);
        }

        return ans;
    }
};

786. K-th Smallest Prime Fraction

題意:給定一個數組,第一個是1,其他是從小到大的一些素數。對裏面任意兩個數p<q能組成有理數p/q,問第k大的有理數是哪一個?輸出p和q

題解:二分答案(double二分),對於每一個有理數r,計算小於等於r的有多少個。

class Solution {
public:
    vector<int> kthSmallestPrimeFraction(vector<int>& A, int K) {
        const int n = A.size();
        double l = 0, r = 1.0;
        while(l < r){
            double m = (l + r) / 2;
            double max_f = 0.0;
            int total = 0;
            int p, q = 0;
            int j = 1;
            for(int i = 0; i < n - 1; ++i){
                while(j < n && A[i] > m * A[j]) ++j;
                total += (n - j);
                if(n == j) break;
                const double f = static_cast<double>(A[i]) / A[j];
                if(f > max_f){
                    p = i;
                    q = j;
                    max_f = f;
                }
            }
            if(total == K){
                return {A[p], A[q]};
            }else if(total > K) r = m;
            else l = m;
        }
        return {};
    }
};

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;
    }
};

782. Transform to Chessboard

題意:給定一個n*n的board。只有0和1。每次操作可以交換兩行,交換兩列。問至少多少次交換可以使得這個board變成一個棋盤(0和1不相鄰)。如果不能,返回-1

題解:首先先判斷構成棋盤的必要條件:只有兩種行,且他們互補(01互換),只有兩種列,他們互補。可以用異或來實現這個判斷。 另外計算一下行中1的個數,爲總行數的一半(當行數爲奇數則還可能是加一的一半)。列也一樣。然後問題就變成一維的(行列分別處理)。給定一個01的數組,多少次交換之後它0和1不相鄰。每次交換恢復兩個位置,比較簡單。

class Solution {
    
public:
    int movesToChessboard(vector<vector<int>>& board) {
        int n = board.size();
 //       vector<int> rows(n, 0), cols(n, 0);
        
//         for(int i = 0;i < n; ++i){
//             for(int j = 0;j < n; ++j){
//                 rows[i] = (rows[i] << 1) | board[i][j];   
//                 cols[j] = (cols[j] << 1) | board[i][j];
//             }
//         }
        
//         set<int> r(rows.begin(), rows.end());
//         set<int> c(cols.begin(), cols.end());
        
//         if(c.size() != 2 or r.size() != 2) return -1;
        
//         int row1 = *r.begin();
//         int col1 = *c.begin();
        
        int first_row = board[0][0], first_col = board[0][0];
        
        for (int i = 1; i < n; ++i) {
            first_row = (first_row << 1) | board[0][i];
            first_col = (first_col << 1) | board[i][0];
            
            for (int j = 1; j < n; ++j)
                if (board[i][j] ^ board[i - 1][j] ^ board[i][j - 1] ^ board[i - 1][j - 1]) return -1;
        }
        
        int row_ones = 0, col_ones = 0;
        int row_move_num = 0, col_move_num = 0;
        
        for(int i = 0; i < n; ++i){
            if(first_col & (1 << i)){
                row_move_num += i & 1;
                ++row_ones;
            }
            if(first_row & (1 << i)) {
                col_move_num += i & 1;
                ++col_ones;
            }
        }
        
        
        if((row_ones  != n >> 1 and row_ones != (n + 1) >> 1) or (col_ones != n >> 1 and col_ones != (n + 1) >> 1)) return -1;
        
        if(n & 1){
            if(row_ones == (n >> 1))row_move_num = (n >> 1) - row_move_num;
            if(col_ones == (n >> 1)) col_move_num = (n >> 1) - col_move_num;
            
        }else{
            row_move_num = min(row_move_num, (n >> 1) - row_move_num);
            col_move_num = min(col_move_num, (n >> 1) - col_move_num);
        }
        
        
        return row_move_num + col_move_num;
        
    }
};

793. Preimage Size of Factorial Zeroes Function

給定一個K,問有多少個非負整數的階乘末尾有K個0

題解:二分。n!末尾的0的個數由5因子個數決定。二分有沒有數的5因子數達到K即可。末尾有K個0答案要麼是5要麼是0

class Solution {
    int divisor_num(int n) {
        int res = 0;
        while (n) res += n, n /= 5;
        return res;
    }
    
public:
    int preimageSizeFZF(int K) {
        
        int left = 0, right = K;
        
        
        while(left <= right){
            int mid = (right - left) / 2 + left;
            if(divisor_num(mid) < K){
                left = mid + 1;
                
            }else{
                right = mid - 1;
                
            }
        }
        if(divisor_num(right + 1) != K) return 0;
        return 5;
    }
};

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;
    }
};

810. Chalkboard XOR Game

題意:給定一個整數數組,Alice和Bob玩一個遊戲。每次從數組中選擇一個元素刪除。當輪到某人時,如果當前剩下的所有數的異或等於0,則獲得勝利。Alice先開始,問Alice能不能贏

題解:先判斷所有數的異或是不是0,如果是,Alice不戰而勝。考慮現在有n個數,當輪到某人的時候,爲了不輸,則要選一個數,和當前所有數的異或值不相同。如果數不是全部相同的,則可以選到。如果全部數都相同,則當數的個數爲奇數的時候,當前遊戲玩家輸,偶數則贏。所以由Alice開始輪流刪數,一定能達到一個狀態,所有數都相同。當數組個數爲偶數時,輪到Bob總是奇數個數,所以無論數相不相同,它都不可能贏,即Alice必勝。當數爲奇數時,則反過來。

class Solution {
public:
    bool xorGame(vector<int>& nums) {
        if(!(nums.size() & 1)) return true;
        for(int i = 1;i < nums.size(); ++i)
            nums[0] ^= nums[i];
        return !nums[0];
    }
};

815. Bus Routes

題意:給定一些公交路線,每個公交路線是一個數組,表示公交途經的節點。給定一個S和一個T,表示出發地和目的地,問最少搭乘多少個公交能從S到達T。

題解:題解:建圖,如果兩個公交路線有相同的站點,那麼它們有一條邊(你可以換乘公交)。問題就是從所有包含S的點出發,到達所有包含T的節點的最短路。可以用BFS解決。

class Solution {
public:
    int numBusesToDestination(vector<vector<int>>& routes, int S, int T) {
        if(S == T) return 0;
        int n = routes.size();
        bool connected[n][n], target[n], vis[n];
        memset(vis, 0, sizeof(vis));
        
        queue<pair<int,int>> Q;
        
        for(int i = 0;i < n; ++i){
            sort(routes[i].begin(), routes[i].end());
            //這裏也可以用interset來計算
            vector<int>::iterator t_iter = lower_bound(routes[i].begin(), routes[i].end(), T),
                                  s_iter = lower_bound(routes[i].begin(), routes[i].end(), S);
            target[i] = (t_iter != routes[i].end() && *t_iter == T);
            
            if(s_iter != routes[i].end() && *s_iter == S) {
                Q.push({i, 1});
                vis[i] = true;
            }
            
            for(int j = i + 1;j < n; ++j){
                int posi = 0, posj = 0;
                connected[i][j] = connected[j][i] = false;
                while(posi < routes[i].size() && posj < routes[j].size()){
                    if(routes[i][posi] < routes[j][posj]) ++posi;
                    else if(routes[i][posi] > routes[j][posj]) ++posj;
                    else{
                        connected[i][j] = connected[j][i] = true;
                        break;
                    }
                }
            }    
        }
        
        //cout<<Q.size()<<endl;
        while(!Q.empty()){
            pair<int, int> u = Q.front(); Q.pop();
            if(target[u.first]) return u.second;
            
            for(int i = 0; i < n; ++i){
                if(vis[i] || !connected[u.first][i]) continue;
                Q.push({i, u.second + 1});
                vis[i] = true;
            }
        }
        
        return -1;
        
    }
};

818. Race Car

題意:從位置0開始,速度爲1。每次可以進行一種操作,A操作,pos+=speed,speed *= 2。R操作,位置保持不變,如果speed爲正,則speed變成-1,如果speed爲負,則變成1。給定一個target(正數)問至少多少次操作可以到達

題解:一開始我想的反過來看成是從target走到0的最小步數。 是用dp[i][v]表示從0到i速度爲v時到達0的最小步數。答案就是min_k dp[target][1](速度都是2的冪次,可以用狀態壓縮表示)。然而狀態轉移不能直接按照兩種操作進行。因爲下個狀態不是最優時,它在求解最優解可能會轉移到當前狀態。也就是不滿足無後效性,需要增加一些約束,比較麻煩。

標準的解法是BFS做的。當pos<0或者pos >= 2 *target時爲搜索邊界(我直覺上也是這麼寫的,但是不知道爲什麼,以下dp方法的證明就會導出這個邊界的由來)


動態規劃的解法。設dp[i]表示從0到i的最小步數。

如果target = 2^t - 1則,t步(t個A)即可。

如果不是,設 2^{n - 1} - 1< t < 2^n - 1

要構造狀態轉移,注意到每次R的時候回重置速度。所以考慮第一次和第二次到R可以轉移到子問題的狀態,如下

第一次移動方法分爲以下兩種

  • 先向前移動n個A超過target然後回頭(問題變成子問題,從2^n - 1走到target即相當於從0走到2^n - 1- target), 也就是 racecar(2^n - target  -1)  + n + 1
  • 另外一種是,走到2^{n - 1} - 1然後回頭走2^m - 1步。變成子問題racecar(target - 2^{n - 1} + 2^m) 再加上n+m+1(n - 1步+1個R+m步再加一個R)

上面狀態會轉移到一個比較小的情況,所以是沒問題的。問題就是爲什麼是這麼轉移的,

難道不會往大的狀態轉移嗎? 下面證明這樣是最優的。

整個操作過程可以描述如下,A_1 R A_2 R A_3 .... 。其中A_i表示一些連續的A。其中奇數位置的A之間是可交換的,偶數之間的和也是可以交換的

     由加法的交換性就可以得到了。

現在我們考慮最優的情況。

我們可以假設A_1是奇數位置中最多A的一個,而A_2是偶數位最少A的一個。假設分別爲i個和k個A(可以爲0個)。則它們的和表示爲2^i - 2^j > 0 (表示成2進制就是i - j個前導1,再並上j個0,例如11100)。如果不大於0的話,則所有正項加起來小於負項,肯定和爲負,不可能到達正數target

2^i  < target ,就是上面第二種移動方案,後面的移動一定構成了子問題的最優移動方案,不然的話替換成最優方案比當前方更優產生了矛盾案。

上面證明了第二種情況是最優的

現在證明第一種移動也是最優的。

由上面可以知道,整個過程可以表示成和target = \sum (2^{i_t} - 2^{k_t}) , i_t > k_t。所以

2^{t_t} < 2 target < 2^{n+1} - 2

故連續的A不能超過n個。證畢

記憶化搜索代碼


class Solution {
    
    int dfs(int target){
        if (dp[target] > 0) {                 
            return dp[target];
        }
        int n = floor(log2(target)) + 1, res;
        if (1 << n == target + 1) {   
            dp[target] = n;
        }
        else {           
            dp[target] = dfs((1 << n) - 1 - target) + n + 1;
            
            for (int m = 0; m < n - 1; ++m) {
                dp[target] = min(dp[target], dfs(target - (1 << (n - 1)) + (1 << m)) + n + m + 1);

            }
        }
        return dp[target];
    }
public:
    int racecar(int target) {
        return dfs(target);
    }
private:
    int dp[10001];
};

827. Making A Large Island

題意:給定一個n*n的網格,1代表陸地,0代表海。每塊陸地和上下左右能夠相連(如果也是陸地)。相連的陸地構成一個島。現在可以選擇一塊海的位置,填充爲陸地。問填完後最大陸地可以爲多大?

題解:並查集。每塊陸地就是一個連通塊,我們可以用並查集來記錄每個塊屬於哪個連通塊,以及該連通塊的面積。然後枚舉填充位置,就是求相鄰的四個塊的連通塊的面積最大。

class Solution {
    int pa[2500], area[2500];
    int find_pa(int x){
        return pa[x] == x? x : pa[x] = find_pa(pa[x]);
    }
    
public:
    int largestIsland(vector<vector<int>>& grid) {
        
        int n = grid.size();
        int largest_area = 1;
        
        int dir_x[] = {1, 0, -1, 0};
        int dir_y[] = {0, 1, 0, -1};
        
        for(int i = 0;i < n * n; ++i) pa[i] = i, area[i] = 1;
        
        for(int i = 0;i < n; ++i){
            for(int j = 0;j < n; ++j){
                if(!grid[i][j]) continue;
                
                int pa1 = find_pa(i * n + j);
                for(int d = 0; d < 4; ++d){
                    int x = i + dir_x[d];
                    int y = j + dir_y[d];
                    if(x < 0 or y < 0 or x >= n or y >= n or !grid[x][y]) continue;
                    int pa2 = find_pa(x * n + y);
                    if(pa1 == pa2) continue;
                    pa[pa2] = pa1;
                    area[pa1] += area[pa2];
                }
                
                largest_area = max(largest_area, area[pa1]);
            }
            
        }

        vector<int> pars;
        for(int i = 0;i < n; ++i){
            for(int j = 0;j < n; ++j){
                if(grid[i][j]) continue;
                
                pars.clear();
                for(int d = 0; d < 4; ++d){
                    int x = i + dir_x[d];
                    int y = j + dir_y[d];
                    if(x < 0 or y < 0 or x >= n or y >= n or !grid[x][y]) continue;
                    pars.push_back(find_pa(x * n + y));
                }
                if(pars.size() <= 0) continue;
                sort(pars.begin(), pars.end());
                
                int cur_area = area[pars[0]] + 1;
                for(int k = 1; k < pars.size(); ++k){
                    if(pars[k] != pars[k - 1]) cur_area += area[pars[k]];
                }
                largest_area = max(largest_area, cur_area);
                
            }
        }
        
        return largest_area;
    }
};

828. Unique Letter String

題意:UNIQ(astring) = string中出現一次的字符個數。求一個串的所有子串的UNIQ值的和

題解:可以用動態規劃,但是沒必要,因爲是子串而不是子序列。我們可以求每個位置的字符在多少個字符串中它只出現一次,可以通過求和它字符相同的上一個位置和下一個位置,起始點在和終止點分別在這兩段中就可以把當前字符包含且只出現一次。

class Solution {
public:
    int uniqueLetterString(string S) {
        int prev[26][2];
        memset(prev, -1, sizeof(prev));
        
        int ans = 0;
        
        for(int i = 0; i < S.length(); ++i){
            char id = S[i] - 'A';
            ans += (prev[id][1] - prev[id][0]) * (i - prev[id][1]);
            prev[id][0] = prev[id][1];
            prev[id][1] = i;
        }
        
        for(int i = 0; i < 26; ++i)
            ans += (prev[i][1] - prev[i][0]) * (int(S.length()) - prev[i][1]);
        
        return ans;
    }
};                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      

829. Consecutive Numbers Sum

題意:給定一個整數N,問能有多少種方法把N分解成連續正整數的和。比如15=8+7=4+5+6=1+2+3+4+5共4種

題解:分析一下將N分成奇數個數的和以及分成偶數個數的和分別能分成多少種。

首先中位數必須比數目的一半大,所以N / i > i / 2

首先是分成奇數個。假設分成i個,則i*中位數=N所以 N % i == 0。這也是充分條件

然後就是偶數個。假設分成i個,則N = (2k+1) * i/2(k是中位數下取整), 也就是 N % i == i  / 2

class Solution {
public:
    int consecutiveNumbersSum(int N) {
        int count = 1;
        //將n分成i個數之和
        for (int i = 2; 2 * N  >  i * i; ++i) {
            //分成奇數個的和 和偶數個的和
            count += ((i & 1) == 0 && N % i == (i >> 1)) || ((i & 1) && N % i == 0);
        }
        
        return count;
    }
};

839. Similar String Groups

題意:對一個字符串數組進行分組。首先這個字符串數組的每個字符串長度相同,字母都相同,但是次序不同。如果兩個字符串能夠通過將其中一個交換兩個位置得到另一個,則稱它們爲相似字符串。將數組分成若干組,每組中,兩兩個字符串未必相似,但是一定有一條路徑從一個字符串到達另一個(相似的字符串之間有一條邊)

題解:其實就是求無向圖連通分量個數。用並查集可解。不過由於字符串可能長也可能短。我這裏採用兩種策略。如果字符串比較長,字符串個數少,那麼就用兩兩比較。如果字符串比較短,字符串個數多,那麼就採用map來記錄下標。然後通過交換字符來查詢。我試過用trie來代替map,實際會佔用很多倍的空間。

class Trie{
    struct Node{
        int val;
        Node *next[26];
        Node(){
            memset(next, NULL, sizeof(next));
        }
    };
    
    //禁止賦值和禁止複製構造,以免引起double free
    Trie(Trie &trie);
    const Trie& operator = (Trie &trie);
    int tot;
    // share_prt<Node> root;
    Node *root;
    //淺拷貝trie tree導致double free
    vector<Node*> nodes;
    
public:
    Trie(): root(0){
        nodes.push_back(root = new Node());
    }
    ~Trie(){
        for(int i = 0;i < nodes.size(); ++i){
            delete nodes[i];
            nodes[i] = NULL;
        }
    }
    void insert(const string &str, int idx){
        Node *p = root;
        //p->val = idx;
        for(int i = 0;i < str.length(); ++i){
            int id = str[i] - 'a';
            if(p->next[id] == NULL){
                nodes.push_back(p->next[id] = new Node());
            }
            p = p->next[id];
        }
        p->val = idx;
    }
    
    int find(string &str){
        Node *p = root;
        for(int i = 0;i < str.length(); ++i){
            int id = str[i] - 'a';
            if(p->next[id] == NULL) return -1;
            p = p->next[id];
        }
        return p->val;
    }
};
class Solution {
    int find_pa(int x, vector<int> &pa){
        return x == pa[x] ? x : pa[x] = find_pa(pa[x], pa);
    }
    
    bool is_similar(const string &a,const string &b){
        int diff = 0;
        for(int i = 0;i < a.length(); ++i){
            diff += a[i] != b[i];
        }
        return diff <= 2;
    }
public:
    int numSimilarGroups(vector<string>& A) {
        
        int n = A.size();
        int word_size = A[0].length();
        vector<int> pa(n);
        for(int i = 0;i < n; ++i) pa[i] = i;
        
        
        if(n > 500){ //詞比較多,每個詞比較短 
            unordered_map<string, int> string2idx;
            //Trie trie_tree;
            for(int i = 0;i < n; ++i) {
                string2idx[A[i]] = i;
                //trie_tree.insert(A[i], i);
            }

            for(int i = 0;i < n; ++i){
                string &t = A[i];
                int pa1 = find_pa(i, pa);
                for(int pos1 = 0; pos1 < word_size; ++pos1){
                    for(int pos2 = pos1 + 1; pos2 < word_size; ++pos2){
                        swap(t[pos1], t[pos2]);
                        //int id = trie_tree.find(t);
                        
                        if(string2idx.count(t)){
                            int pa2 = find_pa(string2idx[t], pa);
                            pa[pa2] = pa1;
                        }
                        swap(t[pos1], t[pos2]);
                    }
                }
            }    
            
        }else{ //詞比較少,每個詞比較長
            
            for(int i = 0;i < n; ++i){
                for(int j = i + 1;j < n; ++j){
                    int pa1 = find_pa(i, pa);
                    int pa2 = find_pa(j, pa);
                    if(pa1 != pa2 && is_similar(A[i], A[j])){
                        pa[pa2] = pa1;
                    } 
                }
            }
        }
        
        int ans = 0;
        for(int i = 0;i < n; ++i) ans += (pa[i] == i);
        return ans;
    }
};class Trie{
    struct Node{
        int val;
        Node *next[26];
        Node(){
            memset(next, NULL, sizeof(next));
        }
    };
    
    //禁止賦值和禁止複製構造,以免引起double free
    Trie(Trie &trie);
    const Trie& operator = (Trie &trie);
    int tot;
    // share_prt<Node> root;
    Node *root;
    //淺拷貝trie tree導致double free
    vector<Node*> nodes;
    
public:
    Trie(): root(0){
        nodes.push_back(root = new Node());
    }
    ~Trie(){
        for(int i = 0;i < nodes.size(); ++i){
            delete nodes[i];
            nodes[i] = NULL;
        }
    }
    void insert(const string &str, int idx){
        Node *p = root;
        //p->val = idx;
        for(int i = 0;i < str.length(); ++i){
            int id = str[i] - 'a';
            if(p->next[id] == NULL){
                nodes.push_back(p->next[id] = new Node());
            }
            p = p->next[id];
        }
        p->val = idx;
    }
    
    int find(string &str){
        Node *p = root;
        for(int i = 0;i < str.length(); ++i){
            int id = str[i] - 'a';
            if(p->next[id] == NULL) return -1;
            p = p->next[id];
        }
        return p->val;
    }
};
class Solution {
    int find_pa(int x, vector<int> &pa){
        return x == pa[x] ? x : pa[x] = find_pa(pa[x], pa);
    }
    
    bool is_similar(const string &a,const string &b){
        int diff = 0;
        for(int i = 0;i < a.length(); ++i){
            diff += a[i] != b[i];
        }
        return diff <= 2;
    }
public:
    int numSimilarGroups(vector<string>& A) {
        
        int n = A.size();
        int word_size = A[0].length();
        vector<int> pa(n);
        for(int i = 0;i < n; ++i) pa[i] = i;
        
        
        if(n > 500){ //詞比較多,每個詞比較短 
            unordered_map<string, int> string2idx;
            //Trie trie_tree;
            for(int i = 0;i < n; ++i) {
                string2idx[A[i]] = i;
                //trie_tree.insert(A[i], i);
            }

            for(int i = 0;i < n; ++i){
                string &t = A[i];
                int pa1 = find_pa(i, pa);
                for(int pos1 = 0; pos1 < word_size; ++pos1){
                    for(int pos2 = pos1 + 1; pos2 < word_size; ++pos2){
                        swap(t[pos1], t[pos2]);
                        //int id = trie_tree.find(t);
                        
                        if(string2idx.count(t)){
                            int pa2 = find_pa(string2idx[t], pa);
                            pa[pa2] = pa1;
                        }
                        swap(t[pos1], t[pos2]);
                    }
                }
            }    
            
        }else{ //詞比較少,每個詞比較長
            
            for(int i = 0;i < n; ++i){
                for(int j = i + 1;j < n; ++j){
                    int pa1 = find_pa(i, pa);
                    int pa2 = find_pa(j, pa);
                    if(pa1 != pa2 && is_similar(A[i], A[j])){
                        pa[pa2] = pa1;
                    } 
                }
            }
        }
        
        int ans = 0;
        for(int i = 0;i < n; ++i) ans += (pa[i] == i);
        return ans;
    }
};

834. Sum of Distances in Tree

題意:給定一棵樹,計算每個節點到其他節點的距離的和

題解:樹DP。兩遍dfs可以得到結果。先計算每個節點到後代的距離之和和後代節點個數。然後再計算到非後代的那部分的距離之和。第二部分從頂向下計算。因爲所有節點都是根的後代,所以根的值已經計算好了。而對於其他點,如果父親的已經計算好了。那麼當前節點和非後代節點的距離和就是父親的距離和減去以它爲後代的距離和,加上非後代節點數(加上父親和它的邊,長度爲1)。

class Solution {
    
    vector<int> edges[10000];
    int sum[10000], num[10000];
    
    void child_num_sum(int x, int fa){
        sum[x] = 0;
        num[x] = 1;
        
        for(int i = 0; i < edges[x].size(); ++i){
            int v = edges[x][i];
            if(v == fa) continue;
        
            child_num_sum(v, x);
            
            sum[x] += sum[v] + num[v];
            num[x] += num[v];
        }
        
    }
    
    
    void get_sum(int x, int fa, int cur_sum, int cur_num){
        
        sum[x] += cur_sum;
        cur_num += num[x];
        
        for(int i = 0; i < edges[x].size(); ++i){
            int v = edges[x][i];
            if(v == fa) continue;
            
            get_sum(v, x, sum[x] - sum[v] + cur_num - 2 * num[v] , cur_num - num[v]);
        }
    }
    
    
public:
    vector<int> sumOfDistancesInTree(int N, vector<vector<int>>& e) {

        for(int i = 0;i < e.size(); ++i){
            int u = e[i][0], v = e[i][1];
            
            edges[u].push_back(v);
            edges[v].push_back(u);
        }
        
        child_num_sum(0, -1);
        
        get_sum(0, -1, 0, 0);
        vector<int> ans(sum, sum + N);
        
        return ans;
        
    }
};

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;
    }
};

854. K-Similar Strings

題意:兩個字符串稱爲k相似,不過每次對A串中的兩個字符進行交換位置,交換k次後可以得到B。現在給定兩個字符串A,B,他們是k相似的,求最小的k。

題解:一開始我以爲可以貪心。因爲這個題目可以用圖進行建模。一系列的交換形成一個個的環,交換次數等於邊數減去環數,也就是求最多有多少個環(把邊分成一些不相交的環)。然而沒法解決。另一個辦法是採用深度搜索,狀態記憶dp。每次找一個可交換的兩個位置進行交換,轉移到另一種轉態,求最小的。

class Solution {
public:
    
    unordered_map<string, int> dp;
    int dfds(int pos, string &A, string &B){
        if (A == B)
            return 0;
    
        if (dp.count(A))
            return dp[A];
        
        while (pos < A.size() && A[pos] == B[pos])
            ++pos;
        
        int ans(numeric_limits<int>::max());
        for (int next = pos + 1; next < A.size(); ++next){
            if (A[next] == B[next] || A[next] != B[pos]) continue;
            
            swap(A[next], A[pos]);
            ans = min(ans, 1 + go(pos + 1, A, B));
            swap(A[next], A[pos]);
        }
        return dp[A] = ans;
    }
    
    int kSimilarity(string A, string B){
        return dfs(0, A, B);
    }
};

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);
        
    }
};

882. Reachable Nodes In Subdivided Graph

題意:給定一個無向圖N個大節點,每個邊以[u,v,n]給出,u,v表示兩個節點,n表示這個邊上有n個小節點。給定一個M,問從0出發,M步能到達的節點有多少個?

題解:和最短路類似。把每個邊上的小節點數當成權重。實際上就是計算到達0長度爲M的節點有多少個。我們用優先隊列計算0到其他節點的最短路(dijkstra算法),每次由邊向外貪心擴展。如果加進來新的一個點,那麼邊上的點都會加進來。如果遇到一個已經遍歷過的點,那麼它的邊肯定已經遍歷過。

有兩種種情況發生:邊上的點都走過了,這時候邊上點數n - (M + d[v]) <= 0。還有剩餘n - (M + d[v]) > 0 ,那麼我們看看從當前u出發還能走多少個點。

class Solution {
    //bool vis[3000];
    int depth[3000];
    
    vector<pair<int,int>> edge_nodes[3000];
    
public:
    int reachableNodes(vector<vector<int>>& edges, int M, int N) {
    
        for(int i = 0;i < edges.size(); ++i){
            
            edge_nodes[edges[i][1]].push_back(make_pair(edges[i][0], edges[i][2]));
            edge_nodes[edges[i][0]].push_back(make_pair(edges[i][1], edges[i][2]));
            
        }
        
        //第一維是depth,第二是節點
        priority_queue<pair<int,int>, vector<pair<int,int>>, greater<pair<int,int>>> Q;
        memset(depth, -1, sizeof(depth));
        Q.push({0, 0});
        
        int ans = 0;
        
        while(!Q.empty()){
            pair<int,int> u_d = Q.top();  Q.pop();
            int u = u_d.second;
            int du = u_d.first;
           
            if(depth[u] != -1) continue;
            depth[u] = du;
            ++ans;
            
            for(int i = 0;i < edge_nodes[u].size(); ++i){
                pair<int,int> v_n = edge_nodes[u][i];
                int v = v_n.first;
                int n = v_n.second;
                
                if(depth[v] == -1){ //v還沒被訪問過
                    //dv <= M
                    if(n + du + 1 <= M){ 
                        Q.push({n + du + 1, v});
                    }
                }else { //v訪問過了
                    n = max(0, n - M + depth[v]);
                }
                
                ans += min(n, M - depth[u]);
                
            }
        }
        
        return 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個字符)。則答案就是

                                                                              \begin{center} \sum_{k} dp[n][k] \end{center}

後數字爲j,前面是0到i中除j的剩下i個數。我們要求他們的合法排列數。注意到對於大於j的數,我們統統減去一得到0到i - 1的排列,它和0到i除去j的排列是一一對應的,合法性也是一致的。所以可以由0到i-1的合法排列得到0到i的合法排列。所以只要枚舉j的前一個字符,構造轉移方程即可,得到

如果S[i] == 'D'

                                                              dp[i][j] =\sum_{k<i} dp[i - 1][k]

如果S[i] == 'I'

                                                             dp[i][j]=\sum_{k<j} dp[i - 1][k]

 

每次求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];
    }
};

924. Minimize Malware Spread

題意:給定一個圖graph,以及些initial節點,表示這些節點被病毒感染。被感染的病毒可以沿着邊傳播。現在可以清除掉一個initial中節點的病毒,問清除哪個可以使得最後被感染的節點最少(清除後仍可被感染)。

題解:就是求連通分量的問題。如果一個連通分量中有兩個initial節點,則無論如何都會全部被感染。當只有一個initial節點時,清除掉它就不會被感染。可以採用dfs或者並查集來做

 

class Solution {
    int part[300];
    vector<vector<int>> g;

    void dfs(int x, int p){
        part[x] = part[x] == -1 ? p : g.size();
        
        for(int i = 0;i < g[x].size(); ++i){
            if(x == i or g[x][i] != 1 or part[i] == p or part[i] == g.size()) continue;
            dfs(i, p);
        }
    }

public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        g.swap(graph);
        memset(part, -1, sizeof(part));
        
        sort(initial.begin(), initial.end());
        for(int i = 0;i < initial.size(); ++i)
            dfs(initial[i], i);

        vector<int> cnt(initial.size(), 0);
        for(int i = 0;i < g.size(); ++i){
            if(part[i] != -1 and part[i] != g.size())
                ++cnt[part[i]];
        }
        return initial[max_element(cnt.begin(), cnt.end()) - cnt.begin()];


    }
};

927. Three Equal Parts

題意,給定一個0,1數組。要求把它分成3個部分使得三個部分所表示的二進制數相等。允許前導零

題解:首先,如果三部分都相等,那麼三部分的1的個數是相等的,所以首先總的1個數爲3的倍數,如果不爲0則可以計算出每部分起始的1的位置和結束的1的位置,這三部分應該相等。然後根據末尾0的數目就知道每個二進制結尾應該有多少個0。然後看看其他兩個能夠滿足。

class Solution {
    int get_first_one_pos(const vector<int> &A, int begin){
        while(!A[begin]) ++begin;
        return begin;
    }
    
    int get_last_one_pos(const vector<int> &A, int begin, const int one_num){
        int one_count = 0;
        while(one_count < one_num) one_count += A[begin++];
        return begin - 1;
    }
public:
    vector<int> threeEqualParts(vector<int>& A) {
        int one_num = 0;
        for(int i = 0;i < A.size(); ++i) one_num += A[i];
        if(one_num == 0) return {0, 2};
        if(one_num % 3) return {-1, -1};
        one_num /= 3;
        
        
        int begin1 = get_first_one_pos(A, 0), end1 = get_last_one_pos(A, begin1, one_num);
        int begin2 = get_first_one_pos(A, end1 + 1), end2 = get_last_one_pos(A, begin2, one_num);
        int begin3 = get_first_one_pos(A, end2 + 1), end3 = get_last_one_pos(A, begin3, one_num);
        
        int tail_zero_num = A.size() - 1 - end3;
        if(begin2 - end1 - 1 < tail_zero_num or 
           begin3 - end2 - 1 < tail_zero_num or 
           begin1 - end1 != begin2 - end2 or 
           begin1 - end1 != begin3 - end3) return {-1, -1};
        
        for(int i = 0;i <= end1 - begin1; ++i){
            if(A[i + begin1] != A[i + begin2] or A[i + begin1] != A[i + begin3]) return {-1, -1};
            
        }
        
        return {end1 + tail_zero_num , end2 + 1 + tail_zero_num};    
    }
};

928. Minimize Malware Spread II

題意:給定一個矩陣表示一個圖的鄰接矩陣。給定一個initial數組,表示有哪些點有malware,它會沿着邊一直傳播,感染其他節點。問去掉哪個節點後,受感染的節點最少。 如果有多個點結果一樣,輸出下標小的節點

題解:從initial節點開始遍歷圖,且不經過其他initial點。如果圖中一個點被兩個initial節點相連,則它一定會被感染,無論去掉哪個節點。而那些去掉某個節點後能變成不感染的只和一個initial點相連。我們再遍歷圖的時候,記錄點和哪個initial點相連。最後計算出第一個且連接點最多的點即可。

class Solution {
    int color[300];
    bool in_init[300];
    int num[301];
    
    vector<vector<int>> graph;
    
    void dfs(int node, int c, int n){
        if(color[node] == c || color[node] == n) return;
        
        if(color[node] == -1)
            color[node] = c;
        else
            color[node] = n;
        
        
        for(int i = 0;i < graph.size(); ++i){
            if(!graph[node][i] || in_init[i]) continue;  
            dfs(i, c, n);
        }
    }
    
    int get_ans(vector<int>& init){
        memset(num, 0, sizeof(num));
        
        for(int i = 0;i < graph.size(); ++i){
            if(color[i] != -1) 
                ++num[color[i]];
        }
        
        int ans = 0, max_num = 0;
        for(int i = 0;i < graph.size(); ++i){
            if(num[i] > max_num){
                max_num = num[i];
                ans = i;
            }
        }
        return ans;
    }
    
    
public:
    int minMalwareSpread(vector<vector<int>>& graph, vector<int>& initial) {
        
        this->graph.swap(graph);
        
        memset(color, -1, sizeof(color));
        memset(in_init, 0, sizeof(in_init));
        
        for(int i = 0;i < initial.size(); ++i){
            in_init[initial[i]] = true; 
        }
        
        for(int i = 0;i < initial.size(); ++i){
            dfs(initial[i], initial[i], this->graph.size());
        }
        
        return get_ans(initial);
    }
};

936. Stamping The Sequence

題意:給定一個stamp字符串和一個target字符串,問能不能從一個長度爲len(target)的'?'字符串變成target。每次操作就是選擇一個長度爲stamp的子串,將其改變爲stamp(後面操作的覆蓋前面)。

題解:逆向貪心。每次選取能匹配的部分把它恢復成?(?能匹配任意字符)。

class Solution {
    
    bool check_done(string &target){
        for(int  i = 0;i < target.length(); ++i) if(target[i] != '.') return false;
        return true;
    }
    
public:
    vector<int> movesToStamp(string stamp, string target) {
        int stamp_size = stamp.size(), target_size = target.size();
        vector<int> ans;
        bool has_match = true;
        int prev = 0;

        while(has_match){
            //下次從prev開始,因爲前面的是重複的部分
            for(int i = prev; i <= target_size - stamp_size; ++i){
                has_match = true;
                bool all_dot = true;
                for(int j = 0; j < stamp_size; ++j){
                    if(target[i + j] == '.' ) continue;
                    all_dot = false;
                    if(target[i + j] == stamp[j]) continue;
                    has_match = false;
                    break;
                }
                
                if(has_match = has_match and not all_dot){
                    ans.push_back(i);
                    prev = -1;
                    for(int j = 0;j < stamp_size; ++j) {
                        if(prev == -1 and target[i + j] != '.') prev = i + j - stamp_size + 1;
                        target[i + j] = '.';
                    }
                    
                    prev = max(0, prev);
                    break;
                }
            }
        }
        if(!check_done(target)) return {};
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

其他人寫的代碼,比我的短。效率差不多

class Solution {
public:
    vector<int> movesToStamp(string stamp, string target) {
	int NS = stamp.size(), NT = target.size();
        vector<int> ans;
        bool has_match;
        do {
            has_match = false;
            for(int i=0;i<=NT-NS;++i) {
                bool ok = true;
                int num_dot = 0;
                for(int j=0;j<NS;++j) { 
                    if(target[i+j]=='.') ++num_dot; // take care we don't match only matched ones
                    if(target[i+j]!='.' && stamp[j]!=target[i+j]) { // simple wildcard matching 
                        ok=false;
                        break;
                    }
                }
                if(ok && num_dot<NS) {
                    has_match = true;
                    ans.push_back(i);
                    for(int j=0;j<NS;j++) target[i+j]='.';
                }
            }

        } while(has_match);
        for(char a:target)if(a!='.')return {};
        reverse(ans.begin(),ans.end());
        return ans;
    }
};

940. Distinct Subsequences II

題意:給定一個字符串。返回其中不同子序列的個數

設dp[i]表示前i個構成的子串中不同子序列的個數。

分以下兩種情況

前面字符沒有合s[i]相同的: dp[i] = 2 * dp[i - 1] + 1 (前面的字符串加或者不加s[i]以及s[i]單獨構成一個)

前面字符在前面出現過: dp[i] = 2 * dp[i - 1] - dp[prev - 1] (減去重複的dp[prev - 1] + 1,前面的0到prev以s[i] == s[prev]結尾的)

然後發現並不用存i - 1和以前的信息,我們只要保存以前出現過的字符的dp值即可。簡化後得到代碼

class Solution {
    
public:
    int distinctSubseqII(string S) {
        long long MOD = 1e9 + 7;
        
        int prev_dp[26];
        int dp = 1;
        memset(prev_dp, -1, sizeof(prev_dp));
        int n = S.length();
        
        prev_dp[S[0] - 'a'] = 0;
        int prev;
        
        for(int i = 1;i < n; ++i){
            prev = dp;
            dp = ((dp << 1) - prev_dp[S[i] - 'a'] + MOD) % MOD;
            prev_dp[S[i] - 'a'] = prev; 
        }
        
        return dp;
    }
};

943. Find the Shortest Superstring

題意:給定一個字符串數組,求一個最短的字符串,它的子串包含這些字符串。 假設沒有一個字符串是另一個字符串的子串

題解:狀態壓縮動態規劃。最小的字符串就是所有全排列中去掉相鄰串重複部分後得到字符串最短那個。因爲每次加入一個字符串的時候增加的長度之和前面的字符串相關,所以我們用dp[state][i]表示,已經加入的字符串爲state,最後一個爲i的時候的最短長度。 則dp[state][i] = min_{k在state中} dp[state 去掉i][k] 。然後記錄轉移過程用於回溯得到字符串即可

 

class Solution {
    int get_remain(const string &a, const string &b){

        int a_len = a.length();
        int b_len = b.length();
        for(int i = max(1, a_len - b_len); i < a_len; ++i){
            if(a.substr(i) == b.substr(0, a_len - i)) 
                return b_len - a_len + i;
        }
        return b_len;
    }
    
    
public:
    string shortestSuperstring(vector<string>& A) {
        // int overlap[12][12];
        int remain[12][12];
        int n = A.size();
        
        for(int i = 0;i < n; ++i){
            for(int j = 0;j < n; ++j){
                if(i == j) continue;
                //overlap[i][j] = get_overlap(A[i], A[j]);
                remain[i][j] = get_remain(A[i], A[j]);
            }
        }
        
        int dp[1<<12][12];
        int prev[1<<12][12];
        memset(dp, 0x3f, sizeof(dp));
        
        for(int i = 0;i < (1 << n); ++i){
            for(int j = 0; j < n; ++j){
                if((i & (1 << j)) == 0) continue;   
                if(i == (1 << j)){
                    dp[i][j] = A[j].length();
                }else{
                    int prev_state = i ^ (1 << j);
                    for(int k = 0; k < n; ++k){
                        if((prev_state & (1 << k)) == 0) continue;
                        if(dp[prev_state][k] + remain[k][j] < dp[i][j]){
                            dp[i][j] = dp[prev_state][k] + remain[k][j];
                            prev[i][j] = k;
                        }
                    }
                }
                
            }
        }
        
        //回溯得到字符串
        int len = numeric_limits<int>::max();
        int idx = -1;
        vector<int> order;
        int state = (1 << n) - 1;
        for(int i = 0;i < n; ++i){
            if(dp[state][i] < len){
                len = dp[(1<<n) - 1][i];
                idx = i;
            }
        }
        
        while(state){
            int next = prev[state][idx];
            order.push_back(idx);
            state ^= (1 << idx);
            idx = next;
        }
            
        //reverse(order.begin(), order.end());
        string ans = A[order[n - 1]];
        for(int i = n - 2;i >= 0; --i){
            ans += A[order[i]].substr(A[order[i]].length() - remain[order[i + 1]][order[i]]);
        }
        
        return ans;
        
    }
};

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;
        
    }
};

956. Tallest Billboard

題意:給定一個整數數組。要求從中選擇兩組不相交(下標不相交)的數,讓他們相等,最大能達到多少?

題解:動態規劃。dp[i][j]表示前i個數兩組數差爲j時,和小的那組的和最大是多少。轉移可以由以下幾種得到:不用當前的數,則保持原樣。用當前的數,加到和較小的組。用當前的數加到和較大的組。我採用遞推方式,以及滾動數組,代碼如下

class Solution {
    int dp[2][2501];
public:
    int tallestBillboard(vector<int>& rods) {
        memset(dp, -1, sizeof(dp));
        dp[0][0] = 0;
        
        
        int cur = 0;
        int sum = 0;
        int prev = 0;
        for(int i = 0;i < rods.size(); ++i) sum += rods[i];
        sum /= 2;
        
        
        
        for(int i = 0; i < rods.size(); ++i){
            prev = cur;
            cur ^= 1;
            memcpy(dp[cur], dp[prev],  (sum + 1) * sizeof(int));
            
            for(int j = 0;j <= sum; ++j){
                if(dp[prev][j] == -1) continue;
                if(j + rods[i] <= sum && dp[prev][j] > dp[cur][j + rods[i]]) 
                    dp[cur][j + rods[i]] = dp[prev][j];
            
                if(dp[prev][j] + rods[i] <= sum){
                    if(j >= rods[i])
                        dp[cur][j - rods[i]] = max(dp[cur][j - rods[i]], dp[prev][j] + rods[i]);
                    else
                        dp[cur][rods[i] - j] = max(dp[cur][rods[i] - j], dp[prev][j] + j);
                }
            }
        }
        
        return dp[cur][0];
    }
};

964. Least Operators to Express Number

題意:給定一個target和一個x,用只含x和運算符+-*/的表達式得到x,最少需要多少個運算符

題解:參考https://blog.csdn.net/lemonmillie/article/details/86628980

鏈接中採用高位到低位的動態規劃,這裏採用低位到高位的動態規劃

將target表示成x進制

                                       target = \sum_{i} a_i x^i

一個x^i需要的運算符的個數爲m=(i == 0): 1: i - 1

如果我們把兩個項間的加號算到右邊去,則需要的運算符爲m=(i == 0): 2: i。比如x^3=+x*x*x需要3個運算符,最後算完減掉1(最左邊的+號)。

要組合成a_i x^i的項,有兩種辦法:

  • 直接組合,不借位,需要的運算符爲a_i \times m
  • 向高位借位,則需要(x -a_i)\times m個運算符

用dp[i]來表示低i項(\sum_{j\le i} a_j x^j)需要的運算符個數

令dp[i][0]表示不向高位借位時需要的運算符個數,dp[i][1]表示向高位借位時需要的運算符個數。

每種情況的最優值就是低位兩種情況下加上當前需要的運算符的最優值

class Solution {
public:
    int leastOpsExpressTarget(int x, int target) {
        int dp[2] = {0};
        int i = 0;
        
        dp[1] = 0x3f3f3f3f;
            
        while(target){
            int r = target % x;
            target /= x;
            int m = (i == 0? 2: i);
            ++i;
            
            int dp0 = min(dp[0] + r * m, dp[1] + (r + 1) * m);
            dp[1] = min(dp[0] + (x - r) * m, dp[1] + (x - r - 1) * m);
            dp[0] = dp0;
        }
        
       return  min(dp[0], dp[1] + i) - 1;
    }

};
// class Solution {
// public:
//     int leastOpsExpressTarget(int x, int target) {
//         int n = log(target) / log(x);
//         int dp[2];
//         int mask = pow(x, n);
//         dp[0] = 0; dp[1] = n + 1;
//         for(int i = n; i >= 0; --i){
//             int d = target / mask;
//             int m = (i == 0? 2 : i);
//             int dp0 = min(dp[1] + (x - d) * m, dp[0] + d * m);
//             dp[1] = min(dp[1] + (x - d - 1) 
//                         * m, dp[0] + (d + 1) * m);
            
//             dp[0] = dp0;
                
                
//             target %= mask;
//             mask /= x;
              
//         }
//         return dp[0] - 1;
//     }

// };

 

968. Binary Tree Cameras

題意:給定一個二叉樹。要求你在其中的某些節點放置照相機使得整個樹的節點都被監視。某個節點放置的照相機可以監視自身節點,父節點和兒子。求最少需要多少照相機。

題解:其實就是節點覆蓋問題(類似着色問題)。最容易想到的就是樹形dp。每個節點設置三個狀態,放置相機,未放置相機,但被監視,未放置相機,未被監視。這樣就可以進行動態規劃。然而並不需要,因爲可以貪心放置相機。如果一個點是葉子(非根)或者兒子都被監視,那麼在他這裏放一個相機肯定沒有在他的父親哪裏放一個好。

/**
 * 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:
    enum { NM = 0, /* not monitored */ MO  /*monitored*/};
    int ca_count(TreeNode *cur, TreeNode *par) {
        if(!cur) return 0;
        
        int c = 0;
        //遞歸計算左邊和右邊
        c = ca_count(cur->left, cur);
        c += ca_count(cur->right, cur);
        
        /* make decisions post-order */
        bool ls = cur->left ? cur->left->val : true;       /* left node state */
        bool rs = cur->right ? cur->right->val : true;     /* right node state */
        //根據左右計算當前節點。 當前節點僅當兒子有未被覆蓋的點時才放置相機
        if(!ls || !rs){
            cur->val = MO; 
            if(par) par->val = MO;
            ++c;
        }
        
        return c;
    }
    
    int minCameraCover(TreeNode* root) {
        //int c = 0;  /* camera count */
        int c = ca_count(root, 0);
        //根如果沒被覆蓋
        c += !(root->val);
        
        return  c;
    }
};

972. Equal Rational Numbers

題意:給定兩個個字符串表示的有理數(有限小數或循環小數),問它們是不是相等的

題解:重新將他們表示成最簡分數形式。再比較分子分母即可。

class Solution {
    void get_three_part(const string &str, int &int_part, pair<int, int> &non_rep, pair<int, int> &rep){
        int pos = 0;
        while(pos < str.length() && str[pos] != '.'){
            int_part = int_part * 10 + str[pos++] - '0';
        }
        ++pos;
        non_rep.second = 1;
        while(pos < str.length() && str[pos] != '('){
            non_rep.first = non_rep.first * 10 + str[pos++] - '0';
            non_rep.second *= 10;
        }
        
        ++pos;
        rep.second = 1;
        while(pos < str.length() && str[pos] != ')'){
            rep.first = rep.first * 10 + str[pos++] - '0';
            rep.second *= 10;
        }
        if(rep.second > 1)  
            --rep.second;
        
    }
    long long gcd(int a, int b){
        return b? gcd(b, a % b) : a;
    }
    
    pair<long long, long long> add(int int_part, pair<int, int> non_rep, pair<int, int> rep){
        pair<long long, long long> res;
        res.second = rep.second * non_rep.second;
        res.first =(long long)non_rep.first * rep.second + rep.first;
        
        long long d = gcd(res.first, res.second);
        res.first /= d;
        res.second /= d;
        res.first += int_part * res.second;
        return res;
    }
    
public:
    bool isRationalEqual(string S, string T) {
        
        int int_part_s = 0,int_part_t = 0;
        //rational number numerator and denominator
        pair<int, int> non_rep_s, non_rep_t;
        pair<int, int> rep_s, rep_t;
        
        //得到三部分,整數,非循環部分,和循環部分
        get_three_part(S, int_part_s, non_rep_s, rep_s);
        get_three_part(T, int_part_t, non_rep_t, rep_t);
        
        //將他們重新表示成最簡分數,看是否相等即可
        return add(int_part_s, non_rep_s, rep_s) == add(int_part_t, non_rep_t, rep_t);
        
    }
};

975. Odd Even Jump

題意:給定一個整數數組,任選一個位置開始往後跳,奇數步的時候往後跳到大於等於它的數中最小的且下標最小的數的位置。

偶數步的時候往後跳到小於等於它的數中最大的且下標最小的位置。問這個數組哪些位置爲起始點能夠跳到最後一個位置(n-1)

題解:動態規劃,dp[i][0]表示輪到i爲奇數步時能不能到達最後一個位置。dp[i][1]則爲偶數步。對於一個i,如果我們能獲得奇數步下個位置j和偶數步下個位置k,則dp[i][0] = dp[j][1], dp[i][1] = dp[k][0]。爲了得到大於等於它的數中最小的且下標最小,可以用一個map將值映射成下標,然後用lower_bound得到。

class Solution {
public:
    int oddEvenJumps(vector<int>& A) {
        int n = A.size();
        map<int, int> val2idx;

        bool good[n][2] = {0};
        good[n - 1][0] = good[n - 1][1] = true;
        val2idx[A[n - 1]] = n - 1;

        int ans = 1;
        for(int i = n - 2; i >= 0; --i){
            map<int, int>::iterator iter = val2idx.lower_bound(A[i]);
           if(iter != val2idx.end())
                if(good[i][0] = good[iter->second][1]) ++ans;
    
            if(iter == val2idx.end() or iter->first > A[i]){
                if(iter == val2idx.begin()) {
                    val2idx[A[i]] = i;
                    continue;
                }
                --iter;
            }
            good[i][1] = good[iter->second][0];
            val2idx[A[i]] = i;
        }
        return ans;
    }
};

980. Unique Paths III

題意:給定一個grid,不超過20個格子。1表示起始位置,2表示終止位置,-1表示障礙不可通過,0表示空位可通過。問從起始走到終止且通過所有非障礙的路徑數。

題解:由於格子少,直接dfs或者狀態壓縮動態規劃即可。如果更大一些,則需要一些剪枝技巧

class Solution {
    pair<int,int> tp;
    vector<vector<int>> grid;
    int n, m;
    vector<int> dir_x, dir_y;

public:
    int uniquePathsIII(vector<vector<int>>& g) {
        grid.swap(g);
        n = grid.size();
        m = grid[0].size();
        dir_x = {0, 0, 1, -1};
        dir_y = {1, -1, 0, 0};


        int todo = 0;
        pair<int,int> sp;

        for(int i = 0;i < n; ++i){
            for(int j = 0;j < m ; ++j){
                switch(grid[i][j]){
                    case 0: ++todo; break;
                    case 1: sp = make_pair(i, j); break;
                    case 2: tp = make_pair(i, j); grid[i][j] = 0; break;
                }
            }
        }
        return cal_path(sp, todo + 2);
    }

    int cal_path(pair<int,int> sp, int todo) {
        if(--todo < 0) return 0;
        if(sp == tp){
            return todo == 0;
        }

        grid[sp.first][sp.second] = -1;
        int res = 0;
        for(int d = 0; d < 4; ++d){
            int nx = sp.first + dir_x[d];
            int ny = sp.second + dir_y[d];
            if(nx < 0 or ny < 0 or nx >= n or ny >= m or grid[nx][ny]) continue;
            res += cal_path({nx, ny}, todo);
        }
        grid[sp.first][sp.second] = 0;
        return res;
    }
};

992. Subarrays with K Different Integers

題意:給定一個數組和一個k。問有多少子數組剛好包含k個不同的數(可以重複)

題解:對於每個位置,我們計算出從i開始到k0的位置剛好有k個數,而到k1位置剛好有k+1個數,則以i爲起始的包含k個數的子數組個數就是k1 - k0。累加起來即可

 

class Solution {
public:
    int subarraysWithKDistinct(vector<int>& A, int K) {
        int n = A.size();
        int cnt0[n + 1] = {0};
        int cnt1[n + 1] = {0};

        int k0 = 0, k1 = 0, k0_pointer = 0, k1_pointer = 0;

        int ans = 0;
        for(int i = 0;i < A.size(); ++i){
            while(k0_pointer < n and k0 < K){
                if(cnt0[A[k0_pointer++]]++ == 0){
                    ++k0;
                }
            }

            while(k1_pointer < n and k1 < K + 1){
                if(cnt1[A[k1_pointer++]]++ == 0){
                    ++k1;
                }
            }
            if(k0 < K) break;
            if(k1 < K + 1 and k1_pointer == n) ++k1_pointer;        
            ans += k1_pointer - k0_pointer;
            
            if(--cnt0[A[i]] == 0) --k0;
            if(--cnt1[A[i]] == 0) --k1;

        }
        return ans;
    }
};

995. Minimum Number of K Consecutive Bit Flips

題意:給定一個01數組A和一個K。一次 K 位翻轉包括選擇一個長度爲 K 的(連續)子數組,同時將子數組中的每個 0 更改爲 1,而每個 1 更改爲 0。返回所需最少的翻轉次數使得A全爲1。

題解:翻轉是可以交換次序的,所以考慮最前一個被交換的位置,它一定是第一個0所在的位置,不然的話,翻轉一次會導致前面多了0,必須要有位置在更前面的翻轉使他變成1,這會導致一個矛盾即在一個位置翻轉兩次。將其和後面K個數翻轉。問題變成一個同樣的問題,只不過下一個0往後移了。按照這個過程從前到後遍歷,每遇到0就翻轉K個。那怎麼翻轉呢?肯定不能一個個翻轉,每次翻K個,用一個數記錄當前翻轉次數(不過不用記錄次數,記錄奇偶即可)。將一次翻轉拆分成兩次,每次翻轉相當於在當前位置i到末尾進行翻轉,然後i+K再做一次翻轉。這樣只要在i+K位置做個標記,當到達這裏時進行一次翻轉即可。

class Solution {
public:
    int minKBitFlips(vector<int>& A, int K) {
        int num = 0, cur_num = 0;
        for(int i = 0;i < A.size(); ++i){
            cur_num ^= (i >= K) && A[i - K];

            if((cur_num & 1) == A[i]){
                if(i > A.size() - K) return -1;
                ++num;
                cur_num ^= A[i] = 1;
            }else{
                A[i] = 0;
            }
        }
        return num;
    }
};

 

1000. Minimum Cost to Merge Stones

題意:給定n堆石子,和一個K,每次可以將連續的K堆合併爲一堆,代價爲這K堆石子的數目和。問這n堆石子合併成一堆的最小代價,如果不能合併爲1堆,則返回-1

題解:可以用動態規劃。設dp[i][j][k]表示區間i 到j合併爲k堆的最小代價。則答案是dp[0][n-1][1]

           採用記憶化搜索。-2表示狀態不合法。因爲要分成k堆,必須有j - i + 1 >= k and (j - i + 1 - k) % (K - 1) == 0

           dp[i][j][k]的轉移方程

  • 如果i == j,那麼就只能是1堆了,代價爲0
  • 如果i != j, k == 1,也就是分成K份,然後合併爲1份,等於原K份的代價加上K份的和也就是i到j的和
  • 如果i != j,k != 1,那麼枚舉i最後到t合併成一份,剩下的合併稱k - 1份,則轉移方程爲

                                       dp[i][j][k]=\min_{(j - i + 1) \% (K - 1)} \left(dp[i][j][k], dp[i][t][1] + dp[t + 1][j][k-1] \right)

      注意一些邊界條件即可。

然而這樣做空間和時間並不是最優的。注意到第三維的狀態其實是冗餘的。因爲只有(j - i + 1 - k) % (K - 1) == 0時狀態纔是合法的。因爲k \in [1, K],所以k  由(j - i + 1) % (K - 1)唯一確定((j - i + 1) % (K - 1) 爲0時k = K)。 

我們也可以這麼看,設dp[i][j] 爲i到j 的石子合併到不能合併時的最小代價。

則當j - i + 1 < K時代價爲0(因爲一次都不能合併)

狀態轉移方程如下(枚舉和i合併成1份的t)

                                                 dp[i][j] = \min_{(t - i) \% (K - 1) = 0} dp[i][t] + dp[t + 1][j]

當(j - i) % (K - 1) ==0 時 dp[i][j] += sum(i,j) 即可以進一步合併成一份,所以加上i到j的和。

以下兩個代碼分別是上面兩種做法。第二種採用遞推會更快,更省空間

class Solution {
    int dp[31][31][31];
    int prefix[31];
    int K;
    
    int dfs(int i, int j, int k){
        int &ans = dp[i][j][k];
        if(ans != 0x3f3f3f3f) return ans;
        
        if(j - i + 1 < k || (j - i + 1 - k) % (K - 1)) return ans = 0x3f3f3fff;
        
        if(i == j) return ans = 0;
        if(k == 1)
            return dfs(i, j, K) + prefix[j + 1] - prefix[i];
        
        
        for(int t = i; t < j; t += K - 1){
            int dp1 = dfs(i, t, 1);
            int dp2 = dfs(t + 1, j, k - 1);
            ans = min(ans, dp1 + dp2);
        }
        return ans;
    }
    
public:
    int mergeStones(vector<int>& stones, int K){
        this->K = K;
        int n = stones.size();
        
        memset(dp, 0x3f, sizeof(dp));
        
        prefix[0] = 0;
        for(int i = 1;i <= n; ++i) prefix[i] = prefix[i - 1] + stones[i - 1];
        
        int ans = dfs(0, n - 1, 1);
        
        return ans >= 0x3f3f3f3f? -1: ans;
    }
};
class Solution {
    int prefix[31];
    int dp[31][31];
    int K;
    
//     int dfs(int i, int j){
//         int &ans = dp[i][j];
        
//         if(ans != 0x3f3f3f3f) return ans;
//         if(j - i + 1 == K) return ans = prefix[j + 1] - prefix[i];
//         if(j - i + 1 < K) return ans = 0;
        
//         int k = (j - i) % (K - 1) + 1;
//         int times = (j - i) / (K - 1);
        
//         for(int t = i; t < j; t += K - 1){
//             ans = min(ans, dfs(i, t) + dfs(t + 1, j)); 
//         }
        
//         if((j - i) % (K - 1) == 0) ans += prefix[j + 1] - prefix[i];
        
//         return ans;
//     }
    
public:
    int mergeStones(vector<int>& stones, int K){
        this->K = K;
        int n = stones.size();
        if((n - 1) % (K - 1)) return -1;
        
        memset(dp, 0, sizeof(dp));
        
        prefix[0] = 0;
        for(int i = 1;i <= n; ++i) prefix[i] = prefix[i - 1] + stones[i - 1];
    
        for(int len = K; len <= n; ++len){
            for(int i = 0;i < n - len + 1; ++i){
                int j = i + len - 1; 
                
                dp[i][j] = 0x3f3f3f3f;
                for(int t = i; t < j; t += K - 1){
                    dp[i][j] = min(dp[i][j], dp[i][t] + dp[t + 1][j]);
                }
                if((j - i) % (K - 1) == 0) dp[i][j] += prefix[j + 1] - prefix[i];
                
            }
        }
        return dp[0][n - 1] >= 0x3f3f3f3f ? -1: dp[0][n - 1];
        
        //         int ans = dfs(0, n - 1);
        // return ans >= 0x3f3f3f3f? -1: 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;
        
    }
};

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章