十一月每日一题打卡

1 每日一题打卡

11.1 检查两个字符串数组是否相等 lc 1662

class Solution {
public:
    bool arrayStringsAreEqual(vector<string>& word1, vector<string>& word2) {
        int n1 = word1.size(), n2 = word2.size();
        int i1 = 0, i2 = 0;
        int j1 = 0, j2 = 0;

        while(i1 < n1 && i2 < n2)
        {
            if(j1 < word1[i1].size() && j2 < word2[i2].size())
            {
                if(word1[i1][j1] != word2[i2][j2])
                    return false;
                cout << j1 << " " << j2 << endl;
                j1++; j2++;
            }
            else
            {
                if(j1 >= word1[i1].size())
                {
                    j1 = 0; i1++;
                }
                if(j2 >= word2[i2].size())
                {
                    j2 = 0; i2++;
                }
            }
        }
        cout << i1 << " " << i2 << endl;
        if(i1 >= n1 && i2 >= n2)
            return true;
        return false;
    }
};

11.3 最大重复字符串 lc 1668

序列dp

class Solution {
public:
    int maxRepeating(string sequence, string word) {
        int sn = sequence.size(), wn = word.size();
        if(sn < wn) return 0;
        int dp[sn + 1];
        int ans = 0;
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= sn; i++)
        {
            if(i < wn) continue;
            string str = sequence.substr(i - wn, wn);
            if(str == word)
                dp[i] = dp[i - wn] + 1;
            ans = max(ans, dp[i]);
        }
        return ans;
    }
};

上述算法在匹配str==word时使用的是暴力一个一个匹配可以转换成kmp匹配字符串。在kmp匹配过程中,匹配上就在之前匹配上+1,取最大值。

字符串匹配算法:KMP 字符串p在字符串s中匹配

//kmp
#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int ne[N];
int n, m;
string p, s;

int main(){
    cin >> n >> p >> m >> s;
    //i是待匹配位置,j是已匹配长度,j - 1是y匹配的最后一个位置
    //与自身匹配i从1开始
    for(int i = 1, j = 0; i < n; i ++){
        while(j && p[i] != p[j]) j = ne[j - 1];
        if(p[i] == p[j]) j ++;
        ne[i] = j;
    }

//与长字符串匹配i从0开始
    for(int i = 0, j = 0; i < m; i ++){
        while(j && s[i] != p[j]) j = ne[j - 1];
        if(s[i] == p[j]){
            j ++;
            if(j == n){
                cout << i - n + 1 << " ";
                j = ne[n - 1];
            }
        }
    }
    return 0;
}
const int N = 110;
int ne[N];
class Solution {
public:
    void getNext(string word)
    {
        memset(ne, 0, sizeof ne);
        int wn = word.size();
        int j = 0;
        for(int i = 1; i < wn; i++)
        {
            while(j && word[i] != word[j]) j = ne[j - 1];
            if(word[i] == word[j]) j++;
            ne[i] = j;
        }
    }
    int maxRepeating(string sequence, string word) {
        int sn = sequence.size(), wn = word.size();
        if(sn < wn) return 0;
        getNext(word);
        int dp[sn];
        int ans = 0;
        memset(dp, 0, sizeof dp);
        int j = 0;
        for(int i = 0; i < sn; i++)
        {
            while(j && sequence[i] != word[j]) j = ne[j - 1];
            if(sequence[i] == word[j]) j++;
            if(j == wn)
            {
                if(i < wn) dp[i] = 1;
                else dp[i] = dp[i - wn] + 1;
                ans = max(ans, dp[i]); 
                j = ne[j - 1];  //接着匹配
            }
        }
        return ans;
    }
};

11.4 到达终点的数字 lc 754

class Solution {
public:
    int reachNumber(int target) {
        target = abs(target);
        int s = 0, n = 0;
        while(s < target || (s - target) % 2)
            s += ++n;
        return n;
    }
};

一道很奇妙的数学题

754-2.png

11.5 解析布尔表达式 lc 1106

一道括号栈模拟,想清楚每一步即可

class Solution {
public:
    stack<char> op;
    stack<int> num;
    //(是 -1
    bool calu(char opp)
    {
        bool ans;
        bool ans1 = true;
        bool ans2 = false;
        while(num.size() && num.top() != -1)
        {
            bool t = num.top();
            num.pop();
            if(opp == '!')
                ans = !t;
            else if(opp == '&')
            {
                if(!t) ans1 = false;
            }
            else if(opp == '|')
            {
                if(t) ans2 = true;
            }
        }
        num.pop();
        if(opp == '!') return ans;
        else if(opp == '&') return ans1;
        else return ans2;
    }
    bool tran(char x)
    {
        return x == 'f'? false: true;
    }
    bool parseBoolExpr(string exp) {
        int n = exp.size();
        int i = 0;
        while(i < n){
            if(exp[i] == '!' || exp[i] == '&' || exp[i] == '|')
                op.push(exp[i++]);
            else if(exp[i] == '('){
                num.push(-1);
                i++;
            }    
            else if(exp[i] == ')'){
                char opp = op.top();
                op.pop();
                bool r = calu(opp);
                num.push(r);
                i++;
            }
            else if(exp[i] == 'f' || exp[i] == 't'){
                num.push(tran(exp[i++]));
                if(i < n && exp[i] == ',') i++;
            }
            else  i++;
        } 
        return num.top();
    }
};

