手把手带你刷Leetcode力扣|各个击破数据结构和算法|大厂面试必备技能【已完结】_哔哩哔哩_bilibili
数组
时间复杂度——访问:O(1) 搜索:O(N) 插入:O(N) 删除:O(N)
485.最大连续1的个数
给定一个二进制数组 nums , 计算其中最大连续 1 的个数。
示例 1:
输入:nums = [1,1,0,1,1,1]
输出:3
解释:开头的两位和最后的三位都是连续 1 ,所以最大连续 1 的个数是 3.
int findMaxConsecutiveOnes(int* nums, int numsSize){
int m=0,n=0;
for(int i=0;i<numsSize;i++){
if(nums[i]==1)
{
m++;
if(m>n)
n=m; //n记录最大连续个数
}
else
m=0;
}
return n;
}
283.移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
请注意 ,必须在不复制数组的情况下原地对数组进行操作。
示例 1:
输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
void moveZeroes(int* nums, int numsSize){
int m = 0; int n = 0;
for (int i = 0; i < numsSize; i++){
if (nums[i] == 0)
m++;
else
nums[n++] = nums[i];//把非零数移到前面
}
for (int i =numsSize-1;i>numsSize-1-m; i--)
nums[i] = 0;
}
链表
时间复杂度——访问:O(N) 搜索:O(N) 插入:O(1) 删除:O(1)
203.移除链表元素
给你一个链表的头节点 head
和一个整数 val
,请你删除链表中所有满足 Node.val == val
的节点,并返回 新的头节点 。
输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]
struct ListNode* removeElements(struct ListNode* head, int val){
struct ListNode phead;
phead.next = head;
struct ListNode* p1 = &phead;
struct ListNode* p2 = head;
while (p2 != NULL){
if (p2->val == val){
p1->next = p2->next;
p2 = p2->next;
}
else{
p1 = p1->next;
p2 = p2->next;
}
}
return phead.next;
}
206.反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
struct ListNode* reverseList(struct ListNode* head){
if (head == NULL)
return NULL;
struct ListNode* p1 = head;
struct ListNode* p2 = head->next;
while (p2 != NULL){
p1->next = p2->next;
p2->next = head;
head = p2;
p2 = p1->next;
}
return head;
}
队列
时间复杂度——访问:O(N) 搜索:O(N) 插入:O(1) 删除:O(1)
933.最近的请求个数
写一个 RecentCounter 类来计算特定时间范围内最近的请求。
请你实现 RecentCounter 类:
RecentCounter() 初始化计数器,请求数为 0 。
int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。
保证 每次对 ping 的调用都使用比之前更大的 t 值。
示例 1:
输入:
["RecentCounter", "ping", "ping", "ping", "ping"]
[[], [1], [100], [3001], [3002]]
输出:
[null, 1, 2, 3, 3]
解释:
RecentCounter recentCounter = new RecentCounter();
recentCounter.ping(1); // requests = [1],范围是 [-2999,1],返回 1
recentCounter.ping(100); // requests = [1, 100],范围是 [-2900,100],返回 2
recentCounter.ping(3001); // requests = [1, 100, 3001],范围是 [1,3001],返回 3
recentCounter.ping(3002); // requests = [1, 100, 3001, 3002],范围是 [2,3002],返回 3
class RecentCounter {
public:
RecentCounter() {
}
int ping(int t) {
q.push(t);
while(t-q.front()>3000)
q.pop();
return q.size();
}
private:
queue<int> q;
};
栈
时间复杂度——访问:O(N) 搜索:O(N) 插入:O(1) 删除:O(1)
20.有效的括号
给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
输入:s = "(]"
输出:false
class Solution {
public:
bool isValid(string s) {
if(s.size()==0)
return true;
stack<int> st;
for(char ch:s){
if(ch=='('||ch=='['||ch=='{')
st.push(ch);
else{
if(st.size()==0)
return false;
else{
char temp=st.top();
st.pop();
if((ch==')'&&temp!='(')||(ch==']'&&temp!='[')
||(ch=='}'&&temp!='{'))
return false;
}
}
}
if(st.size()==0)
return true;
else
return false;
}
};
496.下一个更大元素
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
输入:nums1 = [4,1,2,7], nums2 = [1,4,2,5,7].
输出:[5,4,5,-1]
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
vector<int> arr;
stack<int> st;
for (int i = 0; i<nums2.size(); i++)
st.push(nums2[i]);
stack<int> temp;
for (int i = 0; i < nums1.size(); i++){
int max = -1;
while (st.size() != 0){
int top = st.top();
st.pop();
temp.push(top);
if (top>nums1[i])
max = top;
else if (top == nums1[i])
break;
}
while (temp.size() != 0){
st.push(temp.top());
temp.pop();
}
arr.push_back(max);
}
return arr;
}
};
哈希表
时间复杂度—— 搜索:O(1) 插入:O(1) 删除:O(1)
217.存在重复元素
给你一个整数数组 nums
。如果任一值在数组中出现 至少两次 ,返回 true
;如果数组中每个元素互不相同,返回 false
。
输入:nums = [1,2,3,1]
输出:true
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
unordered_map<int,int> m;
if(nums.size()==0)
return false;
for(int i=0;i<nums.size();i++){
if(m[nums[i]]==0)
m[nums[i]]++;
else
return true;
}
return false;
}
};
389.找不同
给定两个字符串 s
和 t
,它们只包含小写字母。
字符串 t
由字符串 s
随机重排,然后在随机位置添加一个字母。
请找出在 t
中被添加的字母。
输入:s = "abcd", t = "abcde"
输出:"e"
class Solution {
public:
char findTheDifference(string s, string t) {
unordered_map<char,int>m1;
unordered_map<char,int>m2;
for(int i=0;i<s.size();i++)
m1[s[i]]++;
for(int i=0;i<t.size();i++)
m2[t[i]]++;
for(int i=0;i<t.size();i++)
{
int m=m2[t[i]]-m1[t[i]];
if(m!=0)
return t[i];
}
return 0;
}
};
496.下一个更大的元素
nums1 中数字 x 的 下一个更大元素 是指 x 在 nums2 中对应位置 右侧 的 第一个 比 x 大的元素。
给你两个 没有重复元素 的数组 nums1 和 nums2 ,下标从 0 开始计数,其中nums1 是 nums2 的子集。
对于每个 0 <= i < nums1.length ,找出满足 nums1[i] == nums2[j] 的下标 j ,并且在 nums2 确定 nums2[j] 的 下一个更大元素 。如果不存在下一个更大元素,那么本次查询的答案是 -1 。
返回一个长度为 nums1.length 的数组 ans 作为答案,满足 ans[i] 是如上所述的 下一个更大元素.
输入:nums1 = [4,1,2], nums2 = [1,3,4,2].
输出:[-1,3,-1]
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int, int>m;
stack<int> st;
for (int i = 0; i<nums2.size(); i++)
{
st.push(nums2[i]);
if (i == nums2.size() - 1)
{
break;
}
if (nums2[i]>nums2[i + 1])
continue;
else
{
while (st.empty() == 0 && st.top()<nums2[i + 1])
{
m[st.top()] = nums2[i + 1];
st.pop();
}
}
}
while (!st.empty()){
m[st.top()] = -1;
st.pop();
}
vector<int> arr;
for (int i = 0; i < nums1.size(); i++)
{
arr.push_back(m[nums1[i]]);
}
return arr;
}
};
set
特点:没有重复元素
时间复杂度—— 搜索:O(1) 插入:O(1) 删除:O(1)
217.存在重复元素
给你一个整数数组 nums
。如果任一值在数组中出现 至少两次 ,返回 true
;如果数组中每个元素互不相同,返回 false
。
class Solution {
public:
bool containsDuplicate(vector<int>& nums) {
set<int>st(nums.begin(),nums.end());
if(st.size()!=nums.size())
return true;
else
return false;
}
};
705.设计哈希集合
不使用任何内建的哈希表库设计一个哈希集合(HashSet)。
实现 MyHashSet 类:
void add(key) 向哈希集合中插入值 key 。
bool contains(key) 返回哈希集合中是否存在这个值 key 。存在返回T,不存在返回F
void remove(key) 将给定值 key 从哈希集合中删除。如果哈希集合中没有这个值,什么也不做。
class MyHashSet {
private:
vector<bool> arr;
public:
MyHashSet() {
arr.resize(1e6+1,false);
}
void add(int key) {
arr[key]=true;
}
void remove(int key) {
arr[key]=false;
}
bool contains(int key) {
return arr[key];
}
};
树
注意:满二叉树的叶子节点都必须在同一层上
时间复杂度—— 搜索:O(N) 插入:O(N) 删除:O(N)
144.树的先序遍历
class Solution {
public:
vector<int> arr;
vector<int> postorderTraversal(TreeNode* root) {
if(root==NULL)
return arr;
arr.push_back(root->val);
postorderTraversal(root->left);
postorderTraversal(root->right);
return arr;
}
};
94.树的中序遍历
class Solution {
public:
vector<int> arr;
vector<int> postorderTraversal(TreeNode* root) {
if(root==NULL)
return arr;
postorderTraversal(root->left);
arr.push_back(root->val);
postorderTraversal(root->right);
return arr;
}
};
145.树的后序遍历
class Solution {
public:
vector<int> arr;
vector<int> postorderTraversal(TreeNode* root) {
if(root==NULL)
return arr;
postorderTraversal(root->left);
postorderTraversal(root->right);
arr.push_back(root->val);
return arr;
}
};
堆
时间复杂度—— 搜索堆顶元素:O(1)- 插入:O(logN) 删除:O(logN)
215.数组中的第k个最大元素
给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。
请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。
你必须设计并实现时间复杂度为 O(n) 的算法解决此问题
输入:nums=[4,1,3,7,6,9], k=2
输出:[7]
class Solution {
public:
int findKthLargest(vector<int>& nums, int k) {
priority_queue<int>q;
for(int num:nums)
q.push(num);
while(k>1){
q.pop();
k--;
}
return q.top();
}
};
注:priority_queue <int,vector<int>,less<int>> q; // 数组从大到小排序
priority_queue <int,vector<int>,greater<int> > q; // 数组从小到大排序
priority_queue <int> q; //默认从大到小排序
692.前k个高频单词
给定一个单词列表 words 和一个整数 k ,返回前 k 个出现次数最多的单词。
返回的答案应该按单词出现频率由高到低排序。如果不同的单词有相同出现频率, 按字典顺序 排序。
输入: words = ["i", "love", "leetcode", "i", "love", "coding"], k = 2
输出: ["i", "love"]
解析: "i" 和 "love" 为出现次数最多的两个单词,均为2次。注意,按字母顺序 "i" 在 "love" 之前。
class cmp{
public:
bool operator()(const pair<string, int>&p1, const pair<string, int>&p2)
{
return p1.second == p2.second ? p1.first<p2.first : p1.second>p2.second;
}
};
class Solution {
public:
vector<string> topKFrequent(vector<string>& words, int k)
{
unordered_map<string, int>m;
for (string word:words)
m[word]++;
priority_queue <pair<string, int>,vector<pair<string, int>>, cmp>q;
for (auto i=m.begin(); i!=m.end(); i++)
{
q.push(*i);
while (q.size()>k)
q.pop();
}
vector<string>arr;
while (!q.empty())
{
arr.push_back(q.top().first);
q.pop();
}
reverse(arr.begin(), arr.end());
return arr;
}
};
注:利用堆进行排序,并且堆内只能放置k个元素
图
入度:多少边指向该顶点;出度:多少边从该点指向其他点。例如,Bishi有2个入度,0个出度;饲养员有1个入度,1个出度。
双指针
对撞双指针
快慢指针
s 和 f 相遇说明该链表是环形链表
1.两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//先排序,再用对撞指针,最后在原数组中找到对应下标
vector<int>ans;
vector<int>temp;
temp=nums;
sort(temp.begin(),temp.end());
int n=temp.size();
int i=0,j=n-1;
while(i<j)
{
if(temp[i]+temp[j]>target)
j--;
else if(temp[i]+temp[j]<target)
i++;
else
break;
}
for(int k=0;k<n;k++)
{
if(i<n&&nums[k]==temp[i])
{
ans.push_back(k);
i=n;
}
else if(j<n&&nums[k]==temp[j])
{
ans.push_back(k);
j=n;
}
if(i==n&&j==n)
return ans;
}
return ans;
}
};
141.环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。注意:pos 不作为参数进行传递 。仅仅是为了标识链表的实际情况。
如果链表中存在环 ,则返回 true 。 否则,返回 false 。
示例1:
输入:head = [3,2,0,-4], pos = 1
输出:true
解释:链表中有一个环,其尾部连接到第二个节点。
示例2:
输入:head = [1], pos = -1
输出:false
解释:链表中没有环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
bool hasCycle(ListNode *head) {
if(head==NULL)
return false;
ListNode* s=head;
ListNode* f=head;
while(f!=NULL&&f->next!=NULL)
{
s=s->next;
f=f->next->next;
if(s==f)
return true;
}
return false;
}
};
881.救生艇
给定数组 people 。people[i]表示第 i 个人的体重 ,船的数量不限,每艘船可以承载的最大重量为 limit。
每艘船最多可同时载两人,但条件是这些人的重量之和最多为 limit。
返回 承载所有人所需的最小船数 。
输入:people = [3,2,2,1], limit = 3
输出:3
解释:3 艘船分别载 (1, 2), (2) 和 (3)
class Solution {
public:
int numRescueBoats(vector<int>& people, int limit) {
//排序后利用碰撞指针
int result=0;
sort(people.begin(),people.end());
int n=people.size();
int i=0,j=n-1;
while(i<=j)
{
if(people[i]+people[j]<=limit)
{
i++;
j--;
result++;
}
else
{
j--;
result++;
}
}
return result;
}
};
二分查找法
有序数列,时间复杂度为O(logN)
704. 二分查找法
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
class Solution {
public:
int search(vector<int>& nums, int target) {
if (nums.size() == 0)
return -1;
int a = 0, b = nums.size() - 1;
while (a <= b)
{
int mid = (b - a) / 2 + a;
if (nums[mid] < target)
a = mid + 1;
else if (nums[mid] > target)
b = mid - 1;
else
return mid;
}
return -1;
}
};
35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(logN) 的算法。
输入: nums = [1,3,5,6], target = 2
输出: 1
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
if (nums.size() == 0)
return -1;
int a = 0, b = nums.size() - 1;
int mid=0;
while (a <= b)
{
mid = (b - a) / 2 + a;
if (nums[mid] < target)
a = mid + 1;
else if (nums[mid] > target)
b = mid - 1;
else
return mid;
}
if(nums[mid]>target)
return mid;
else
return mid+1;
}
};
162.寻找峰值
峰值元素是指其值严格大于左右相邻值的元素。
给你一个整数数组 nums,找到峰值元素并返回其索引。数组可能包含多个峰值,在这种情况下,返回 任何一个峰值 所在位置即可。
你可以假设 nums[-1] = nums[n] = -∞ 。
你必须实现时间复杂度为 O(log n) 的算法来解决此问题。
输入:nums = [1,2,1,3,5,6,4]
输出:1 或 5
解释:你的函数可以返回索引 1,其峰值元素为 2;或者返回索引 5, 其峰值元素为 6。
class Solution {
public:
int findPeakElement(vector<int>& nums) {
int a=0,b=nums.size()-1;
while(a<b)
{
int mid=(b-a)/2+a;
if(nums[mid]>nums[mid+1])
b=mid;
else
a=mid+1;
}
return a;
}
};
74.搜索二维矩阵
编写一个高效的算法来判断 mxn 矩阵中,是否存在一个目标值。该矩阵具有如下特性:
1、每行中的整数从左到右按升序排列。
2、每行的第一个整数大于前一行的最后一个整数。
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
if(matrix.size()==0)
return false;
int r=matrix.size();
int c=matrix[0].size();
int a=0,b=r*c-1;
while(a<=b)
{
int mid=(b-a)/2+a;
int rr=mid/c,cc=mid%c;
if(matrix[rr][cc]>target)
b=mid-1;
else if(matrix[rr][cc]<target)
a=mid+1;
else
return true;
}
return false;
}
};
滑动窗口
长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度。如果不存在符合条件的子数组,返回 0
示例1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3]是该条件下的长度最小的子数组。
示例2:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
class Solution {
public:
int minSubArrayLen(int target, vector<int>& nums) {
if(nums.size()==0)
return 0;
int i=0,j=0,sum=0;//i和j分别指向滑动窗口的开头和结尾
int n=nums.size(),result=n+1;
while(j<n)
{
sum+=nums[j];
while(sum>=target)
{
result=min(result,j-i+1);
sum-=nums[i];
i++;
}
j++;
}
return result==n+1?0:result;
}
};
1456.定长子串中元音的最大数目
给你字符串 s 和整数 k 。
请返回字符串 s 中长度为 k 的单个子字符串中可能包含的最大元音字母数。
英文中的 元音字母 为(a, e, i, o, u)。
输入:s = "abciiidef", k = 3
输出:3
解释:子字符串 "iii" 包含 3 个元音字母。
class Solution {
public:
int maxVowels(string s, int k) {
if(s.size()==0||s.size()<k)
return 0;
int i=0,sum=0,result=0;
unordered_set<char> m={'a','e','i','o','u'};
for(;i<k;i++)
{
if(m.find(s[i])!=m.end())
sum++;
}
result=max(result,sum);
i--;
while(i<s.size())
{
if(m.find(s[i+1-k])!=m.end())
sum--;
i++;
if(m.find(s[i])!=m.end())
sum++;
result=max(result,sum);
}
return result;
}
};
递归
509.斐波那契数
斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1
F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n ,请计算 F(n) 。
输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = F(2)+F(1)+F(1)+F(0)=3F(1)+2F(0)=3
class Solution {
public:
int fib(int n) {
if(n<2)
return n==1?1:0;
int m=fib(n-1)+fib(n-2);
return m;
}
};
206.反转链表
给你单链表的头节点 head
,请你反转链表,并返回反转后的链表。
输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==NULL||head->next==NULL)
return head;
ListNode* newhead=reverseList(head->next);
head->next->next=head;
head->next=NULL;
return newhead;
}
};
344.反转字符串
编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。
不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
//双指针,分别指向头和尾
class Solution {
public:
void reverseString(vector<char>& s) {
for(int i=0,j=s.size()-1;i<s.size()/2;i++,j--)
swap(s[i],s[j]);
}
};
//递归
class Solution {
public:
void reverse(vector<char>&s,int left,int right){
if(left>=right)
return;
reverse(s,left+1,right-1);
swap(s[left],s[right]);
return;
}
void reverseString(vector<char>& s) {
if(s.size()==0)
return;
int left=0,right=s.size()-1;
reverse(s,left,right);
}
};
动态规划
1531.压缩字符串 II
行程长度编码 是一种常用的字符串压缩方法,它将连续的相同字符(重复 2 次或更多次)替换为字符和表示字符计数的数字(行程长度)。例如,用此方法压缩字符串 "aabccc" ,将 "aa" 替换为 "a2" ,"ccc" 替换为` "c3" 。因此压缩后的字符串变为 "a2bc3" 。
注意,本问题中,压缩时没有在单个字符后附加计数 '1' 。
给你一个字符串 s 和一个整数 k 。你需要从字符串 s 中删除最多 k 个字符,以使 s 的行程长度编码长度最小。
请你返回删除最多 k 个字符后,s 行程长度编码的最小长度 。
输入:s = "aabbaa", k = 2
输出:2
解释:如果删去两个 'b' 字符,那么压缩后的字符串是长度为 2 的 "a4" 。
给定的字符串 s 为「原串」,压缩后的字符串 t 为「压缩串」。我们的目标是从 s 中删除至多 k 个字符,使得其对应的 t 的长度最小。
压缩串 t 由字母和数字间隔组成:
cx 表示字母,dx 表示数字,(cx,dx)表示 s 中连续出现了dx 次字母cx
用 f[i][j] 表示对于原串 s 的前 i 个字符,通过删除其中的 j 个字符,剩余的 i-j 个字符可以得到的最小的压缩串的长度。i 的最大值为原串 s 的长度,j 的最大值为 k 。
- 如果第 i 个字符被删除,那么前 i-1 个字符中就有 j-1 个字符被删除,状态转移方程为:
f[i][j]=f[i−1][j−1]
- 如果第 i 个字符没有被删除,要在 [i0, i)的范围内再选择若干个(包括零个)与 s[i] 相同的字符,一起进行压缩。在选择的范围内与 s[i] 不相同的字符,则会全部被删除。令
diff(i0,i)表示[i0,i]中与s[i]不同的字符数,same(i0,i)表示[i0,i]中与s[i]相同的字符数,有:
cost(dx) 表示压缩 dx 个字符得到的长度:
class Solution {
public:
int getLengthOfOptimalCompression(string s, int k) {
int n=s.size();
vector<vector<int>> f(n+1,vector<int>(k+1,INT_MAX>>1));
f[0][0]=0;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=k&&j<=i;j++)
{
if(j>0)
f[i][j]=f[i-1][j-1];
int same=0,diff=0;
for(int i0=i;i0>=1&&diff<=j;i0--)
{
if(s[i0-1]==s[i-1])
{
same++;
f[i][j]=min(f[i][j],f[i0-1][j-diff]+calc(same));
}
else
diff++;
}
}
}
return f[n][k];
}
int calc(int x)
{
if(x==1)
return 1;
else if(x<10)
return 2;
else if(x<100)
return 3;
else
return 4;
}
};