杂题部分
杂题部分
1.3131. 找出与数组相加的整数 I
思路:快排+直接找差值
void quickSort(int *arr,int low,int high) { if(high<=low) return; int i=low; int j=high; int pivot=arr[low]; while(1) { // 1. 自左到右搜索,直到比pivot大后停止。 while(arr[i]<=pivot) { i++; if(i==high) break; } // 2. 自右到左搜索,直到比pivot小后停止。 while(arr[j]>=pivot) { j--; if(j==low) break; } // 3. 两个指针相遇时或跨越后,停止。 if(i>=j) break; // 4.交换两个指针的值。 int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } // 交换pivot与右指针的值 arr[low]=arr[j]; arr[j]=pivot; // 分段进行快排 quickSort(arr,low,j-1); quickSort(arr,j+1,high); } int addedInteger(int* nums1, int nums1Size, int* nums2, int nums2Size) { quickSort(nums1,0,nums1Size-1); quickSort(nums2,0,nums2Size-1); return nums2[0]-nums1[0]; }
2.3132. 找出与数组相加的整数 II
解法如下(3132)
void quickSort(int *arr,int low,int high) { if(high<=low) return; int i=low; int j=high; int pivot=arr[low]; while(1) { // 1. 自左到右搜索,直到比pivot大后停止。 while(arr[i]<=pivot) { i++; if(i==high) break; } // 2. 自右到左搜索,直到比pivot小后停止。 while(arr[j]>=pivot) { j--; if(j==low) break; } // 3. 两个指针相遇时或跨越后,停止。 if(i>=j) break; // 4.交换两个指针的值。 int temp=arr[i]; arr[i]=arr[j]; arr[j]=temp; } // 交换pivot与右指针的值 arr[low]=arr[j]; arr[j]=pivot; // 分段进行快排 quickSort(arr,low,j-1); quickSort(arr,j+1,high); } int minimumAddedInteger(int* nums1, int nums1Size, int* nums2, int nums2Size) { quickSort(nums1,0,nums1Size-1); quickSort(nums2,0,nums2Size-1); for(int i=2;i>=0;i--) { int left=i+1,right=1; while(left<nums1Size && right < nums2Size) { if(nums1[left]-nums2[right]==nums1[i]-nums2[0]) right++; left++; } if(right==nums2Size) return nums2[0]-nums1[i]; } return 0; }
3.3148. 矩阵中的最大得分
思路:两种方法
方法一:动态规划+前缀和
思路与算法
记
对于往右走,我们有:
这里的
就算不考虑往右走的情况,上述状态转移方程的时间复杂度也达到了
注意到
记
此时状态转移方程变为简单的递推:
同理,对于往左走,我们有:
这里
题解
int maxScore(int** grid, int gridSize, int* gridColSize) { // dp 2-dimensional matrix. int **dp=(int**)malloc(sizeof(int*)*gridSize); int **preRow=(int**)malloc(sizeof(int*)*gridSize); int **preCol=(int**)malloc(sizeof(int*)*gridSize); for(int i=0;i<gridSize;i++) { dp[i]=(int*)malloc(sizeof(int)*gridColSize[i]); preRow[i]=(int*)malloc(sizeof(int)*gridColSize[i]); preCol[i]=(int*)malloc(sizeof(int)*gridColSize[i]); for(int j=0;j<gridColSize[i];j++) { dp[i][j]=-1e8; preRow[i][j]=0; preCol[i][j]=0; } } int ans=-1e8; for(int i=0;i<gridSize;i++) { for(int j=0;j<gridColSize[i];j++) { if(i>0) dp[i][j]=dp[i][j]>grid[i][j]+preCol[i-1][j]?dp[i][j]:preCol[i-1][j]+grid[i][j]; if(j>0) dp[i][j]=dp[i][j]>grid[i][j]+preRow[i][j-1]?dp[i][j]:grid[i][j]+preRow[i][j-1]; ans=ans>dp[i][j]?ans:dp[i][j]; preRow[i][j]=preCol[i][j]=(dp[i][j]>0?dp[i][j]:0)-grid[i][j]; if(i>0) preCol[i][j]=preCol[i][j]>preCol[i-1][j]?preCol[i][j]:preCol[i-1][j]; if(j>0) preRow[i][j]=preRow[i][j]>preRow[i][j-1]?preRow[i][j]:preRow[i][j-1]; } } return ans; }
4.2552.统计四元上升数组
思路
解法
class Solution { public: long long countQuadruplets(vector<int>& nums) { int n=nums.size(); vector<int> pre(n+1); long long ans=0,cur=0; for(int j=0;j<n;j++) { cur=0; for(int k=n-1;k>j;k--) // 从后往前进行枚举 { if(nums[j]>nums[k]) ans += (long long)(pre[nums[k]])*cur; else cur++; } for (int x=nums[j]+1;x<=n;x++) pre[x]++; // 统计从0到j-1的小于x的数 } return ans; } };
5.2306. 公司命名
思路(Hash set)
将每一个字符以首字母为key,去掉首字母为后缀存储在hashset中。这样我们就有
a: str1,str2,str3...
b: str1,str2,str3...
然后我们观察是否有重复的后缀。有则去掉。这样就有了
ans+=(a-repeat)*(b-repeat)
作为最终结果。
Code
class Solution { public: int get_size(unordered_set<string> &a, unordered_set<string> &b) { // Count for the intersected suffix. int ans=0; for(const string &s:a) { if(b.count(s)) ans++; } return ans; } long long distinctNames(vector<string>& ideas) { unordered_map<char, unordered_set<string> > names; // Hash set put all the names with the same first letter together. for (string &idea:ideas) { names[idea[0]].insert(idea.substr(1,idea.size()-1)); } long long ans=0; // Check for intersects and count. for(auto [key1,val1]:names) { for(auto [key2,val2]:names) { if(key1==key2) continue; int intersections=get_size(val1,val2); ans+=static_cast<long long> (val1.size()-intersections)*(val2.size()-intersections); } } return ans; } };
6.交替组II
思路:组合数学
统计交替的数字出现的次数。当交替数字出现的频数>=k时,说明出现了一个符合交替的数组。否则,重新将cnt设为1.
注意因为是循环数组,因此我们需要前进到i=n+k-2.
class Solution { public: int numberOfAlternatingGroups(vector<int>& colors, int k) { int cnt = 1; int n = colors.size(); int ans = 0; for(int i = 1; i < n + k - 1; i ++) { if(colors[i % n] != colors[(i-1) % n]) cnt ++; else cnt = 1; if(cnt >= k) ans ++; } return ans; } };
7.3251. 单调数组对的数目II
方法:前缀和 + 二维动态规划
主问题:从下标0到n-1,求取满足arr1单调递增且arr2单调递减(非严格)且arr1[i]+arr2[i]=nums[i]
的数组的数目。
子问题:从下标0到下标i-1,满足上述条件数组的数目。
维度扩展:计算当arr1[i]=j
时满足条件的数组数目dp[i][j]
.
这样,我们需要满足条件就是:arr1[i-1]<=j
,并且nums[i-1] - arr1[i-1] >= nums[i]-j
。 这样我们就有:
我们可以写成紧凑形式:arr[i-1]<=j+min(nums[i-1]-nums[i],0)
这样我们就可以累加得到
采用前缀和维护,也就是
时间复杂度:因为需要计算nums的大小的行和对对应的nums[i]进行迭代,因此为
空间复杂度:如上。
class Solution { public: int countOfPairs(vector<int>& nums) { int n = *(max_element(nums.begin(),nums.end()))+1; int m = nums.size()+1; const int MOD = 1e9+7; // 0. The dynamic arrays. vector<vector<long long> > dp(m,vector<long long >(n,0)); vector<long long> sum(n,0); // 1. Initial value. for(int i=0;i <= nums[0]; i++) { dp[0][i] = 1; } partial_sum(dp[0].begin(),dp[0].end(),sum.begin()); // 2. dp. for(int i = 1; i < m-1; i++) { for(int j = 0; j <= nums[i]; j++) { int flag = j + min(nums[i-1]-nums[i],0); dp[i][j] = flag < 0 ? 0 : sum[flag] % MOD; } partial_sum(dp[i].begin(),dp[i].end(),sum.begin()); } // 3. final answer: long long ans = 0; for(int i = 0; i <= nums[m-2]; i++) ans = (ans + dp[m-2][i]) % MOD; return ans; } };
8.[3282. 到達數組末尾的最大得分]
思路:遍歷得到最大的數字就進行跳躍
class Solution { public: long long findMaximumScore(vector<int>& nums) { long long ans = 0; int mx = 0; for (int i = 0; i + 1 < nums.size(); i++) { mx = max(mx, nums[i]); ans += mx; } return ans; } };
9.51. N皇后
思路:回溯+记录对角线元素2n+1
题解9
class Solution { public: void dfs(vector<vector<string> > &ans, vector<int> &queens, vector<int> &cols, vector<int> &diag1, vector<int> &diag2, int r, int n) { // 1. 递归终点:到达最后一行。根据记录下来的第i行所在的第几列编写答案。 if (r==n) { vector<string> temp; for(auto i:queens) { string s = string(i, '.')+'Q'+string((n-1-i),'.'); temp.push_back(s); } ans.push_back(temp); return; } // 2. 递归部分。对每一行进行递归。 for(int i = 0; i < cols.size(); i++) { if (!cols[i] && !diag1[r+i] && !diag2[r-i+n-1]) { // 没有违反情况的地方放置。 queens[r]=i; cols[i]=1; diag1[r+i]=1; diag2[r-i+n-1]=1; // 进入下一步递归。 dfs(ans,queens,cols,diag1,diag2,r+1,n); // 发现没有情况满足时回溯。 // 抹去曾经放置的。 cols[i]=0; diag1[r+i]=0; diag2[r-i+n-1]=0; } } } vector<vector<string>> solveNQueens(int n) { vector<vector<string> > ans; vector<int> queens(n,0); // 皇后放置在第i行,第queens[i]列上。 /** * 主对角线上行数-列数不变,总共有2n-1个对角线。也就是r-c+n-1. * 副对角线上列数+行数不变,有2n-1个对角线。也就是r+c。 */ vector<int> cols(n,0), diag1(2*n-1,0), diag2(2*n-1,0); dfs(ans,queens,cols,diag1,diag2,0,n); return ans; } };
10.2056. 棋盘上有效移动组合的数目
题解10
struct pos { int x,y; // 起点。 int dx,dy; // 移动的方向。 int step; // 移动 }; // 确定移动方向。 vector<pair<int,int> > directions = {{-1,0},{1,0},{0,-1},{0,1},{1,1},{-1,1},{-1,-1},{1,-1}}; // 棋子类型。 string rook = "rook"; string bishop = "bishop"; string queen = "queen"; // 棋盘大小。 const int SIZE = 8; class Solution { public: // 计算位于(x,y)的棋子在可以移动方向上的合法移动到的位置。 vector<pos> generate_pos(int x0, int y0, vector<pair<int,int> > &directions) { vector<pos> positions{{x0,y0,0,0,0}}; for(auto [dx, dy]:directions) { int x = x0+dx; int y = y0+dy; for(int step = 1; x > 0 && x <= SIZE && y > 0 && y <= SIZE; step++) { positions.emplace_back(x0,y0,dx,dy,step); x += dx; y += dy; } } return positions; } // 判断合法性。 bool is_valid(pos &p1, pos &p2) { int x1 = p1.x; int y1 = p1.y; int x2 = p2.x; int y2 = p2.y; for(int i=0; i < max(p1.step, p2.step); i++) { // 一起各走一步。 if(i < p1.step) x1 += p1.dx, y1 += p1.dy; if(i < p2.step) x2 += p2.dx, y2 += p2.dy; if(x1 == x2 && y1 == y2) return false; } return true; } void dfs(int &ans, int i, int n, vector<vector<pos> > &all_legal_moves, vector<pos> &path) { if(i == n) { ans ++; return; } for(auto &pos1: all_legal_moves[i]) { bool flag = true; for(int j = 0; j < i; j++) { if(!is_valid(pos1, path[j])) { flag = false; break; } } if(flag) { path[i] = pos1; dfs(ans, i+1, n, all_legal_moves, path); } } } int countCombinations(vector<string>& pieces, vector<vector<int>>& positions) { // 具体到棋子。 vector<pair<int,int> >queen_directions = directions; vector<pair<int,int> > rook_directions; vector<pair<int,int> > bishop_directions; for(int i = 0; i < 4; i++) { rook_directions.push_back(directions[i]); bishop_directions.push_back(directions[i+4]); } int n = pieces.size(); // initialize legal movement。 vector<vector<pos> > all_legal_moves(n); for(int i = 0; i < n; i++) { string type = pieces[i]; if(type == rook) all_legal_moves[i] = generate_pos(positions[i][0], positions[i][1], rook_directions); else if(type == queen) all_legal_moves[i] = generate_pos(positions[i][0], positions[i][1], queen_directions); else if(type == bishop) all_legal_moves[i] = generate_pos(positions[i][0], positions[i][1], bishop_directions); } vector<pos> path(n); int ans = 0; dfs(ans, 0, n, all_legal_moves, path); return ans; } };
11.767. 重构字符串
思路:贪心构造
设一个字符串的长度为
题解10.1: 基于最大堆的贪心
每次取出最大堆中的出现次数最多的两个字母进行重构。剩下一个字母时直接拼接即可。
这里搓了一个自己喜欢的最大堆。
template<class T> class heap { /** * @brief The left child is 2*i+1 and the right child is 2*(i+1). Heap stored in the vector. */ private: vector<T> container; vector<int> counter; int _size; // size for currently how many of them were inside. int _capacity; // The container's size; function<bool(const T&, const T&, const vector<int>&)> comp; void swap(int id1, int id2) { T temp = container[id1]; container[id1] = container[id2]; container[id2] = temp; } void heap_sort() { for(int i = 1; i < _size; i++) { int index = i; int parent_index = index % 2 == 0 ? index / 2 - 1 : index / 2; adjust_bottom_up(index, parent_index); } } // Adjust heap. Compared with the parent. When true, up. void adjust_bottom_up(int index, int parent_index) { // 子节点对母节点偏序关系成立,交换。 while(index > 0 && comp(container[index], container[parent_index], counter)) { swap(parent_index, index); index = parent_index; parent_index = index % 2 == 0 ? index / 2 - 1 : index / 2; } } // for heap top to bottom. void adjust_top_bottom(int index) { while(index < _size) { int left_child = index * 2 + 1, right_child = (index+1) * 2; if(left_child < _size && comp(container[left_child], container[index], counter)) { if(right_child < _size && comp(container[right_child], container[index], counter) && comp(container[right_child], container[left_child], counter)) { swap(right_child,index); index = right_child; } else { swap(left_child,index); index = left_child; } } else if(right_child < _size && comp(container[right_child], container[index], counter)) { swap(right_child,index); index = right_child; } else break; } } public: friend class Solution; heap(function<bool(const T&, const T&, const vector<int>&)> &cmp) { container = vector<T>(); counter = vector<T>(); _size = 0; _capacity = 0; comp = cmp; } heap(vector<T> &data, vector<int> &cnt, bool(*cmp)(const T&, const T&, const vector<int>&)) { if(data.size()!=cnt.size()) throw "Error! Size not match!\n"; comp = *cmp; container = data; counter = cnt; _size = data.size(); _capacity = _size; heap_sort(); } heap(vector<int> &cnt, bool(*cmp)(const T&, const T&, const vector<int>&)) { comp = *cmp; container = vector<T>(); counter = vector<int>(cnt); _size = 0; _capacity = 0; heap_sort(); } T top() const { return container[0]; } void pop() { container[0] = container[_size - 1]; _size--; adjust_top_bottom(0); } void print() { for(int i = 0; i < _size; i++) { cout << "element: " << container[i] << " count: " << counter[container[i] - 'a']; cout << '\n'; } } void push(T element) { _size++; if(_size > container.size()) container.push_back(element); else container[_size-1] = element; int index = _size-1; int parent_index = index % 2 == 0 ? index / 2 - 1 : index / 2; adjust_bottom_up(index, parent_index); } int size() { return _size; } int &operator[](int index) { return counter[index]; } bool empty() { return _size == 0; } }; class Solution { public: static bool compare(const char &a, const char &b, const vector<int> &count) { return count[a-'a'] > count[b-'a']; } bool (*comp)(const char &, const char &, const vector<int> &) = compare; string reorganizeString(string s) { string ans = ""; vector<int> count(26,0); int maxcount = 0; for(auto ch: s) { count[ch-'a']++; maxcount = max(maxcount, count[ch-'a']); } if(maxcount > (s.length() + 1)/2) return ans; heap h(count,comp); for(int i = 0; i < 26; i++) { if(count[i]) h.push(char (i+'a')); } while(h.size() > 1) { char temp1,temp2; temp1=h.top(); ans.push_back(temp1); h.pop(); temp2=h.top(); ans.push_back(temp2); h.pop(); h[temp1-'a']--; h[temp2-'a']--; if(h[temp1-'a']) h.push(temp1); if(h[temp2-'a']) h.push(temp2); } if(!h.empty()) { ans.push_back(h.top()); } return ans; } };
12.78.子集I
思路一:迭代法
利用二进制进行枚举,从而可以得到所有的子集。
例如集合
二进制 | 集合 | 十进制 |
---|---|---|
000 | {} | 0 |
001 | 1 | |
010 | 2 | |
011 | 3 | |
100 | 4 | |
101 | 5 | |
110 | 6 | |
111 | 7 |
这样通过二进制进行枚举得到子集即可。这里我们通过 &
符号可以快速进行转换。我们让迭代从0到7,并通过&判断每一位上是否具有置位1。
class Solution { public: vector<vector<int>> subsets(vector<int>& nums) { vector<vector<int> > ans; vector<int> temp; int n = nums.size(); for(int i=0; i < (1<<n); i++) { temp = vector<int>(); // 3个位的bit置位判断。 for(int j=0; j<n; j++) { if(i & (1 << j)) temp.push_back(nums[j]); } ans.push_back(temp); } return ans; } };
思路二:递归实现子集枚举
采用dfs,dfs将会判断是否会选择当前数进入。选择了则进入递归,否则不选择。
13.90.子集II
思路一:迭代去重
假设我们有集合
二进制 | 集合 | 十进制 |
---|---|---|
000 | {} | 0 |
001 | 1 | |
010 | 2 | |
011 | 3 | |
100 | 4 | |
101 | 5 | |
110 | 6 | |
111 | 7 |
我们发现当经过排序后,上个数没有选择并且当前被选择的数字与上一个相同,则需要跳过,不需要选择。因此我们有:
class Solution { public: vector<vector<int>> subsetsWithDup(vector<int>& nums) { vector<int> temp; vector<vector<int> > ans; int n = nums.size(); sort(nums.begin(),nums.end()); for(int i = 0; i < (1 << n); i++) { temp = vector<int>(); int flag = 1; for(int j = 0; j < n; j++) { // 判断是否有没有置位 if(i & (1 << j)) { // 出现1-0情况:该位置位,上一位未置位并且相同。 if(j > 0 && !(i & (1 << (j-1))) && (nums[j] == nums[j-1])) { flag = 0; break; } else temp.push_back(nums[j]); } } if(flag) ans.push_back(temp); } return ans; } };
寻找最大值最小化:二分
对于每一个袋子,假设最后最大值为
那么这样我们可以从1到
class Solution { public: int minimumSize(vector<int> & nums, int maxOperations) { // 采用二分查找的模式。 int left = 1,right = 0; for(auto i: nums) right = right > i ? right : i; long long sumOp = 0; int ans; while(left <= right) { sumOp = 0; int optimized = (left + right) / 2; for(auto i: nums) sumOp += (i - 1) / optimized; if(sumOp > maxOperations) left = optimized + 1; else { right = optimized - 1; ans = optimized; } } return ans; } };
13.2209.用地毯覆盖后最少的白色砖块
通过观察知道,我们需要选择动态规划来解决这样的问题。
动态规划
确认子问题
- 原问题可以定义成:从
地砖上,铺上j块地毯使得其变得更短。 - 我们需要从右向左覆盖地砖。因为从左向右覆盖地砖需要我们倒过来进行处理(从字符串最末尾处理到开头),因此我们在第
块地砖上铺上地毯,则 将都被铺上地毯。 - 这样的话,地毯数量将会增加1. 问题将会变成:从
块地砖上,铺上 块地砖使得其变得更短。 - 这样我们就需要关注两个指标:
- i: 从
上字符串(地板)的情况。 - j: 采用了多少块毯子。
- i: 从
确定转移方程
-
我们根据上面的讨论得到,我们可以选择在第i块地砖铺上毯子与否。
- 铺上毯子,则j变为j+1,并且地面上
全部被覆盖。因此将dp视为最小的白色瓷砖数,则就有转移方程: - 不铺上毯子,则最小白色瓷砖数将会变成前一个的转移+当前是否为白色瓷砖。
- 这里将地砖的起始下标设为1.
- 铺上毯子,则j变为j+1,并且地面上
-
这样总体的转移方程为:
class Solution { public: int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) { // Using 2-dimension dynamic programming. vector<vector<int> > dp(floor.length() + 1, vector<int>(numCarpets + 1, (int)1e5)); // 第0块作为起始,不具有白色瓷砖。 for(int i = 0; i <= numCarpets; i++) { dp[0][i] = 0; } // 不使用任何一块地毯,因此具有初始值为白色瓷块数。 for(int i = 1; i <= floor.length(); i++) { dp[i][0] = dp[i-1][0] + (floor[i - 1] == '1'); } for(int i = 1; i <= floor.length(); i++) { for(int j = 1; j <= numCarpets; j++) { if(i-carpetLen >= 0) dp[i][j] = min(dp[i-1][j] + (floor[i - 1] == '1'), dp[i-carpetLen][j-1]); else dp[i][j] = min(dp[0][j], dp[i-1][j]+(floor[i - 1] == '1')); } } return dp[floor.length()][numCarpets]; } };
空间优化
这样我们就有:
class Solution { public: int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) { vector<int> f(floor.length() + 1); // dp[][j] main we use. vector<int> g(floor.length() + 1); // dp[][j-1] prev states. g[0] = 0; // 初始状态:对于每个g[i] = dp[i][0],记录白色瓷砖数。 for(int i = 1; i <= floor.length(); i++) { g[i] = g[i-1] + (floor[i-1] == '1'); } for(int j = 1; j <= numCarpets; j++) { // 对于每个f[0] = dp[0][j],均等于0. f[0] = 0; for(int i = 1; i <= floor.length(); i++) { // g[i-carpetLen] = dp[i-carpetLen][j-1] // f[i-1] = f[i-1][j] f[i] = min(g[max(0, i - carpetLen)], f[i - 1] + (floor[i-1] == '1')); } // 转移给g上一个状态。 g = f; } return f[floor.length()]; } };
那么,我们是否还可以进行进一步的优化呢?注意到我们的最小值仅取决于已经计算好的j块地毯的值和先前状态的值,我们就可以首先利用先前状态和初始值计算出一个已经计算好的值(从尾部到头部),再从头部到尾部利用计算好的值进行计算。
class Solution { public: int minimumWhiteTiles(string floor, int numCarpets, int carpetLen) { vector<int> f(floor.length() + 1); // dp[][j] main we use. f[0] = 0; for(int i = 1; i <= floor.length(); i++) { f[i] = f[i-1] + (floor[i-1] == '1'); } for(int j = 1; j <= numCarpets; j++) { f[0] = 0; // 现在拥有j-1状态的值,于是从尾部到头部计算。 for(int i = floor.length(); i > 0; i--) f[i] = min(f[max(0, i-carpetLen)], f[i]); // 现在拥有了计算好的值,再从头部到尾部计算。 for(int i = 1; i <= floor.length(); i++) f[i] = min(f[i], f[i-1] + (floor[i-1] == '1')); } return f[floor.length()]; } };
14.2222.选择建筑的方案数
方法一:计算101和010出现的数目
- 题目中仅有3栋建筑选择。
- 因此我们只需要考虑交叉的建筑物选择,101或010.
- 那么只需要得到0左右两边的建筑数目,1左右两边的建筑数目就可以了。
class Solution { public: long long numberOfWays(string s) { using ll = long long; vector<ll> count0, count1; ll countFor0 = 0, countFor1 = 0; for(auto i: s) { if(i == '1') { countFor1++; } else { countFor0++; } count0.push_back(countFor0); count1.push_back(countFor1); } ll sumOf0 = count0.back(), sumOf1 = count1.back(); ll ans = 0; for(int i = 0; i < s.length(); i++) { if(s[i] == '1') { ans += count0[i] * (sumOf0 - count0[i]); } else ans += count1[i] * (sumOf1 - count1[i]); } return ans; } };
方法二:空间优化
15.131.分割回文串(2025.03.01)
回溯法
-
首先,我们需要先了解最基础的处理回文串的方法。
-
我们可以定义s[0,...,i-1]为一个回文串。接下来我们就需要的是判断s[i,...,j]是不是一个回文字符串即可。
-
这样,我们可以采用递归的方式进行:
- 首先从s[0,...,i1],当i1为回文字符串时,进入新的递归,递归起点为i1+1。
- 接着从s[i1+1,...,i2],继续进行递归。
- 到达末尾,结束,储存分割结果,并回溯,并继续上述递归的遍历(例如从i1+1 到 i2+1)。
-
但是这样会导致出现重复判断。
-
对于字符串
aaba
,我们有:- s[0] -> s[1] -> s[2] -> s[3], 递归第一次到达终点,有
a,a,b,a
- 回溯到s[2,3],不是回文字符串。继续回溯。
- s[0] -> s[1,2,3], 储存
a,aba
。 - s[0,1] -> s[2] -> s[3] 有
aa,b,a
- s[0,1] -> s[2,3] x
- s[0,1,2] x
- s[0,1,2,3] x
- s[0] -> s[1] -> s[2] -> s[3], 递归第一次到达终点,有
-
这里会出现重复进行判断的情况。对于s[2,3]可能有重复判断多次的情况。我们可以使用记忆化搜索或者是预处理来解决这样的问题。
记忆化搜索
我们可以发现,我们可以建立记忆化搜索方法,判断是否为回文字符串后,直接利用结果进行使用即可。例如,针对s="aaba"
,我们进行记忆化搜索后,可以实现:
0 | 1 | 2 | 3 | |
---|---|---|---|---|
0 | 1 | 1 | 0 | 0 |
1 | / | 1 | 0 | 1 |
2 | / | / | 1 | 0 |
3 | / | / | / | 1 |
预先处理
我们发现我们可以利用我们已经做好的判断,来辅助我们进行的回文字符串的判断。当我们的s[i,...,j]为回文字符串,且s[i-1],s[j+1]均为相同字符时,字符串可以从中间扩展开来。这样我们就有
class Solution { public: vector<vector<string> > partition(string s) { int n = s.length(); vector<vector<int> > isPalindrome(n, vector<int>(n,0)); // First: pre dealing with the palindromes. for(int i = 0; i < n; i++) { for(int j = 0; j <= i; j++) { isPalindrome[i][j] = 1; } } for(int i = n - 2; i >= 0; i--) { for(int j = n - 1; j > i; j--) { if(isPalindrome[i+1][j-1] && s[i]==s[j]) { isPalindrome[i][j] = 1; } } } vector<vector<string> > ans; vector<string> tmp; // 进入dfs回溯。 auto dfs = [&](this auto && self, int i){ if(i == n) { ans.push_back(tmp); return; } for(int j = i; j < n; j++) { if(isPalindrome[i][j]) { tmp.push_back(s.substr(i,j-i+1)); self(j+1); tmp.pop_back(); } } }; dfs(0); return ans; } };
15.132.分割回文串II(2025.03.02)
预先处理+dp转移
我们还是像上次一样进行预处理后,进行动态规划的转移。我们可以:
- 枚举从0到i的回文字符串,选择其中分裂最少的值。
- 当当前的j使得0到j不是回文字符串时,有:
- dp[j] = min(dp[i]) + 1
- 其中i+1到j是回文字符串。
- 当自身为回文字符串时,就不再需要分割了,因此处理为0.
class Solution { public: int minCut(string s) { int n = s.length(); vector<vector<int> > isPalindrome(n, vector<int>(n, 0)); for(int i = 0; i < n; i++) { for(int j = 0; j <= i; j++) { isPalindrome[i][j] = 1; } } for(int i = n-2; i >= 0; i--) { for(int j = i+1; j < n; j++) { if(isPalindrome[i+1][j-1] && s[i] == s[j]) isPalindrome[i][j] = 1; } } vector<int> ans(n, (int)1e9); for(int i = 0; i < n; i++) { // 当还是回文串时,记成0 if(isPalindrome[0][i]) ans[i] = 0; else { for(int j = 1; j <= i; j++) { if(isPalindrome[j][i]) { ans[i] = min(ans[i], ans[j - 1] + 1); } } } } return ans[n-1]; } };
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了