11.7 模糊字符串 lc 816

很烦的一道题,在于我没有理清逻辑,一步一步的写

分为加逗号和加小数点,加完逗号去加小数点。判断小数点是否合理:整数部分如果大于1个数的话开头不能是0,小数部分最后不能是0。

class Solution {
public:
    vector<string> ambiguousCoordinates(string s) {
        s = s.substr(1, s.size() - 2);
        int n = s.size();
        vector<string> ans;
        for(int i = 1; i < n; i++)
        {
            vector<string> xp = addPoint(s.substr(0, i));
            vector<string> yp = addPoint(s.substr(i));
            for(int j = 0; j < xp.size(); j++)
            {
                for(int k = 0; k < yp.size(); k++)
                {
                    ans.push_back("(" + xp[j] + ", " + yp[k] + ")");
                }
            }
        }
        return ans;
    }
    vector<string> addPoint(string s)
    {
        if(s.size() == 1) return {s};
        vector<string> ans;
        if(s.size() > 0 && s[0] != '0') ans.push_back(s);
        for(int i = 1; i < s.size(); i++)
        {
            string a = s.substr(0, i);
            string b = s.substr(i);
            if(a.size() > 1 && a[0] == '0') continue;
            if(b.size() > 0 && b[b.size() - 1] == '0') continue;
            ans.push_back(a + "." + b);
        }
        return ans;
    }
};

11.9 最大加法符号 lc 764

简单dfs可以过,注意mines数组不是图里的二维矩阵,需要先转换一下。复杂度O(n2)O(n^2)O(n2)的指数级别。

int dx[4] = {-1, 1, 0, 0};
int dy[4] = {0, 0, -1, 1};
class Solution {
public:
    int ans = 0;
    int n;
    
    void dfs(vector<vector<int>>& grid, int x, int y, int& k)
    {
        for(int i = 0; i < 4; i++)
        {
            int nx = x + k * dx[i];
            int ny = y + k * dy[i];
            if(nx < 0 || nx >= n || ny < 0 || ny >= n)
                return;
            if(grid[nx][ny] == 0)
                return;
        }
        k++;
        dfs(grid, x, y, k);
    }
    int orderOfLargestPlusSign(int nn, vector<vector<int>>& mines) {
        n = nn;
        vector<vector<int>> grid(n, vector<int>(n, 1));
        for(int i = 0; i < mines.size(); i++)
            grid[mines[i][0]][mines[i][1]] = 0;
        for(int i = 0; i < n; i++)
        {
            for(int j = 0; j < n; j++)
            {
                if(grid[i][j] == 1)
                {
                    int k = 1;
                    dfs(grid, i, j, k); 
                    ans = max(ans, k);
                }
            }
        }
        return ans;
    }
};

动态规划:也不算dp啦,四个方向上的前缀和。搞四个数组,up[i] [j]表示(i, j)向上能最多扩张几个1, down[i] [j], left[i] [j], right[i] [j]同理。注意down和right要倒着遍历。复杂度O(n2)O(n^2)O(n2)

class Solution {
public:
    int orderOfLargestPlusSign(int n, vector<vector<int>>& mines) {
        int ans = 0;
        vector<vector<int>> grid(n, vector<int>(n, 1));
        for(int i = 0; i < mines.size(); i++)
            grid[mines[i][0]][mines[i][1]] = 0;
        int up[n + 2][n + 2], down[n + 2][n + 2], left[n + 2][n + 2], right[n + 2][n + 2];
        memset(up, 0, sizeof up);
        memset(down, 0, sizeof down);
        memset(left, 0, sizeof left);
        memset(right, 0, sizeof right);
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= n; j++)
            {
                if(grid[i - 1][j - 1] == 1)
                {
                    up[i][j] = up[i - 1][j] + 1;
                    left[i][j] = left[i][j - 1] + 1;
                }
                ans = max(ans, min(min(up[i][j], down[i][j]), min(left[i][j], right[i][j])));
            }
        }
        for(int i = n; i >= 1; i--)
        {
            for(int j = n; j >= 1; j--)
            {
                if(grid[i - 1][j - 1] == 1)
                {
                    down[i][j] = down[i + 1][j] + 1;
                    right[i][j] = right[i][j + 1] + 1;
                }
                ans = max(ans, min(min(up[i][j], down[i][j]), min(left[i][j], right[i][j])));
            }
        }
        return ans;
    }
};

