class Solution {
public:
bool arrayStringsAreEqual(vector& word1, vector& 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;
}
};
序列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
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;
}
};
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;
}
};
一道很奇妙的数学题
一道括号栈模拟,想清楚每一步即可
class Solution {
public:
stack op;
stack 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();
}
};
很烦的一道题,在于我没有理清逻辑,一步一步的写
分为加逗号和加小数点,加完逗号去加小数点。判断小数点是否合理:整数部分如果大于1个数的话开头不能是0,小数部分最后不能是0。
class Solution {
public:
vector ambiguousCoordinates(string s) {
s = s.substr(1, s.size() - 2);
int n = s.size();
vector ans;
for(int i = 1; i < n; i++)
{
vector xp = addPoint(s.substr(0, i));
vector 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 addPoint(string s)
{
if(s.size() == 1) return {s};
vector 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;
}
};
简单dfs可以过,注意mines数组不是图里的二维矩阵,需要先转换一下。复杂度 O ( n 2 ) 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>& 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>& mines) {
n = nn;
vector> grid(n, vector(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 ( n 2 ) O(n^2) O(n2)
class Solution {
public:
int orderOfLargestPlusSign(int n, vector>& mines) {
int ans = 0;
vector> grid(n, vector(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;
}
};
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);
}
};
图中的最短路径:BFS很好用
这道题的关键在于visited数组不是取决于这个坐标是否访问过,如果获得了新的钥匙可以重新返回原来的路径,也就是还需要加入钥匙的状态,需要状态压缩。
allKeyState = (1 << keyN) - 1
1向左左移keyN然后-1使得全为1(k >> ch) & 1
k向右移ch位并获取k | (1 << ch)
class Solution {
public:
struct State{
int x, y, key;
};
int shortestPathAllKeys(vector& 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 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>> visited(m, vector>(n, vector(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;
}
};
还行比较简单
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;
}
};
通过计算我们可以得到avg(A)=avg(B) = avg(nums),sum(A) = sum(nums) * k / n,为了防止浮点数,我们可以将所有数*n再减去avg(nums),这样就转换成在n个数中找k个数使他们的和为0即可。也就是n个数每个数的选与不选问题,复杂度是 2 n 2^n 2n,n取到30太大了。考虑折半
也就是说分为A0和A1两个数组,各一半。从A0中找tot,A1中找-tot,加一块等于0也行。或者说在A0/A1中就找到了和为0的也行。但是有一种情况不行,就是全选左边(或者右边,全选左边就代表也必须全选右边因为lsum=-rsum,这样A数组就全选了所有nums中的,B就没有了)
二进制枚举问题
n个数中选取k个的方案问题,一共有[0, 2 n − 1 2^n - 1 2n−1]这些方案。
遍历这些方案for(int i = 0; i < (1 << n); i++)
获取每个方案中是否选了第j个 if(i & (1 << j))
class Solution {
public:
bool splitArraySameAverage(vector& 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 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;
}
};
dp[i]表示当前位置能否跳到,一个一个跳着向前找。
适当剪枝:如果跳几个就已经可以了,就不必向前找了。
class Solution {
public:
bool canJump(vector& 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& 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]都可以。
不用拘泥于每次究竟跳跳几步,而是看覆盖范围,覆盖范围内一定是可以跳过来的,不用管是怎么跳的。
在前面dp的基础上改进即可,找到前面能够跳跃的位置+1,取每个的最小值。5%太慢了, O ( n 2 ) O(n^2) O(n2)
class Solution {
public:
int jump(vector& 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];
}
};
贪心解法:
class Solution {
public:
int rob(vector& 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];
}
};
分两种情况:
class Solution {
public:
int rob(vector& 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]);
}
};
前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];
}
};
添加了一个通配符,把每个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];
}
};
不太理解这个差值
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& 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;
}
};
每次都是合并相邻两堆:这样最后合并的两堆一定是左边区间和右边区间
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 ] + s u m ( 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];
完全背包问题
class Solution {
public:
bool wordBreak(string s, vector& 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];
}
};
暴力dfs过了
class Solution {
public:
vector ans;
int n;
vector wordBreak(string s, vector& wordDict) {
n = s.size();
string tmp = "";
dfs(s, wordDict, 0, 0, tmp);
return ans;
}
void dfs(string s, vector& 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而不是""
记忆化搜索: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;
}
};
一道想不到的dp,题解第二次做的时候再写吧
题解来啦!
嗯嗯做第二遍是有意义的,第一遍很多地方没搞明白。
首先要将一个人走两遍转换成两一个一块走走到最后,然后dp状态是两个人的坐标即dp[x1] [y1] [x2] [y2],由于两个人一块走,走的步数一样都是k=横纵坐标之和。所以可以简略成dp[k] [x1] [x2]。
const int N = 55;
class Solution {
public:
int cherryPickup(vector>& 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];
}
};
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);
}
};
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];
}
};
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;
}
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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;
}
};
返回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> f(n + 1, vector(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];
}
};
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jMQ0KJzP-1668236421450)(11_leetcode.assets/image-20221111114814671.png)]
如果a和b长度不相等,那么那个长的就是最长特殊序列,因为短的里面没有
如果a和b长度相等且a!= b也是同理,a整个长度在b中没有,答案就是二者的长度
如果a== b那么没有
返回一个字符串数组中的最长特殊子序列
使用每个字符串和其他字符串比较,看自身是否是其他所有字符串的最长公共子序列
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int n = text1.size(), m = text2.size();
vector> f(n + 1, vector(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& 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;
}
};
以两个字符串(l = m, l = n)为公共子序列的最短字符串(l = k + m - k + n - k)
思路就是:先找到最短公共子序列(l = k),然后在其基础上填填补补。
但是没想到用dp去做,这算是一个dp求解具体方案数的一个问题。
根据dp结果反推前面的情况是什么
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;
}
};
class Solution {
public:
int lengthOfLIS(vector& 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;
}
};
class Solution {
public:
int findNumberOfLIS(vector& 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;
}
};
深刻体会到定义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];
}
};
题目翻译一下是从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];
}
};