总体结构
一、数据结构
1.1 链表
1.1.1 双指针
1、合并两个链表 leetcode
题解:
方法一:
时间复杂度:O(m+n)
空间复杂度:O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
ListNode* dummyNode = new ListNode();
ListNode* root = dummyNode;
ListNode* p = list1;
ListNode* q = list2;
while(p!=nullptr && q!=nullptr)
{
if(p->val < q->val)
{
root->next = p;
p= p->next;
}else{
root->next = q;
q = q->next;
}
root = root->next;
}
while(p!=nullptr)
{
root->next = p;
p= p->next;
root = root->next;
}
while(q!=nullptr)
{
root->next = q;
q = q->next;
root = root->next;
}
return dummyNode->next;
}
};
方法二:
时间复杂度:O(m+n)
空间复杂度:O(m+n)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
if(list1==nullptr)
{
return list2;
}else if(list2==nullptr)
{
return list1;
}else{
if(list1->val<list2->val)
{
list1->next=mergeTwoLists(list1->next, list2);
return list1;
}else{
list2->next = mergeTwoLists(list1, list2->next);
return list2;
}
}
}
};
2、分隔链表 leetcode
题解:
时间复杂度O(n)
空间复杂度O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* partition(ListNode* head, int x) {
if(head==nullptr)
return head;
ListNode* head1 = new ListNode(-1);
ListNode* head2 = new ListNode(-1);
ListNode* p = head;
ListNode* p1 = head1;
ListNode* p2 = head2;
while(p!=nullptr)
{
if(p->val < x)
{
p1->next = p;
p1 = p;
}else{
p2->next = p;
p2 = p;
}
ListNode* tmp = p->next;
p->next = nullptr;
p = tmp;
}
p1->next = head2->next;
return head1->next;
}
};
3、合并K个升序链表 leetcode
题解:
时间复杂度O(K2*n)
空间复杂度O(1)
class Solution {
public:
ListNode* mergeTwoLists(ListNode *a, ListNode *b) {
if ((!a) || (!b)) return a ? a : b;
ListNode head, *tail = &head, *aPtr = a, *bPtr = b;
while (aPtr && bPtr) {
if (aPtr->val < bPtr->val) {
tail->next = aPtr; aPtr = aPtr->next;
} else {
tail->next = bPtr; bPtr = bPtr->next;
}
tail = tail->next;
}
tail->next = (aPtr ? aPtr : bPtr);
return head.next;
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
ListNode *ans = nullptr;
for (size_t i = 0; i < lists.size(); ++i) {
ans = mergeTwoLists(ans, lists[i]);
}
return ans;
}
};
题解:最小堆
时间复杂度O(Kn * logk)
空间复杂度O(kn)
class Solution {
public:
void Heapfity(vector<ListNode*>& nums, int index, int heapSize)
{
int left=2*index+1, right = 2*index+2, minest = index;
if(left<heapSize && nums[left]->val < nums[minest]->val)
{
minest = left;
}
if(right<heapSize && nums[right]->val < nums[minest]->val)
{
minest = right;
}
if(minest!=index)
{
swap(nums[minest], nums[index]);
Heapfity(nums, minest, heapSize);
}
}
void buildMinHeap(vector<ListNode*>& nums, int heapSize)
{
for(int i=heapSize/2;i>=0;i--)
{
Heapfity(nums, i, heapSize);
}
}
ListNode* mergeKLists(vector<ListNode*>& lists) {
vector<ListNode*> nums;
for(int i=0;i<lists.size();i++)
{
ListNode* p = lists[i];
while(p!=nullptr)
{
ListNode* node = new ListNode(p->val);
nums.push_back(node);
p=p->next;
}
}
int heapSize = nums.size();
if(heapSize==0)
{
return nullptr;
}
buildMinHeap(nums, heapSize);
ListNode* dummyHead = new ListNode();
ListNode* last = dummyHead;
int num = heapSize;
for(int i=0;i<num;i++)
{
last->next = nums[0];
last = last->next;
swap(nums[0], nums[heapSize-1]);
heapSize--;
Heapfity(nums, 0, heapSize);
}
return dummyHead->next;
}
};
4、删除链表的倒数第N个节点leetcode
题解:
时间复杂度O(L)
空间复杂度O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* removeNthFromEnd(ListNode* head, int n) {
if(head==nullptr)
{
return head;
}
ListNode* dummyHead = new ListNode();
dummyHead->next = head;
ListNode* p = head;
for(int i=0;i<n;i++)
{
p = p->next;
}
ListNode* q = head;
ListNode* pre = dummyHead;
while(p!=nullptr)
{
p=p->next;
q =q->next;
}
while(pre->next!=q)
{
pre=pre->next;
}
pre->next = q->next;
return dummyHead->next;
}
};
5、单链表的中点 leetcode
题解:快慢指针
时间复杂度O(n)
空间复杂度O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* middleNode(ListNode* head) {
ListNode* slow = head;
ListNode* fast = head;
while(fast!=nullptr && fast->next!=nullptr)
{
slow = slow->next;
fast = fast->next;
if(fast!=nullptr)
fast = fast->next;
}
return slow;
}
};
6、141. 环形链表 leetcode
题解:快慢指针
/**
* 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) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=NULL && fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast){
return true;
}
}
return false;
}
};
7、142. 环形链表 II leetcode
题解:
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *detectCycle(ListNode *head) {
ListNode* slow = head;
ListNode* fast = head;
bool is_cycle = false;
while(fast!=NULL && fast->next!=NULL)
{
slow = slow->next;
fast = fast->next->next;
if(slow==fast)
{
is_cycle = true;
break;
}
}
if(!is_cycle)
{
return NULL;
}
ListNode* a = head;
while(a!=slow)
{
a = a->next;
slow = slow->next;
}
return a;
}
};
8、相交链表 leetcode
题解:双指针
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
int numA = 0;
int numB = 0;
ListNode* pA = headA;
ListNode* pB = headB;
while(pA!=NULL)
{
numA++;
pA = pA->next;
}
while(pB!=NULL)
{
numB++;
pB = pB->next;
}
ListNode* longP = NULL;
ListNode* shortP = NULL;
int dis = 0;
if(numA > numB)
{
longP = headA;
shortP = headB;
dis = numA - numB;
}else{
longP = headB;
shortP = headA;
dis = numB - numA;
}
for(int i=0;i<dis;i++)
{
longP = longP->next;
}
while(longP!=NULL && shortP!=NULL)
{
if(shortP==longP)
{
return longP;
}
longP = longP->next;
shortP = shortP->next;
}
return NULL;
}
};
1.1.2 链表递归
9、反转链表 leetcode
题解一:迭代
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* dummyHead = new ListNode();
ListNode* last = nullptr;
ListNode* p = head;
while(p!=nullptr)
{
ListNode* tmp = p->next;
dummyHead->next = p;
p->next = last;
last = p;
p = tmp;
}
return dummyHead->next;
}
};
题解二:递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverseList(ListNode* head) {
if(head==nullptr || head->next==nullptr)
{
return head;
}
ListNode* newHead = reverseList(head->next);
head->next->next = head;
head->next = nullptr;
return newHead;
}
};
10、反转链表 II leetcode
题解:
递归
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* successor = nullptr;
ListNode* reverseN(ListNode* head, int n) {
if (n == 1) {
// 记录第 n + 1 个节点
successor = head->next;
return head;
}
// 以 head.next 为起点,需要反转前 n - 1 个节点
ListNode* last = reverseN(head->next, n - 1);
head->next->next = head;
// 让反转之后的 head 节点和后面的节点连起来
head->next = successor;
return last;
}
ListNode* reverseBetween(ListNode* head, int left, int right) {
if(left==1)
{
return reverseN(head, right);
}
head->next = reverseBetween(head->next, left-1, right-1);
return head;
}
};
11、回文链表 leetcode
题解一:
时间复杂度:O(n)
空间复杂度:O(n)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
bool isPalindrome(ListNode* head) {
vector<int> list;
ListNode* p =head;
while(p!=nullptr)
{
list.push_back(p->val);
p=p->next;
}
int l=0, r=list.size()-1;
while(l<r)
{
if(list[l]!=list[r])
{
return false;
}
l++;
r--;
}
return true;
}
};
题解二:找链表中点,再翻转后半链表
时间复杂度:O(n)
空间复杂度:O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* reverse(ListNode* head)
{
ListNode* pre = nullptr;
ListNode* cur = head;
ListNode* nxt = head;
while(cur!=nullptr)
{
nxt = cur->next;
cur->next = pre;
pre = cur;
cur = nxt;
}
return pre;
}
bool isPalindrome(ListNode* head) {
ListNode* fast = head;
ListNode* slow = head;
while(fast!=nullptr && fast->next!=nullptr)
{
fast = fast->next->next;
slow = slow->next;
}
if(fast!=nullptr) // 如果fast指针没有指向null,说明链表长度为奇数,slow还要再前进一步:
{
slow = slow->next;
}
ListNode* right = reverse(slow);
ListNode* left = head;
while(right!=nullptr)
{
if(left->val!=right->val)
{
return false;
}
left=left->next;
right = right->next;
}
return true;
}
};
1.2数组
1.2.1 双指针
1、删除有序数组中的重复项 leetcode
题解:快慢指针
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()==0)
{
return 0;
}
int slow=0, fast=0;
while(fast<nums.size())
{
if(nums[slow]!=nums[fast])
{
slow++;
nums[slow] = nums[fast];
}
fast++;
}
return slow+1;
}
};
2、删除排序链表中的重复元素 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode() : val(0), next(nullptr) {}
* ListNode(int x) : val(x), next(nullptr) {}
* ListNode(int x, ListNode *next) : val(x), next(next) {}
* };
*/
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if(head==nullptr)
{
return nullptr;
}
ListNode* slow = head;
ListNode* fast = head;
while(fast!=nullptr)
{
if(slow->val != fast->val)
{
slow = slow->next;
slow->val = fast->val;
}
fast = fast->next;
}
slow->next = nullptr;
return head;
}
};
3、移除元素 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slow = 0;
int fast = 0;
while(fast<nums.size())
{
if(nums[fast]!=val)
{
nums[slow] = nums[fast];
slow++;
}
fast++;
}
return slow;
}
};
4、移动零 leetcode
题解:快慢指针
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
void moveZeroes(vector<int>& nums) {
int slow = 0;
int fast = 0;
while(fast<nums.size())
{
if(nums[fast]!=0)
{
nums[slow] = nums[fast];
slow++;
}
fast++;
}
for(int i=slow;i<nums.size();i++)
{
nums[i]=0;
}
}
};
5、两数之和 II - 输入有序数组
题解:双指针,二分
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
vector<int> twoSum(vector<int>& numbers, int target) {
int left = 0;
int right = numbers.size()-1;
while(left<right)
{
int sum = numbers[left] + numbers[right];
if(sum == target)
{
return {left+1, right+1};
}else if(sum<target)
{
left++;
}else{
right--;
}
}
return {-1, -1};
}
};
6、反转字符串 leetcode
题解:反转
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
void reverseString(vector<char>& s) {
int i = 0;
int j = s.size()-1;
while(i<j)
{
swap(s[i], s[j]);
i++;
j--;
}
}
};
7、最长回文子串 leetcode
首先判断回文:
boolean isPalindrome(String s) {
// 一左一右两个指针相向而行
int left = 0, right = s.length() - 1;
while (left < right) {
if (s.charAt(left) != s.charAt(right)) {
return false;
}
left++;
right--;
}
return true;
}
题解:双指针
时间复杂度:O(n^2)
空间复杂度:O(1)
class Solution {
public:
pair<int, int> findPalindrome(string s, int l, int r)
{
while(l>=0 && r<s.size() && s[l]==s[r])
{
l--;
r++;
}
return {l+1, r-1};
}
string longestPalindrome(string s) {
string ans="";
for(int i=0;i<s.size();i++)
{
auto [l1, r1] = findPalindrome(s, i, i);
auto [l2, r2] = findPalindrome(s, i, i+1);
if(r1-l1+1>ans.size())
{
ans = s.substr(l1, r1-l1+1);
}
if(r2-l2+1>ans.size())
{
ans = s.substr(l2, r2-l2+1);
}
}
return ans;
}
};
1.2.2 前缀和
前缀和主要适用的场景是原始数组不会被修改的情况下,频繁查询某个区间的累加和。
1、区域和检索 - 数组不可变 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class NumArray {
public:
vector<int> preSum;
NumArray(vector<int>& nums) {
preSum = vector<int>(nums.size()+1);
preSum[0] = 0;
for(int i=0;i<nums.size();i++)
{
preSum[i+1] = preSum[i]+nums[i];
}
}
int sumRange(int left, int right) {
return preSum[right+1] - preSum[left];
}
};
/**
* Your NumArray object will be instantiated and called as such:
* NumArray* obj = new NumArray(nums);
* int param_1 = obj->sumRange(left,right);
*/
2、二维区域和检索 - 矩阵不可变 leetcode
题解:二维前缀和
时间复杂度:O(mn)
空间复杂度:O(mn)
class NumMatrix {
public:
vector<vector<int> > preSum;
NumMatrix(vector<vector<int>>& matrix) {
int m = matrix.size();
int n = matrix[0].size();
preSum = vector<vector<int> >(m+1, vector<int>(n+1, 0));
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
preSum[i][j] = preSum[i-1][j] + preSum[i][j-1] + matrix[i-1][j-1] - preSum[i-1][j-1];
}
}
}
int sumRegion(int row1, int col1, int row2, int col2) {
return preSum[row2+1][col2+1] - preSum[row1][col2+1] - preSum[row2+1][col1] + preSum[row1][col1];
}
};
/**
* Your NumMatrix object will be instantiated and called as such:
* NumMatrix* obj = new NumMatrix(matrix);
* int param_1 = obj->sumRegion(row1,col1,row2,col2);
*/
1.2.3 差分数组
差分数组的主要适用场景是频繁对原始数组的某个区间的元素进行增减。
1、航班预订统计 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
vector<int> diff(n, 0);
for(int i=0;i<bookings.size();i++)
{
int left = bookings[i][0]-1;
int right = bookings[i][1]-1;
int val = bookings[i][2];
diff[left]+=val;
if(right+1<n)
{
diff[right+1]-=val;
}
}
vector<int> ans(n);
ans[0] = diff[0];
for(int i=1;i<n;i++)
{
ans[i] = diff[i]+ans[i-1];
}
return ans;
}
};
2、拼车 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
bool carPooling(vector<vector<int>>& trips, int capacity) {
vector<int> diff(1010, 0);
for(int i=0;i<trips.size();i++)
{
int num = trips[i][0];
int from = trips[i][1];
int to = trips[i][2]-1;
diff[from]+=num;
diff[to+1]-=num;
}
vector<int> res = vector<int>(1010, 0);
res[0] = diff[0];
if(res[0]>capacity)
{
return false;
}
for(int i=1;i<diff.size();i++)
{
res[i] = res[i-1] + diff[i];
if(res[i]>capacity)
{
return false;
}
}
return true;
}
};
1.2.4 二维数组
1、旋转图像 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(1)
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
int n = matrix.size();
// 先按斜对角线翻转
for(int i=0;i<n;i++)
{
for(int j=i;j<n;j++)
{
swap(matrix[i][j], matrix[j][i]);
}
}
// 再按竖对称轴翻转
for(int k=0;k<n;k++)
{
int i=0;
int j=n-1;
while(i<j)
{
swap(matrix[k][i], matrix[k][j]);
i++;
j--;
}
}
}
};
2、颠倒字符串中的单词 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n)
class Solution {
public:
void reverse(string &s)
{
int i=0;
int j=s.size()-1;
while(i<j)
{
swap(s[i], s[j]);
i++;
j--;
}
}
string reverseWords(string s) {
string tmp = "";
string tmp2 = "";
int i=0;
while(i<s.size())
{
if(s[i]==' ')
{
i++;
}else{
tmp = s.substr(i, s.size()-i+1);
break;
}
}
i=tmp.size()-1;
while(i>=0)
{
if(tmp[i]==' ')
{
i--;
}else{
tmp2 = tmp.substr(0, i+1);
break;
}
}
reverse(tmp2);
string ans="";
string word="";
for(int p=0;p<tmp2.size();p++)
{
if(tmp2[p]!=' ')
{
word+=tmp2[p];
}else{
if(word!="")
{
reverse(word);
ans+=word;
ans+=tmp2[p];
word="";
}
}
}
reverse(word);
ans+=word;
return ans;
}
};
3、螺旋矩阵 leetcode
题解:
时间复杂度:O(mn)
空间复杂度:O(1)
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> ans;
int m = matrix.size();
int n = matrix[0].size();
int num = m*n;
int up=0, down=m-1, left=0, right=n-1;
while(num>0)
{
for(int i=left;i<=right;i++)
{
ans.push_back(matrix[up][i]);
num--;
if(num==0)
{
return ans;
}
}
up++;
for(int i=up;i<=down;i++)
{
ans.push_back(matrix[i][right]);
num--;
if(num==0)
{
return ans;
}
}
right--;
for(int i=right;i>=left;i--)
{
ans.push_back(matrix[down][i]);
num--;
if(num==0)
{
return ans;
}
}
down--;
for(int i=down;i>=up;i--)
{
ans.push_back(matrix[i][left]);
num--;
if(num==0)
{
return ans;
}
}
left++;
}
return ans;
}
};
4、螺旋矩阵 II leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(1)
class Solution {
public:
vector<vector<int>> generateMatrix(int n) {
vector<vector<int> > matrix(n, vector<int>(n, 0));
int up=0, down=n-1, left=0, right=n-1;
int k=1;
while(k<=n*n)
{
for(int i=left;i<=right;i++)
{
matrix[up][i]=k;
k++;
if(k>n*n)
{
return matrix;
}
}
up++;
for(int i=up;i<=down;i++)
{
matrix[i][right]=k;
k++;
if(k>n*n)
{
return matrix;
}
}
right--;
for(int i=right;i>=left;i--)
{
matrix[down][i]=k;
k++;
if(k>n*n)
{
return matrix;
}
}
down--;
for(int i=down;i>=up;i--)
{
matrix[i][left]=k;
k++;
if(k>n*n)
{
return matrix;
}
}
left++;
}
return matrix;
}
};
1.2.5 滑动窗口
框架
/* 滑动窗口算法框架 */
void slidingWindow(string s) {
unordered_map<char, int> window;
int left = 0, right = 0;
while (right < s.size()) {
// c 是将移入窗口的字符
char c = s[right];
// 增大窗口
right++;
// 进行窗口内数据的一系列更新
...
/*** debug 输出的位置 ***/
printf("window: [%d, %d)\n", left, right);
/********************/
// 判断左侧窗口是否要收缩
while (window needs shrink) {
// d 是将移出窗口的字符
char d = s[left];
// 缩小窗口
left++;
// 进行窗口内数据的一系列更新
...
}
}
}
1、最小覆盖子串 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
string minWindow(string s, string t) {
string ans="";
int start=0;
int len = INT_MAX;
unordered_map<char, int> need, window;
for(int i=0;i<t.size();i++)
{
need[t[i]]++;
}
int valid=0;
int left=0, right=0;
while(right<s.size())
{
char c = s[right];
right++;
if(need.count(c)!=0)
{
window[c]++;
if(window[c]==need[c])
{
valid++;
}
}
while(valid==need.size()){
if(right-left < len)
{
start = left;
len = right - left;
}
char d = s[left];
left++;
if(need.count(d)!=0)
{
if(window[d]==need[d])
{
valid--;
}
window[d]--;
}
}
}
return len==INT_MAX? "" : s.substr(start, len);
}
};
2、字符串的排列 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
bool checkInclusion(string s1, string s2) {
unordered_map<char, int> need, window;
for(char c : s1)
{
need[c]++;
}
int left=0, right=0;
int valid=0;
while(right<s2.size())
{
char c = s2[right];
right++;
if(need.count(c)!=0)
{
window[c]++;
if(window[c]==need[c])
{
valid++;
}
}
while(right-left >= s1.size())
{
if(valid==need.size())
{
return true;
}
char d = s2[left];
left++;
if(window.count(d)!=0)
{
if(window[d]==need[d])
{
valid--;
}
window[d]--;
}
}
}
return false;
}
};
3、找到字符串中所有字母异位词 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> ans;
unordered_map<char, int> need, window;
for(char c:p)
{
need[c]++;
}
int valid=0;
int left=0, right=0;
while(right<s.size())
{
char c = s[right];
right++;
if(need.count(c)!=0)
{
window[c]++;
if(window[c]==need[c])
{
valid++;
}
}
while(right-left>=p.size())
{
if(valid==need.size())
{
ans.push_back(left);
}
char d = s[left];
left++;
if(window.count(d)!=0)
{
if(window[d]==need[d])
{
valid--;
}
window[d]--;
}
}
}
return ans;
}
};
4、无重复字符的最长子串 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int lengthOfLongestSubstring(string s) {
unordered_map<char, int> window;
int left=0, right=0;
int ans=0;
while(right<s.size())
{
char c = s[right];
right++;
window[c]++;
while(window[c]>1)
{
char d = s[left];
left++;
window[d]--;
}
ans = max(ans, right-left);
}
return ans;
}
};
1.2.6 Rabin-Karp 算法
1、重复的DNA序列 leetcode
题解:
时间复杂度:O(nL)
空间复杂度:O(nL)
class Solution {
public:
vector<string> findRepeatedDnaSequences(string s) {
vector<string> ans;
unordered_map<string, int> mp;
int left=0, right=0;
string tmp="";
while(right<s.size())
{
char c = s[right];
right++;
tmp+=c;
while(tmp.size()==10)
{
char d = s[left];
left++;
mp[tmp]++;
tmp = tmp.substr(1);
}
}
for(auto iter = mp.begin(); iter!=mp.end(); iter++)
{
if(iter->second>1)
{
ans.push_back(iter->first);
}
}
return ans;
}
};
2、实现 strStr() leetcode
题解:Rabin-Karp 指纹字符串查找算法
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int strStr(string txt, string pat) {
// 位数
int L = pat.length();
// 进制(只考虑 ASCII 编码)
int R = 256;
// 取一个比较大的素数作为求模的除数
long Q = 1658598167;
// R^(L - 1) 的结果
long RL = 1;
for (int i = 1; i <= L - 1; i++) {
// 计算过程中不断求模,避免溢出
RL = (RL * R) % Q;
}
// 计算模式串的哈希值,时间 O(L)
long patHash = 0;
for (int i = 0; i < pat.length(); i++) {
patHash = (R * patHash + long(pat[i])) % Q;
}
// 滑动窗口中子字符串的哈希值
long windowHash = 0;
// 滑动窗口代码框架,时间 O(N)
int left = 0, right = 0;
while (right < txt.length()) {
// 扩大窗口,移入字符
windowHash = ((R * windowHash) % Q + long(txt[right])) % Q;
right++;
// 当子串的长度达到要求
if (right - left == L) {
// 根据哈希值判断是否匹配模式串
if (windowHash == patHash) {
// 当前窗口中的子串哈希值等于模式串的哈希值
// 还需进一步确认窗口子串是否真的和模式串相同,避免哈希冲突
string tmp = txt.substr(left, right-left);
if (pat==tmp) {
return left;
}
}
// 缩小窗口,移出字符
windowHash = (windowHash - (long(txt[left]) * RL) % Q + Q) % Q;
// X % Q == (X + Q) % Q 是一个模运算法则
// 因为 windowHash - (txt[left] * RL) % Q 可能是负数
// 所以额外再加一个 Q,保证 windowHash 不会是负数
left++;
}
}
// 没有找到模式串
return -1;
}
};
1.2.7 二分搜索
二分框架:
int binarySearch(int[] nums, int target) {
int left = 0, right = ...;
while(...) {
int mid = left + (right - left) / 2;
if (nums[mid] == target) {
...
} else if (nums[mid] < target) {
left = ...
} else if (nums[mid] > target) {
right = ...
}
}
return ...;
}
不要出现 else,而是把所有情况用 else if 写清楚,这样可以清楚地展现所有细节。
1、二分查找 leetcode
题解:
时间复杂度:O(logn)
空间复杂度:O(1)
class Solution {
public:
int search(vector<int>& nums, int target) {
int left=0, right = nums.size()-1;
while(left<=right)
{
int mid = left + (right - left) / 2;
if(nums[mid]==target)
{
return mid;
}else if(nums[mid]>target)
{
right = mid-1;
}else if(nums[mid]<target)
{
left = mid+1;
}
}
return -1;
}
};
2、在排序数组中查找元素的第一个和最后一个位置 leetcode
题解:
时间复杂度:O(logn)
空间复杂度:O(1)
class Solution {
public:
vector<int> searchRange(vector<int>& nums, int target) {
if(nums.size()==0)
{
return {-1, -1};
}
vector<int> ans;
// 找左边界
int left=0, right=nums.size()-1;
while(left<=right)
{
int mid = left + (right-left) / 2;
if(nums[mid]==target)
{
right = mid-1;
}else if(nums[mid]<target)
{
left = mid+1;
}else if(nums[mid]>target)
{
right=mid-1;
}
}
if(left==nums.size())
{
ans.push_back(-1);
}else if(nums[left]!=target){
ans.push_back(-1);
}else{
ans.push_back(left);
}
// 找右边界
left=0;
right=nums.size()-1;
while(left<=right)
{
int mid = left + (right-left) / 2;
if(nums[mid]==target)
{
left = mid+1;
}else if(nums[mid]<target)
{
left=mid+1;
}else if(nums[mid]>target)
{
right = mid-1;
}
}
if(right<0)
{
ans.push_back(-1);
}else if(nums[right]!=target){
ans.push_back(-1);
}else{
ans.push_back(right);
}
return ans;
}
};
3、在 D 天内送达包裹的能力 leetcode
题解:
时间复杂度:O(n*logw) w表示weights数组求和数组的长度
空间复杂度:O(1)
class Solution {
public:
int shipWithinDays(vector<int>& weights, int days) {
int left=0;
int right=0;
for(int i=0;i<weights.size();i++)
{
if(weights[i]>left)
{
left = weights[i];
}
right+=weights[i];
}
// 因为最小载重肯定得是最大weight,
// 最大载重那就是所有weight和
while(left<right)
{
int mid = left + (right-left)/2;
int need_day=1; // 需要运输天数
int cur_weight=0; // 当前重量
for(int w : weights)
{
if(cur_weight + w > mid)
{
need_day++;
cur_weight=0;
}
cur_weight+=w;
}
if(need_day <= days)
{
right = mid;
}else if(need_day > days){
left = mid + 1;
}
}
return left;
}
};
4、分割数组的最大值 leetcode
题解:
时间复杂度:O(n*logw) w表示sum-max_value的长度
空间复杂度:O(1)
class Solution {
public:
int splitArray(vector<int>& nums, int m) {
int max_value=0;
int sum=0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]>max_value)
{
max_value = nums[i];
}
sum+=nums[i];
}
int left=max_value, right=sum;
while(left<right)
{
int mid = left + (right - left)/2;
int cur_sum=0;
int m_num=1;
for(int num : nums)
{
if(cur_sum + num > mid)
{
m_num++;
cur_sum=0;
}
cur_sum+=num;
}
if(m_num<=m)
{
right=mid;
}else{
left=mid+1;
}
}
return left;
}
};
5、爱吃香蕉的珂珂 leetcode
题解:
时间复杂度:O(nlogw) w表示1到最大值的长度
空间复杂度:O(1)
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int left=1, right=0;
for(int i=0;i<piles.size();i++)
{
if(piles[i] > right)
{
right = piles[i];
}
}
while(left<right)
{
int mid = left + (right-left)/2;
int cur_h=0;
for(int i=0;i<piles.size();i++)
{
if(piles[i]%mid==0)
{
cur_h+= piles[i]/mid;
}else{
cur_h+= piles[i]/mid+1;
}
}
if(cur_h<=h)
{
right=mid;
}else{
left=mid+1;
}
}
return left;
}
};
1.2.8 带权重的随机选择算法
1、按权重随机选择 leetcode
题解:
时间复杂度:O(logn)
空间复杂度:O(n)
class Solution {
public:
vector<int> preSum;
Solution(vector<int>& w) {
preSum = vector<int>(w.size()+1);
preSum[0]=0;
for(int i=1;i<=w.size();i++)
{
preSum[i] = preSum[i-1] + w[i-1];
}
}
int pickIndex() {
int n = preSum.size();
int target = rand() % preSum[n-1] + 1; // 取[1, 总和]范围的随机数.rand()%n 表示从0到n-1的数
int left=0, right=preSum.size();
while(left<right)
{
int mid = left + (right-left)/2;
if(preSum[mid]>=target)
{
right=mid;
}else if(preSum[mid] < target)
{
left=mid+1;
}
}
return left-1; // 将前缀和数组的下标对应到原数组
}
};
/**
* Your Solution object will be instantiated and called as such:
* Solution* obj = new Solution(w);
* int param_1 = obj->pickIndex();
*/
1.2.9 随机读取元素
想高效地,等概率地随机获取元素,就要使用数组作为底层容器。
要保持数组元素的紧凑性,可以把待删除元素换到最后,然后 pop 掉末尾的元素,这样时间复杂度就是 O(1) 了。当然,我们需要额外的哈希表记录值到索引的映射。
1、O(1) 时间插入、删除和获取随机元素 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class RandomizedSet {
public:
vector<int> nums;
unordered_map<int, int> val2Idx;
RandomizedSet() {
}
bool insert(int val) {
if(val2Idx.count(val)==0)
{
val2Idx[val] = nums.size();
nums.push_back(val);
return true;
}
return false;
}
bool remove(int val) {
if(val2Idx.count(val)==0)
{
return false;
}
int idx = val2Idx[val];
val2Idx[nums.back()]=idx;
swap(nums[idx], nums[nums.size()-1]); // 把要删的数放到最后去,然后删除
nums.pop_back();
val2Idx.erase(val);
return true;
}
int getRandom() {
return nums[rand() % nums.size()];
}
};
/**
* Your RandomizedSet object will be instantiated and called as such:
* RandomizedSet* obj = new RandomizedSet();
* bool param_1 = obj->insert(val);
* bool param_2 = obj->remove(val);
* int param_3 = obj->getRandom();
*/
2、黑名单中的随机数 leetcode
题解:
时间复杂度:O(m)
空间复杂度:O(m)
class Solution {
unordered_map<int, int> b2w;
int bound;
public:
Solution(int n, vector<int> &blacklist) {
int m = blacklist.size();
bound = n - m;
unordered_set<int> black;
for (int b: blacklist) {
if (b >= bound) {
black.emplace(b);
}
}
int w = bound;
for (int b: blacklist) {
if (b < bound) {
while (black.count(w)) {
++w;
}
b2w[b] = w++;
}
}
}
int pick() {
int x = rand() % bound;
return b2w.count(x) ? b2w[x] : x;
}
};
1.2.10 数组去重
1、去除重复字母 leetcode
题解:贪心+ 单调栈
时间复杂度:O(N)
空间复杂度:O(256)
class Solution {
public:
string removeDuplicateLetters(string s) {
string ans = "";
stack<char> stk;
vector<bool> instk(256);
vector<int> count(256);
for(char c : s)
{
count[int(c)]++; // 记录字符串中每个字母的出现次数
}
for(char c:s) // 遍历字符串
{
count[int(c)]--;
if(instk[int(c)]) // 当前字符在栈中
{
continue;
}
while(!stk.empty() && stk.top()>c) // 说明新来的字符更小,要准备开始出栈
{
if(count[int(stk.top())] == 0) // 当前栈顶字符的计数为0了,说明不能出栈
{
break;
}
char t = stk.top();
instk[int(t)] = false;
stk.pop();
}
stk.push(c);
instk[int(c)]=true;
}
while(!stk.empty())
{
char t = stk.top();
stk.pop();
ans+=t;
}
reverse(ans.begin(), ans.end());
return ans;
}
};
1.3 二叉树
1.3.1 基础
1、二叉树的最大深度 leetcode
题解:迭代
时间复杂度:O(N)
空间复杂度:O(N)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr)
{
return 0;
}
queue<TreeNode*> q;
q.push(root);
int height=0;
while(!q.empty())
{
int size = q.size();
while(size)
{
TreeNode* node = q.front();
q.pop();
if(node->left!=nullptr)
{
q.push(node->left);
}
if(node->right!=nullptr)
{
q.push(node->right);
}
size--;
}
height++;
}
return height;
}
};
题解:递归
时间复杂度:O(N)
空间复杂度:O(height)
class Solution {
public:
int maxDepth(TreeNode* root) {
if(root==nullptr)
{
return 0;
}else{
return max(maxDepth(root->left), maxDepth(root->right))+1;
}
}
};
2、二叉树的直径 leetcode
题解:递归
时间复杂度:O(N)
空间复杂度:O(height)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int ans=0;
int maxDepth(TreeNode* root)
{
if(root==nullptr)
{
return 0;
}
int left_h = maxDepth(root->left);
int right_h = maxDepth(root->right);
int res = left_h + right_h; // 当前直径
ans = max(ans, res);
return max(left_h, right_h) + 1;
}
int diameterOfBinaryTree(TreeNode* root) {
maxDepth(root);
return ans;
}
};
3、翻转二叉树 leetcode
题解:递归
时间复杂度:O(N)
空间复杂度:O(height)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if(root==nullptr)
{
return nullptr;
}
invertTree(root->left);
invertTree(root->right);
swap(root->left, root->right);
return root;
}
};
4、填充每个节点的下一个右侧节点指针 leetcode
题解:迭代
时间复杂度:O(N)
空间复杂度:O(height)
/*
// Definition for a Node.
class Node {
public:
int val;
Node* left;
Node* right;
Node* next;
Node() : val(0), left(NULL), right(NULL), next(NULL) {}
Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}
Node(int _val, Node* _left, Node* _right, Node* _next)
: val(_val), left(_left), right(_right), next(_next) {}
};
*/
class Solution {
public:
Node* connect(Node* root) {
if(root==NULL)
{
return NULL;
}
queue<Node*> q;
q.push(root);
while(!q.empty())
{
int size = q.size();
while(size)
{
Node* node = q.front();
q.pop();
if(size==1)
{
node->next=NULL;
}else{
Node* node2 = q.front();
node->next = node2;
}
if(node->left!=NULL)
{
q.push(node->left);
}
if(node->right!=NULL)
{
q.push(node->right);
}
size--;
}
}
return root;
}
};
题解:递归
时间复杂度:O(N)
空间复杂度:O(height)
class Solution {
public:
void traverse(Node* node1, Node* node2)
{
if(node1==NULL || node2==NULL)
{
return;
}
traverse(node1->left, node1->right);
traverse(node2->left, node2->right);
traverse(node1->right, node2->left);
node1->next = node2;
}
Node* connect(Node* root) {
if(root==NULL)
return NULL;
traverse(root->left, root->right);
return root;
}
};
5、二叉树展开为链表 leetcode
题解:递归
时间复杂度:O(N)
空间复杂度:O(height)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void flatten(TreeNode* root) {
if(root==nullptr)
{
return ;
}
flatten(root->left);
flatten(root->right);
TreeNode* node1 = root->left;
TreeNode* node2 = root->right;
root->left = nullptr;
root->right = node1;
TreeNode* p = root;
while(p->right!=nullptr)
{
p=p->right;
}
p->right = node2;
}
};
1.3.2 构造
1、最大二叉树 leetcode
题解:递归
时间复杂度:O(N^2)
空间复杂度:O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* build(vector<int> nums, int start, int end)
{
if(start<=end)
{
int max_idx=start;
for(int i=start;i<=end;i++)
{
if(nums[i]>nums[max_idx])
{
max_idx = i;
}
}
TreeNode* left_node = build(nums, start, max_idx-1);
TreeNode* right_node =build(nums, max_idx+1, end);
TreeNode* root = new TreeNode(nums[max_idx]);
root->left = left_node;
root->right = right_node;
return root;
}else{
return nullptr;
}
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* root = build(nums, 0, nums.size()-1);
return root;
}
};
2、从前序与中序遍历序列构造二叉树 leetcode
题解:递归(用哈希表记录索引,避免重复找)
时间复杂度:O(N)
空间复杂度:O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
unordered_map<int, int> val2idx;
TreeNode* build(vector<int> preorder, int pre_f, int pre_e, vector<int> inorder, int in_f, int in_e)
{
if(pre_f>pre_e || in_f>in_e)
{
return nullptr;
}
TreeNode* root = new TreeNode(preorder[pre_f]);
int mid_root_idx=val2idx[preorder[pre_f]];
int left_len = mid_root_idx-in_f;
int right_len = in_e - mid_root_idx;
root->left = build(preorder, pre_f+1, pre_f+left_len, inorder, in_f, in_f+left_len-1);
root->right = build(preorder, pre_f+left_len+1, pre_e, inorder, mid_root_idx+1, in_e);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
for(int i=0;i<inorder.size();i++)
{
val2idx[inorder[i]]=i;
}
TreeNode* root = build(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1);
return root;
}
};
题解:迭代
时间复杂度:O(N)
空间复杂度:O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0)
{
return nullptr;
}
stack<TreeNode*> stk;
TreeNode* root = new TreeNode(preorder[0]);
stk.push(root);
int inorder_idx=0;
for(int i=1;i<preorder.size();i++)
{
int preorder_val = preorder[i];
TreeNode* node = stk.top();
if(node->val != inorder[inorder_idx])
{
TreeNode* node2 = new TreeNode(preorder_val);
node->left = node2;
stk.push(node2);
}else{
while(!stk.empty() && stk.top()->val == inorder[inorder_idx])
{
node = stk.top();
stk.pop();
inorder_idx++;
}
node->right = new TreeNode(preorder_val);
stk.push(node->right);
}
}
return root;
}
};
3、从中序与后序遍历序列构造二叉树 添加链接描述
题解:迭代
时间复杂度:O(N)
空间复杂度:O(n)
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(postorder.size()==0)
{
return nullptr;
}
stack<TreeNode* > stk;
TreeNode* root = new TreeNode(postorder[postorder.size()-1]);
stk.push(root);
int inorder_idx=inorder.size()-1;
for(int i=postorder.size()-2;i>=0;i--)
{
int post_val = postorder[i];
TreeNode* node = stk.top();
if(node->val!=inorder[inorder_idx])
{
node->right = new TreeNode(post_val);
stk.push(node->right);
}else{
while(!stk.empty() && stk.top()->val==inorder[inorder_idx])
{
node = stk.top();
stk.pop();
inorder_idx--;
}
node->left = new TreeNode(post_val);
stk.push(node->left);
}
}
return root;
}
};
4、根据前序和后序遍历构造二叉树
题解:递归
时间复杂度:O(N)
空间复杂度:O(n)
class Solution {
public:
unordered_map<int, int> val2idx;
TreeNode* build(vector<int> preorder, int pre_f, int pre_e, vector<int> postorder, int post_f, int post_e)
{
if(pre_f>pre_e)
{
return nullptr;
}
TreeNode* root = new TreeNode(preorder[pre_f]);
if(pre_f==pre_e)
{
return root;
}
int post_idx = val2idx[preorder[pre_f+1]];
int left_len = post_idx - post_f+1;
root->left = build(preorder, pre_f+1, pre_f+left_len, postorder, post_f, post_idx);
root->right = build(preorder, pre_f+left_len+1, pre_e, postorder, post_f+left_len, post_e-1);
return root;
}
TreeNode* constructFromPrePost(vector<int>& preorder, vector<int>& postorder) {
for(int i=0;i<postorder.size();i++)
{
val2idx[postorder[i]]=i;
}
TreeNode* root = build(preorder, 0, preorder.size()-1, postorder, 0, postorder.size()-1);
return root;
}
};
1.3.3 序列化
1、二叉树的序列化与反序列化 leetcode
题解:
时间复杂度:O(N)
空间复杂度:O(n)
class Codec {
public:
void rserialize(TreeNode* root, string& str) {
if (root == nullptr) {
str += "None,";
} else {
str += to_string(root->val) + ",";
rserialize(root->left, str);
rserialize(root->right, str);
}
}
string serialize(TreeNode* root) {
string ret;
rserialize(root, ret);
return ret;
}
TreeNode* rdeserialize(list<string>& dataArray) {
if (dataArray.front() == "None") {
dataArray.erase(dataArray.begin());
return nullptr;
}
TreeNode* root = new TreeNode(stoi(dataArray.front()));
dataArray.erase(dataArray.begin());
root->left = rdeserialize(dataArray);
root->right = rdeserialize(dataArray);
return root;
}
TreeNode* deserialize(string data) {
list<string> dataArray;
string str;
for (auto& ch : data) {
if (ch == ',') {
dataArray.push_back(str);
str.clear();
} else {
str.push_back(ch);
}
}
if (!str.empty()) {
dataArray.push_back(str);
str.clear();
}
return rdeserialize(dataArray);
}
};
1.3.4 后序
1、寻找重复的子树 leetcode
题解:
时间复杂度:O(N)
空间复杂度:O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<TreeNode*> ans;
unordered_map<string, int> mp; // 记录出现过的子树
string traverse(TreeNode* root)
{
if(root==nullptr)
{
return "#";
}
string left = traverse(root->left);
string right = traverse(root->right);
string subTree = left + "," + right + "," + to_string(root->val);
if(mp.count(subTree)!=0)
{
int freq = mp[subTree];
if(freq==1)
{
ans.push_back(root);
}
}
mp[subTree]++;
return subTree;
}
vector<TreeNode*> findDuplicateSubtrees(TreeNode* root) {
traverse(root);
return ans;
}
};
1.3.5 归并排序
模板
时间复杂度:O(nlogn)
class Solution {
public:
vector<int> tmp;
void mergSort(vector<int>& nums, int left, int right)
{
if(left>=right)
return;
int mid = (right+left)/2;
mergSort(nums, left, mid);
mergSort(nums, mid+1, right);
int i=left, j=mid+1;
int k=0;
while(i<=mid && j<=right)
{
if(nums[i]<=nums[j])
{
tmp[k++]=nums[i];
i++;
}else{
tmp[k++]=nums[j];
j++;
}
}
while(i<=mid)
{
tmp[k++]=nums[i++];
}
while(j<=right)
{
tmp[k++]=nums[j++];
}
for(int i=0;i<right-left+1;i++)
{
nums[i+left]=tmp[i];
}
}
vector<int> sortArray(vector<int>& nums) {
tmp.resize((int)nums.size(), 0);
mergSort(nums, 0, (int)nums.size()-1);
return nums;
}
};
1、剑指 Offer 51. 数组中的逆序对 leetcode
题解:归并排序
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
int ans=0;
vector<int> tmp;
void mergSort(vector<int>& nums, int left, int right)
{
if(left>=right)
return;
int mid = (left + right) / 2;
mergSort(nums, left, mid);
mergSort(nums, mid+1, right);
int i=left, j=mid+1, k=0;
while(i<=mid && j<=right)
{
if(nums[i]<=nums[j])
{
tmp[k++]=nums[i++];
ans+= (j-(mid+1)); // 因为此时 i 指向的数比 j小,但是比j之前的数大,构成逆序对
}else{
tmp[k++]=nums[j++];
}
}
while(i<=mid)
{
tmp[k++]=nums[i++];
ans+= (j-(mid+1));
}
while(j<=right)
{
tmp[k++]=nums[j++];
}
for(int i=0;i<right-left+1;i++)
{
nums[i+left] = tmp[i];
}
}
int reversePairs(vector<int>& nums) {
if(nums.size()==0)
{
return 0;
}
tmp.resize((int)nums.size(), 0);
mergSort(nums, 0, (int)nums.size()-1);
return ans;
}
};
题解:离散化树状数组
时间复杂度:O(nlogn)
空间复杂度:O(n)
class BIT{
public:
vector<int> tree;
int n;
BIT(int _n): n(_n), tree(_n+1) {}
static int lowbit(int x)
{
return x & (-x);
}
int query(int x)
{
int ret=0;
while(x)
{
ret+=tree[x];
x-=lowbit(x);
}
return ret;
}
void update(int x)
{
while(x<=n)
{
++tree[x];
x+=lowbit(x);
}
}
};
class Solution {
public:
int reversePairs(vector<int>& nums) {
int n = nums.size();
vector<int> tmp = nums;
// 离散化 离散化一个序列的前提是我们只关心这个序列里面元素的相对大小,而不关心绝对大小(即只关心元素在序列中的排名)
sort(tmp.begin(), tmp.end());
for (int& num: nums)
{
num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1;
}
// 树状数组统计逆序对
BIT bit(n);
int ans = 0;
for (int i = n - 1; i >= 0; --i) {
ans += bit.query(nums[i] - 1);
bit.update(nums[i]);
}
return ans;
}
};
2、计算右侧小于当前元素的个数 leetcode
题解:归并,就是在上一题归并的基础上,记录值对应的索引
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
typedef pair<int, int> PII;
vector<int> ans;
vector<PII> data;
void merge(int left, int mid, int right) {
int len = right - left + 1;
vector<PII> temp(len);
int cur = 0;
int i = left, j = mid + 1;
while (i <= mid && j <= right) {
if (data[i].first <= data[j].first) {
ans[data[i].second] += j-mid-1;
temp[cur++] = data[i++];
}
else {
temp[cur++] = data[j++];
}
}
while (i <= mid) {
ans[data[i].second] += j-mid-1;
temp[cur++] = data[i++];
}
while (j <= right) {
temp[cur++] = data[j++];
}
cur = 0;
for (int i = left; i <= right; ++i) {
data[i] = temp[cur++];
}
}
void mergeSort(int left, int right) {
if (left >= right) {
return;
}
int mid = (left + right) >> 1;
mergeSort(left, mid);
mergeSort(mid + 1, right);
merge(left, mid, right);
}
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
data.reserve(n);
ans.resize(n, 0);
for (int i = 0; i < n; ++i) {
data.emplace_back(nums[i], i); // 记录值 和对应的下标
}
mergeSort(0, n - 1);
return ans;
}
};
题解:离散化树状数组
时间复杂度:O(nlogn)
空间复杂度:O(n)
class BIT{
public:
vector<int> tree;
int n;
BIT(int _n): n(_n), tree(_n+1) {}
static int lowbit(int x)
{
return x & (-x);
}
int query(int x)
{
int ret = 0;
while(x)
{
ret+=tree[x];
x-=lowbit(x);
}
return ret;
}
void update(int x)
{
while(x<=n)
{
tree[x]++;
x+=lowbit(x);
}
}
};
class Solution {
public:
vector<int> countSmaller(vector<int>& nums) {
int n = nums.size();
vector<int> ans(n, 0);
vector<int> tmp = nums;
sort(tmp.begin(), tmp.end());
for(int &num:nums)
{
num = lower_bound(tmp.begin(), tmp.end(), num) - tmp.begin() + 1;
}
BIT bit(n);
for(int i=n-1;i>=0;i--)
{
ans[i]+=bit.query(nums[i]-1);
bit.update(nums[i]);
}
return ans;
}
};
3、翻转对 leetcode
题解:归并
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
int ans=0;
vector<int> tmp;
void mergSort(vector<int>& nums, int left, int right)
{
if(left>=right)
return;
int mid = (left + right) / 2;
mergSort(nums, left, mid);
mergSort(nums, mid+1, right);
int i=left, j = mid+1, k=0;
while(i<=mid)
{
while(j<=right && (long long)nums[i]>2* (long long)nums[j])
{
j++;
}
ans+=j-mid-1;
i++;
}
i=left;
j = mid+1;
while(i<=mid && j<=right)
{
if(nums[i]<=nums[j])
{
tmp[k++] = nums[i++];
}else{
tmp[k++] = nums[j++];
}
}
while(i<=mid)
{
tmp[k++] = nums[i++];
}
while(j<=right)
{
tmp[k++] = nums[j++];
}
for(int i=0;i<right-left+1;i++)
{
nums[i+left] = tmp[i];
}
}
int reversePairs(vector<int>& nums) {
tmp.resize((int)nums.size());
mergSort(nums, 0, nums.size()-1);
return ans;
}
};
4、区间和的个数 leetcode
题解:归并
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
long ans=0;
vector<int> tmp;
void mergSort(vector<long>& preSum, int left, int right, int lower, int upper)
{
if(left>=right)
return;
int mid = (left + right)/2;
mergSort(preSum, left, mid, lower, upper);
mergSort(preSum, mid+1, right, lower, upper);
// 首先统计下标对的数量
int i = left;
int l = mid + 1;
int r = mid + 1;
while (i <= mid) {
while (l <= right && preSum[l] - preSum[i] < lower) l++;
while (r <= right && preSum[r] - preSum[i] <= upper) r++;
ans += (r - l);
i++;
}
// 随后合并两个排序数组
vector<long> sorted(right - left + 1);
int p1 = left, p2 = mid + 1;
int p = 0;
while (p1 <= mid || p2 <= right) {
if (p1 > mid) {
sorted[p++] = preSum[p2++];
} else if (p2 > right) {
sorted[p++] = preSum[p1++];
} else {
if (preSum[p1] < preSum[p2]) {
sorted[p++] = preSum[p1++];
} else {
sorted[p++] = preSum[p2++];
}
}
}
for (int i = 0; i < sorted.size(); i++) {
preSum[left + i] = sorted[i];
}
}
int countRangeSum(vector<int>& nums, int lower, int upper) {
vector<long> preSum(nums.size()+1, 0);
for(int i=1;i<=nums.size();i++)
{
preSum[i]=preSum[i-1]+nums[i-1];
}
tmp.resize(preSum.size());
mergSort(preSum, 0, preSum.size()-1, lower, upper);
return ans;
}
};
1.3.6 二叉搜索树
1、二叉搜索树中第K小的元素 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int num=0;
int ans=0;
void inorder(TreeNode* root, int k)
{
if(root!=nullptr)
{
inorder(root->left, k);
num++;
if(num==k)
{
ans=root->val;
return;
}
inorder(root->right, k);
}
}
int kthSmallest(TreeNode* root, int k) {
if(root==nullptr)
return 0;
inorder(root, k);
return ans;
}
};
2、把二叉搜索树转换为累加树 leetcode
题解:降序打印 BST 节点的值,只要把中序遍历反一下
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int sum=0;
void inorder(TreeNode* root)
{
if(root!=nullptr)
{
inorder(root->right);
sum+=root->val;
root->val = sum;
inorder(root->left);
}
}
TreeNode* convertBST(TreeNode* root) {
inorder(root);
return root;
}
};
3、验证二叉搜索树 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool isvalid(TreeNode* root, TreeNode* min_node, TreeNode* max_node)
{
if(root==nullptr)
return true;
if(min_node!=nullptr && root->val <= min_node->val)
{
return false;
}
if(max_node!=nullptr && root->val >= max_node->val)
{
return false;
}
return isvalid(root->left, min_node, root) && isvalid(root->right, root, max_node);
}
bool isValidBST(TreeNode* root) {
return isvalid(root, nullptr, nullptr);
}
};
另一种解法是中序遍历,看结果是不是够成有序数组。
4、二叉搜索树中的搜索 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
TreeNode* ans = nullptr;
void inorder(TreeNode* root, int val)
{
if(root!=nullptr)
{
if(root->val==val)
{
ans=root;
return;
}else if(root->val < val)
{
inorder(root->right, val);
}else{
inorder(root->left, val);
}
}
}
TreeNode* searchBST(TreeNode* root, int val) {
inorder(root, val);
return ans;
}
};
5、更改BST
(1)插入一个数
TreeNode insertIntoBST(TreeNode root, int val) {
// 找到空位置插入新节点
if (root == null) return new TreeNode(val);
// if (root.val == val)
// BST 中一般不会插入已存在元素
if (root.val < val)
root.right = insertIntoBST(root.right, val);
if (root.val > val)
root.left = insertIntoBST(root.left, val);
return root;
}
(2)删除一个数
TreeNode deleteNode(TreeNode root, int key) {
if (root == null) return null;
if (root.val == key) {
// 这两个 if 把情况 1 和 2 都正确处理了(没有子树,或只有一个子树)
if (root.left == null) return root.right;
if (root.right == null) return root.left;
// 处理情况 3:有左右子树时的删除
// 获得右子树最小的节点
TreeNode minNode = getMin(root.right);
// 删除右子树最小的节点
root.right = deleteNode(root.right, minNode.val);
// 用右子树最小的节点替换 root 节点
minNode.left = root.left;
minNode.right = root.right;
root = minNode;
} else if (root.val > key) {
root.left = deleteNode(root.left, key);
} else if (root.val < key) {
root.right = deleteNode(root.right, key);
}
return root;
}
TreeNode getMin(TreeNode node) {
// BST 最左边的就是最小的
while (node.left != null) node = node.left;
return node;
}
6、不同的二叉搜索树 leetcode
题解:
时间复杂度:O(nlogn)
空间复杂度:O(n^2)
class Solution {
public:
vector<vector<int> > memo;
int count(int low, int high)
{
if(low>high)
return 1;
if(memo[low][high]!=0)
{
return memo[low][high];
}
int res=0;
for(int mid=low;mid<=high;mid++)
{
int left=count(low, mid-1);
int right =count(mid+1, high);
res+=left * right;
}
memo[low][high]=res;
return res;
}
int numTrees(int n) {
memo = vector<vector<int> > (n+1, vector<int>(n+1, 0));
return count(1, n);
}
};
7、不同的二叉搜索树 II leetcode
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<TreeNode*> build(int low, int high)
{
vector<TreeNode*> res;
if(low>high)
{
res.push_back(nullptr);
return res;
}
for(int i=low;i<=high;i++)
{
vector<TreeNode*> leftTree = build(low, i-1);
vector<TreeNode*> rightTree = build(i+1, high);
for(TreeNode* left : leftTree)
{
for(TreeNode* right : rightTree)
{
TreeNode* root = new TreeNode(i);
root->left = left;
root->right = right;
res.push_back(root);
}
}
}
return res;
}
vector<TreeNode*> generateTrees(int n) {
if(n==0)
{
return {nullptr};
}
return build(1, n);
}
};
1.3.7 快排
框架,其实类似于前序。
void sort(int[] nums, int lo, int hi) {
if (lo >= hi) {
return;
}
// 对 nums[lo..hi] 进行切分
// 使得 nums[lo..p-1] <= nums[p] < nums[p+1..hi]
int p = partition(nums, lo, hi);
// 去左右子数组进行切分
sort(nums, lo, p - 1);
sort(nums, p + 1, hi);
}
1、数组中的第K个最大元素 leetcode
题解:快速选择
时间复杂度:O(n)
空间复杂度:O(logn)
class Solution {
public:
int quickSelect(vector<int>& nums, int l, int r, int index)
{
int q = randomPartition(nums, l, r);
if (q == index) {
return nums[q];
} else {
return q < index ? quickSelect(nums, q + 1, r, index) : quickSelect(nums, l, q - 1, index);
}
}
int randomPartition(vector<int>& nums, int l, int r) {
int i = rand() % (r - l + 1) + l;
swap(nums[i], nums[r]);
return partition(nums, l, r);
}
int partition(vector<int>& nums, int l, int r) {
int x = nums[r], i = l - 1;
for (int j = l; j < r; ++j) {
if (nums[j] <= x) {
swap(nums[++i], nums[j]);
}
}
swap(nums[i + 1], nums[r]);
return i + 1;
}
int findKthLargest(vector<int>& nums, int k) {
srand(time(0));
return quickSelect(nums, 0, nums.size()-1, nums.size()-k);
}
};
题解:小顶堆
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
void MinHeapify(vector<int>& nums, int i, int heapSize)
{
int l = 2*i+1, r=2*i+2, smallest=i;
if(l<heapSize && nums[l]<nums[smallest])
{
smallest=l;
}
if(r<heapSize && nums[r] < nums[smallest])
{
smallest=r;
}
if(smallest!=i)
{
swap(nums[i], nums[smallest]);
MinHeapify(nums, smallest, heapSize);
}
}
void buildMinHeap(vector<int>& nums, int heapSize)
{
for(int i=heapSize/2;i>=0;i--)
{
MinHeapify(nums, i, heapSize);
}
}
int findKthLargest(vector<int>& nums, int k) {
vector<int> tmp;
for(int i=0;i<k;i++)
{
tmp.push_back(nums[i]);
}
int heapSize = tmp.size();
buildMinHeap(tmp, heapSize); // 先对tmp构建包含k个元素的小顶堆
for(int i=k;i<nums.size();i++)
{
if(nums[i]>tmp[0]) // 如果该元素比堆顶元素更大,那么去掉堆顶,再调整堆
{
swap(tmp[0], nums[i]);
MinHeapify(tmp, 0, heapSize);
}
}
return tmp[0];
}
};
题解:大顶堆求第n-k+1个最小元素
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
void MaxHeapify(vector<int>& nums, int i, int heapSize)
{
int l = 2*i+1, r=2*i+2, largest=i;
if(l<heapSize && nums[l]>nums[largest])
{
largest=l;
}
if(r<heapSize && nums[r] > nums[largest])
{
largest=r;
}
if(largest!=i)
{
swap(nums[i], nums[largest]);
MaxHeapify(nums, largest, heapSize);
}
}
void buildMaxHeap(vector<int>& nums, int heapSize)
{
for(int i=heapSize/2;i>=0;i--)
{
MaxHeapify(nums, i, heapSize);
}
}
int findKthLargest(vector<int>& nums, int k) {
int heapSize = nums.size();
buildMaxHeap(nums, heapSize);
for(int i=nums.size()-1;i>=nums.size()-k+1;i--)
{
swap(nums[0], nums[i]);
heapSize--;
MaxHeapify(nums, 0, heapSize);
}
return nums[0];
}
};
1.3.8 最近公共祖先
1、二叉搜索树的最近公共祖先 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
TreeNode* node = NULL;
void traverse(TreeNode* root, TreeNode* p, TreeNode* q)
{
if(root!=NULL)
{
if(root->val > p->val && root->val > q->val)
{
traverse(root->left, p, q);
}
else if(root->val < p->val && root->val < q->val)
{
traverse(root->right, p, q);
}else{
node = root;
return;
}
}
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
traverse(root, p, q);
return node;
}
};
2、二叉树的最近公共祖先 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root==NULL)
return NULL;
if(root==p || root==q)
return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
if(left!=NULL && right!=NULL)
{
return root;
}
if(left==NULL && right!=NULL)
{
return right;
}
return left==NULL? right : left;
}
};
1.3.9 完全二叉树
性质:
- 具有n个结点的完全二叉树的深度为 l o g 2 n + 1 log_{2}n + 1 log2n+1(注:向下取整)
- 如果 i=1, 则结点 i 是二叉树的根, 无双亲; 如果 i>1, 则其双亲parent (i) 是结点 i/2 (向下取整)
- 如果 2i>n, 则结点 i 无左孩子, 否则其左孩子lchild (i) 是结点2i
- 如果 2i+1>n, 则结点 i 无右孩子, 否则其右孩子rchild (i) 是结点2i+1
- 一个深度为k,结点数为 2 k − 1 2^k-1 2k−1的二叉树是满二叉树
1、完全二叉树的节点个数 leetcode
题解:普通遍历
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==nullptr)
{
return 0;
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
题解:
时间复杂度:O(logNlogN)
递归的次数 x 每次递归的时间复杂度,递归次数就是树的高度 O(logN),每次递归所花费的时间就是 while 循环,需要 O(logN),所以总体的时间复杂度是 O(logNlogN)。
空间复杂度:O(1)
class Solution {
public:
int countNodes(TreeNode* root) {
int hl=0, hr=0;
TreeNode* l = root;
TreeNode* r = root;
while(l!=0)
{
l= l->left;
hl++;
}
while(r!=0)
{
r= r->right;
hr++;
}
if(hl==hr) // 是一个满二叉树
{
return (int)pow(2, hl)-1;
}
return 1+ countNodes(root->left) + countNodes(root->right);
}
};
1.4 数据结构设计
1.4.1 LRU
struct DLinkedNode {
int key, value;
DLinkedNode* prev;
DLinkedNode* next;
DLinkedNode(): key(0), value(0), prev(nullptr), next(nullptr) {}
DLinkedNode(int _key, int _value): key(_key), value(_value), prev(nullptr), next(nullptr) {}
};
class LRUCache {
public:
unordered_map<int, DLinkedNode*> cache;
DLinkedNode* head;
DLinkedNode* tail;
int size;
int capacity;
LRUCache(int _capacity) {
head = new DLinkedNode();
tail = new DLinkedNode();
head->next = tail;
tail->prev = head;
size=0;
capacity = _capacity;
}
int get(int key) {
if(cache.count(key)!=0)
{
DLinkedNode* node = cache[key];
moveToHead(node);
return node->value;
}else{
return -1;
}
}
void removeNode(DLinkedNode* node)
{
node->prev->next = node->next;
node->next->prev = node->prev;
}
void addToHead(DLinkedNode* node)
{
node->prev = head;
node->next = head->next;
head->next->prev = node;
head->next = node;
}
void moveToHead(DLinkedNode* node)
{
removeNode(node);
addToHead(node);
}
DLinkedNode* removeTail() {
DLinkedNode* node = tail->prev;
removeNode(node);
return node;
}
void put(int key, int value) {
if (!cache.count(key)) {
// 如果 key 不存在,创建一个新的节点
DLinkedNode* node = new DLinkedNode(key, value);
// 添加进哈希表
cache[key] = node;
// 添加至双向链表的头部
addToHead(node);
++size;
if (size > capacity) {
// 如果超出容量,删除双向链表的尾部节点
DLinkedNode* removed = removeTail();
// 删除哈希表中对应的项
cache.erase(removed->key);
// 防止内存泄漏
delete removed;
--size;
}
}
else {
// 如果 key 存在,先通过哈希表定位,再修改 value,并移到头部
DLinkedNode* node = cache[key];
node->value = value;
moveToHead(node);
}
}
};
/**
* Your LRUCache object will be instantiated and called as such:
* LRUCache* obj = new LRUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
1.4.2 LFU
struct Node{
int key, val, freq;
Node(int _key, int _val, int _freq):key(_key), val(_val), freq(_freq){}
};
class LFUCache {
public:
unordered_map<int, list<Node>::iterator> key_table;
unordered_map<int, list<Node> > freq_table;
int min_freq;
int capacity;
LFUCache(int _capacity) {
min_freq=0;
capacity = _capacity;
key_table.clear();
freq_table.clear();
}
int get(int key) {
if(capacity==0)
return -1;
auto it = key_table.find(key);
if(it==key_table.end()) // 没有这个key
{
return -1;
}
// 有这个key
list<Node>::iterator node = it->second;
int val = node->val;
int freq = node->freq;
freq_table[freq].erase(node); // 现在读过这个node,不再属于这个freq的list中
if(freq_table[freq].size()==0) // 如果这个计数list为空,那么就要清除
{
freq_table.erase(freq);
if(min_freq == freq) // 如果当前读的计数刚好是最小计数,那么就要更新最小计数
min_freq++;
}
freq_table[freq+1].push_front(Node(key, val, freq+1)); // 放到新的计数 list中
key_table[key] = freq_table[freq+1].begin();
return val;
}
void put(int key, int value) {
if(capacity==0)
return;
auto it = key_table.find(key);
if(it==key_table.end()) // 没有这个key,那就要插入,进行淘汰
{
if(key_table.size()==capacity) // 满了,要淘汰
{
auto erase_it = freq_table[min_freq].back(); // 淘汰当前计数最小中最久未使用的
key_table.erase(erase_it.key);
freq_table[min_freq].pop_back();
if(freq_table[min_freq].size()==0)
{
freq_table.erase(min_freq);
}
}
// 插入新来的节点
min_freq=1; // 因为新来的计数是1
freq_table[1].push_front(Node(key, value, 1));
key_table[key] = freq_table[1].begin();
}else{ // 原来有这个key,那就要更新
list<Node>::iterator node = it -> second;
int freq = node -> freq;
freq_table[freq].erase(node);
if(freq_table[freq].size()==0)
{
freq_table.erase(freq);
if(min_freq==freq)
min_freq+=1;
}
freq_table[freq+1].push_front(Node(key, value, freq+1));
key_table[key] = freq_table[freq+1].begin();
}
}
};
/**
* Your LFUCache object will be instantiated and called as such:
* LFUCache* obj = new LFUCache(capacity);
* int param_1 = obj->get(key);
* obj->put(key,value);
*/
1.4.3 前缀树
Trie 树又叫字典树、前缀树、单词查找树,是一种二叉树衍生出来的高级数据结构,主要应用场景是处理字符串前缀相关的操作。
1、实现 Trie (前缀树) leetcode
题解:
时间复杂度:初始化为 O(1),其余操作为 O(|S|),其中 |S| 是每次插入或查询的字符串的长度。
空间复杂度:O(∣T∣⋅Σ),其中 |T| 为所有插入字符串的长度之和,Σ 为字符集的大小,本题 Σ=26。
class Trie {
private:
vector<Trie*> children;
bool isEnd;
Trie* searchPrefix(string prefix)
{
Trie* node = this;
for(char ch : prefix)
{
ch-='a';
if(node->children[ch]==nullptr)
{
return nullptr;
}
node = node->children[ch];
}
return node;
}
public:
Trie() : children(26), isEnd(false) {}
void insert(string word) {
Trie* node = this;
for(char ch : word)
{
ch-='a';
if(node->children[ch]==nullptr)
{
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isEnd=true;
}
bool search(string word) {
Trie* node = this->searchPrefix(word);
return node==nullptr ? false : true && node->isEnd;
}
bool startsWith(string prefix) {
return this->searchPrefix(prefix)!=nullptr;
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/
2、添加与搜索单词 - 数据结构设计 leetcode
题解:
时间复杂度:初始化为 O(1),添加单词为 O(|S|),搜索单词为 O(|
Σ
∣
∣
S
∣
\Sigma|^{|S|}
Σ∣∣S∣),其中 |S|是每次添加或搜索的单词的长度,
Σ
\Sigma
Σ 是字符集,这道题中的字符集为全部小写英语字母,
∣
Σ
∣
|\Sigma|
∣Σ∣ = 26。
空间复杂度:O(∣T∣⋅Σ),其中 |T| 为所有插入字符串的长度之和,Σ 为字符集的大小,本题 Σ=26。
struct TrieNode{
vector<TrieNode *> child;
bool isEnd;
TrieNode() {
this->child = vector<TrieNode *>(26,nullptr);
this->isEnd = false;
}
};
void insert(TrieNode * root, const string & word) {
TrieNode* node = root;
for (auto c : word) {
if (node->child[c - 'a'] == nullptr) {
node->child[c - 'a'] = new TrieNode();
}
node = node->child[c - 'a'];
}
node->isEnd = true;
}
class WordDictionary {
public:
TrieNode* trie;
WordDictionary() {
trie = new TrieNode();
}
void addWord(string word) {
insert(trie,word);
}
bool search(string word) {
return dfs(word, 0, trie);
}
bool dfs(const string & word,int index,TrieNode * node) {
if (index == word.size()) {
return node->isEnd;
}
char ch = word[index];
if (ch >= 'a' && ch <= 'z') {
TrieNode * child = node->child[ch - 'a'];
if (child != nullptr && dfs(word, index + 1, child)) {
return true;
}
} else if (ch == '.') {
for (int i = 0; i < 26; i++) {
TrieNode * child = node->child[i];
if (child != nullptr && dfs(word, index + 1, child)) {
return true;
}
}
}
return false;
}
};
/**
* Your WordDictionary object will be instantiated and called as such:
* WordDictionary* obj = new WordDictionary();
* obj->addWord(word);
* bool param_2 = obj->search(word);
*/
3、单词替换 leetcode
题解:
struct Trie{
vector<Trie*> children;
bool isEnd;
Trie():children(26), isEnd(false){}
};
class Solution {
public:
void insert(Trie* root, string str)
{
Trie* cur = root;
for (char ch: str) {
ch-='a';
if (cur->children[ch]==nullptr)
{
cur->children[ch] = new Trie();
}
cur = cur->children[ch];
}
cur->isEnd = true;
}
string findRoot(Trie* trie, string word) {
string root="";
Trie* cur = trie;
for (char ch : word) {
ch-='a';
if (cur->isEnd) {
return root;
}
if (cur->children[ch]==nullptr) {
return word;
}
root+=(ch+'a');
cur = cur->children[ch];
}
return root;
}
string replaceWords(vector<string>& dictionary, string sentence) {
Trie* trie = new Trie();
for(auto str : dictionary)
{
insert(trie, str);
}
string ans="";
string tmp = "";
for(int i=0;i<sentence.size();i++)
{
if(sentence[i]!=' ')
{
tmp+=sentence[i];
}else{
string root = findRoot(trie, tmp);
if(root!="")
{
ans+=root;
}else{
ans+=tmp;
}
ans+=' ';
tmp="";
}
}
string root = findRoot(trie, tmp);
if(root!="")
{
ans+=root;
}else{
ans+=tmp;
}
return ans;
}
};
4、键值映射 leetcode
题解:哈希表
时间复杂度:O(N)
空间复杂度:O(N)
class MapSum {
public:
unordered_map<string, int> mp;
MapSum() {
}
void insert(string key, int val) {
mp[key]=val;
}
int sum(string prefix) {
int ans=0;
for(auto iter = mp.begin();iter!=mp.end();iter++)
{
if(iter->first.find(prefix)==0)
{
ans+=iter->second;
}
}
return ans;
}
};
题解:字典树
时间复杂度:O(N)
空间复杂度:O(NM)
struct TrieNode {
int val;
TrieNode * next[26];
TrieNode() {
this->val = 0;
for (int i = 0; i < 26; ++i) {
this->next[i] = nullptr;
}
}
};
class MapSum {
public:
MapSum() {
this->root = new TrieNode();
}
void insert(string key, int val) {
int delta = val;
if (cnt.count(key)) {
delta -= cnt[key];
}
cnt[key] = val;
TrieNode * node = root;
for (auto c : key) {
if (node->next[c - 'a'] == nullptr) {
node->next[c - 'a'] = new TrieNode();
}
node = node->next[c - 'a'];
node->val += delta;
}
}
int sum(string prefix) {
TrieNode * node = root;
for (auto c : prefix) {
if (node->next[c - 'a'] == nullptr) {
return 0;
} else {
node = node->next[c - 'a'];
}
}
return node->val;
}
private:
TrieNode * root;
unordered_map<string, int> cnt;
};
1.4.4 中位数
题解:
时间复杂度:O(logn)
空间复杂度:O(n)
class MedianFinder {
public:
priority_queue<int, vector<int>, less<int> > queMin;
priority_queue<int, vector<int>, greater<int> > queMax;
MedianFinder() {
}
void addNum(int num) {
if(queMin.empty()||num<=queMin.top())
{
queMin.push(num);
if(queMax.size()+1 < queMin.size())
{
queMax.push(queMin.top());
queMin.pop();
}
}else{
queMax.push(num);
if(queMax.size() > queMin.size())
{
queMin.push(queMax.top());
queMax.pop();
}
}
}
double findMedian() {
if(queMin.size()>queMax.size())
{
return queMin.top();
}
return (queMin.top() + queMax.top())/2.0;
}
};
/**
* Your MedianFinder object will be instantiated and called as such:
* MedianFinder* obj = new MedianFinder();
* obj->addNum(num);
* double param_2 = obj->findMedian();
*/
1.4.5 单调栈
1、下一个更大元素 I leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int> hashmap;
vector<int> ans;
int k=0;
stack<int> stk;
for(int i=nums2.size()-1;i>=0;i--)
{
int num = nums2[i];
while(!stk.empty() && num >= stk.top())
{
stk.pop();
}
hashmap[num] = stk.empty() ? -1 : stk.top();
stk.push(num);
}
for(int i=0;i<nums1.size();i++)
{
ans.push_back(hashmap[nums1[i]]);
}
return ans;
}
};
2、下一个更大元素 II leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
vector<int> nextGreaterElements(vector<int>& nums) {
int n = nums.size();
vector<int> tmp(2*n-1, 0); // 把前n-1个元素贴在数组后面,够成“循环数组”
for(int i=0;i<n-1;i++)
{
tmp[i] = nums[i];
tmp[i+n]=nums[i];
}
tmp[n-1]=nums[n-1];
vector<int> ans(n, 0);
stack<int> stk;
for(int i=2*n-2;i>=0;i--)
{
int num = tmp[i];
while(!stk.empty() && num >= stk.top())
{
stk.pop();
}
if(i<n)
{
ans[i] = stk.empty() ? -1 : stk.top();
}
stk.push(num);
}
return ans;
}
};
3、每日温度 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
vector<int> dailyTemperatures(vector<int>& temperatures) {
int n = temperatures.size();
vector<int> ans(n, 0);
stack<pair<int, int>> stk;
for(int i=n-1;i>=0;i--)
{
int num = temperatures[i];
while(!stk.empty() && num>=stk.top().second)
{
stk.pop();
}
ans[i] = stk.empty() ? 0 : stk.top().first - i;
stk.push(make_pair(i, num));
}
return ans;
}
};
1.4.6 单调队列
1、滑动窗口最大值 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(k)
class Solution {
public:
vector<int> maxSlidingWindow(vector<int>& nums, int k) {
deque<int> q; // 维护一个窗口内,严格单调递减的序列,记录下标
for(int i=0;i<k;i++)
{
while(!q.empty() && nums[i] >= nums[q.back()])
{
q.pop_back();
}
q.push_back(i);
}
vector<int> ans;
ans.push_back(nums[q.front()]);
for(int i=k;i<nums.size();i++)
{
while(!q.empty() && nums[i]>=nums[q.back()])
{
q.pop_back();
}
q.push_back(i);
while(i - q.front()>=k) // 要维护窗口大小,离开窗口的要出队
{
q.pop_front();
}
ans.push_back(nums[q.front()]);
}
return ans;
}
};
1.4.7 队列实现栈,栈实现队列
1、用队列实现栈 leetcode
class MyStack {
public:
queue<int> queue1;
queue<int> queue2;
/** Initialize your data structure here. */
MyStack() {
}
/** Push element x onto stack. */
void push(int x) {
queue2.push(x);
while (!queue1.empty()) {
queue2.push(queue1.front());
queue1.pop();
}
swap(queue1, queue2);
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int r = queue1.front();
queue1.pop();
return r;
}
/** Get the top element. */
int top() {
int r = queue1.front();
return r;
}
/** Returns whether the stack is empty. */
bool empty() {
return queue1.empty();
}
};
2、用栈实现队列 leetcode
class MyQueue {
public:
stack<int> s1;
stack<int> s2;
int front;
MyQueue() {
}
void push(int x) {
if(s1.empty())
front=x;
s1.push(x);
}
int pop() {
if(s2.empty())
{
while(!s1.empty())
{
int x = s1.top();
s1.pop();
s2.push(x);
}
}
int val = s2.top();
s2.pop();
return val;
}
int peek() {
if(s2.empty())
{
return front;
}
int val = s2.top();
return val;
}
bool empty() {
return s1.empty() && s2.empty();
}
};
/**
* Your MyQueue object will be instantiated and called as such:
* MyQueue* obj = new MyQueue();
* obj->push(x);
* int param_2 = obj->pop();
* int param_3 = obj->peek();
* bool param_4 = obj->empty();
*/
二、动态规划
2.1 动态规划基本技巧
2.1.1 动态规划解题框架
明确 base case -> 明确「状态」-> 明确「选择」 -> 定义 dp 数组/函数的含义。
1、零钱兑换 leetcode
题解:
时间复杂度:O(Sn) S是金额数,n是硬币面值数
空间复杂度:O(S)
class Solution {
public:
int coinChange(vector<int>& coins, int amount) {
int len = coins.size();
vector<int> dp(amount+1, amount+1);
dp[0]=0;
for(int i=1;i<=amount;i++)
{
for(int j=0;j<coins.size();j++)
{
if(i>=coins[j])
{
dp[i]=min(dp[i], dp[i-coins[j]]+1);
}
}
}
if(dp[amount]==amount+1)
{
return -1;
}
return dp[amount];
}
};
2.1.2 最长递增子序列
1、最长递增子序列 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n==0)
{
return 0;
}
vector<int> dp(n, 1);
int ans=1;
for(int i=0;i<n;i++)
{
for(int j=i-1;j>=0;j--)
{
if(nums[i]>nums[j])
{
dp[i]=max(dp[i], dp[j]+1);
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
题解:贪心+二分查找
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
int lengthOfLIS(vector<int>& nums) {
int n = nums.size();
if(n==0)
{
return 0;
}
vector<int> dp(n+1, 0); // 始终维护一个最长递增子序列
int len=1;
dp[len] = nums[0];
for(int i=1;i<n;i++)
{
if(nums[i]>dp[len])
{
dp[++len] = nums[i]; // 来了一个更大的数,放在末尾
}else{
int l=1, r=len;
int pos=0; // 二分查找,找到第一个比Nums[i]小的数,然后让Nums[i]放在他后面
while(l<=r)
{
int mid = l + (r-l)/2;
if(dp[mid] < nums[i])
{
pos=mid;
l=mid+1;
}else{
r=mid-1;
}
}
dp[pos+1] = nums[i];
}
}
return len;
}
};
2、俄罗斯套娃信封问题 leetcode
题解:动态规划(超时)
时间复杂度:O(n^2)
空间复杂度:O(n)
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b)
{
if(a[0]==b[0])
return a[1]>b[1];
else{
return a[0]<b[0];
}
}
int maxEnvelopes(vector<vector<int>>& envelopes) {
sort(envelopes.begin(), envelopes.end(), cmp);
int n = envelopes.size();
vector<int> dp(n, 1);
int ans=1;
for(int i=1;i<n;i++)
{
for(int j=0;j<i;j++)
{
if(envelopes[i][1]>envelopes[j][1])
{
dp[i]=max(dp[i], dp[j]+1);
}
}
ans = max(ans, dp[i]);
}
return ans;
}
};
题解:贪心+二分
时间复杂度:O(nlogn)
空间复杂度:O(n)
class Solution {
public:
static bool cmp(vector<int>& a, vector<int>& b)
{
if(a[0]==b[0])
return a[1]>b[1];
else{
return a[0]<b[0];
}
}
int maxEnvelopes(vector<vector<int>>& envelopes) {
sort(envelopes.begin(), envelopes.end(), cmp);
int n = envelopes.size();
vector<int> f(n+1, 0);
f[1]=envelopes[0][1];
int len=1;
for(int i=1;i<n;i++)
{
if(envelopes[i][1]>f[len])
{
f[++len] = envelopes[i][1];
}else{
int l=1, r=len;
int pos=0;
while(l<=r)
{
int mid = (l+r)/2;
if(f[mid] < envelopes[i][1])
{
pos=mid;
l = mid+1;
}else{
r=mid-1;
}
}
f[pos+1] = envelopes[i][1];
}
}
return len;
}
};
2.1.3 确定base case
1、下降路径最小和 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int minFallingPathSum(vector<vector<int>>& matrix) {
int ans = INT_MAX;
int n = matrix.size();
vector<vector<int> > dp(n, vector<int>(n, 0));
for(int i=0;i<n;i++)
{
dp[0][i]=matrix[0][i];
}
for(int i=1;i<n;i++)
{
for(int j=0;j<n;j++)
{
if(j>=1 && j<n-1)
dp[i][j]=min(dp[i-1][j], min(dp[i-1][j+1], dp[i-1][j-1])) + matrix[i][j];
else if(j==0){
dp[i][j]=min(dp[i-1][j], dp[i-1][j+1]) + matrix[i][j];
}else if(j==n-1)
{
dp[i][j]=min(dp[i-1][j], dp[i-1][j-1]) + matrix[i][j];
}
}
}
for(int j=0;j<n;j++)
{
ans = min(ans, dp[n-1][j]);
}
return ans;
}
};
2.2 子序列类型
2.2.1 编辑距离
题解:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
if(len1==0 && len2==0)
{
return 0;
}else if(len1==0 && len2!=0)
{
return len2;
}else if(len1!=0 && len2==0)
{
return len1;
}
vector<vector<int> > dp(len1+1, vector<int>(len2+1, 0));
for(int i=1;i<=len1;i++)
{
dp[i][0]=i;
}
for(int j=1;j<=len2;j++)
{
dp[0][j]=j;
}
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(word1[i-1]!=word2[j-1])
{
dp[i][j]=min(dp[i-1][j-1], min(dp[i-1][j], dp[i][j-1])) + 1;
}else{
dp[i][j]=dp[i-1][j-1];
}
}
}
return dp[len1][len2];
}
};
2.2.2 最大子数组
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int n = nums.size();
int val=nums[0];
int ans=nums[0];
for(int i=1;i<n;i++)
{
if(val + nums[i] > nums[i])
{
val = val + nums[i];
}else{
val = nums[i];
}
ans = max(ans, val);
}
return ans;
}
};
2.2.3 最长公共子序列
1、最长公共子序列 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int longestCommonSubsequence(string text1, string text2) {
int len1 = text1.size();
int len2 = text2.size();
vector<vector<int> > dp(len1+1, vector<int>(len2+1, 0));
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;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]);
}
}
}
return dp[len1][len2];
}
};
2、两个字符串的删除操作 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int minDistance(string word1, string word2) {
int len1 = word1.size();
int len2 = word2.size();
vector<vector<int> > dp(len1+1, vector<int>(len2+1, 0));
for(int i=1;i<=len1;i++)
{
dp[i][0]=i;
}
for(int j=1;j<=len2;j++)
{
dp[0][j]=j;
}
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(word1[i-1]!=word2[j-1])
{
dp[i][j] = min(dp[i-1][j], dp[i][j-1]) + 1;
}else{
dp[i][j]=dp[i-1][j-1];
}
}
}
return dp[len1][len2];
}
};
3、两个字符串的最小ASCII删除和 leetcode
题解:
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int minimumDeleteSum(string s1, string s2) {
int len1 = s1.size();
int len2 = s2.size();
vector<vector<int> > dp(len1+1, vector<int>(len2+1, 0));
for(int i=1;i<=len1;i++)
{
dp[i][0]+=dp[i-1][0] + int(s1[i-1]);
}
for(int j=1;j<=len2;j++)
{
dp[0][j]+=dp[0][j-1] + int(s2[j-1]);
}
for(int i=1;i<=len1;i++)
{
for(int j=1;j<=len2;j++)
{
if(s1[i-1]!=s2[j-1])
{
dp[i][j]=min(dp[i-1][j] + int(s1[i-1]), dp[i][j-1] + int(s2[j-1]));
}else{
dp[i][j]=dp[i-1][j-1];
}
}
}
return dp[len1][len2];
}
};
2.3.1 子序列模板
1、让字符串成为回文串的最少插入次数 leetcode
题解:实际上就是在求最长公共子序列
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int minInsertions(string s) {
string t = s;
reverse(t.begin(), t.end());
int n = s.size();
vector<vector<int> > dp(n+1, vector<int>(n+1, 0));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i-1]==t[j-1])
{
dp[i][j] = max(dp[i][j], dp[i-1][j-1] + 1);
}else{
dp[i][j] = max(dp[i-1][j], dp[i][j-1]);
}
}
}
return n - dp[n][n];
}
};
2、最长回文子序列 leetcode
题解:也可以用最长公共子x
时间复杂度:O(n^2)
空间复杂度:O(n^2)
class Solution {
public:
int longestPalindromeSubseq(string s) {
string t = s;
reverse(t.begin(), t.end());
int n = s.size();
vector<vector<int> > dp(n+1, vector<int>(n+1, 0));
for(int i=1;i<=n;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i-1]==t[j-1])
{
dp[i][j]=dp[i-1][j-1]+1;
}else{
dp[i][j]=max(dp[i-1][j], dp[i][j-1]);
}
}
}
return dp[n][n];
}
};
2.4 背包问题
2.4.1 0-1 背包问题
第一步要明确两点,「状态」和「选择」。
- 选择就是「装」或者「不装」。
- 状态有两个,就是「背包的容量」和「可选择的物品」。
基本框架:
for 状态1 in 状态1的所有取值:
for 状态2 in 状态2的所有取值:
for ...
dp[状态1][状态2][...] = 择优(选择1,选择2...)
去所有状态中循环取值,再根据选择确定这些状态下的最优值。
第二步要明确 dp 数组的定义。
刚才找到的「状态」,有两个,也就是说我们需要一个二维 dp 数组。
dp[i][w] 的定义如下:对于前 i 个物品,当前背包的容量为 w,这种情况下可以装的最大价值是 dp[i][w]。
int[][] dp[N+1][W+1]
dp[0][..] = 0 // base case
dp[..][0] = 0 // base case
for i in [1..N]:
for w in [1..W]:
dp[i][w] = max(
把物品 i 装进背包,
不把物品 i 装进背包
)
return dp[N][W]
第三步,根据「选择」,思考状态转移的逻辑。
- 如果你没有把这第 i 个物品装入背包,那么很显然,最大价值 dp[i][w] 应该等于 dp[i-1][w],继承之前的结果。
- 如果你把这第 i 个物品装入了背包,那么 dp[i][w] 应该等于 val[i-1] + dp[i-1][w - wt[i-1]]。
for i in [1..N]:
for w in [1..W]:
dp[i][w] = max(
dp[i-1][w],
dp[i-1][w - wt[i-1]] + val[i-1]
)
return dp[N][W]
2.4.2 子集背包问题
1、分割等和子集 leetcode
题解:
时间复杂度:O(n×target)
空间复杂度:O(n×target)
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(int num : nums)
{
sum+=num;
}
if(sum%2!=0)
{
return false;
}
int target = sum/2;
int n = nums.size();
vector<vector<bool> > dp(n+1, vector<bool>(target+1, false));
for (int i = 0; i <= n; i++)
dp[i][0] = true;
for(int i=1;i<=n;i++)
{
for(int j=1;j<=target;j++)
{
if(j>=nums[i-1])
dp[i][j] = dp[i-1][j] || dp[i-1][j-nums[i-1]];
else{ // 此时装不进背包
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][target];
}
};
题解:空间优化
时间复杂度:O(n×target)
空间复杂度:O(target)
class Solution {
public:
bool canPartition(vector<int>& nums) {
int sum=0;
for(int num : nums)
{
sum+=num;
}
if(sum%2!=0)
{
return false;
}
int target = sum/2;
int n = nums.size();
vector<bool> dp(target+1, false);
dp[0]=1;
for(int i=1;i<=n;i++)
{
for(int j=target;j>=nums[i-1];j--)
{
if(dp[target]==true)
{
return true;
}
dp[j] = dp[j] || dp[j-nums[i-1]];
}
}
return dp[target];
}
};
2.4.3 完全背包问题
1、零钱兑换 II leetcode
题解:
时间复杂度:O(namount)
空间复杂度:O(namount)
class Solution {
public:
int change(int amount, vector<int>& coins) {
int n = coins.size();
vector<vector<int> > dp(n+1, vector<int>(amount+1, 0));
for(int i=0;i<=n;i++)
{
dp[i][0]=1;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=amount;j++)
{
if(j>=coins[i-1])
{
dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]];
}else{
dp[i][j]=dp[i-1][j];
}
}
}
return dp[n][amount];
}
};
题解:空间优化
时间复杂度:O(n*amount)
空间复杂度:O(amount)
class Solution {
public:
int change(int amount, vector<int>& coins) {
int n = coins.size();
vector<int> dp(amount+1, 0);
dp[0]=1;
for(int i=0;i<n;i++)
{
for(int j=coins[i];j<=amount;j++) // 这里为什么能够正着遍历。感觉应该是由于原来的公式是dp[i][j] = dp[i-1][j] + dp[i][j-coins[i-1]]; 后面一项是和dp[i][...]有关,也就是本行。之前是dp[i-1][...],都在上一行。
{
dp[j] = dp[j] + dp[j-coins[i]];
}
}
return dp[amount];
}
};
2.4.4 动态规划和回溯
1、目标和 leetcode
题解:回溯
时间复杂度:O(2^n)
空间复杂度:O(n) 递归空间栈是n
class Solution {
public:
int ans=0;
void backtrace(vector<int>& nums, int index, int target, int sum)
{
if(index>=nums.size())
{
if(sum==target)
{
ans++;
}
return;
}
backtrace(nums, index+1, target, sum + nums[index]);
backtrace(nums, index+1, target, sum - nums[index]);
}
int findTargetSumWays(vector<int>& nums, int target) {
backtrace(nums, 0, target, 0);
return ans;
}
};
题解:动态规划
时间复杂度:O(npos)
空间复杂度:O(npos)
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(int num : nums)
{
sum+=num;
}
int pos = (target + sum);
if(pos%2!=0 || pos<0)
return 0;
pos = pos / 2;
int n = nums.size();
vector<vector<int> > dp(n+1, vector<int>(pos+1, 0));
dp[0][0]=1;
for(int i=1;i<=n;i++)
{
for(int j=0;j<=pos;j++)
{
if(j>=nums[i-1])
{
dp[i][j] = dp[i-1][j] + dp[i-1][j-nums[i-1]];
}else{
dp[i][j] = dp[i-1][j];
}
}
}
return dp[n][pos];
}
};
题解:动态规划——空间优化
时间复杂度:O(n*pos)
空间复杂度:O(pos)
class Solution {
public:
int findTargetSumWays(vector<int>& nums, int target) {
int sum=0;
for(int num : nums)
{
sum+=num;
}
int pos = (target + sum);
if(pos%2!=0 || pos<0)
return 0;
pos = pos / 2;
int n = nums.size();
vector<int> dp(pos+1, 0);
dp[0] = 1;
for(int i=1;i<=n;i++)
{
for(int j=pos;j>=nums[i-1];j--)
{
dp[j] += dp[j-nums[i-1]];
}
}
return dp[pos];
}
};
2.5 用动态规划玩游戏
2.5.1 最小路径和
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int minPathSum(vector<vector<int>>& grid) {
int m = grid.size();
int n = grid[0].size();
vector<vector<int> > dp(m, vector<int>(n, 0));
dp[0][0] = grid[0][0];
for(int i=1;i<m;i++)
{
dp[i][0] = dp[i-1][0] + grid[i][0];
}
for(int j=1;j<n;j++)
{
dp[0][j] = dp[0][j-1] + grid[0][j];
}
for(int i=1;i<m;i++)
{
for(int j=1;j<n;j++)
{
dp[i][j] = min(dp[i][j-1], dp[i-1][j]) + grid[i][j];
}
}
return dp[m-1][n-1];
}
};
2.5.2 地下城游戏
题解:
时间复杂度:O(mn)
空间复杂度:O(mn)
class Solution {
public:
int calculateMinimumHP(vector<vector<int>>& dungeon) {
int m = dungeon.size();
int n = dungeon[0].size();
vector<vector<int> > dp(m, vector<int>(n, 0));
dp[m-1][n-1] = dungeon[m-1][n-1]<0 ? -dungeon[m-1][n-1] + 1 : 1;
for(int i=m-2;i>=0;i--)
{
dp[i][n-1] = dp[i+1][n-1] - dungeon[i][n-1];
if(dp[i][n-1]<=0)
{
dp[i][n-1]=1;
}
}
for(int j=n-2;j>=0;j--)
{
dp[m-1][j] = dp[m-1][j+1] - dungeon[m-1][j];
if(dp[m-1][j]<=0)
{
dp[m-1][j]=1;
}
}
for(int i=m-2;i>=0;i--)
{
for(int j=n-2;j>=0;j--)
{
dp[i][j] = min(dp[i][j+1], dp[i+1][j]) - dungeon[i][j];
if(dp[i][j]<=0)
{
dp[i][j]=1;
}
}
}
return dp[0][0];
}
};
2.5.3 自由之路
题解:
时间复杂度:O(mn^2)
空间复杂度:O(mn)
class Solution {
public:
int findRotateSteps(string ring, string key) {
int n = ring.size(), m = key.size();
vector<int> pos[26]; // 记录ring每个字母的索引下标
for (int i = 0; i < n; ++i) {
pos[ring[i] - 'a'].push_back(i);
}
vector<vector<int>> dp(m, vector<int>(n, INT_MAX));
for (auto& i: pos[key[0] - 'a']) {
dp[0][i] = min(i, n - i) + 1; // 要转到key第一个字符再按下的最小步数
}
for (int i = 1; i < m; ++i) {
for (auto& j: pos[key[i] - 'a']) { // 获取key当前字符在ring的下标
for (auto& k: pos[key[i - 1] - 'a']) { // 获取key当前字符的前一个字符在ring的下标
dp[i][j] = min(dp[i][j], dp[i - 1][k] + min(abs(j - k), n - abs(j - k)) + 1);
}
}
}
return *min_element(dp[m - 1].begin(), dp[m - 1].end());
}
};
2.5.4 加权最短路径
K 站中转内最便宜的航班 leetcode
题解:
时间复杂度:O(km) m为航班数
空间复杂度:O(kn)
class Solution {
static constexpr int INF = 10000 * 101 + 1;
public:
int findCheapestPrice(int n, vector<vector<int>>& flights, int src, int dst, int k) {
vector<vector<int> > dp(k+2, vector<int>(n, INF)); // 最多k站中转=k+1个航班,因此数组开k+2
dp[0][src]=0;
for(int t=1;t<=k+1;t++)
{
for(auto flight : flights)
{
int j = flight[0], i = flight[1], cost = flight[2];
dp[t][i] = min(dp[t-1][j] + cost, dp[t][i]);
}
}
int ans = INF;
for(int t=0;t<=k+1;t++)
{
ans = min(ans, dp[t][dst]);
}
return ans == INF ? -1 : ans;
}
};
2.5.5 正则表达式
题解:
时间复杂度:O(km)
空间复杂度:O(kn)
class Solution {
public:
bool isMatch(string s, string p) {
int m = s.size();
int n = p.size();
vector<vector<int> > dp(m+1, vector<int>(n+1, 0));
dp[0][0]=1;
for(int j=1;j<=n;j++)
{
if(p[j-1]=='*')
{
dp[0][j]=dp[0][j-2];
}
}
for(int i=1;i<=m;i++)
{
for(int j=1;j<=n;j++)
{
if(s[i-1]==p[j-1]||p[j-1]=='.') // 当前第i个字符和第j个字符能匹配
{
dp[i][j]=dp[i-1][j-1];
}else if(p[j-1]=='*'){
if(s[i-1]==p[j-2] || p[j-2]=='.') //看p中*前面一个字符能不能和s的第i个字符匹配上
{
dp[i][j]=dp[i][j-2] // 匹配0次,就是消除了第j-1个字符,直接看j-2的情况
|| dp[i-1][j]; // 匹配1次或多次
}else{ // 不能匹配,那么直接匹配0次,消除掉前一个字符的影响
dp[i][j]=dp[i][j-2];
}
}
}
}
return dp[m][n];
}
};
2.5.6 戳气球
class Solution {
public:
int maxCoins(vector<int>& nums) {
vector<int> val;
val.push_back(1);
for(int num : nums)
{
val.push_back(num);
}
val.push_back(1);
int n = nums.size();
vector<vector<int> > dp(n+2, vector<int>(n+2, 0));
for(int i=n-1;i>=0;i--)
{
for(int j=i+2;j<=n+1;j++)
{
for(int k=i+1;k<j;k++)
{
int sum = val[i]*val[k]*val[j];
dp[i][j] = max(dp[i][j], sum + dp[i][k] + dp[k][j]);
}
}
}
return dp[0][n+1];
}
};
2.5.7 博弈问题
1、预测赢家 leetcode
class Solution {
public:
bool PredictTheWinner(vector<int>& nums) {
int n = nums.size();
vector<vector<int> > dp(n, vector<int>(n, 0));
for(int i=0;i<n;i++)
{
dp[i][i] = nums[i];
}
for(int i=n-2;i>=0;i--)
{
for(int j=i+1;j<n;j++)
{
dp[i][j] = max(nums[i] - dp[i+1][j], nums[j] - dp[i][j-1]);
}
}
return dp[0][n-1]>=0;
}
};
2、石子游戏 leetcode
class Solution {
public:
bool stoneGame(vector<int>& piles) {
int n = piles.size();
vector<vector<int> > dp(n, vector<int>(n, 0));
for(int i=0;i<n;i++)
{
dp[i][i] = piles[i];
}
for(int i=n-2;i>=0;i--)
{
for(int j=i+1;j<n;j++)
{
dp[i][j] = max(piles[i] - dp[i+1][j], piles[j] - dp[i][j-1]);
}
}
return dp[0][n-1] > 0;
}
};
2.5.8 四键键盘
class Solution {
public:
/**
* @param n: an integer
* @return: return an integer
*/
int maxA(int n) {
// write your code here
vector<int> dp(n + 1, 0);
for (int i = 1; i <= n; i++)
{
dp[i] = dp[i - 1] + 1; // 要么一直按1
for (int j = 1; j + 2 < i; j++) // 要么234一起按,且按多次4。这里枚举按4的次数为j,还要按2、3,所以j+2<i
dp[i] = max(dp[i], dp[i - 2 - j] * (j + 1));
}
return dp[n];
}
};
2.5.9 打家劫舍
1、打家劫舍 leetcode
class Solution {
public:
int rob(vector<int>& nums) {
int n = nums.size();
if(n==1)
{
return nums[0];
}else if(n==2)
{
return max(nums[0], nums[1]);
}
vector<int> dp(n+1, 0);
dp[1]=nums[0];
dp[2] = max(nums[0], nums[1]);
for(int i=3;i<=n;i++)
{
dp[i] = max(dp[i-1], dp[i-2]+nums[i-1]);
}
return dp[n];
}
};
2、打家劫舍 II leetcode
class Solution {
public:
int robRange(vector<int>& nums, int start, int end) {
int first = nums[start], second = max(nums[start], nums[start + 1]);
for (int i = start + 2; i <= end; i++) {
int temp = second;
second = max(first + nums[i], second);
first = temp;
}
return second;
}
int rob(vector<int>& nums) {
int length = nums.size();
if (length == 1) {
return nums[0];
} else if (length == 2) {
return max(nums[0], nums[1]);
}
return max(robRange(nums, 0, length - 2), robRange(nums, 1, length - 1));
}
};
3、打家劫舍 III leetcode
class Solution {
public:
unordered_map <TreeNode*, int> f, g; // f表示选这个结点后,最大值;g表示不选这个结点后,最大值
void dfs(TreeNode* root)
{
if(root==nullptr)
{
return;
}
dfs(root->left);
dfs(root->right);
f[root] = root->val + g[root->left] + g[root->right];
g[root] = max(f[root->left], g[root->left]) + max(f[root->right], g[root->right]);
}
int rob(TreeNode* root) {
dfs(root);
return max(f[root], g[root]);
}
};
2.5.10 买卖股票的最佳时机
1、买卖股票的最佳时机 leetcode
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int ans = 0;
int min_value = prices[0];
for(int i=1;i<n;i++)
{
min_value = min(min_value, prices[i]);
if(prices[i]>min_value)
{
ans = max(ans, prices[i] - min_value);
}
}
return ans;
}
};
2、买卖股票的最佳时机 II leetcode
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int min_value = prices[0];
int ans=0;
for(int i=1;i<n;i++)
{
if(prices[i]>min_value)
{
ans+=prices[i] - min_value;
min_value = prices[i];
}
min_value = min(min_value, prices[i]);
}
return ans;
}
};
3、买卖股票的最佳时机 III leetcode
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
int buy1 = -prices[0];
int sell1 = 0;
int buy2 = -prices[0];
int sell2 = 0;
for(int i=1;i<n;i++)
{
buy1 = max(buy1, -prices[i]); // 不买,或者买新的
sell1 = max(buy1 + prices[i], sell1); // 已经买了现在要出售,或者不卖
buy2 = max(sell1 - prices[i], buy2); // 第二次继续买,或者不买
sell2 = max(buy2 + prices[i], sell2); // 第二次出售,或者不卖
}
return sell2;
}
};
4、买卖股票的最佳时机 IV leetcode
class Solution {
public:
int maxProfit(int k, vector<int>& prices) {
int n = prices.size();
vector<vector<vector<int> > > dp(n+1, vector<vector<int> >(k+1, vector<int>(2,0)));
for(int j=0;j<=k;j++)
{
dp[0][j][1] = INT_MIN;
}
for(int i=0;i<=n;i++)
{
dp[i][0][1] = INT_MIN;
}
for(int i=1;i<=n;i++)
{
for(int j=1;j<=k;j++)
{
dp[i][j][0] = max(dp[i-1][j][0], dp[i-1][j][1] + prices[i-1]);
dp[i][j][1] = max(dp[i-1][j-1][0] - prices[i-1], dp[i-1][j][1]);
}
}
return dp[n][k][0];
}
};
5、最佳买卖股票时机含冷冻期 leetcode
class Solution {
public:
int maxProfit(vector<int>& prices) {
int n = prices.size();
vector<vector<int> > dp(n+1, vector<int>(3, 0));
for(int i=0;i<=n;i++)
{
dp[i][1] = INT_MIN;
}
for(int i=1;i<=n;i++)
{
dp[i][0] = max(dp[i-1][0], dp[i-1][2]);
dp[i][1] = max(dp[i-1][1], dp[i-1][0] - prices[i-1]);
dp[i][2] = dp[i-1][1] + prices[i-1];
}
return max(dp[n][0], dp[n][2]);
}
};
6、买卖股票的最佳时机含手续费 leetcode
class Solution {
public:
int maxProfit(vector<int>& prices, int fee) {
int min_value = prices[0]+fee;
int ans=0;
for(int i=1;i<prices.size();i++)
{
if(prices[i]+fee<min_value) // 把手续费算到股票价格中
{
min_value = prices[i]+fee;
}else if(prices[i]>min_value)
{
ans+=prices[i]-min_value; // 此时min已经包含了手续费
min_value = prices[i];
}
}
return ans;
}
};
2.5.11 KMP
题解:有限状态机
时间复杂度:O(n)
空间复杂度:O(256m)=O(m)
class Solution {
public:
vector<vector<int> > dp;
void KMP(string pat)
{
int m = pat.size();
dp = vector<vector<int> > (m, vector<int>(256,0)); // 表示当前i状态,遇到字符j时,要转移到下一个状态
dp[0][pat[0]]=1; // base case:当前状态为0时,遇到自己的第一个字符当然要向前一个状态
int x=0; // 影子状态,来保存和当前状态j相同前缀的状态,这样回退时,尽可能少退
for(int j=1;j<m;j++)
{
for(int c=0;c<256;c++)
{
if(pat[j]==c) // 匹配上了下一个字符
{
dp[j][c]=j+1; // 状态推进
}else{
dp[j][c] = dp[x][c]; // 回退状态
}
}
x = dp[x][pat[j]]; // 更新影子状态
}
}
int strStr(string haystack, string needle) {
KMP(needle); // 构造dp有限状态机
int m = needle.size();
int n = haystack.size();
int j=0;
for(int i=0;i<n;i++)
{
j = dp[j][haystack[i]]; // 当前状态j时,遇到新字符所对应的下一个状态
if(j==m) // 到达终止状态
{
return i-m+1;
}
}
return -1;
}
};
题解:一维next数组
时间复杂度:O(n)
空间复杂度:O(m)
class Solution {
public:
int strStr(string haystack, string needle) {
int n = haystack.size(), m=needle.size();
if(m==0)
{
return 0;
}
vector<int> pi(m);
// 构建next数组
for(int i=1,j=0;i<m;i++)
{
while(j>0&&needle[i]!=needle[j])
{
j=pi[j-1];
}
if(needle[i]==needle[j])
{
j++;
}
pi[i]=j;
}
// 开始查找
for(int i=0,j=0;i<n;i++)
{
while(j>0 && haystack[i]!=needle[j])
{
j=pi[j-1]; // 不匹配时进行回退
}
if(haystack[i]==needle[j])
{
j++;
}
if(j==m)
{
return i-m+1;
}
}
return -1;
}
};
2.6 贪心
2.6.1 区间调度问题
1、无重叠区间 leetcode
题解:
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
int eraseOverlapIntervals(vector<vector<int>>& intervals) {
int n = intervals.size();
if(n==1)
{
return 0;
}
sort(intervals.begin(), intervals.end(),
[](const vector<int>& a, const vector<int>& b)
{
return a[1]<b[1]; // 让右端点小
});
int ans=1; // 记录没被删除的区间数量
int right=intervals[0][1];
for(int i=1;i<n;i++)
{
if(intervals[i][0]>=right)
{
ans++;
right = intervals[i][1];
}
}
return n - ans;
}
};
2、用最少数量的箭引爆气球 leetcode
题解:
时间复杂度:O(nlogn)
空间复杂度:O(logn)
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
sort(points.begin(), points.end(),
[](const vector<int>& a, const vector<int>& b)
{
return a[1]<b[1];
}
);
int ans=1;
int right = points[0][1];
for(int i=1;i<points.size();i++)
{
if(points[i][0]>right)
{
ans++;
right = points[i][1];
}
}
return ans;
}
};
2.6.2 扫描线技巧:安排会议室
1、给你输入若干形如 [begin, end] 的区间,代表若干会议的开始时间和结束时间,请你计算至少需要申请多少间会议室。
思路:
如果会议之间的时间有重叠,那就得额外申请会议室来开会,想求至少需要多少间会议室,就是让你计算同一时刻最多有多少会议在同时进行。
换句话说,如果把每个会议的起始时间看做一个线段区间,那么题目就是让你求最多有几个重叠区间。
扫描线过程:
碰到一个起始点就+1,碰到一个结束点就-1。
int minMeetingRooms(int[][] meetings) {
int n = meetings.length;
int[] begin = new int[n];
int[] end = new int[n];
for(int i = 0; i < n; i++) {
begin[i] = meetings[i][0];
end[i] = meetings[i][1];
}
Arrays.sort(begin);
Arrays.sort(end);
// 扫描过程中的计数器
int count = 0;
// 双指针技巧
int res = 0, i = 0, j = 0;
while (i < n && j < n) {
if (begin[i] < end[j]) {
// 扫描到一个红点
count++;
i++;
} else { // 此时碰到了绿点,说明有会议结束了
// 扫描到一个绿点
count--;
j++;
}
// 记录扫描过程中的最大值
res = Math.max(res, count);
}
return res;
}
2.6.3 剪视频
题解:
时间复杂度:O(nlogn)
空间复杂度:O(1)
class Solution {
public:
int videoStitching(vector<vector<int>>& clips, int time) {
sort(clips.begin(), clips.end(),
[](const vector<int>& a, const vector<int>& b)
{
if(a[0]!=b[0])
{
return a[0]<b[0];
}else{
return a[1]>b[1];
}
}
);
int ans=1;
if(clips[0][0]!=0)
{
return -1;
}
int curEnd = clips[0][1];
if(curEnd>=time) // 当前就已经到达最终点
{
return 1;
}
int n = clips.size();
for(int i=1;i<n;i++)
{
if(clips[i][0]<=curEnd) // 当前片段比右边界小,说明有交集
{
int nextEnd = 0;
while(i<n && clips[i][0]<=curEnd) // 一直在有交集的区间中,找最大右边界
{
nextEnd = max(nextEnd, clips[i][1]);
i++;
}
curEnd = nextEnd; // 更新当前最大右边界
ans++; // 同时计数++
if(curEnd>=time) // 达到了最大范围,就可以跳出循环
{
break;
}
i--;
}else{ // 说明后面不存在与当前边界相交的片段,那么无法到达最终点
return -1;
}
}
if(curEnd<time)
{
return -1;
}
return ans;
}
};
题解:
时间复杂度:O(n)
空间复杂度:O(time)
class Solution {
public:
int videoStitching(vector<vector<int>>& clips, int time) {
// 可以理解为跳格子的问题。需要知道在当前位置能够跳到的位置,然后用最少的次数跳到目的地。
vector<int> maxn(time);
for(auto t:clips)
{
if(t[0]<time)
{
maxn[t[0]]=max(maxn[t[0]], t[1]); // 确定每个区间左端点,能够碰到的最右端点
}
}
int ans=0;
int last=0;
int pre=0;
for(int i=0;i<time;i++)
{
last = max(last, maxn[i]); // 从该位置能碰到的最右边
if(i==last) // 说明该位置能碰到的最右边还是该位置,不能继续往后
{
return -1;
}
if(i==pre) // 要走到上一步能到的最后位置,才能更新所跳的步数
{
ans++;
pre=last;
}
}
return ans;
}
};
2.6.4 跳跃游戏
1、跳跃游戏 leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
bool canJump(vector<int>& nums) {
int n = nums.size();
int rightmost=0; // 当前能到达的最远位置
for(int i=0;i<n;i++)
{
if(i<=rightmost)
{
rightmost = max(rightmost, i+nums[i]); // 更新最远位置,因为跳到这个地方后能够继续跳,所以最远位置能够更新
if(rightmost>=n-1)
{
return true;
}
}
}
return false;
}
};
2、跳跃游戏 II leetcode
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int jump(vector<int>& nums) {
int ans=0;
int end=0; // 记录上一次跳的最远位置
int maxpos=0;
for(int i=0;i<nums.size()-1;i++)
{
maxpos = max(maxpos, nums[i]+i); // 更新从当前位置 i 能跳到的最远位置
if(i==end) // 到了这个位置后,就可以继续跳
{
end=maxpos; // 更新上一次跳的最远位置
ans++; // 跳的次数++
}
}
return ans;
}
};
2.6.5 加油站
题解:
时间复杂度:O(n)
空间复杂度:O(1)
class Solution {
public:
int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
int curSum=0, totalSum=0;
int start=0;
for(int i=0;i<gas.size();i++)
{
// gas[i]-cost[i]是什么意思:gas[i]是 i 处能够加的油,加了油之后要往 i+1去,中间消耗cost[i],那么相减其实就是等价于在 i 处能够加gas[i]-cost[i]油到达 i+1,可正可负
curSum+=gas[i]-cost[i];
totalSum+=gas[i]-cost[i];
if(curSum<0) // 如果x到不了y+1(但能到y),那么从x到y的任一点出发都不可能到达y+1。因为从其中任一点出发的话,相当于从0开始加油,而如果从x出发到该点则不一定是从0开始加油,可能还有剩余的油。既然不从0开始都到不了y+1,那么从0开始就更不可能到达y+1了。
{
curSum=0;
start=i+1;
}
}
if(totalSum<0) // 总剩余油量<0,说明肯定走不完一圈
{
return -1;
}
return start;
}
};