11.9 扁平化多级双向链表 lc 430

dfs是获得以head为头节点的已经整理好的链表,分为三步:保存next节点,将整理好的dfs(child)节点作为next,整理好prev和child指针指向。然后找到左侧最后面那个节点,将整理好的dfs(tmp)作为最后那个节点的next,整理好prev和child指针指向。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* prev;
    Node* next;
    Node* child;
};
*/

class Solution {
public:
    Node* dfs(Node* head){
        if(head == nullptr)
            return nullptr;
        
        Node* tmp = head->next;
        
        Node* childNext = dfs(head->child);
        if(childNext == nullptr)
        {
            head->next = dfs(tmp);
            return head;
        }
        head->next = childNext;
        childNext->prev = head;
        head->child = nullptr;
        
        Node* p = head;
        while(p->next != nullptr)
            p = p->next;
        Node* nodeNext = dfs(tmp);
        if(nodeNext == nullptr)
            return head;
        p->next = nodeNext;
        nodeNext->prev = p;
        return head;
        
    }
    Node* flatten(Node* head) {
        if(head == nullptr) return nullptr;
        return dfs(head);
    }
};

11.10 获得所有钥匙的最短路径 lc 864

图中的最短路径:BFS很好用

这道题的关键在于visited数组不是取决于这个坐标是否访问过,如果获得了新的钥匙可以重新返回原来的路径,也就是还需要加入钥匙的状态,需要状态压缩。

  • 初始化全部钥匙都获得的状态allKeyState = (1 << keyN) - 1 1向左左移keyN然后-1使得全为1
  • 获取第ch位是否为1:(k >> ch) & 1 k向右移ch位并获取
  • 使第ch位为1:k | (1 << ch)
class Solution { 
public:
    struct State{
        int x, y, key;
    };
    int shortestPathAllKeys(vector<string>& grid) {
        int m = grid.size(), n = grid[0].size();
        int keyN = 0, cnt = 0, ans = 0;
        int dx[4] = {-1, 1, 0, 0};
        int dy[4] = {0, 0, -1, 1};
        State s;
        queue<State> q;
        for(int i = 0; i < m; i++)
        {
            for(int j = 0; j < n; j++)
            {
                if(grid[i][j] == '@')
                {s.x = i; s.y = j; s.key = 0;}
                if(islower(grid[i][j])) keyN++;
            }
        }
        q.push(s);
        int allKeyState = (1 << keyN) - 1;
        vector<vector<vector<bool>>> visited(m, vector<vector<bool>>(n, vector<bool>(allKeyState + 1, false)));
        while(q.size())
        {
            int qn = q.size();
            for(int i = 0; i < qn; i++)
            {
                State tmp = q.front();
                q.pop();
                if(tmp.key == allKeyState)
                    return ans;
                for(int j = 0; j < 4; j++)
                {
                    int nx = tmp.x + dx[j], ny = tmp.y + dy[j], k = tmp.key;
                    if(nx < 0 || nx >= m || ny < 0 || ny >= n)
                        continue;
                    char ch = grid[nx][ny];
                    if(ch == '#')
                        continue;
                    if(isupper(ch) && (k >> (ch - 'A') & 1) == 0)
                        continue;
                    if(islower(ch))
                        k |= 1 << (ch - 'a');
                    if(visited[nx][ny][k])
                        continue;
                    visited[nx][ny][k] = true;
                    q.push(State{nx, ny, k});
                }
            }
            ans++;
        }
        return -1;
    }
};

11.13 自定义字符串排序

还行比较简单

class Solution {
public:
    string customSortString(string order, string s) {
        int cnt[26];
        memset(cnt, 0, sizeof cnt);
        for(int i = 0; i < s.size(); i++)
            cnt[s[i] - 'a']++;
        string ans;
        for(int i = 0; i < order.size(); i++)
        {
            while(cnt[order[i] - 'a']--)
                ans += order[i];
        }
        for(int i = 0; i < 26; i++)
        {
            while(cnt[i] > 0)
            {
                ans +=  (i + 'a');
                cnt[i]--;
            }
        }
        return ans;
    }
};

11.14 数组的均值分割 lc 805

通过计算我们可以得到avg(A)=avg(B) = avg(nums),sum(A) = sum(nums) * k / n,为了防止浮点数,我们可以将所有数*n再减去avg(nums),这样就转换成在n个数中找k个数使他们的和为0即可。也就是n个数每个数的选与不选问题,复杂度是2n2^n2n,n取到30太大了。考虑折半

也就是说分为A0和A1两个数组,各一半。从A0中找tot,A1中找-tot,加一块等于0也行。或者说在A0/A1中就找到了和为0的也行。但是有一种情况不行,就是全选左边(或者右边,全选左边就代表也必须全选右边因为lsum=-rsum,这样A数组就全选了所有nums中的,B就没有了)

二进制枚举问题

n个数中选取k个的方案问题,一共有[0, 2n−12^n - 12n1]这些方案。

遍历这些方案for(int i = 0; i < (1 << n); i++)

获取每个方案中是否选了第j个 if(i & (1 << j))

class Solution {
public:
    bool splitArraySameAverage(vector<int>& nums) {
        int n = nums.size();
        if(n == 1) return false;
        int m = n / 2;
        int sum = 0;
        for(int x: nums) sum += x;
        for(int& x: nums)
            x = x * n - sum;
        cout << m << endl;
        unordered_set<int> leftsum;
        for(int i = 1; i < (1 << m); i++) //方案二进制们
        {
            //不能从0开始,也就是A0中一个都不选,可以这样做,不适合这个循环,可以之间跳过。
            int tot = 0;
            for(int j = 0; j < m; j++) //第j个元素
            {
                if(i & (1 << j))
                    tot += nums[j];
            }
            if(tot == 0)
                return true;
            leftsum.insert(tot);
        }
        int rsum = 0;
        for(int i = m; i < n; i++) rsum += nums[i];
        cout << rsum << endl;
        for(int i = 1; i < 1 << (n - m); i++)
        {
            int tot = 0;
            for(int j = m; j < n; j++)
            {
                if(i & (1 << (j - m)))
                    tot += nums[j];
            }
            if(tot == 0 || (rsum != tot && leftsum.find(-tot) != leftsum.end()))
                return true;
        }
        return false;
    }
};

2 DP专题

在这里插入图片描述

11.1 跳跃游戏

跳跃游戏I lc 55

dp[i]表示当前位置能否跳到,一个一个跳着向前找。

适当剪枝:如果跳几个就已经可以了,就不必向前找了。

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int n = nums.size();
        bool dp[n];
        memset(dp, false, sizeof dp);
        dp[0] = true; 
        for(int i = 1; i < n; i++)
        {
            for(int j = 1; j <= i; j++)
            {
                if(j <= nums[i - j])
                {
                    dp[i] = dp[i] || dp[i - j];
                    if(dp[i])
                        break;
                }
            }
        }
        return dp[n - 1];
    }
};
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int k = 0; //当前能跳的最远距离
        for(int i = 0; i < nums.size(); i++)
        {
            if(i > k)
                return false;
            k = max(k, i + nums[i]);
            if(k >= nums.size() - 1)
                return true;
        }
        return true;
    }
};

k表示当前能跳的最远距离,如果i大于最远距离当然不行,如果k大于整个数组长度,那就可以啦!

原理就是如果这个位置能跳到,那么左侧所有位置都能跳到。因为nums[i]存储的是最大距离,0, 1, 2, …到nums[i]都可以。

不用拘泥于每次究竟跳跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。

跳跃游戏II lc 45

在前面dp的基础上改进即可,找到前面能够跳跃的位置+1,取每个的最小值。5%太慢了,O(n2)O(n^2)O(n2)

class Solution {
public:
    int jump(vector<int>& nums) {
        int n = nums.size();
        int dp[n];
        memset(dp, 0x3f, sizeof dp);
        dp[0] = 0; 
        for(int i = 1; i < n; i++)
        {
            for(int j = 1; j <= i; j++)
            {
                if(j <= nums[i - j])
                    dp[i] = min(dp[i], dp[i - j] + 1);
            }
        }
        return dp[n - 1];
    }
};

贪心解法:

11.2 打家劫舍系列

打家劫舍 I lc 198

class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        int dp[n + 1];
        dp[0] = 0; dp[1] = nums[0];
        for(int i = 2; i <= n; i++)
        {
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        }
        return dp[n];
    }
};

打家劫舍 II lc 213

分两种情况:

  • 第一个房子偷:那只能偷到第n- 1个
  • 第一个房子不偷:那可以偷到第n个
class Solution {
public:
    int rob(vector<int>& nums) {
        int n = nums.size();
        if(n == 1) return nums[0];
        int dp[n + 1];
        dp[0] = 0; dp[1] = 0;
        
        for(int i = 2; i <= n; i++)
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        dp[0] = 0; dp[1] = nums[0];
        for(int i = 2; i <= n - 1; i++)
            dp[i] = max(dp[i - 1], dp[i - 2] + nums[i - 1]);
        return max(dp[n - 1], dp[n]);
    }
};

11.3 解码方式系列

解码方式 I lc 91

前i位的解码方案个数取决于和第i位组成的是两位还是一位。

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        int dp[n + 1];
        if(s[0] == '0') return 0;
        memset(dp, 0, sizeof dp);
        dp[0] = 1; 
        dp[1] = 1;
        for(int i = 2; i <= n; i++)
        {
            if(s[i - 1] != '0')
                dp[i] += dp[i - 1];
            if(s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6'))
                dp[i] += dp[i - 2];
        }
        return dp[n];
    }
};

解码方式 II lc 639

添加了一个通配符,把每个if else搞清楚就行

class Solution {
public:
    const int mod = 1e9 + 7;
    int numDecodings(string s) {
        int n = s.size();
        long long dp[n + 1];
        memset(dp, 0, sizeof dp);
        dp[0] = 1;
        if(s[0] == '0') return 0;
        else if(s[0] == '*') dp[1] = 9;
        else dp[1] = 1;
        for(int i = 2; i <= n; i++)
        {
            if(s[i - 1] != '0' && s[i - 1] != '*')
                dp[i] += dp[i - 1] % mod;
            else if(s[i - 1] == '*')
                dp[i] += dp[i - 1] * 9 % mod;
            //if(dp[i] < 0)
                //dp[i] += mod;
            dp[i] = dp[i] % mod;
            if(s[i - 1] != '*' && (s[i - 2] == '1' || (s[i - 2] == '2' && s[i - 1] <= '6' && s[i - 1] >= '0')))
                dp[i] += dp[i - 2] % mod;
            else if(s[i - 2] == '*' && s[i - 1] != '*')
            {
                if(s[i - 1] <= '6' && s[i - 1] >= '0')
                    dp[i] += dp[i - 2] * 2 % mod;
                else
                    dp[i] += dp[i - 2] % mod;
            }
            else if(s[i - 2] != '*' && s[i - 1] == '*')
            {
                if(s[i - 2] == '1')
                    dp[i] += dp[i - 2] * 9 % mod;
                else if(s[i - 2] == '2')
                    dp[i] += dp[i - 2] * 6 % mod;
            }
            else if(s[i - 2] == '*' && s[i - 1] == '*')
                dp[i] += dp[i - 2] * 15 % mod;
            dp[i] = dp[i] % mod;
        }
        return dp[n];
    }
};

11.4 石子问题(区间dp)

石子游戏 lc 877

不太理解这个差值

f[l] [r]表示先手与后手的最大差值得分

先手取左边f[l] [r] = p[l - 1] - f[l + 1] [r]

先手取右边 f[l] [r] = p[r - 1] + f[l] [r]

大区间依赖于小区间:区间dp的常规操作就是枚举区间长度和左区间

class Solution {
public:
    bool stoneGame(vector<int>& piles) {
        int n = piles.size();
        int f[n + 2][n + 2];
        memset(f, 0, sizeof f);
        for(int len = 1; len <= n; len++)
        {
            for(int l = 1; l + len - 1 <= n; l++)
            {
                int r = l + len - 1;
                f[l][r] = max(piles[l - 1] - f[l + 1][r], piles[r - 1] - f[l][r - 1]);
            }
        }
        return f[1][n] > 0;
    }
};

石子合并 acwing

每次都是合并相邻两堆:这样最后合并的两堆一定是左边区间和右边区间

f[l] [r]表示合并从[l, r]区间的最小体力

集合:所有将[i, j]区间合并的方案

f[l] [r] 从[l, r]中选一个k,分治到[l, k] [k + 1] [r]

f[l][r]=f[l][k]+f[k+1][r]+sum(i,j)f[l][r] = f[l][k] + f[k + 1][r] + sum(i, j)f[l][r]=f[l][k]+f[k+1][r]+sum(i,j)加上合并最后两堆的体力值,就是里面所有数目的和

int n = stones.size();
    int dp[n + 1][n + 1];
    memset(dp, 0x3f, sizeof dp);

    for(int len = 2; len <= n; len++)
    {
        for(int l = 1; i + len - 1 <= n; i++)
        {
            int r = i + len - 1;
            for(int k = l + 1; k <= j - 1; k++)
                f[i][j] = min(f[i][j], f[l][k] + f[k + 1][r] + sum(i, j));
        }
    }
    return f[1][n];

11.5 单词划分系列

单词划分I lc 139

完全背包问题

class Solution {
public:
    bool wordBreak(string s, vector<string>& wordDict) {
        int n = s.size();
        bool dp[n + 1];
        memset(dp, false, sizeof dp);
        dp[0] = true;
        for(int i = 1; i <= n; i++)
        {
            for(string x: wordDict)
            {
                int xn = x.size();
                if(i < xn) continue;
                string s1 = s.substr(i - xn, xn);
                if(s1 == x)
                    dp[i] = dp[i] || dp[i - xn];
            }
        }
        return dp[n];
    }
};

单词划分 II lc 140

暴力dfs过了

class Solution {
public:
    vector<string> ans;
    int n;
    vector<string> wordBreak(string s, vector<string>& wordDict) {
        n = s.size();
        string tmp = "";
        dfs(s, wordDict, 0, 0, tmp);
        return ans;
    }
    void dfs(string s, vector<string>& wordDict, int start, int end, string tmp)
    {
        if(start == n)
        {
            tmp.pop_back();
            ans.push_back(tmp);
            tmp = "";
            return;
        }
        if(end >= n)
            return;
        string str = s.substr(start, end - start + 1);
        for(string x: wordDict)
        {
            if(str == x)
                dfs(s, wordDict, end + 1, end + 1, tmp + x + " ");
        }
        dfs(s, wordDict, start, end + 1, tmp);
    }
};

什么时候要tmp =“” 没有影响,因为你回到之前的栈还是没加x的tmp而不是""

11.6 猜数字大小系列

猜数字大小 lc 374

猜数字大小 II lc 375

记忆化搜索:dfs(l, r)是指l, r区间内花费的最小值

const int N = 210;
int cache[N][N];
class Solution {
public:
    int getMoneyAmount(int n) {
        memset(cache, 0, sizeof cache);
        return dfs(1, n);
    }
    int dfs(int l, int r)
    {
        if(l >= r) return 0;
        if(cache[l][r] != 0) return cache[l][r];
        int c = INT_MAX;
        for(int i = l; i <= r; i++)
        {
            int cur = max(dfs(l, i - 1), dfs(i + 1, r)) + i;
            c = min(c, cur);
        }
        cache[l][r] = c;
        return c;
    }
};

11.7 摘樱桃 lc 741

一道想不到的dp,题解第二次做的时候再写吧
题解来啦!
嗯嗯做第二遍是有意义的,第一遍很多地方没搞明白。

首先要将一个人走两遍转换成两一个一块走走到最后,然后dp状态是两个人的坐标即dp[x1] [y1] [x2] [y2],由于两个人一块走,走的步数一样都是k=横纵坐标之和。所以可以简略成dp[k] [x1] [x2]。

const int N = 55;
class Solution {
public:
    int cherryPickup(vector<vector<int>>& grid) {
        int dp[2 * N][N][N];
        for(int i = 0; i < 2 * N; i++)
        {
            for(int j = 0; j < N; j++)
            {
                for(int k = 0; k < N; k++)
                {
                    dp[i][j][k] = INT_MIN;
                }
            }
        }
        dp[2][1][1] = grid[0][0]; //初始点位于(0, 0)即第一行第一列 1 + 1 = 2
        int n = grid.size();
        for(int k = 3; k <= 2 * n; k++) //最后位于n行n列 n + n = 2 * n
        {
            for(int r1 = 1; r1 <= n; r1++)
            {
                for(int r2 = 1; r2 <= n; r2++)
                {
                    //k是横纵坐标之和
                    //dp[k][r1][r2]表示走了k步,其中一人在r1行,另一人在r2行时摘到的最大樱桃数
                    int c1 = k - r1, c2 = k - r2;
                    if(c1 <= 0 || c1 > n || c2 <= 0 || c2 > n)
                        continue;
                    int p1 = grid[r1 - 1][c1 - 1], p2 = grid[r2 - 1][c2 - 1];
                    if(p1 == -1 || p2 == -1)
                        continue;
                    //上一次两个人的四个可能状态
                    int x1 = dp[k - 1][r1 - 1][r2], x2 = dp[k - 1][r1][r2 - 1];
                    int x3 = dp[k - 1][r1 - 1][r2 - 1], x4 = dp[k - 1][r1][r2];
                    int pre = max(max(x1, x2), max(x3, x4));
                    cout << pre << endl;
                    //如果两个人在同一个坐标处,加一个值就行
                    if(r1 == r2)
                        dp[k][r1][r2] = pre + p1;
                    else
                        dp[k][r1][r2] = pre + p1 + p2;
                }
            }
        }
        return dp[2 * n][n][n] < 0 ? 0: dp[2 * n][n][n];
    }
};

11.8 只有两个键的键盘 lc650

1. dfs

class Solution {
public:
    const int INF = 0x3f3f3f3f;
    int minSteps(int n) {
        return dfs(n, 1, 0);
    }
    int dfs(int n, int cur, int paste)
    {
        if(cur == n) return 0;
        if(cur > n) return INF;
        //选择copy
        int r1 = INF, r2 = INF;
        if(cur != paste) r1 = dfs(n, cur, cur) + 1;
        if(paste > 0)    r2 = dfs(n, cur + paste, paste) + 1;
        return min(r1, r2);
    }
};

2. dfs + 记忆化

const int N =  1100;
int cache[N][N];
class Solution {
public:
    
    const int INF = 0x3f3f3f3f;
    int minSteps(int n) {
        memset(cache, -1, sizeof cache);
        return dfs(n, 1, 0);
    }
    int dfs(int n, int cur, int paste)
    {
        if(cur < N && cache[cur][paste] != -1) return cache[cur][paste];
        if(cur == n) return 0;
        if(cur > n) return INF;
        //选择copy
        int r1 = INF, r2 = INF;
        if(cur != paste) r1 = dfs(n, cur, cur) + 1;
        if(paste > 0)    r2 = dfs(n, cur + paste, paste) + 1;
        cache[cur][paste] = min(r1, r2);
        return cache[cur][paste];
    }
};

3.dp

const int N = 1100;
class Solution {
public:
    int minSteps(int n) {
        int f[n + 1][n + 1];
        memset(f, 0x3f, sizeof f);
        f[1][0] = 0; f[1][1] = 1;
        for(int i = 2; i <= n; i++)
        {
            int m = INT_MAX;
            for(int j = 0; j <= i; j++)
            {
                //上一次操作是paste j
                f[i][j] = f[i - j][j] + 1;
                //上一次操作是copy
                m = min(m, f[i][j]);
            }
            //最后得到是i个字符,然后复制i个: 这个取决于再上一次也就是f[i - j][j] j <= i
            f[i][i] = m + 1;
        }
        int ans = INT_MAX;;
        for(int j = 0; j <= n; j++)
            ans = min(ans, f[n][j]);
        return ans;
    }
};

4.数学

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DYnZzknF-1668236421449)(11_leetcode.assets/image-20221108104531781.png)]

class Solution {
public:
    int minSteps(int n) {
        int ans = 0;
        for(int i = 2; i <= n; i++)
        {
            while(n % i == 0)
            {
                ans += i;
                n /= i;
            }
        }
        if(n != 1) ans += n;
        return ans;
    }
};

11.10 最长公共子序列系列LCS

最长公共子序列裸题 lc1143

返回text1和text2的最长公共子序列长度

d[i] [j]表示前i个text1和前j个text2字符串的LCS长度

  • f[i][j] = f[i - 1][j - 1] + 1;
  • f[i][j] = max(f[i][j - 1], f[i - 1][j]);
class Solution {
public:
    
    int longestCommonSubsequence(string text1, string text2) {
        int n = text1.size(), m = text2.size();
        vector<vector<int>> f(n + 1, vector<int>(m + 1, 0));
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                if(text1[i - 1] == text2[j - 1])
                    f[i][j] = f[i - 1][j - 1] + 1;
                else
                    f[i][j] = max(f[i][j - 1], f[i - 1][j]);
            }
        }
        return f[n][m];
    }
};

最长特殊子序列 LCS应用

简单版:lc521

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMQ0KJzP-1668236421450)(11_leetcode.assets/image-20221111114814671.png)]

如果a和b长度不相等,那么那个长的就是最长特殊序列,因为短的里面没有

如果a和b长度相等且a!= b也是同理,a整个长度在b中没有,答案就是二者的长度

如果a== b那么没有

第二版: lc522

返回一个字符串数组中的最长特殊子序列

使用每个字符串和其他字符串比较,看自身是否是其他所有字符串的最长公共子序列

class Solution {
public:
    int longestCommonSubsequence(string text1, string text2) {
        int n = text1.size(), m = text2.size();
        vector<vector<int>> f(n + 1, vector<int>(m + 1, 0));
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                if(text1[i - 1] == text2[j - 1])
                    f[i][j] = f[i - 1][j - 1] + 1;
                else
                    f[i][j] = max(f[i][j - 1], f[i - 1][j]);
            }
        }
        return f[n][m];
    }
    int findLUSlength(vector<string>& s) {
        int n = s.size();
        int ans = -1;
        for(int i = 0; i < n; i++)
        {
            int sn = s[i].size();
            if(sn < ans)
                continue;
            bool f = false;
            for(int j = 0; j < n; j++)
            {
                if(i == j) continue;
                if(longestCommonSubsequence(s[i], s[j]) == sn) 
                {
                    f = true;
                    break;
                }
            }
            if(!f) ans = max(ans, sn);
        }
        return ans;
    }
};

最短公共超序列 lc 1092

以两个字符串(l = m, l = n)为公共子序列的最短字符串(l = k + m - k + n - k)

思路就是:先找到最短公共子序列(l = k),然后在其基础上填填补补。

但是没想到用dp去做,这算是一个dp求解具体方案数的一个问题。

根据dp结果反推前面的情况是什么

  • dp[i] [j] == dp[i - 1] [j - 1]时,也即是s1[i] == s2[j],这时是公共子序列需要加上它
  • dp[i] [j] == dp[i - 1] [j]时,也就是使用了s1[i]
  • dp[i] [j] == dp[i] [j - 1]时,使用了s2[j]
class Solution {
public:
    string shortestCommonSupersequence(string text1, string text2) {
        int n = text1.size(), m = text2.size();
        int dp[n + 1][m + 1];
        memset(dp, 0, sizeof dp);
        for(int i = 1; i <= n; i++)
        {
            for(int j = 1; j <= m; j++)
            {
                if(text1[i - 1] == text2[j - 1])
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                else 
                    dp[i][j] = max(dp[i - 1][j], dp[i][j - 1]);
            }
        }
        int i = n, j = m;
        string ans;
        while(i > 0 || j > 0)
        {
            if(i == 0)
            {
                ans += text2[j - 1];
                j--;
            }
            else if(j == 0)
            {
                ans += text1[i - 1];
                i--;
            }
            else if(text1[i - 1] == text2[j - 1])
            {
                ans += text1[i - 1];
                i--; j--;
            }
            else if(dp[i][j] == dp[i - 1][j])
            {
                ans += text1[i - 1];
                i--;
            }
            else
            {
                ans += text2[j - 1];
                j--;
            }
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }
};

11.10 最长递增子序列系列

最长递增子序列裸题 lc300

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        int dp[n];
        int ans = 0, ma = 1;
        for(int i = 1; i < n; i++)
        {
            dp[i] = 1;
            for(int j = i - 1; j >= 0; j--)
            {
                if(nums[i] > nums[j])
                    dp[i] = max(dp[i], dp[j] + 1);
            }
            ma = max(ma, dp[i]);
        }
        return ans;
    }
};

最长递增子序列的方案数 lc673

class Solution {
public:
    int findNumberOfLIS(vector<int>& nums) {
        int n = nums.size();
        int dp[n], g[n];
        int ma = 1, ans = 0;;
        for(int i = 0; i < n; i++)
        {
            dp[i] = 1; g[i] = 1;
            for(int j = 0; j < i; j++)
            {
                if(nums[i] > nums[j])
                {
                    if(dp[i] < dp[j] + 1)
                    {
                        g[i] = g[j];
                        dp[i] = dp[j] + 1;
                    }
                    else if(dp[i] == dp[j] + 1)
                        g[i] += g[j];
                }
            }
            ma = max(ma, dp[i]);
        }
        for(int i = 0; i < n; i++)
        {
            if(dp[i] == ma)
                ans += g[i];
        }
        return ans;
    }
};

11.12 多米诺和托米诺平铺 lc 790

深刻体会到定义dp数组的重要性,定义好dp数组答案就呼之欲出了

class Solution {
public:
    const int mod = 1e9 + 7;
    int numTilings(int n) {
        long long dp[n + 1][4];
        memset(dp, 0, sizeof dp);
        dp[0][3] = 1;
        for(int i = 1; i <= n; i++)
        {
            dp[i][0] = dp[i - 1][3] % mod;
            dp[i][1] = (dp[i - 1][2] + dp[i - 1][0]) % mod;
            dp[i][2] = (dp[i - 1][1] + dp[i - 1][0]) % mod;
            dp[i][3] = (dp[i - 1][0] + dp[i - 1][3] + dp[i - 1][1] + dp[i - 1][2]) % mod;
        }
        return dp[n][3];
    }
};

在这里插入图片描述

11.14 不同的子序列 lc115

题目翻译一下是从s的子序列中选出t的个数

class Solution {
public:
    const int mod = 1e9 + 7;
    int numDistinct(string s, string t) {
        int sn = s.size(), tn = t.size();
        int dp[sn + 1][tn + 1];
        //dp[i][j]表示st中使用前i个去匹配t中的前j个用的个数
        memset(dp, 0, sizeof dp);
        for(int i = 0; i <= sn; i++)
            dp[i][0] = 1;
        for(int i = 1; i <= sn; i++)
        {
            for(int j = 1; j <= tn; j++)
            {
                //匹配上了:我们可以选择用不用s[i]去匹配,不用是s[i]是后面可能还有,我们先不用s[i]
                if(s[i - 1] == t[j - 1])
                    dp[i][j] = (dp[i - 1][j - 1] + dp[i - 1][j]) % mod;
                else
                    dp[i][j] = dp[i - 1][j];
            }
        }
        return dp[sn][tn];
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值