1th:1.两数之和
给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
//暴力求解
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
int i , j;
for(i = 0; i<nums.size()-1; i++){
// if(nums[i]<=target){
for(j = i+1; j<nums.size(); j++){
if(nums[i] + nums[j] == target){
return {i,j};
}
}
//}
}
return {};
}
};
//哈希求解
class Solution {
public:
vector<int> twoSum(vector<int>& nums, int target) {
//使用哈希表unordered_map solve the problem
//take i as values and nums[i] as keys
unordered_map<int,int> ht;
for(int i = 0; i<nums.size(); i++){
auto iter = ht.find(target-nums[i]);
if(iter!=ht.end()){
//find the position
return {iter->second,i};
}
ht[nums[i]] = i;
}
return {};
}
};
2th:2.删除排序数组中的重复项
给定一个排序数组,你需要在 原地 删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
//没有审好题,输入的是有序数组,这样写好吗?这不好。
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
//use set to remove duplicates
unsigned int count = 0;
set<int> s(nums.begin(),nums.end());
set<int>::iterator iter;
for(iter = s.begin(); iter!=s.end();iter++){
nums[count] = *iter;
count++;
}
return count;
}
};
//两个指针
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
int i,j;
for(i = 0,j = 1; j<nums.size();){
if(nums[i] != nums[j])
{
if(j-i == 1){
i++;
}else{
i++;
//swap the elements
//看了官方的代码,发现这里交换元素纯粹鸡肋,直接赋值就好
nums[i] = nums[i]^nums[j];
nums[j] = nums[i]^nums[j];
nums[i] = nums[i]^nums[j];
}
}
j++;
}
return i+1;
}
};
//官方思路
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
if(nums.size()<=1) return nums.size();
int i,j;
for(i = 0,j = 1; j<nums.size();){
if(nums[i] != nums[j])
{
i++;
nums[i] = nums[j];
}
j++;
}
return i+1;
}
};
3th:27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素
//双指针,和val相同的数在第一个位置的时候,有很多重复的操作,官方代码有优化方法
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i= 0, j = 0;
for(; i<nums.size();i++){
if(nums[i] != val) nums[j++] = nums[i];
}
return j;
}
};
//官方
/*元素的顺序可以改变,利用这种方式,每次知道与val相等的元素,就
从后向前取值,覆盖对应位置上的元素,然后将数组大小减1,使得迭
代的次数减小,就算覆盖的数值等于val也没有关系,会再次计算当前
位置元素是否等于val
*/
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int i = 0;
int n = nums.size();
while (i < n) {
if (nums[i] == val) {
nums[i] = nums[n - 1];
n--;
} else {
i++;
}
}
return n;
}
};
4th:35.搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。你可以假设数组中无重复元素。
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
int i;
for(i = 0; i<nums.size(); i++){
if(target<=nums[i]) return i;
}
return i;
}
};
//二分查找
class Solution {
public:
int searchInsert(vector<int>& nums, int target) {
//二分查找
int left = 0, right = nums.size()-1;
while(left<=right){
int mid = (left+right)/2;
if(nums[mid] == target) return mid;
else if(target > nums[mid]) left = mid+1;
else right = mid-1;
}
return left;
}
};
5th:452.用最少数量的箭引爆气球
在二维空间中有许多球形的气球。对于每个气球,提供的输入是水平方向上,气球直径的开始和结束坐标。由于它是水平的,所以纵坐标并不重要,因此只要知道开始和结束的横坐标就足够了。开始坐标总是小于结束坐标。
一支弓箭可以沿着 x 轴从不同点完全垂直地射出。在坐标 x 处射出一支箭,若有一个气球的直径的开始和结束坐标为 xstart,xend, 且满足 xstart ≤ x ≤ xend,则该气球会被引爆。可以射出的弓箭的数量没有限制。 弓箭一旦被射出之后,可以无限地前进。我们想找到使得所有气球全部被引爆,所需的弓箭的最小数量。
给你一个数组 points ,其中 points [i] = [xstart,xend] ,返回引爆所有气球所必须射出的最小弓箭数。
class Solution {
public:
int findMinArrowShots(vector<vector<int>>& points) {
if(points.size() == 0) return 0;
//找最小右边界
/*一定存在一种最优(射出的箭数最小)的方法,使得每一支
箭的射出位置都恰好对应着某一个气球的右边界。*/
sort(points.begin(),points.end(),[](const vector<int> &u, const vector<int> &v)
{
return u[1]<v[1];
});
int pos = points[0][1]; //右边界
int ans = 1;
for(const vector<int> &point: points){
if(point[0] > pos){
pos = point[1]; //更新右边界
ans++;
}
}
return ans;
}
};
6th:66.加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
short add = 1;
short cur;
for(int i = digits.size()-1; i>=0; i--){
cur = digits[i]+add;
digits[i] = cur%10;
if(cur > 9) add = 1;
else add = 0;
if(add == 1 && i == 0){
vector<int> tmp(digits.size()+1);
tmp[0] = 1;
for(int j = 1; j<digits.size(); j++)
tmp[j] = digits[j];
return tmp;
}
}
return digits;
}
};
7th:53.最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
//暴力破解
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int max = INT_MIN;
for(int i = 0; i<nums.size(); i++){
int sum = 0;
for(int j = i; j<nums.size(); j++){
//计算以i为起点的所有连续序列的和,并求最大值
sum = sum + nums[j];
if(sum>max)
max = sum;
}
}
return max;
}
};
//动态规划
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = nums[0];
//vector pre;
int pre = 0; //表示以nums[i]结尾的最大子序列的和
for (const auto &num: nums){
pre = max(pre+num,num);
res = max(res,pre);
}
return res;
}
};
//贪心
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int sum = 0;
int res = nums[0];
for(int i = 0; i<nums.size(); i++){
sum += nums[i];
res = max(sum,res);
if(sum<0)
sum = 0;
}
return res;
}
};
8th:941.有效山脉数组
给定一个整数数组 A,如果它是有效的山脉数组就返回 true,否则返回 false
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
int len = arr.size();
if(len < 3 ) return false;
int i;
for(i = 0; i<len-1 && arr[i] < arr[i+1]; i++){}
if(i == 0 || i == len-1) return false;
int j;
for(j = len-1; j>0 && arr[j] < arr[j-1]; j--){}
if(j == len-1 || j ==0) return false;
return i==j;
}
};
class Solution {
public:
bool validMountainArray(vector<int>& arr) {
int len = arr.size();
int i = 0;
int j = len-1;
while(i<len-1 && arr[i]<arr[i+1]) i++;
while(j>0 && arr[j-1]>arr[j]) j--;
return i>0 && j<len-1 && i == j;
}
};
9th:136. 只出现一次的数字
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
//eor运算,利用异或运算不进位相加的特性,以及交换结合律
//使得重复的数字a^a = 0, 0^b = b.
class Solution {
public:
int singleNumber(vector<int>& nums) {
//利用异或运算
int eor = 0;
for(int i = 0; i<nums.size(); i++){
eor ^= nums[i];
}
return eor;
}
};
10th:260. 只出现一次的数字v3
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。
class Solution {
public:
vector<int> singleNumber(vector<int>& nums) {
int eor = 0;
for(int i = 0; i<nums.size(); i++){
eor ^= nums[i];
}
//找到二进制中最右边的1,这里其实找到任意的1都是可以的
int rightOne = eor & ((~eor)+1); //x^y 00000010
int eor2 = 0;
/*
假设rightone为0010,即第二位为1,则nums数组中,
可以分为两类,第二位是1的数和第二位是0的数,
并且一定是成对的,但是x和y一定在不同侧,这样的话,
xy就实现了分离。
下面在任意一侧再次异或,就可以得到其中一个数了
*/
for(int i = 0; i<nums.size(); i++){
//注意优先级,== !=高于位运算符
if( (nums[i] & rightOne) != 0){
eor2 ^= nums[i];
}
}
return {eor2,eor^eor2};
}
};
11th:137. 只出现一次的数字v2
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素
class Solution {
public:
int singleNumber(vector<int>& nums) {
//O(nlogn),先排序然后就好找了
//实际上是不符合要求的,不是线性复杂度
sort(nums.begin(),nums.end());
if(nums.size() == 1) return nums[0];
int i;
for(i = 0; i<nums.size()-2; i++){
//cout<
if(nums[i] == nums[i+2]){
i = i+2;
}else return nums[i];
}
return nums[i];
}
};
//set和数学知识
class Solution {
public:
int singleNumber(vector<int>& nums) {
//set
//3(a+b+c) - (a+a+a+b+b+b+c) = 2c,可找出只出现一次的数
set<int> s(nums.begin(),nums.end());
long sum = 0;
for(int i = 0; i<nums.size(); i++){
sum += nums[i];
}
long sumSet = 0;
//set访问智能通过迭代器,插入可用s.insert()
for(set<int>::iterator it = s.begin(); it!=s.end(); it++){
sumSet += *it;
}
return (3*sumSet-sum)/2;
}
};
//这种方法个人觉得比较容易理解,并且易于实现
//官网上还给出了 另一种骚操作,很难理解,以后再看吧
//拆分每一位来看,然后求出所求数字的每一位,组合在一起
class Solution {
public:
int singleNumber(vector<int>& nums) {
//考虑某一位,比如第一位,1001 1001 1001 1111 1111 1111 1011
//第一位之和sum = 3的倍数或者3的倍数加1, 对3 取模,可得到出现一次的数
int len = sizeof(int)*8;
int i = 0;
int res = 0;
while(i<len){
int sum = 0;
for(int j = 0; j<nums.size(); j++){
//注意优先级,记住括号
sum = sum + ((nums[j]>>i) & 1);
}
cout<<"i = "<<i<<" sum = "<<sum<<endl;
res = res | (sum%3)<<i;
i++;
}
return res;
}
12th:面试题:消失的数组
数组nums包含从0到n的所有整数,但其中缺了一个。请编写代码找出那个缺失的整数。你有办法在O(n)时间内完成吗?
//可以在O(n)内完成,但是额外的空间复杂度也有O(n)
//用空间换时间的操作,以元素为索引,建立新的数组,再查找
class Solution {
public:
int missingNumber(vector<int>& nums) {
int n = nums.size();
vector<int> s(n+1,0);
for(int i = 0; i<n; i++){
s[nums[i]] = 1;
}
for(int i = 0; i<n+1; i++){
if(s[i] == 0) return i;
}
return 0;
}
};
//利用index和数组元素有大量相同的元素,这样的话消失的是数字
//实际上有两个,x和nums.size(),连续eor得到x^nums.size()
//nums.size()已知,可以很容易分离出消失的数字
class Solution {
public:
int missingNumber(vector<int>& nums) {
//官方给的,有点厉害啊,把index和数组中元素一起异或,
//就可以找出来那个数
int eor = 0;
for(int i = 0; i<nums.size(); i++){
eor ^= i;
eor ^= nums[i];
}
//得到的eor的结果是x^nums.size(),
//因为index中nums.size()也是缺失的
return eor ^nums.size();
}
};
13th:消失的两个数字
给定一个数组,包含从 1 到 N 所有的整数,但其中缺了两个数字。你能在 O(N) 时间内只用 O(1) 的空间找到它们吗?
以任意顺序返回这两个数字均可。
class Solution {
public:
vector<int> missingTwo(vector<int>& nums) {
int n = nums.size()+2; //4
int eor = 0;
//eor = 2^3
for(int i = 0; i<nums.size(); i++)
eor = eor ^ nums[i];
//eor = 2^3^1^4^2^3
for(int i = 1; i<=n; i++)
eor = eor ^ i;
//x^y eor = 1^4
int rightone = eor &((~eor)+1); //rightone
//cout<
int eor2 = 0;
//假设是
for(int i = 1; i<=n; i++){
if( (i & rightone) != 0) {eor2 ^= i;}
}
cout<<eor2<<endl;
for(int i = 0; i<nums.size(); i++){
if( (nums[i]&rightone) != 0) eor2 ^= nums[i];
}
return {eor2,eor^eor2};
}
};
14th:867.转置矩阵
给定一个矩阵 A, 返回 A 的转置矩阵。矩阵的转置是指将矩阵的主对角线翻转,交换矩阵的行索引与列索引。
class Solution {
public:
vector<vector<int>> transpose(vector<vector<int>>& A) {
if(A.size() == 0) return {};
int col = A.size();
int row = A[0].size();
vector<vector<int>> res;
for(int i = 0; i<row; i++){
vector<int> tmp;
for(int j = 0; j<col; j++){
tmp.push_back(A[j][i]);
}
res.push_back(tmp);
}
return res;
}
};
15th:子矩阵查询
请你实现一个类 SubrectangleQueries ,它的构造函数的参数是一个 rows x cols 的矩形(这里用整数矩阵表示),并支持以下两种操作:
1 updateSubrectangle(int row1, int col1, int row2, int col2, int newValue)
用 newValue 更新以 (row1,col1) 为左上角且以 (row2,col2) 为右下角的子矩形。
2 getValue(int row, int col)
返回矩形中坐标 (row,col) 的当前值。
class SubrectangleQueries {
private:
vector<vector<int>> rec;
//用his保存对rec的修改记录
vector<vector<int>> his;
public:
SubrectangleQueries(vector<vector<int>>& rectangle) {
rec = rectangle;
}
void updateSubrectangle(int row1, int col1, int row2, int col2, int newValue) {
his.push_back({row1,col1,row2,col2,newValue});
}
int getValue(int row, int col) {
for(int i = his.size()-1; i>=0; i--){
if(row>=his[i][0] && row<=his[i][2] &&col>=his[i][1]&&col<=his[i][3])
return his[i][4];
}
return rec[row][col];
}
};
/**
* Your SubrectangleQueries object will be instantiated and called as such:
* SubrectangleQueries* obj = new SubrectangleQueries(rectangle);
* obj->updateSubrectangle(row1,col1,row2,col2,newValue);
* int param_2 = obj->getValue(row,col);
*/
16th: 24. 反转链表
定义一个函数,输入一个链表的头节点,反转该链表并输出反转后链表的头节点。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
//1、循环迭代,用两个指针操作
/*ListNode* reverseList(ListNode* head) {
ListNode *prev = 0;
ListNode *pNext = 0;
while(head){
pNext = head->next;
head->next = prev;
prev = head;
head = pNext;
}
return prev;
}*/
//2、用递归写,也不是很懂,稀里糊涂就写了
ListNode * recur(ListNode *prev,ListNode *head,ListNode *pNext){
pNext = head->next;
head->next = prev;
prev = head;
head = pNext;
//std::cout<val<
if(head == NULL) return prev;
return recur(prev,head,pNext);
}
ListNode* reverseList(ListNode* head) {
//递归
if(head == NULL) return head;
ListNode *prev = 0;
ListNode * pNext = 0;
return recur(prev,head,pNext);
}
//3、官方给出的递归版本
ListNode* reverseList(ListNode* head){
//递归结束条件.如果是空链表或者是最后一个节点那么返回head
if(!head || !head->next)
return head;
//递归调用,一直递归到最后一个节点,遇到结束的条件return 最后一个节点
ListNode *tail = reverseList(head->next);
head->next->next = head;
head->next = NULL;
return tail;
}
};
17th:141. 环形链表
给定一个链表,判断链表中是否有环。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
//1、每个链表的val小于100000,循环访问节点并用1000001做标记
//一圈过后就可以知道是否有环啦
/*bool hasCycle(ListNode *head) {
if(head == 0) return false;
bool hasCircle = false;
while(head){
if(head->val == 100001){
hasCircle = true;
break;
}
head->val = 100001;
head = head->next;
}
return hasCircle;
}*/
//2、官方实现1:利用哈希表来做。将每个节点存放到哈希表中,如果存放有重复 那么有环
//比较耗费空间
/*bool hasCycle(ListNode *head){
unordered_set s;
while(head)
{
std::cout<val< 0)
return true;
s.insert(head);
head = head->next;
}
return false;
}*/
//3、官方实现2:使用快慢指针,快指针,一次循环走两步,慢指针一次循环走一步
//如果有环,那么一定会相遇
bool hasCycle(ListNode *head){
if(!head || !head->next) return false;
ListNode *slow = head;
ListNode *fast = head->next;
while(fast && fast->next)
{
if( fast == slow)
return true;
slow = slow->next;
fast = fast->next->next;
}
return false;
}
};
18th:合并两个有序链表
输入两个递增排序的链表,合并这两个链表并使新链表中的节点仍然是递增排序的。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
//1、
/*ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
if(l1 == 0) return l2;
if(l2 == 0) return l1;
ListNode *head_1= l1->val <= l2->val? l1:l2;
ListNode * prev = 0, *l = head_1;
ListNode *head_2= (head_1==l1) ?l2:l1;
ListNode *prev_2 = 0;
while(head_2){
while(head_1 != NULL && head_1->val <= head_2->val) {
prev = head_1;
head_1 = head_1->next;
}
//插入到prev的后面
prev_2 = head_2;
head_2 = head_2->next;
prev_2->next = head_1;
prev->next = prev_2;
prev = prev_2;
}
return l;
}*/
//2、官方实现,生成一个伪头结点,然后再循环中比较大小,选择性插入
//然后再连接没有插入完的链表
ListNode* mergeTwoLists(ListNode* l1, ListNode* l2){
ListNode *phead = (ListNode *)malloc(sizeof(*phead));
ListNode *cur = phead;
while(l1 && l2)
{
if(l1->val <= l2->val){
cur->next = l1;
l1 = l1->next;
}else{
cur->next = l2;
l2 = l2->next;
}
cur = cur->next;
}
cur->next = l1?l1:l2;
return phead->next;
}
};
19th:349. 两个数组的交集
给定两个数组,编写一个函数来计算它们的交集
class Solution {
public:
vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
if(nums1.empty() || nums2.empty()) return {};
set<int> s1;
set<int> s2;
vector<int> res;
//将两个数组分别装入两个集合中,去重
for(auto n:nums1) s1.insert(n);
for(auto n:nums2) s2.insert(n);
int size = s1.size();
//不断向s1中插入s2中的元素,如果不能够插入说明s1中也有这个元素,即使我们所求的内容
for(set<int>::iterator iter = s2.begin(); iter!=s2.end(); iter++)
{
s1.insert(*iter);
if( s1.size() == size)
res.push_back(*iter);
else size = s1.size();
}
return res;
}
};
20th:1122. 数组的相对排序
给你两个数组,arr1 和 arr2,
arr2 中的元素各不相同
arr2 中的每个元素都出现在 arr1 中
对 arr1 中的元素进行排序,使 arr1 中项的相对顺序和 arr2 中的相对顺序相同。未在 arr2 中出现过的元素需要按照升序放在 arr1 的末尾。
class Solution {
public:
//1、计数排序
//首先将arr1以元素大小为索引存入一个数组restore中
//然后遍历arr2,找出restor中相同的部分,存入返回数组。
//并且索引归0,这样restore中的元素就是不喝arr2重合的部分
//再将不重合的遮部分数据push_back到返回数组中
vector<int> relativeSortArray(vector<int>& arr1, vector<int>& arr2) {
vector<int> res;
vector<int> restore(10001,0);
for(auto i:arr1) restore[i]++;
for(auto i:arr2){
for(int j = 0; j<restore[i]; j++){
res.push_back(i);
}
restore[i] = 0;
}
for(int i = 0; i<restore.size(); i++)
{
for(int j = 0; j<restore[i]; j++)
{
res.push_back(i);
}
}
return res;
}
};
21th:柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false
class Solution {
public:
bool lemonadeChange(vector<int>& bills) {
vector<int> box(5,0); // 0 1 2 3 4 -> null 5 10 null 20
int i, size = bills.size();
for(i = 0; i<size; i++){
box[bills[i]/5]++;
if(bills[i] == 10){
//需要找5块钱
if(box[1] < 1) return false;
box[1]--;
continue;
}
if(bills[i] == 20){
//需要找15,首先看有没有十块
if(box[2] >= 1){
if(box[1]>=1){
box[2]--;
box[1]--;
}else return false;
}else{
//如果没有10块的,那么用五块的找
if(box[1]>=3) box[1] -= 3;
else return false;
}
}
}//for
return true;
}
};
22th:455. 分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
class Solution {
public:
//1、使用优先队列来做,优先队列pop的值是由顺序的,用空间省去排序的时间,比较花费空间
//也可以直接排序来做。贪心的思想,有能满足孩子的最小饼干来分发,就可以照顾到最多的小孩
int findContentChildren(vector<int>& g, vector<int>& s) {
priority_queue<int> q_g;//大顶堆; priority_q2ueue,greater> 小顶堆
priority_queue<int> s_g;
for(auto i:g) q_g.push(i);
for(auto i:s) s_g.push(i);
int cnt = 0;
while(!q_g.empty() && !s_g.empty()){
if(q_g.top() <= s_g.top()){
s_g.pop();
cnt++;
}
q_g.pop();
}
return cnt;
}
//2、直接排序
int findContentChildren(vector<int>& g, vector<int>& s){
sort(g.rbegin(),g.rend());
sort(s.rbegin(),s.rend());
int i = 0, j = 0, cnt = 0;
int g_size = g.size(), s_size = s.size();
while(i<g_size && j<s_size){
if(g[i] <= s[j]){
cnt++;
j++;
}
i++;
}
return cnt;
}
};
23th:605. 种花问题
假设有一个很长的花坛,一部分地块种植了花,另一部分却没有。可是,花不能种植在相邻的地块上,它们会争夺水源,两者都会死去。
给你一个整数数组 flowerbed 表示花坛,由若干 0 和 1 组成,其中 0 表示没种植花,1 表示种植了花。另有一个数 n ,能否在不打破种植规则的情况下种入 n 朵花?能则返回 true ,不能则返回 false。
class Solution {
public:
bool canPlaceFlowers(vector<int>& flowerbed, int n) {
int size= flowerbed.size();
if(n == 0) return true;
if(size <= 1) return flowerbed[0]==1?0:1;
int insert = 0, i = 0;
for(i = 0; i<size; i++){
if(i == 0 && flowerbed[i] == 0 && flowerbed[i+1] == 0)
{flowerbed[i] = 1;insert++; i++; continue;}
if(i<size-1&& flowerbed[i] == 0 && flowerbed[i+1] == 0 && flowerbed[i-1]==0)
{flowerbed[i] = 1;insert++;i++;continue;}
if(i == size-1 && flowerbed[i-1] == 0 && flowerbed[i] == 0)
{flowerbed[i]=1;insert++;continue;}
if(insert >= n) return true;
}
return insert>=n;
}
};
24th:121. 买卖股票的最佳时机
给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。
class Solution {
public:
//1、暴力
int maxProfit(vector<int>& prices) {
int max = 0;
for(int i = 0; i<prices.size()-1; i++){
for(int j = i+1; j<prices.size() && prices[j]>prices[i]; j++){
if(prices[j]-prices[i] > max) max = prices[j]-prices[i];
}
}
return max;
}
//2、sold为卖彩票的时间,我们只需要求出sold之前的最小值就可以算出sold这天买的最大利润
int maxProfit(vector<int>& prices) {
int min = prices[0];
int max = 0;
for(auto sold:prices){
if(sold<min) {min = sold;continue;}
if(sold-min > max) max = sold-min;
}
return max;
}
};
25th:122. 买卖股票的最佳时机 2
给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
class Solution {
public:
//1、动态规划
int maxProfit(vector<int>& prices) {
int dp0 = 0; //第一天没有买股票的利润
int dp1 = -prices[0]; //第一天卖了股票的利润
int i = 1, size = prices.size();
for(i = 1; i<size; i++){
//当前手头没有股票,那么手头最大的利润,可以表示为昨天没有股票 ,
//和昨天有股票(但是今天卖了)的利润较大值
dp0 = max(dp0,dp1+prices[i]);
//今天手头有股票,那么今天手头最大的利润,
//可以表示为昨天没有股票(但是今天卖了)和昨天也有股票的较大值
dp1 = max(dp1,dp0-prices[i]);
}
return dp0;
}
//2、贪心算法
/*贪心思想:经典问题:背包问题和活动安排问题,
基本思想是找出整体当中每个小的局部的最优解,并且将所有的这些局部最优解合起来形成整体上的一个最优解。
在对问题求解时,总是作出在当前看来是最好的选择。也就是说,不从整体上加以考虑,它所作出的仅仅是在某种意义上的局部最优解(是否是全局最优,需要证明)。
特别注意: 若要用贪心算法求解某问题的整体最优解,必须首先证明贪心思想在该问题的应用结果就是最优解!!
*/
int maxProfit(vector<int>& prices){
int size = prices.size();
int res = 0;
for(int i = 1; i<size; i++){
if(prices[i]-prices[i-1] > 0) res += prices[i]-prices[i-1];
}
return res;
}
};
26th:按奇偶排序数组
给定一个非负整数数组 A,返回一个数组,在该数组中, A 的所有偶数元素之后跟着所有奇数元素。
你可以返回满足此条件的任何数组作为答案
class Solution {
public:
//1、两个指针,两边扫描
vector<int> sortArrayByParity(vector<int>& A) {
int size = A.size();
int odd = size-1, even = 0;
while(even<odd){
if(A[even]&1){ //even 指向奇数
//用odd指针找下一个奇数
//odd = even;
while(odd>even){
if(A[odd]&1) odd--;
else break; //找到偶数
}
//交换数值
int temp;
temp = A[even];
A[even] = A[odd];
A[odd] = temp;
//std:;cout<
//even++;
odd--;
}
even++;
}
return A;
}
//2、两次循环,分别找出奇数和偶数,很容易写
};
27th:1370. 上升下降字符串
给你一个字符串 s ,请你根据下面的算法重新构造字符串:
从 s 中选出 最小 的字符,将它 接在 结果字符串的后面。
从 s 剩余字符中选出 最小 的字符,且该字符比上一个添加的字符大,将它 接在 结果字符串后面。
重复步骤 2 ,直到你没法从 s 中选择字符。
从 s 中选出 最大 的字符,将它 接在 结果字符串的后面。
从 s 剩余字符中选出 最大 的字符,且该字符比上一个添加的字符小,将它 接在 结果字符串后面。
重复步骤 5 ,直到你没法从 s 中选择字符。
重复步骤 1 到 6 ,直到 s 中所有字符都已经被选过。
在任何一步中,如果最小或者最大字符不止一个 ,你可以选择其中任意一个,并将其添加到结果字符串。
请你返回将 s 中字符重新排序后的 结果字符串 。
class Solution {
public:
//桶计数法
string sortString(string s) {
int max = 0; //最大重复数
string res = "";
vector<char> count(26,0);
for(auto c:s){
count[c-'a']++;
if(count[c-'a'] > max) max = count[c-'a'];
}
for(int i =0; i<max;i++){
for(int j = 0; j<26; j++){
if(count[j] > 0){count[j]--;res += j+'a';}
}
for(int j = 25; j>=0; j--){
if(count[j] > 0){count[j]--; res += j+'a';}
}
i++;
}
return res;
}
};
28th:976. 三角形的最大周长
给定由一些正数(代表长度)组成的数组 A,返回由其中三个长度组成的、面积不为零的三角形的最大周长。
如果不能形成任何面积不为零的三角形,返回 0。
class Solution {
public:
//贪心+排序
int largestPerimeter(vector<int>& A) {
/*
三角形三条边 a<=b<=c,能够构成三角形的重复必要条件是 a+b > c
因此,首先确定最大边c,然后从大到小找a和b的值,即可确定最大周长
*/
::sort(A.rbegin(),A.rend()); //降序排序
for(int i = 0; i<A.size()-2;i++)
{
if(A[i] < A[i+1] + A[i+2])
return A[i]+A[i+1]+A[i+2];
}
return 0;
}
};
29th:14. 最长公共前缀
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
class Solution {
public:
//1、从输入找一个字符串实例,和其他所有的字符串对比,计算前缀最短的那个就是最大公共前缀
string longestCommonPrefix(vector<string>& strs) {
if(strs.size() == 0) return "";
int i, len, tmp;
string inst = strs[0];
int min = inst.size();
for(auto str:strs){
len = str.size()>inst.size()?inst.size():str.size();
tmp = 0;
for(i= 0;i<len &&(str[i] == inst[i]); i++){
tmp++;
}
if(tmp == 0) return "";
if(tmp<min) min = tmp;
}
string res;
for(i = 0; i<min; i++)
res += inst[i];
return res;
}
};
30th:13. 罗马数字转整数
罗马数字包含以下七种字符: I, V, X, L,C,D 和 M。
字符 数值
I 1
V 5
X 10
L 50
C 100
D 500
M 1000
例如, 罗马数字 2 写做 II ,即为两个并列的 1。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。
通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:
I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。输入确保在 1 到 3999 的范围内。
public:
//直接写就好了
int romanToInt(string s) {
unordered_map<char,int> mapping = {{'I',1},{'V',5},{'X',10},{'L',50},
{'C',100},{'D',500},{'M',1000}};
int i, size = s.size();
int sum = 0;
for(i = 0; i<size; i++){
if(i != size-1){
if(s[i] == 'I' && (s[i+1]=='V'||s[i+1]=='X'))
{sum -= mapping[s[i]];continue;}
if(s[i] == 'X' && (s[i+1]=='L'||s[i+1]=='C'))
{sum -= mapping[s[i]];continue;}
if(s[i] == 'C' && (s[i+1]=='D'||s[i+1]=='M'))
{sum -= mapping[s[i]];continue;}
}
sum += mapping[s[i]];
}
return sum;
}
};
31th:28. 实现 strStr()
实现 strStr() 函数。
给定一个 haystack 字符串和一个 needle 字符串,在 haystack 字符串中找出 needle 字符串出现的第一个位置 (从0开始)。如果不存在,则返回 -1。
class Solution {
public:
//暴力。。。
int strStr(string haystack, string needle) {
if(needle=="") return 0;
int needle_size = needle.size();
int hay_size = haystack.size();
int j;
for(int i =0; i<hay_size; i++){
if((haystack[i] ^ needle[0]) == 0)
{
int flag = 0;
for(j = 0; j<needle_size && i+j<hay_size; j++){
if(haystack[i+j] ^ needle[j]) //不相同
{
flag = 1;
break;
}
}
if(flag == 0 && j == needle.size()) return i;
}
}
return -1;
}
//双指针
int strStr(string haystack, string needle){
if(needle=="") return 0;
int i;
int ndl = 0;
int same = -1;
for(i = 0; i<haystack.size() && ndl<needle.size(); i++){
if(haystack[i] == needle[ndl]){
if(!ndl) same = i;
ndl++;
continue;
}
if(same != -1){
//上一个头一样的字符串没有完全匹配上
i = same;
same = -1;
ndl = 0;
}
}
if(ndl == needle.size()) return same;
return -1;
}
};
32th:38. 外观数列
给定一个正整数 n ,输出外观数列的第 n 项。
「外观数列」是一个整数序列,从数字 1 开始,序列中的每一项都是对前一项的描述。
你可以将其视作是由递归公式定义的数字字符串序列:
countAndSay(1) = “1”
countAndSay(n) 是对 countAndSay(n-1) 的描述,然后转换成另一个数字字符串。
前五项如下:
1
11
21
1211
111221
第一项是数字 1
描述前一项,这个数是 1 即 “ 一 个 1 ”,记作 “11”
描述前一项,这个数是 11 即 “ 二 个 1 ” ,记作 “21”
描述前一项,这个数是 21 即 “ 一 个 2 + 一 个 1 ” ,记作 “1211”
描述前一项,这个数是 1211 即 “ 一 个 1 + 一 个 2 + 二 个 1 ” ,记作 “111221”
class Solution {
public:
//1、直接迭代求解
string countAndSay(int n) {
//n = 5;
int count = 0;
int val;
string res;
vector<int> tmp;
if(n == 1) return "1";
tmp.push_back(1);
while(n-1){
val = tmp[0];
count = 0;
vector<int> store;
for(int i = 0; i<tmp.size(); i++){
if(tmp[i] == val)
count++;
else{
//开始计数
store.push_back(count);store.push_back(val);
val = tmp[i];
count = 1;
}
}
store.push_back(count);
store.push_back(val);
tmp = store;
n--;
}//while
for(auto i:tmp)
res += (i+'0');
return res;
}
//2、递归
string countAndSay(int n){
//n= 2;
if(n == 1) return "1";
string res = countAndSay(n-1);
std::cout<<res;
int count = 0;
char val = res[0];
string tmp;
for(int i = 0; i<res.size(); i++){
if(val == res[i])
{
count++;
//continue;
}else{
tmp += (count+'0');
tmp += val;
val = res[i];
count = 1;
}
}//for
tmp += (count+'0');
tmp += val;
return tmp;
}
};
33th:58. 最后一个单词的长度
给你一个字符串 s,由若干单词组成,单词之间用空格隔开。返回字符串中最后一个单词的长度。如果不存在最后一个单词,请返回 0 .单词 是指仅由字母组成、不包含任何空格字符的最大子字符串。
class Solution {
public:
int lengthOfLastWord(string s) {
if(s==" " || s=="") return 0;
int i, j;
for(j = s.size()-1; j>=0; j--)
if(s[j] != ' ') break;
for(i = j; i>=0; i--){
if(s[i] == ' ') return j-i;
}
if(i < 0) return j+1;
return 0;
}
};
34th:144. 二叉树的前序遍历
给你二叉树的根节点 root ,返回它节点值的 前序 遍历
/**
* 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) {}
* };
*/
/*二叉树前序遍历,首先root,然后左子树,最后右子树*/
class Solution {
public:
//1、递归
/*vector preorderTraversal(TreeNode* root) {
vector res;
//递归终止条件
if(root == nullptr) return res;
res.push_back(root->val);
vector tmp = preorderTraversal(root->left);
//在res的end处,插入tmp所有的元素
res.insert(res.end(),tmp.begin(),tmp.end());
tmp = preorderTraversal(root->right);
res.insert(res.end(),tmp.begin(),tmp.end());
return res;
}*/
/*我们也可以用迭代的方式实现方法一的递归函数,两种方式是等价的,
区别在于递归的时候隐式地维护了一个栈,而我们在迭代的时候需要显式地将这个栈模拟出来,
其余的实现与细节都相同*/
//2、迭代。深度优先遍历(先序后序中序)利用栈来做迭代。广度优先遍历(层序)利用队列做迭代
vector<int> preorderTraversal(TreeNode* root){
vector<int> res;
if(root == nullptr) return res;
//使用栈结构
stack<TreeNode*> st;
int flag = 0;
TreeNode *node = root;
while(!st.empty() || node != nullptr){
while(node != nullptr){
res.push_back(node->val);
st.push(node);
node = node->left;
}
node = st.top();
st.pop();
node = node->right;
}
return res;
}
};
35th:94. 二叉树的中序遍历
给定一个二叉树的根节点 root ,返回它的 中序 遍历。
/*二叉树的中序遍历,左中右 的顺序,属于深度优先遍历的一种 */
class Solution {
public:
//1、递归实现
/*vector inorderTraversal(TreeNode* root) {
vector res;
if(root == nullptr) return res;
vector tmp = inorderTraversal(root->left);
res.insert(res.end(),tmp.begin(),tmp.end());
res.push_back(root->val);
tmp = inorderTraversal(root->right);
res.insert(res.end(),tmp.begin(),tmp.end());
return res;
}*/
//2、迭代实现,利用栈
vector<int> inorderTraversal(TreeNode* root){
vector<int> res;
stack<TreeNode*> st;
if(root == nullptr) return res;
while(!st.empty() || root != nullptr){
while(root != nullptr){
st.push(root);
root = root->left;
}
root = st.top();
//std::cout<val<
res.push_back(root->val);
st.pop();
root = root->right;
}
return res;
}
};
36th:145. 二叉树的后序遍历
给定一个二叉树,返回它的 后序 遍
/*二叉树的后续遍历,左右中*/
class Solution {
public:
//1、递归实现
/*vector postorderTraversal(TreeNode* root) {
vector res;
if(root == nullptr) return res;
vector tmp = postorderTraversal(root->left);
res.insert(res.end(),tmp.begin(),tmp.end());
tmp = postorderTraversal(root->right);
res.insert(res.end(),tmp.begin(),tmp.end());
res.push_back(root->val);
return res;
}*/
//2、迭代实现,利用栈,只有右子树已经访问或者右子树为空时,访问当前节点
vector<int> postorderTraversal(TreeNode* root){
vector<int> res;
stack<TreeNode*> st;
TreeNode *prev;
if(root == nullptr) return res;
while(!st.empty() || root != nullptr){
while(root != nullptr){
//根节点压栈
st.push(root);
root = root->left;
}
root = st.top();
st.pop();
//如果没有右子树,或者上一个节点是右子树(刚刚处理完右子树),那么将当前节点的值保存
//记录prev
if (root->right == nullptr || root->right == prev) {
//st.pop();
res.push_back(root->val);
prev = root;
//刚刚遍历完某个节点,那么需要弹栈
root = nullptr;
} else {
st.push(root);
root = root->right;
}
}
return res;
}
};
class Solution {
public:
/*
后续遍历的跌打写法有点难想,可以 利用前序遍历转换视角
前序遍历:res的结果:中左右
如果将res的结果倒过来: 右左中
在将right和left调换: 左右中 --> 后序遍历结果
*/
vector<int> postorderTraversal(TreeNode* root) {
post(root);
vector<int> r;
int size = res.size();
for(int i = size-1; i>=0; --i)
r.push_back(res[i]);
return r;
}
vector<int> res;
void post(TreeNode *root){
stack<TreeNode*> st;
TreeNode *pre = 0;
while(!st.empty() || root){
while(root){
res.push_back(root->val); //前序遍历
st.push(root);
root = root->right; //1.right
}
root = st.top();
st.pop();
root = root->left;
/*if(root->right == nullptr || root->right == pre){
res.push_back(root->val);
pre = root;
root = nullptr;
}else{
st.push(root);
root = root->right;
}*/
}
}
};
37th:226. 翻转二叉树
翻转一棵二叉树。
class Solution {
public:
/* 交换某一个节点的左右子树 */
TreeNode* invertTree(TreeNode* root) {
if(root == nullptr) return root;
//交换节点,先序遍历,可以
TreeNode *temp = root->right;
root->right = root->left;
root->left = temp;
invertTree(root->left);
/*//交换节点,中序遍历,放在这里不能正确翻转
//左中右的顺序,首先翻转根节点左子树左子树,然后翻转跟节点(左子树成为了右子树)
//最后翻转根节点右子树,相当于再次翻转了原先的左子树,而原先的右子树没有翻转
TreeNode *temp = root->right;
root->right = root->left;
root->left = temp;*/
invertTree(root->right);
/*//交换节点,后序遍历,可以
TreeNode *temp = root->right;
root->right = root->left;
root->left = temp;
/*
return root;
}
};
38th:102. 二叉树的层序遍历
给你一个二叉树,请你返回其按 层序遍历 得到的节点值。 (即逐层地,从左到右访问所有节点)。
/*二叉树层序遍历使用队列*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
vector<int> rest;
if(root==nullptr) return res;
queue<TreeNode*> q;
q.push(root);
while(true){
queue<TreeNode*> tmp; //用于保存下一层的所有节点
//res.push_back(q.front());
while(!q.empty()){
if(q.front() != nullptr)
{
rest.push_back(q.front()->val);
tmp.push(q.front()->left);
tmp.push(q.front()->right);
}
q.pop();
}
if(rest.size() == 0) break;
res.push_back(rest);
rest.clear();
q = tmp;
}
return res;
}
};
/*二叉树层序遍历使用队列, 不使那么多的内存*/
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> res;
vector<int> rest;
if(root==nullptr) return res;
queue<TreeNode*> q;
q.push(root);
int size;
while(true){
//queue tmp; //用于保存下一层的所有节点
int size = q.size();
for(int i = 0; i<size; i++){
if(q.front() != nullptr)
{
rest.push_back(q.front()->val);
q.push(q.front()->left);
q.push(q.front()->right);
}
q.pop();
}
if(rest.size() == 0) break;
res.push_back(rest);
rest.clear();
//q = tmp;
}
return res;
}
};
39th:116. 填充每个节点的下一个右侧节点指针
给定一个 完美二叉树 ,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下:
struct Node {
int val;
Node *left;
Node *right;
Node *next;
}
填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。
初始状态下,所有 next 指针都被设置为 NULL。
class Solution {
public:
//1、层序遍历
Node* connect(Node* root) {
if(root == nullptr ) return root;
queue<Node*> q;
//root->next = nullptr;
q.push(root);
int qsize;
while(!q.empty()){
qsize = q.size();
Node *tmp = nullptr;
for(int i = 0; i<qsize; i++){
if(q.front())
{
q.front()->next = tmp;
tmp = q.front();
q.push(q.front()->right);
q.push(q.front()->left);
}
q.pop();
}
}
return root;
}
};
40th:114. 二叉树展开为链表
给你二叉树的根结点 root ,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 right 子指针指向链表中下一个结点,而左子指针始终为 null 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
class Solution {
public:
//1、递归
void flatten(TreeNode* root) {
if(root == nullptr) return;
flatten(root->left);
flatten(root->right);
TreeNode *left = root->left;
TreeNode *right = root->right;
root->left = nullptr;
root->right = left;
//将root的right连接到right上
TreeNode *tmp = root;
while(tmp->right != nullptr)
tmp = tmp->right;
tmp->right = right;
}
};
41th:654. 最大二叉树
给定一个不含重复元素的整数数组 nums 。一个以此数组直接递归构建的 最大二叉树 定义如下:
二叉树的根是数组 nums 中的最大元素。
左子树是通过数组中 最大值左边部分 递归构造出的最大二叉树。
右子树是通过数组中 最大值右边部分 递归构造出的最大二叉树。
返回有给定数组 nums 构建的 最大二叉树 。
class Solution {
public:
//直接递归就是了,写递归的时候不要进入递归思考,很容易迷糊,
//脑子压不了几个栈的
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
//TreeNode *root;
if(nums.size() == 0) return nullptr;
int max = 0;
//找到最大值的索引位置
for(int i = 1; i<nums.size(); i++)
{
if(nums[i] > nums[max]) max = i;
}
TreeNode *root = new TreeNode(nums[max]);
vector<int> left_num(nums.begin(),nums.begin()+max);
vector<int> right_num(nums.begin()+max+1,nums.end());
root->left = constructMaximumBinaryTree(left_num);
root->right = constructMaximumBinaryTree(right_num);
return root;
}
};
42th:105. 从前序与中序遍历序列构造二叉树
根据一棵树的前序遍历与中序遍历构造二叉树
class Solution {
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
/*经由前序遍历找到跟节点,然后在后序遍历中找左右子树*/
if(preorder.size() == 0 || inorder.size() == 0) return nullptr;
int rootval = preorder[0];
int i;
for(i = 0; i<inorder.size(); i++){
if(inorder[i] == rootval) break;
}
TreeNode *root = new TreeNode(rootval);
vector<int> leftpre(preorder.begin()+1,preorder.begin()+i+1);
vector<int> leftin(inorder.begin(),inorder.begin()+i);
root->left = buildTree(leftpre,leftin);
vector<int> rigthpre(preorder.begin()+i+1,preorder.end());
vector<int> rightin(inorder.begin()+i+1,inorder.end());
root->right = buildTree(rigthpre,rightin);
return root;
}
};
43th:106. 从中序与后序遍历序列构造二叉树
根据一棵树的中序遍历与后序遍历构造二叉树。
/*要想恢复出二叉树,必须要有中序遍历的结果*/
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0) return nullptr;
int rootval = postorder[postorder.size()-1];
int i;
for(i = 0; i<inorder.size(); i++){
if(inorder[i] == rootval) break;
}
TreeNode *root = new TreeNode(rootval);
vector<int> leftin(inorder.begin(),inorder.begin()+i);
vector<int> leftpost(postorder.begin(),postorder.begin()+i);
root->left = buildTree(leftin,leftpost);
vector<int> rightin(inorder.begin()+i+1,inorder.end());
vector<int> rightpost(postorder.begin()+i,postorder.end()-1);
root->right = buildTree(rightin,rightpost);
return root;
}
};
44th:652. 寻找重复的子树
给定一棵二叉树,返回所有重复的子树。对于同一类的重复子树,你只需要返回其中任意一棵的根结点即可。
两棵树重复是指它们具有相同的结构以及相同的结点值。
/**
* 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*> findDuplicateSubtrees(TreeNode* root) {
vector<TreeNode*> res;
unordered_map<string, int> mp;
dfs(root, res, mp);
return res;
}
string dfs(TreeNode *root, vector<TreeNode *> &res, unordered_map<string,int> &mp){
if(root == 0) return "";
//字符串序列化
string value = to_string(root->val)+ "#"+
dfs(root->left,res,mp)+"#"+dfs(root->right,res,mp);
/*,c++中的unordered_map就是这样设计的。如果采用mp[key]的方式去访问key的话,
此时map如果没有这个key,那么就会自动创建这个key。其对应的value就是value的类型的默认值。
这里value类型是int,所以默认值就是0*/
if(mp[value] == 1) res.push_back(root);
mp[value] ++;
return value;
}
};
45th:剑指 Offer 55 - II. 平衡二叉树
输入一棵二叉树的根节点,判断该树是不是平衡二叉树。如果某二叉树中任意节点的左右子树的深度相差不超过1,那么它就是一棵平衡二叉树。
class Solution {
public:
bool isBalanced(TreeNode* root) {
if(root == nullptr) return true;
bool flag = true;
flag = abs(depth(root->left) - depth(root->right)) <= 1;
return flag && isBalanced(root->left) && isBalanced(root->right);
}
int depth(TreeNode *root){
//递归结束条件
if(root == nullptr) return 0;
int left = depth(root->left);
int right = depth(root->right);
return left>right?left+1:right+1;
}
//2.
bool isBalanced(TreeNode* root){
if(root == nullptr) return true;
return dfs(root) != -1;
}
int dfs(TreeNode *root){
if(root == nullptr) return 0;
int left = dfs(root->left);
if(left == -1) return -1;
int right = dfs(root->right);
if(right == -1) return -1;
if(abs(right - left) > 1) return -1;
return max(left,right)+1;
}
};
47th:230. 二叉搜索树中第K小的元素
给定一个二叉搜索树的根节点 root ,和一个整数 k ,请你设计一个算法查找其中第 k 个最小元素(从 1 开始计数)
/*
BST性质:
1、二叉搜索树(BST),BST的每个节点node,
左子树节点都比node的值小,右子树节点的值都比node的值大
2、直接基于AVL树,红黑树等,拥有了自平衡性质,可以提供logN的增删查改效率
还有B+数,线段树等都是基于BST思想设计的。
3、BST的中序遍历结果是升序的
*/
class Solution {
public:
//1、中序遍历,找元素
/*int rank = 0;
int cnt = 0;
int kthSmallest(TreeNode* root, int k) {
this->cnt = k;
vector res = helperFunction(root);
return res[k-1];
}
vector helperFunction(TreeNode *root){
vector res;
if(root == nullptr) return res;
vector temp = helperFunction(root->left);
res.insert(res.end(),temp.begin(),temp.end());
res.push_back(root->val);
if(res.size() == this->cnt) return res;
temp = helperFunction(root->right);
res.insert(res.end(),temp.begin(),temp.end());
return res;
}*/
//2-1、记录遍历的个数,然后输出第k个值
/*int rank = 0;
int kthSmallest(TreeNode* root, int k)
{
TreeNode *res = helper(root,k);
return res->val;
}
//2-1、返回root节点,也可以将结果保存,见helper2
TreeNode *helper(TreeNode *root, int k)
{
if(root == nullptr) return nullptr;
TreeNode *temp = helper(root->left,k);
if(temp) return temp;
rank++;
if(rank == k) return root;
temp = helper(root->right,k);
if(temp) return temp;
return nullptr;
}*/
//2-2
int rank = 0;
int res;
int kthSmallest(TreeNode* root, int k)
{
helper2(root,k);
return res;
}
void helper2(TreeNode *root, int k)
{
if(root == nullptr) return;
helper2(root->left,k);
rank++;
if(rank == k)
{
res = root->val;
return;
}
helper2(root->right,k);
return ;
}
};
48th:538. 把二叉搜索树转换为累加树
给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。
提醒一下,二叉搜索树满足下列约束条件:
节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。
/*
中序遍历可以升序得到序列,怎么样得到降序的序列?如果能得到降序的序列,这道题就好写了
*/
class Solution {
public:
int sum = 0;
TreeNode* convertBST(TreeNode* root) {
if(root == nullptr) return nullptr;
//首先遍历右子树,那么就可以得到降序序列
convertBST(root->right);
sum += root->val;
root->val = sum;
convertBST(root->left);
return root;
}
};
49th:98. 验证二叉搜索树
给定一个二叉树,判断其是否是一个有效的二叉搜索树。
假设一个二叉搜索树具有如下特征:
节点的左子树只包含小于当前节点的数。
节点的右子树只包含大于当前节点的数。
所有左子树和右子树自身必须也是二叉搜索树。
class Solution {
public:
/* 需要满足每个节点左边小于跟小于右边,同时左子树的最大值要小于跟小于右子树最小值*/
bool isValidBST(TreeNode* root) {
return helper3(root);
}
bool helper(TreeNode *root, long min, long max)
{
if(root == nullptr) return true;
if(root->val>=max || root->val <= min) return false;
//限定左子树的最大值为root->val,右子树的最小值为root->val
return helper(root->left,min,root->val) &&
helper(root->right,root->val,max);
}
/*中序遍历判断是否是递增的,保持一个prev记录上一个值的大小*/
long prev = LONG_MIN;
bool helper2(TreeNode *root)
{
if(root == nullptr) return true;
//中序遍历,判断中序遍历的结果是否是递增的
if(helper2(root->left) == false) return false;
if(root->val <= prev) return false;
prev = root->val;
return helper2(root->right);
}
/*试试用迭代的方法,用栈*/
bool helper3(TreeNode *root){
stack<TreeNode *> st;
TreeNode *node;
long tmp = LONG_MIN;
while(!st.empty() || root != nullptr){
while(root!=nullptr){
st.push(root);
root = root->left;
}
root = st.top();
//中序遍历
if(root->val <= tmp) return false;
tmp = root->val;
//弹栈
st.pop();
root = root->right;
}
return true;
}
};
50th:700. 二叉搜索树中的搜索
给定二叉搜索树(BST)的根节点和一个值。 你需要在BST中找到节点值等于给定值的节点。 返回以该节点为根的子树。 如果节点不存在,则返回 NULL。
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
/*if(root == nullptr) return nullptr;
if(root->val > val) return helper(root->left,val); //左子树
if(root->val < val) return helper(root->right,val); //右子树
return root;*/
helper2(root,val);
return res;
}
/*递归*/
TreeNode *helper(TreeNode *root, int val){
if(root == nullptr) return nullptr;
TreeNode * tmp = helper(root->left,val);
if(tmp && tmp->val == val) return tmp;
if(root->val == val) return root;
//if(root->val > val) return nullptr;
tmp = helper(root->right,val);
if(tmp &&tmp->val == val) return tmp;
return nullptr;
/*
if(root == nullptr) return false;
if(root->val == val) return true;
return helper(root->left,val) || helper(root->right,val);
*/
}
/*
针对BST的遍历框架
void BST(TreeNode root, int target) {
if (root.val == target)
// 找到目标,做点什么
if (root.val < target)
BST(root.right, target);
if (root.val > target)
BST(root.left, target);
}
*/
TreeNode *res = nullptr;
void helper2(TreeNode *root, int val){
if(root == nullptr) return;
if(root->val == val) {res = root; return;}
if(root->val>val) return helper2(root->left,val);
if(root->val<val) return helper2(root->right,val);
}
};
51th:701. 二叉搜索树中的插入操作
给定二叉搜索树(BST)的根节点和要插入树中的值,将值插入二叉搜索树。 返回插入后二叉搜索树的根节点。 输入数据 保证 ,新值和原始二叉搜索树中的任意节点值都不同。
注意,可能存在多种有效的插入方式,只要树在插入后仍保持为二叉搜索树即可。 你可以返回 任意有效的结果 。
class Solution {
public:
TreeNode* insertIntoBST(TreeNode* root, int val) {
return helper(root,val);
}
/* 遍历 */
TreeNode *helper(TreeNode *root, int val){
if(root == nullptr) return new TreeNode(val);
if(root->val > val){
//左子树
root->left = helper(root->left,val);
}
if(root->val < val){
//右子树
root->right = helper(root->right,val);
}
return root;
}
};
52th:450. 删除二叉搜索树中的节点
给定一个二叉搜索树的根节点 root 和一个值 key,删除二叉搜索树中的 key 对应的节点,并保证二叉搜索树的性质不变。返回二叉搜索树(有可能被更新)的根节点的引用。
一般来说,删除节点可分为两个步骤:
首先找到需要删除的节点;
如果找到了,删除它。
说明: 要求算法时间复杂度为 O(h),h 为树的高度。
class Solution {
public:
TreeNode* deleteNode(TreeNode* root, int key) {
return helper(root,key);
}
/* 1. */
TreeNode *getmin(TreeNode *root){
while(root->left) root = root->left;
return root;
}
TreeNode *helper(TreeNode *root, int val){
if(root == nullptr) return nullptr;
if(val == root->val){
//如果待删除节点得左子树或者右子树为空的话
if(root->left == nullptr) return root->right;
if(root->right == nullptr) return root->left;
//否则要在右子树中查找数值最小的一个节点
TreeNode *min = getmin(root->right);
//然后将待删除节点的值和查找到的最小值节点的值调换,然后删除最小值节点
root->val = min->val;
root->right = helper(root->right,min->val);
}
if(val < root->val)
{
root->left = helper(root->left,val);
}
if(val > root->val)
{
root->right = helper(root->right,val);
}
return root;
}
};
53th:92. 反转链表 II
反转从位置 m 到 n 的链表。请使用一趟扫描完成反转。
说明:
1 ≤ 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* reverseBetween(ListNode* head, int left, int right) {
//base case
if(left ==1)
return reverseN(head,right);
//前进到翻转的最后,触发base case
head->next = reverseBetween(head->next,left-1,right-1);
return head;
}
/*翻转链表前n个节点*/
ListNode *pNext = nullptr;
ListNode* reverseN(ListNode *head, int n){
if(n == 1){
//最后一个节点,需要记录下一个节点
pNext = head->next;
//返回需要翻转的最后一个节点,并且赋值给last
return head;
}
ListNode * last = reverseN(head->next, n-1);
head->next->next = head;
//指向子链表的下一个节点
head->next = pNext;
return last;
}
};
54th:25. K 个一组翻转链表
给你一个链表,每 k 个节点一组进行翻转,请你返回翻转后的链表。
k 是一个正整数,它的值小于或等于链表的长度。
如果节点总数不是 k 的整数倍,那么请将最后剩余的节点保持原有顺序。
进阶:
你可以设计一个只使用常数额外空间的算法来解决此问题吗?
你不能只是单纯的改变节点内部的值,而是需要实际进行节点交换。
class Solution {
public:
ListNode *rec = nullptr;
ListNode* reverseKGroup(ListNode* head, int k) {
ListNode *a = head, *b = head;
for(int i = 0; i<k; ++i){
if(b == nullptr) return head;
b = b->next;
}
ListNode * pHead = reverse(a,b); //翻转a,b之间的链表,不包括b
a->next = reverseKGroup(b,k); //翻转前k-1的链表,a的指向没有在reverse中改变
return pHead;
}
/* 翻转left和right之间的链表,不包括right,返回头节点*/
ListNode * reverse(ListNode *left, ListNode *right){
ListNode *prev = nullptr;
ListNode *pNext = nullptr;
while(left != right){
pNext = left->next;
left->next = prev;
prev = left;
left = pNext;
}
return prev;
}
};
57th:5. 最长回文子串
\给你一个字符串 s,找到 s 中最长的回文子串。
class Solution {
public:
string longestPalindrome(string s) {
int posc = 0;
int posl = 0;
int maxc = 0;
int maxt = 0;
//奇数回文子串
for(int i = 0; i<s.size(); i++){
int len = findPalindrome(s,i,i);
if(len>maxc){
maxc = len;
posc = i;
}
}
//偶数回文子串
for(int i = 0; i<s.size()-1; i++){
int len = findPalindrome(s,i,i+1);
if(len>maxt){
maxt = len;
posl = i;
}
}
if(maxc > maxt){
return string(s.begin()+posc-(maxc-1)/2,s.begin()+posc+(maxc-1)/2+1);
}else{
return string(s.begin()+posl-(maxt-2)/2,s.begin()+posl+1+(maxt-2)/2+1);
}
return s;
}
/*helper function to find the length of the palindrome*/
int findPalindrome(string &s, int left, int right){
if(s[left] != s[right]) return 0;
int count = left==right?1:2;
while(left>0 && right<s.size()-1){
if(s[--left] == s[++right]){
count += 2;
}else break;
}
return count;
}
};
58th:234. 回文链表
请判断一个链表是否为回文链表。
class Solution {
public:
/*
1、方法 1、找中点,然后翻转后半部分或者前半部分的链表,逐一比较
2、用利用递归的栈,比较
3、直接用栈,压栈然后弹栈比较
*/
ListNode *left;
bool isPalindrome(ListNode* head) {
/*left = head;
return helper(head);*/
return helper02(head);
}
/*递归调用,利用递归找到最后一个节点,然后逐个比较*/
bool helper(ListNode *right){
//base case
if(right == nullptr) return true;
bool res = helper(right->next); //
res = res && (right->val == left->val);
left = left->next;
return res;
}
/*利用快慢指针找到中点*/
bool helper02(ListNode *head){
if(head == nullptr) return false;
ListNode *slow = head;
ListNode *fast = head;
int cnt = 0;
//快慢指针,边界条件
while(fast != nullptr && fast->next != nullptr){
++cnt;
slow = slow->next;
fast = fast->next->next;
}
if(fast != nullptr) //如果fast不是空,那么是奇数个节点,需要向后移动一位slow
slow = slow->next;
//翻转后半部分的链表
ListNode *head_r = reverse(slow);
for(int i = 0; i<cnt; ++i){
if(head->val != head_r->val)
return false;
head = head->next;
head_r = head_r->next;
}
return true;
}
ListNode *reverse(ListNode *head){
ListNode *prev = 0, *pNext = 0;
while(head){
pNext = head->next;
head->next = prev;
prev = head;
head = pNext;
}
return prev;
}
};
59th:70. 爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
class Solution {
public:
int climbStairs(int n) {
//return helper_mem(n);
return helper_dp(n);
}
/*直接递归:超时*/
int helper(int n){
if(n == 1) return 1;
if(n == 2) return 2;
return helper(n-1) + helper(n-2);
}
/*带有备忘录的递归*/
unordered_map<int,int> mem;
int helper_mem(int n){
if(n == 1) return 1;
if(n == 2) return 2;
if(mem[n] != 0) return mem[n];
mem[n] = helper_mem(n-1)+helper_mem(n-2);
return mem[n];
}
/*动态规划
dp(i)表有i个台阶,有多少方法可以到达楼顶
*/
int helper_dp(int n){
vector<int> dp(n+2,0);
dp[1] = 1;
dp[2] = 2;
for(int i= 3; i<=n; ++i){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
};
60th:704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
class Solution {
public:
/*
二分查找,思路很简单,细节是魔鬼
尽量不要用else,用else if写清楚所有细节
为了防止溢出, (left+right)/2 一般写成 left+(right-left)/2
有什么缺陷:nums = [1,2,2,2,3]使用二分查找2,返回的是索引2位置的2
如果要是想找target的左侧边界或者右侧边界,就没有办法处理了
1、寻找左侧边界的二分查找
2、寻找右侧边界的二分查找
见第34题
*/
int search(vector<int>& nums, int target) {
return binarySearch(nums,target);
}
int binarySearch(vector<int> &nums, int target){
//定义左右指针
int left = 0;
int right = nums.size()-1; //1、注意
//while跳出的条件是left
while(left<=right){ //2、注意
int mid = left + (right-left)/2; //3、注意
if(nums[mid] == target)
return mid;
else if(nums[mid]<target)
left = mid+1; //4、注意
else if(nums[mid]>target)
right = mid-1; //5、注意
}
return -1;
}
};
61th:面试题 08.06. 汉诺塔问题
在经典汉诺塔问题中,有 3 根柱子及 N 个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。
请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。
class Solution {
public:
/*
典型的分治思想,分治常见的实现就是递归算法
n个盘子,将n-1个盘子移动到b中,然后将最后一个移动到c中
然后计算n-1个盘子移动到c中
*/
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
move(A.size(),A,B,C);
}
/*move函数,n个盘子,从a移动到c*/
void move(int n, vector<int> &a, vector<int> &b, vector<int> &c){
if(n <= 1){
//如果只有一个盘子,那么直接移动到c
c.push_back(a.back());
a.pop_back();
return;
}
//将n-1个盘子从c移动到b
move(n-1,a,c,b);
//将一个盘子从c移动到a
move(1,a,b,c);
//将n-1个盘子从b移动到c
move(n-1,b,a,c);
}
};
62th:剑指 Offer 38. 字符串的排列
输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
class Solution {
public:
vector<string> res; //剪枝去重
set<string> ret; //用集合去重
vector<string> permutation(string s) {
//helper(s,0);
//return vector(ret.begin(),ret.end());
helper_cut(s,0);
return res;
}
/*定义helper为固定第n位得到的全排列*/
/* 回溯算法吗?*/
void helper(string &s,int n){
if(n == s.size()-1){
ret.insert(s);
return;
}
for(int i = n; i<s.size(); ++i){
swap(s[i],s[n]); //交换第i和n位
helper(s,n+1);
swap(s[i],s[n]); //交换回来
}
}
void helper_cut(string &s, int n){
if(n == s.size()-1){
res.push_back(s);
return;
}
set<char> cut;
for(int i = n; i<s.size(); ++i){
if(cut.count(s[i])) continue; //如果已经有了
swap(s[i],s[n]);
helper_cut(s,n+1);
swap(s[i],s[n]);
cut.insert(s[i]);
}
}
};
63th:940. 不同的子序列 II
给定一个字符串 S,计算 S 的不同非空子序列的个数。
因为结果可能很大,所以返回答案模 10^9 + 7.
class Solution {
public:
string res = "";
int distinctSubseqII(string S) {
return helper(S);
}
/* x表示s的第x个字符,x从0开始*/
//dp[i]表示s[0...i]范围内的子序列个数
int helper(string &S){
int n = S.size();
long long dp[n + 1];
memset(dp, 0, sizeof(dp)); //虚指 S的前i个,有多少种情况
dp[0] = 1; //S的第-1个元素,就是空“”,也是一种情况
unordered_map<char, int> last_appear_idx;
for (int i = 0; i < n; i ++)
{
char c = S[i];
dp[i + 1] = dp[i] * 2; //前面原有的是一份, 加上我又是一份
if (last_appear_idx.count(c) != 0) //如果前面自己出现过一次
dp[i + 1] -= dp[last_appear_idx[c]]; //减掉自己在前面的贡献值
dp[i + 1] %= 1000000007;
last_appear_idx[c] = i; //上次出现的索引位置
}
if (dp[n] - 1 < 0) //中途取余,可能会导致大的反而小了,做差会出现负
dp[n] += 1000000007;
return (dp[n] - 1) % 1000000007; //前面的计算,都把空“”算进去了
}
};
64th:51. N 皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
class Solution {
public:
/*回溯算法:每一层相当于没一行,决策是哪一列放置一个皇后,同时为了不被攻击,放置的列还有限制*/
vector<vector<string>> solveNQueens(int n) {
string a = "";
for(int i = 0;i<n; ++i)
a+=".";
vector<string> tmp(n,a);
helper(0,n,tmp);
return res;
}
/*1、每次判断是否是合法的*/
bool isOk(vector<string> res, int row, int col){
for(int i = 0; i<row; ++i){
//对于已经填入的每一行
for(int j =0; j<res[i].size(); ++j){
if(res[i][j] == 'Q'){
if(col == j)
return false;
if(col == row-i + j)
return false;
if(col == j-row+i)
return false;
}
}
}
return true;
}
vector<vector<string>> res;
void helper(int k, int n, vector<string> &tmp){
if(k == n){
res.push_back(tmp);
return;
}
//i表示放在第列行
//k表示放在第几行
//map,int> filter; //存放不合法位置
for(int i = 0; i<n; i++){
if(isOk(tmp,k,i) == false) continue;
//选择
tmp[k][i] = 'Q'; //第k行第i列放一个皇后
helper(k+1,n,tmp);
//撤销选择
tmp[k][i] = '.';
}
return;
}
};
65th:剑指 Offer 52. 两个链表的第一个公共节点
输入两个链表,找出它们的第一个公共节点。
class Solution {
public:
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
ListNode *node1 = headA;
ListNode *node2 = headB;
while(node1 != node2){
node1 = node1 == NULL?headA:node1->next;
node2 = node2 ==NULL?headB:node2->next;
}
return node2;
}
};
66th:142. 环形链表 II
给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。
为了表示给定链表中的环,我们使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。 如果 pos 是 -1,则在该链表中没有环。注意,pos 仅仅是用于标识环的情况,并不会作为参数传递到函数中。
说明:不允许修改给定的链表。
进阶:
你是否可以使用 O(1) 空间解决此题?
/**
* 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;
int step = 0;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
if(slow == fast) //指针相遇
break;
}
//没有环
if(fast == NULL || fast->next ==NULL) return NULL;
//相遇了说明fast比slow多走了一圈,假设slow走了k ,那么fast走了2k
//假设环的起点到slow的距离为m,那么head到环起点的位置为k-m,恰好从slow的 位置顺着环走
//距离也是k-m
slow = head;
while(slow != fast)
{
slow = slow->next;
fast = fast->next;
++step;
}
return slow;
}
};
67th:876. 链表的中间结点
给定一个头结点为 head 的非空单链表,返回链表的中间结点。
如果有两个中间结点,则返回第二个中间结点。
class Solution {
public:
/*
可以遍历一下,然后在遍历一下打印
也可以用快慢指针
*/
ListNode* middleNode(ListNode* head) {
ListNode *slow = head;
ListNode *fast = head;
while(fast && fast->next){
slow = slow->next;
fast = fast->next->next;
}
return slow;
}
};
68th:剑指 Offer 22. 链表中倒数第k个节点
输入一个链表,输出该链表中倒数第k个节点。为了符合大多数人的习惯,本题从1开始计数,即链表的尾节点是倒数第1个节点。
例如,一个链表有 6 个节点,从头节点开始,它们的值依次是 1、2、3、4、5、6。这个链表的倒数第 3 个节点是值为 4 的节点。
class Solution {
public:
ListNode* getKthFromEnd(ListNode* head, int k) {
/*stack mystack;
while(head){
mystack.push(head);
head = head->next;
}
ListNode *link = 0;
for(int j=1; j<=k; j++){
mystack.top()->next = link;
link = mystack.top();
mystack.pop();
}
return link;*/
return helper(head,k);
}
ListNode *helper(ListNode *head, int k){
/*利用快慢指针*/
int step = 0;
ListNode *fast = head;
ListNode *slow = head;
while(step < k){
++step;
fast = fast->next;
}
while(fast)
{
slow = slow->next;
fast = fast->next;
}
return slow;
}
};
69th:1288. 删除被覆盖区间
给你一个区间列表,请你删除列表中被其他区间所覆盖的区间。
只有当 c <= a 且 b <= d 时,我们才认为区间 [a,b) 被区间 [c,d) 覆盖。
在完成所有删除操作后,请你返回列表中剩余区间的数目。
//起点升序,终点降序
bool cmp(const vector<int> &a, const vector<int> &b){
if(a[0] == b[0])
return a[1]>b[1];
return a[0]<b[0];
}
class Solution {
public:
/*
所谓区间问题,就是线段问题,让你合并所有线段、找出线段的交集等等。主要有两个技巧:
1、排序。常见的排序方法就是按照区间起点排序,或者先按照起点升序排序,若起点相同,则按照终点降序排序。当然,如果你非要按照终点排序,无非对称操作,本质都是一样的。
2、画图。就是说不要偷懒,勤动手,两个区间的相对位置到底有几种可能,不同的相对位置我们的代码应该怎么去处理。
*/
int removeCoveredIntervals(vector<vector<int>>& intervals) {
/*起点升序排列,中点降序排列*/
sort(intervals.begin(),intervals.end(),cmp);
int cnt = 0;
int right = intervals[0][1];
int left = intervals[0][0];
for(int i = 1; i<intervals.size(); ++i){
if(intervals[i][1] <= right && intervals[i][0]>=left)
cnt++;
if(intervals[i][1]>right && intervals[i][0]<=right)
right = intervals[i][1];
if(intervals[i][0] > right){
right = intervals[i][1];
left = intervals[i][0];
}
}
return intervals.size()-cnt;
}
};
70th:56. 合并区间
以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间。
/*区间问题解法很类似参考上一题*/
bool cmp(vector<int> &a, vector<int> &b){
if(a[0] == b[0])
return a[1]<b[1];//ac
return a[0]<b[0]; //de
}
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
::sort(intervals.begin(),intervals.end(),cmp);
vector<int> res(2,0);
int left = intervals[0][0];
int right = intervals[0][1];
if(n <= 1) return intervals;
vector<vector<int>> result;
for(int i = 1; i<n; ++i){
/*if(intervals[i][1]<=right)
continue;*/
if(intervals[i][0]<=right && intervals[i][1]>right){
right = intervals[i][1];
}
if(intervals[i][0]>right){
std::cout<<i<<std::endl;
res[0] = left;
res[1] = right;
result.push_back(res);
left = intervals[i][0];
right = intervals[i][1];
}
if(i == n-1){
res[0] = left;
res[1] = right;
result.push_back(res);
}
} //for
return result;
}
};
71th:986. 区间列表的交集
给定两个由一些 闭区间 组成的列表,firstList 和 secondList ,其中 firstList[i] = [starti, endi] 而 secondList[j] = [startj, endj] 。每个区间列表都是成对 不相交 的,并且 已经排序 。返回这 两个区间列表的交集 。形式上,闭区间 [a, b](其中 a <= b)表示实数 x 的集合,而 a <= x <= b 。两个闭区间的 交集 是一组实数,要么为空集,要么为闭区间。例如,[1, 3] 和 [2, 4] 的交集为 [2, 3] 。
class Solution {
public:
vector<vector<int>> intervalIntersection(vector<vector<int>>& firstList, vector<vector<int>>& secondList) {
int i = 0;
int j = 0;
vector<int> tmp(2,0);
vector<vector<int> > res;
while(i<firstList.size() && j<secondList.size()){
int a_left = firstList[i][0], a_right = firstList[i][1];
int b_left = secondList[j][0], b_right = secondList[j][1];
//如果区间有交集,那么取交集区间
if(!(a_right<b_left || a_left>b_right )){
tmp[0] = max(a_left,b_left); //较大的就是相交区间的左边界
tmp[1] = min(b_right,a_right); //较小的是相交区间的右边界
res.push_back(tmp);
}
if(a_right>b_right) j++;
else i++;
}
return res;
}
};
N sum问题
72th:1. 两数之和
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。
你可以按任意顺序返回答案。
class Solution {
public:
/*
暴力搜索
先排序,然后用双指针,只能找出元素,位置找不到
用哈希表存数据
*/
vector<int> twoSum(vector<int>& nums, int target) {
return helper_hash(nums,target);
}
/*哈希*/
vector<int> helper_hash(vector<int> &nums, int target){
unordered_map<int,int> tmp;
vector<int> res(2,0);
for(int i = 0; i<nums.size(); ++i){
if(tmp[target-nums[i]] != 0){
res[0]=tmp[target-nums[i]]-1; //索引
res[1] = i;
break;
}
tmp[nums[i]] = i+1; //存放索引
}
return res;
}
/*排序+双指针*/
vector<int> helper_sort_pointer(vector<int> &nums, int target){
/*::sort(nums.begin(),nums.end()); //默认升序
//vector res(2,0);
int i = 0;
int j = nums.size()-1;
while(i<=j){
if(nums[i] + nums[j] < target)
i++;
else if(nums[i]+nums[j]>target)
j--;
else{
return {i,j};
}
}*/
return {};
}
};
73th:15. 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
class Solution {
public:
vector<vector<int>> threeSum(vector<int>& nums) {
if(nums.size() < 3) return {};
vector<vector<int> > res;
sort(nums.begin(),nums.end()); //外面排序,里面就不用排序了
//穷举第一个数,后面两个数调用twosum(不会重复),还要保证第一个数不重复
//就可以保证三个数不重复
for(int i = 0; i<nums.size()-1; ++i){
//寻找两数之和等于target-nums[i]的所有不重复元素
vector<vector<int> > tuples = twoSum(nums,i+1,0-nums[i]);
for(auto tuple:tuples){
//std::cout<<"i: "<
res.push_back({nums[i],tuple[0],tuple[1]});
}
//跳过第一个数字重复的位置
while(i<nums.size()-1 && nums[i] == nums[i+1]) i++;
}//while
return res;
}
/*two sumTarget:返回两数之和等于target的所有元素,不重复*/
vector<vector<int> > twoSum(vector<int> &nums, int start, int target){
int i = start, j = nums.size()-1;
//::sort(nums.begin(),nums.end()); //升序排序
vector<vector<int> > res;
while(i<j){
int sum = nums[i]+nums[j];
int left= nums[i], right = nums[j];
if(sum > target)
while(i<j && nums[j]==right) j--;
else if(sum<target)
while(i<j && nums[i]==left) i++;
else{
res.push_back({nums[i],nums[j]});
//去重 ,因为数组已经排好序了,这样就可以保证不重复
while(i<j && nums[i] == left) i++;
while(i<j && nums[j]==right) j--;
}
}
return res;
}
};
74th:18. 四数之和
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
注意:答案中不可以包含重复的四元组。
class Solution {
public:
/*
可以根据3数之和,算出4sum,5sum一直到N sum的问题
很明显这是一个递归的过程,所以直接写递归算法
修改n的值,可算n sum问题
*/
vector<vector<int>> fourSum(vector<int>& nums, int target) {
::sort(nums.begin(),nums.end()); //首先排序,不要在递归中排序
//return helper(nums,)
return helper(nums,target,4,0);
}
vector<vector<int>> helper(vector<int>& nums, int target, int n, int start){
vector<vector<int> > res;
//保证传入的大小足够大
if(nums.size()-start < n) return {};
//base case
if(n == 2){
int i = start; int j = nums.size()-1;
while(i<j){
int left = nums[i], right = nums[j];
if(nums[i]+nums[j]>target)
while(i<j && nums[j] == right) j--;
else if(nums[i]+nums[j]<target)
while(i<j && nums[i]==left) i++;
else{
res.push_back({nums[i],nums[j]});
while(i<j && nums[i]==left) i++;
while(i<j && nums[j]==right) j--;
}
}
return res;
}else{
for(int i = start; i<nums.size()-1; ++i){
vector<vector<int> > temp = helper(nums,target-nums[i],n-1,i+1);
//添加首元素
for(auto t:temp){
t.push_back(nums[i]);
res.push_back(t);
}
//去重
while(i<nums.size()-1 && nums[i]==nums[i+1]) ++i;
}
}
return res;
}
};
75th:111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
class Solution {
public:
/*
广度优先遍历bfs(层序遍历)
利用队列做遍历
*/
int minDepth(TreeNode* root) {
if(root == nullptr) return 0;
queue<TreeNode *> q;
q.push(root);
int depth = 1;
while(!q.empty()){
int sz = q.size();
for(int i = 0; i<sz; ++i){
TreeNode *tmp = q.front();
//是否是叶子节点
if(tmp->left==nullptr && tmp->right == nullptr)
return depth;
if(tmp->left) q.push(tmp->left);
if(tmp->right) q.push(tmp->right);
q.pop(); //删除元素
}
depth++;
}
return depth;
}
};
76th:752. 打开转盘锁
你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: ‘0’, ‘1’, ‘2’, ‘3’, ‘4’, ‘5’, ‘6’, ‘7’, ‘8’, ‘9’ 。每个拨轮可以自由旋转:例如把 ‘9’ 变为 ‘0’,‘0’ 变为 ‘9’ 。每次旋转都只能旋转一个拨轮的一位数字。
锁的初始数字为 ‘0000’ ,一个代表四个拨轮的数字的字符串。
列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。
字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。
class Solution {
public:
/*
bfs用队列,一般求最短路径啥的
queue q;
常用操作; q.push()从后面添加元素 q.front()取第一个元素的引用
q.back()取最后一个元素的引用 q.pop()删除第一个元素 e.empty()是否为空
*/
int openLock(vector<string>& deadends, string target) {
queue<string> q;
//记录已经拨到过的密码
set<string> visited;
visited.insert("0000");
q.push("0000");
set<string> dead;
for(auto d:deadends)
dead.insert(d);
int times = 0;
int ret;
while(!q.empty()){
int sz = q.size();
for(int i = 0; i<sz; ++i){
string tmp = q.front();
q.pop(); //删除元素
//判断是否锁住,或者遇到target
if(tmp == target) return times;
if(dead.count(tmp) != 0) continue;
//对于每一组密码,分别拨动四位
for(int j = 0; j<4; ++j){
//每次拨动可能往上也可能往下
string up = pushup(tmp,j);
if(0==visited.count(up)){
q.push(up);
visited.insert(up);
}
string down = pushdown(tmp,j);
if(visited.count(down) == 0){
q.push(down);
visited.insert(down);
}
}
}
times++;
}
return -1;
}
/*拨动s的第pos位*/
string pushup(string s, int pos){
if(s[pos] == '0')
s[pos] = '9';
else
s[pos] = s[pos]-1;
return s;
}
string pushdown(string s, int pos){
if(s[pos] == '9')
s[pos] = '0';
else
s[pos] = s[pos]+1;
return s;
}
};
77th:34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
进阶:
你可以设计并实现时间复杂度为 O(log n) 的算法解决此问题吗?
class Solution {
public:
/*
使用二分查找方法,寻找左边界和右边界
*/
vector<int> searchRange(vector<int>& nums, int target) {
//普通的遍历方法
/*vector res;
int cnt = 0;
for(int i = 0; i(2,-1):res;*/
vector<int> res;
res.push_back(binSearchLeft(nums,target));
res.push_back(binSearchRigth(nums,target));
return res;
}
//二分法寻找左边界
int binSearchLeft(vector<int> & nums, int target){
int left = 0;
int 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)
right = mid-1; //注意这里不要直接返回,继续向左收[left,mid-1]
else if(nums[mid]<target)
left = mid+1;
}
//处理越界
//退while时left = right+1,处理越界
if(left>=nums.size() || nums[left] != target)
return -1;
//left的含义是,比target小的数的个数有left个
return left;
}
//二分法寻找右边界
int binSearchRigth(vector<int> &nums, int target){
int left = 0;
int right = nums.size()-1; //1
while(left<=right){ //2
int mid = left+(right-left)/2;
if(nums[mid]>target)
right = mid-1;
if(nums[mid]==target)
left = mid+1; //3 这里不要直接返回,继续向右收缩[mid+1,right]
if(nums[mid]<target)
left = mid+1;
}
if(right<0 || nums[right] != target)
return -1;
return right;
}
};
78th:130. 被围绕的区域
/并查集的应用/
给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。
/*并查集算法
包括一个class UF,有3个属性count和parent,size(记录树的节点个数,重量)
还要实现两个api:
bool connected(int p, int q) //p q是否连通
int count(); //有多少连同分量
void union(int p, int q) //使得p,q节点连通
还有一个底层的find(int)函数是关键,设计的好可以使得上面两个api时间复杂度为O(1)
*/
class UF{
private:
int count; //记录连通分量
int *parent; //节点x的根节点是parent[x]
int *size; //
public:
//构造函数
UF(int n){
count = n;
parent = new int[n];
size = new int[n];
for(int i = 0; i<n; ++i){
parent[i] = i; //刚刚开始每个节点的父节点指针指向自己
size[i] = 1; //刚刚开始每个以某个节点为根的树的重量为1
}
}
//找到节点x的根节点
int find(int x){
//根节点的parent[x]为自身
while(parent[x] != x){
//进行了路径压缩,使得树的高度不大于3
parent[x] = parent[parent[x]];
x = parent[x];
}
return x;
}
void Union(int p, int q){
int rootP = find(p);
int rootQ = find(q);
if(rootP == rootQ) return;
//将小数接到大树下面,使得树比较平衡,不至于退化成链表
if(size[rootP] < size[rootQ]){
parent[rootP] = rootQ;//将较小的树根节点加入到另一棵树下面
size[rootQ] += size[rootP];
}else{
parent[rootQ]= rootP;
size[rootP] += size[rootQ];
}
count--; //连通分量减少1
}
bool connected(int p, int q){
int rootP = find(p);
int rootQ = find(q);
return rootP==rootQ;
}
int Count(){
return count;
}
~UF(){
delete [] size;
delete [] parent;
}
};
class Solution {
public:
/*
有两种方法去做,dfs和并查集,很多dfs的题目都可以用并查集来求解
*/
void solve(vector<vector<char>>& board) {
//helper_dfs(board);
helper_uf(board);
}
/*并查集算法*/
void helper_uf(vector<vector<char>> &board){
int m = board.size();
int n = board[0].size();
int d = m*n; //一个虚拟的节点,其他所有不被包围的‘O’将与这个节点连通
UF *uf = new UF(m*n+1); //初始化一个并查集
//将所有位于列边界的‘O’与d相连通
for(int i= 0; i<m; ++i){
if(board[i][0] == 'O')
uf->Union(i*n,d); //i*n+y:将二维的索引变为一维的
if(board[i][n-1] == 'O')
uf->Union(i*n+n-1,d);
}
//将所有位于行边界的‘O’与d连通
for(int j = 0; j<n; j++){
if(board[0][j] == 'O')
uf->Union(j,d);
if(board[m-1][j] == 'O')
uf->Union(n*(m-1)+j,d);
}
//将所有的与边界的‘O’相连的‘O’加入到连通域中
int dir[4][2] = {{1,0},{0,1},{0,-1},{-1,0}};//方向数组 dir,常用技巧
for(int i = 1; i<m-1; ++i)
for(int j = 1; j<n-1; ++j){
if(board[i][j] == 'O'){
//将此O与上下左右的O连通
for(int k = 0; k<4; ++k){
int x = i+dir[k][0];
int y = j+dir[k][1];
if(board[x][y] == 'O')
uf->Union(x*n+y,i*n+j);
}
}
}
//将所有不和d连通的O都要被替换
for(int i = 1; i<m-1; ++i)
for(int j = 1; j<n-1; ++j)
if(board[i][j] == 'O' && !uf->connected(d,i*n+j)){
board[i][j] = 'X';
}
}
/*dfs:先遍历边界的o,然后dfs递归搜索没有被包围的O,然后标记为'v',然后再次遍历,将其他没有被标记为V的O
改为X,将V改为O即可*/
void helper_dfs(vector<vector<char>> &board){
if(board.size() == 0 ) return;
int row = board.size();
int col = board[0].size();
//首先遍历一遍,找到边缘的O
for(int i =0; i<row; ++i)
for(int j = 0; j<col; ++j){
if((i==0 || j==0 || i==row-1 || j == col-1) && board[i][j] == 'O')
dfs(board,i,j);
}
//改变状态
for(int i =0; i<row; ++i)
for(int j = 0; j<col; ++j){
if(board[i][j] == 'O') board[i][j] = 'X';
if(board[i][j] == 'v') board[i][j] = 'O';
}
}
void dfs(vector<vector<char>> &board, int i, int j){
if(i<0 || j<0 || i>=board.size() || j>=board[0].size() || board[i][j] == 'v' || board[i][j] == 'X')
return;
board[i][j] = 'v';
dfs(board,i-1,j);
dfs(board,i,j-1);
dfs(board,i,j+1);
dfs(board,i+1,j);
}
};
79th:990. 等式方程的可满足性
给定一个由表示变量之间关系的字符串方程组成的数组,每个字符串方程 equations[i] 的长度为 4,并采用两种不同的形式之一:“a==b” 或 “a!=b”。在这里,a 和 b 是小写字母(不一定不同),表示单字母变量名。
只有当可以将整数分配给变量名,以便满足所有给定的方程时才返回 true,否则返回 false。
/*并查集算法:*/
class UF{
private:
int count;
int *size;
int *parent;
public:
/*构造函数,初始化节点个数,初始化parent和size数组*/
UF(int n){
count = n;
parent = new int[n];
size = new int[n];
for(int i = 0; i<n; ++i){
size[i] = 1;
parent[i] = i;
}
}
/*带有路径优化的find函数,每次迭代将自己向上移动,改变树的形状,使得树高很小 O(1)*/
int find(int x){
while(parent[x] != x){
parent[x]=parent[parent[x]];
x = parent[x];
}
return x;
}
/*将pq节点连通,实际上是找到各自的根节点,然后将根节点连通,小树连接到大树下面,使其更为平衡 O(1)*/
void Union(int p, int q){
int r_p = find(p);
int r_q = find(q);
if(r_p == r_q) return;
if(size[r_p] < size[r_q]){
parent[r_p] = r_q;
size[r_q] += size[r_p];
}else{
parent[r_q] = r_p;
size[r_p] += size[r_q];
}
count--;
}
/*判断是否连通,直接找根节点是否相同即可,O(1)*/
bool connected(int q, int p){
int r_p = find(p);
int r_q = find(q);
return r_p == r_q;
}
int Count(){
return count;
}
~UF(){
delete [] size;
delete [] parent;
}
};
class Solution {
public:
bool equationsPossible(vector<string>& equations) {
UF *uf = new UF(26); //总共 26个字母
//查找相等关系的字母,并且放入到一个连通域中
for(auto c:equations){
if(c[1] == '='){
//如果相等,则将两个字母添加到连通域中
uf->Union(c[0]-'a',c[3]-'a');
}
}
//查找不相等关系的字母,验证和uf中的逻辑是否想冲
for(auto c:equations){
if(c[1] == '!'){
if(uf->connected(c[0]-'a',c[3]-'a')) //如果连通,即相等,逻辑冲突
return false;
}
}
return true;
}
};
80th:146. LRU 缓存机制
运用你所掌握的数据结构,设计和实现一个 LRU (最近最少使用) 缓存机制 。
实现 LRUCache 类:
LRUCache(int capacity) 以正整数作为容量 capacity 初始化 LRU 缓存 int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
void put(int key, int value) 如果关键字已经存在,则变更其数据值;如果关键字不存在,则插入该组「关键字-值」。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间。
/*采用哈希表加链表结合的方法,一般要求双向链表自己建立*/
typedef struct Node{
int key,val;
Node * next, *prev;
Node():key(0),val(0),next(nullptr),prev(nullptr){}
Node(int _k, int _v):key(_k),val(_v),next(nullptr),prev(nullptr){}
}Node;
/*用BiList保持双向链表*/
class BiList{
private:
Node *head, *tail;
int size;
friend class LRUCache;
public:
BiList(){
//明显,这里有两个虚节点。
head = new Node(0,0);
tail = new Node(0,0);
head->next = tail;
tail->prev = head;
size = 0;
}
/*实现几个双向链表的API*/
//添加节点到尾部 O(1),添加后size++
void addtoTail(Node *x){
x->prev = tail->prev;
x->next = tail;
tail->prev->next = x;
tail->prev = x;
size++;
}
//删除给定节点 O(1),删除后size--
void remove(Node *x){
x->prev->next = x->next;
x->next->prev = x->prev;
size--;
}
//删除链表中第一个节点,并返回节点
Node * removeAndRetFirst(){
if(head->next == tail)
return nullptr;
Node *first = head->next;
remove(first);
return first;
}
//返回链表的长度
int getSize(){
return size;
}
};
/*
在哈希表中查找,O(1),然后在链表中做增删查改操作 O(1)
*/
class LRUCache {
private:
int cap;
unordered_map<int,Node*> map;
BiList *cache;
//将key提升为最近使用的
//首先删除这个节点,然后插入到队尾
void makeRecent(int key){
Node *x = map[key];
cache->remove(x);
cache->addtoTail(x);
}
//添加最近使用的元素,添加到链表中还要添加到字典中
void addRecent(int key, int val){
Node *x = new Node(key,val);
cache->addtoTail(x);
map[key] = x;
}
//删除某一个key
void delKey(int key){
//要在字典中删除还要在链表中删除
Node *x = map[key];
cache->remove(x);
map[key] = 0;
}
//删除最久未使用的元素,从链表中获取node,然后在哈希表中删除
void removeLeastRecent(){
//就是链表的第一个有效节点
Node *del = cache->removeAndRetFirst();
//从map中删除
int delKey = del->key;
map[delKey]= 0;
}
public:
/*先实现几个函数,做一层抽象*/
LRUCache(int capacity) {
cap = capacity;
cache = new BiList();
}
int get(int key) {
if(!map[key])
return -1;
//将key提升为最近使用的
makeRecent(key);
return map[key]->val;
}
void put(int key, int val) {
//如果已经存在对应的key,先删除然后再添加
if(map[key]){
delKey(key);
//添加新的键值对到缓存中
addRecent(key,val);
return;
}
//判断是否需要删除最久的值
if(cap == cache->size)
removeLeastRecent();
//添加为最近使用的
addRecent(key,val);
}
};
/**
* 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);
*/
81th:42. 接雨水
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
class Solution {
public:
/*
接雨水:对于i位置能够接到的最多的雨水为 min(height[0...i-1],height[i....])-height[i]的大小
先用两个数组分别保存左边最大和右边最大值
然后再遍历一遍就OK了
*/
int trap(vector<int>& height) {
int n = height.size();
if(n == 0) return 0;
vector<int> left_max(n);
vector<int> right_max(n);
//int max = height[0];
//记录左边最大值
left_max[0] = height[0];
for(int i =1; i<n; i++){
if(height[i] > left_max[i-1])
left_max[i] = height[i];
else left_max[i] = left_max[i-1];
}
//记录右边最大值
right_max[n-1] = height[n-1];
for(int i = n-2; i>=0; --i){
if(height[i] > right_max[i+1])
right_max[i] = height[i];
else right_max[i] = right_max[i+1];
}
int sum = 0;
for(int i = 0; i<n; ++i){
sum += min(left_max[i],right_max[i])-height[i];
}
return sum;
}
/**/
/* 双指针解法 */
int trap_2(vector<int> &height){
if (height.empty()) return 0;
int n = height.size();
int left = 0, right = n - 1;
int res = 0;
int l_max = height[0];
int r_max = height[n - 1];
while (left <= right) {
l_max = max(l_max, height[left]);
r_max = max(r_max, height[right]);
// res += min(l_max, r_max) - height[i]
if (l_max < r_max) {
res += l_max - height[left];
left++;
} else {
res += r_max - height[right];
right--;
}
}
return res;
}
};
82th:567. 字符串的排列
给定两个字符串 s1 和 s2,写一个函数来判断 s2 是否包含 s1 的排列。
换句话说,第一个字符串的排列之一是第二个字符串的 子串 。
class Solution {
public:
/*
滑动窗口:[left,right)
right向右滑动,知道区间中的字符串包括s1的排列
然后left向右滑动,知道区间中的字符串不包括s1的排列
然后,right继续向右滑动
*/
bool checkInclusion(string s1, string s2) {
int left = 0, right = 0;
int valid = 0, n = s1.size();
unordered_map<char,int> need, win;
for(auto c:s1) need[c]++;
while(right<s2.size()){
char c = s2[right];
right++;
/*
need.count 和 count[c]的结果有时候不一样。。。
*/
if(need.count(c)){
win[c]++;
if(need[c] == win[c])
valid++;
}
while(valid == need.size()){
if(right-left == n) return true; //如果相等,则包含排列
char d = s2[left];
left++;
if(need.count(d)){
if(need[d] == win[d])
valid--;
win[d]--;
}
}
}
return false;
}
};
83th:76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 “” 。
注意:如果 s 中存在这样的子串,我们保证它是唯一的答案。
class Solution {
public:
/*滑动窗口算法,利用双指针滑动窗口*/
/*
首先固定left,不断扩展right,直到[left,right)满足要求
然后再收缩left,直到[left,right)不再符合要求
然后再不断扩张right,重复下去
*/
string minWindow(string s, string t) {
int left = 0, right = 0; //滑动窗口的前沿和后沿
int valid = 0;
int start = 0, len = INT_MAX; //记录最小子串的起始位置和长度
unordered_map<char,int> need, window; //定义两个窗口,分别用于记录t以及滑动窗口中的元素
for(auto c:t) need[c]++;
while(right<s.size()){
char c = s[right];
right++;
//如果c存在于need中,即使有重复的也放进去,但是valid只会增加一次
//window可以保证need和window中的key是一样的,但是val的值不一定一样
if(need.count(c)){
window[c]++; //放入滑动窗口中
if(need[c] == window[c])
valid++;
}
//找到包括t的子串了,left向左收缩,去除重复的元素,知道滑动窗口不包括t
while(valid == need.size()){
if(right-left<len){
len = right-left;
start = left;
}
char d = s[left];
left++;
if(need.count(d)){
if(need[d] == window[d])
valid--;
window[d]--;
}
} //while循环出来后 window将不包括t,需要将right向右移动
}
return len==INT_MAX?"":s.substr(start,len);
}
};
84th:438. 找到字符串中所有字母异位词
给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 20100。
说明:
字母异位词指字母相同,但排列不同的字符串。
不考虑答案输出的顺序。
class Solution {
public:
vector<int> findAnagrams(string s, string p) {
vector<int> res;
int left = 0, right = 0;
int valid = 0;
unordered_map<char,int> tmpt, win;
for(auto c:p) tmpt[c]++;
while(right<s.size()){
char c = s[right];
right++;
if(tmpt.count(c)){
win[c]++;
if(win[c] == tmpt[c])
valid++;
}
//收缩
while(valid == tmpt.size()){
if(right-left == p.size())
res.push_back(left);
char d = s[left];
left++;
if(tmpt.count(d)){
if(tmpt[d] == win[d])
valid--;
win[d]--;
}
}
}//while
return res;
}
};
85th:3. 无重复字符的最长子串
给定一个字符串,请你找出其中不含有重复字符的 最长子串 的长度。
class Solution {
public:
int lengthOfLongestSubstring(string s) {
int left = 0,right = 0;
int maxlen = 0;
unordered_map<char,int> win;
while(right < s.size()){
char c = s[right];
right++;
win[c]++;
//左边界收缩,直到没有重复元素
while(win[c]>1){
char d = s[left];
left++;
win[d]--;
}
maxlen = max(maxlen,right-left);
}//while
return maxlen;
}
};
86th:4. 寻找两个正序数组的中位数
给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数 。
class Solution {
public:
double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
int n = nums1.size();
int m = nums2.size();
//如果总共有奇数个,那么所求的是第k_left小的数
//如果总共有偶数个,那么所求的是第k_left和k_right的平均值
int k_left = (n+m+1)/2;
int k_right = (n+m+2)/2;
//合并奇数和偶数的情况
if(!((n+m) & 1)) //奇数
return (getKth(nums1,0,n-1,nums2,0,m-1,k_left) + getKth(nums1,0,n-1,nums2,0,m-1,k_right))/2.0;
else return getKth(nums1,0,n-1,nums2,0,m-1,k_left);
}
int getKth(vector<int> &nums1, int start1, int end1, vector<int> &nums2, int start2, int end2, int k){
int len1 = end1-start1+1;
int len2 = end2-start2+1;
//如果len1比较长,那么交换他们的位置,保证如果有空数组那么一定是len1,简化后面的判断
if(len1 > len2) return getKth(nums2,start2,end2,nums1,start1,end1,k);
//什么时候退出呢?
if(0 == len1) return nums2[start2+k-1]; //如果len1空了,那么d第k小的数就在nums2中,直接返回就好了
if(1 == k) return min(nums1[start1],nums2[start2]); //第一小的数就在start1和start2中产生
int i = start1 + min(len1,k/2)-1; //保证如果k/2超过了界限,就取值数组的最后的值
int j = start2 + min(len2,k/2)-1;
if(nums1[i] > nums2[j]) //len1的值比较大,则砍掉len2前面的值,因为不可能在那个区间中
return getKth(nums1,start1,end1,nums2,j+1,end2,k-(j-start2+1));
else
return getKth(nums1,i+1,end1,nums2,start2,end2,k-(i-start1+1));
}
};
87th:496. 下一个更大元素 I
给你两个 没有重复元素 的数组 nums1 和 nums2 ,其中nums1 是 nums2 的子集。
请你找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
nums1 中数字 x 的下一个更大元素是指 x 在 nums2 中对应位置的右边的第一个比 x 大的元素。如果不存在,对应位置输出 -1
class Solution {
public:
/*
单调栈:从后向前入栈,然后按照条件弹栈
//利用栈,从后向前找,然后将小于当前元素的元素弹栈,这样剩下的就是右边比当前元素大的第一个元素
//如果栈为空,那么说明没有比当前元素大的,填入-1
//最后将当前元素入栈,用于下一次迭代
O(N)
*/
vector<int> nextGreaterElement(vector<int>& nums1, vector<int>& nums2) {
unordered_map<int,int> ele;
vector<int> res(nums1.size());
for(int i = 0; i<nums1.size(); ++i)
ele[nums1[i]] = i+1;
stack<int> st;
for(int i = nums2.size()-1; i>=0; i--){
//if(ele[nums2[i]])
while(!st.empty() && nums2[i] > st.top())
st.pop();
if(ele[nums2[i]] > 0)
{
int index = ele[nums2[i]]-1;
res[index] = st.empty()?-1:st.top();
}
st.push(nums2[i]);
}
return res;
}
};
88th:503. 下一个更大元素 II
给定一个循环数组(最后一个元素的下一个元素是数组的第一个元素),输出每个元素的下一个更大元素。数字 x 的下一个更大的元素是按数组遍历顺序,这个数字之后的第一个比它更大的数,这意味着你应该循环地搜索它的下一个更大的数。如果不存在,则输出 -1。
class Solution {
public:
/*
相比于第一题,将数组重复即可
[2 1 4 3] 重要的是如何找到3的下一个比他大的数4
变化为 [2 1 4 3 2 1 4 3]就可
*/
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> res(nums.size());
stack<int> st;
//首先push一轮,相当于重复的数组
for(int i = nums.size()-1; i>=0; --i)
st.push(nums[i]);
for(int i = nums.size()-1; i>=0; --i){
while(!st.empty() && nums[i]>=st.top())
st.pop();
res[i] = st.empty()?-1:st.top();
st.push(nums[i]);
}
return res;
}
};
89th:232. 用栈实现队列
请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):
实现 MyQueue 类:
void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false
class MyQueue {
public:
stack<int> st1;
stack<int> st2;
/** Initialize your data structure here. */
MyQueue() {
//do nothing...
}
/** Push element x to the back of queue. */
void push(int x) {
st1.push(x);
}
/** Removes the element from in front of queue and returns that element. */
int pop() {
if(st2.empty()){
while(!st1.empty()){
st2.push(st1.top());
st1.pop();
}
}
int tmp = st2.top();
st2.pop();
return tmp;
}
/** Get the front element. */
int peek() {
if(st2.empty()){
while(!st1.empty()){
st2.push(st1.top());
st1.pop();
}
}
int tmp = st2.top();
return tmp;
}
/** Returns whether the queue is empty. */
bool empty() {
return st1.empty() && st2.empty();
}
};
90th:225. 用队列实现栈
请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通队列的全部四种操作(push、top、pop 和 empty)。
实现 MyStack 类:
void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。
class MyStack {
public:
queue<int> q1;
queue<int> q2;
/*可以用两个栈也可以用一个栈实现*/
/** Initialize your data structure here. */
MyStack() {
//do nothing ....
}
/** Push element x onto stack. */
void push(int x) {
/*在push的时候就进行元素的调换*/
int n = q1.size();
q1.push(x);
//将队列当做循环队列来用,1 2 3的队列,push了4,经过了循环之后变为 4 1 2 3,这样第一个元素
//就是栈的pop的元素
for(int i = 0; i<n; ++i){
q1.push(q1.front());
q1.pop();
}
}
/** Removes the element on top of the stack and returns that element. */
int pop() {
int res = q1.front();
q1.pop();
return res;
}
/** Get the top element. */
int top() {
return q1.front();
}
/** Returns whether the stack is empty. */
bool empty() {
return q1.empty();
}
};
91th:875. 爱吃香蕉的珂珂
珂珂喜欢吃香蕉。这里有 N 堆香蕉,第 i 堆中有 piles[i] 根香蕉。警卫已经离开了,将在 H 小时后回来。
珂珂可以决定她吃香蕉的速度 K (单位:根/小时)。每个小时,她将会选择一堆香蕉,从中吃掉 K 根。如果这堆香蕉少于 K 根,她将吃掉这堆的所有香蕉,然后这一小时内不会再吃更多的香蕉。
珂珂喜欢慢慢吃,但仍然想在警卫回来前吃掉所有的香蕉。
返回她可以在 H 小时内吃掉所有香蕉的最小速度 K(K 为整数)。
class Solution {
public:
int minEatingSpeed(vector<int>& piles, int h) {
int n = piles.size();
int maxS = piles[0];
for(int i = 1; i<n; ++i)
maxS = piles[i]>maxS?piles[i]:maxS;
/* 暴力解法,超时
for(int s = 1; s<=maxS; ++s){
if(eatup(piles,h,s))
return s;
}*/
//二分法
int left = 1, right = maxS;
while(left <= right){
int mid = left + (right-left)/2;
//计算mid是否能满足要求
int res = eatup2(piles,h,mid);
if(res == 1){
//耗时大于h
left = mid +1;
}else if(res == 0)
//找到了 ,但是求得是最小值,向左收缩
right = mid - 1;
else{
right = mid-1;
}
}
return left;
}
/*暴力解法,超时*/
bool eatup(vector<int> &p, int h, int s){
int hour = 0;
for(int i = 0; i<p.size(); ++i){
int x = p[i]%s;
hour += p[i]/s;
if(x != 0) hour++;
if(hour > h)
return false;
}
return true;
}
int eatup2(vector<int> &p, int h, int s){
int hour = 0;
for(int i = 0; i<p.size(); ++i){
int x = p[i]%s;
hour += p[i]/s;
if(x != 0) hour++;
if(hour > h)
return 1;
}
if(hour == h) return 0;
return -1;
}
};
92th:1011. 在 D 天内送达包裹的能力.
传送带上的包裹必须在 D 天内从一个港口运送到另一个港口。
传送带上的第 i 个包裹的重量为 weights[i]。每一天,我们都会按给出重量的顺序往传送带上装载包裹。我们装载的重量不会超过船的最大运载重量。
返回能在 D 天内将传送带上的所有包裹送达的船的最低运载能力。
class Solution {
public:
int shipWithinDays(vector<int>& weights, int D) {
int n = weights.size();
int left = 0, right = 0;
for(int i = 0; i<n; ++i){
right += weights[i];
if(weights[i]>left) left = weights[i];
}
while(left<=right){
int mid = left + (right-left)/2;
int res = pass(weights,D,mid);
if(res == 1)
left = mid +1;
else if(res == 0)
right = mid-1;
else right = mid -1;
}
return left;
}
int pass(vector<int> &w, int d, int cap){
int day = 0;
int acc = 0;
for(int i = 0; i<w.size(); ++i){
if(acc+w[i] > cap){
acc = w[i];
day ++;
}else{
acc += w[i];
}
if(day > d) return 1;
}
day++;
if(day > d) return 1;
if(day == d) return 0;
return -1;
}
};
93th:实现一个特殊功能的栈,在实现栈的基本功能的基础上,再实现返回栈中最小元素的操作。
class Solution {
public:
/**
* return a array which include all ans for op3
* @param op int整型vector> operator
* @return int整型vector
*/
vector<int> getMinStack(vector<vector<int> >& op) {
// write code here
vector<int> res;
for(auto o:op){
if(o[0] == 1)
push(o[1]);
else if(o[0] == 2)
pop();
else if(o[0] == 3)
res.push_back(getMin());
}
return res;
}
stack<int> st, min_st;
void push(int x){
st.push(x);
if(min_st.empty() || min_st.top() >= x) min_st.push(x);
}
void pop(void){
if(!st.empty()){
//如果要弹出的数据是最小值
if(st.top() == min_st.top()) min_st.pop();
st.pop();
}
}
int getMin(){
return min_st.top();
}
};
94:牛客网 寻找第K大的数
class Solution {
public:
//1、找第K大,用小根堆
//2、用快排 + 二分的思想
int findKth(vector<int> a, int n, int k) {
// write code here
//第k大的数,堆
//小根堆
priority_queue<int,vector<int>, greater<int> > q; //Mintree
if(k > a.size() || k<=0) return 0;
for(auto i: a){
if(q.size() >= k){
if(i > q.top()){
q.pop();
q.push(i);
}
continue;
}
q.push(i);
}
return q.top();
//return kth(a, 0, a.size()-1, k);
}
//快排 + 二分
void swap(vector<int> &nums, int i, int j){
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
int part(vector<int> &nums, int l, int r){
int i = l;
int pivot = nums[r];
for(int j = l; j<r; ++j){
if(nums[j] < pivot){
swap(nums,i,j);
i++;
}
}
swap(nums,i,r);
return i;
}
int kth(vector<int> &nums, int l, int r, int k){
if( l <= r){
int q = part(nums,l,r);
//第q+1小的数
if(q+1 == nums.size()+1-k)
return nums[q];
else if(q+1 < nums.size()+1-k) //righ side
return kth(nums, q+1, r, k);
else
return kth(nums, l, q-1, k); //left side
}
return -1;
}
};
95:kmp算法
//kmp算法不会重复扫描txt字符串,而是用一个dp数组中存储的信息
//把pat移动到正确的位置,然后继续匹配
//有限状态机计算dp数组,dp数组的计算只和pat有关
//状态转移需要明确两个变量:当前的状态, 当前遇到的字符,确定这两个量之后就可以确定下一个状态是什么
//构建dp数组:
/* dp[j][c] = next;
j:代表当前的状态 0<=j
void make_dp(string pat, vector<vector<int>> &dp){
/* 状态推进, 状态回退 */
int m = pat.size();
//vector dp(m,vector(256,0));
//base case,状态为0的时候,遇到pat的第一个字符才进入状态1
dp[0][pat[0]] = 1;
//影子状态 x,x状态与当前状态具有相同的前缀
int x = 0;
for(int j = 1; j<m; ++j){
for(int c = 0; c<256; ++c){
//如果遇到的字符和pat的字符匹配上了,那么 状态推进
if(pat[j] == c) dp[j][c] = j+1;
else dp[j][c] = dp[x][c]; //没有匹配上,那么我们需要利用影子状态来更新当前状态
}
//更新影子状态,当前状态是x,遇到字符pat[j]
//相当于在pat字符串中匹配pat[1:],
x = dp[x][pat[j]];
}
}
int kmp(string pat, string txt){
int m = pat.size();
int n = txt.size();
int j = 0; //一开始是0状态
//可以看到,首先需要将这个dp数组设定好,设定的时候仅仅需要pat就足够了
//在进行字符串匹配的时候,直接遍历txt字符串一遍,然后根据这个dp数组进行状态转变即可,如果匹配上直接输出索引
//那么怎么设定这个dp数组呢?
vector<vector<int>> dp(m,vector<int>(256,0));
make_dp(pat,dp);
for(int i = 0; i<n; ++i){
j = dp[j][txt[i]]; //计算下一个状态
if(j == m) return i-m+1;
}
return -1;
}
};
96th:打乱数组
给你一个整数数组 nums ,设计算法来打乱一个没有重复元素的数组。
实现 Solution class:
Solution(int[] nums) 使用整数数组 nums 初始化对象
int[] reset() 重设数组到它的初始状态并返回
int[] shuffle() 返回数组随机打乱后的结果
#include
class Solution {
public:
vector<int> num;
vector<int> ori;
Solution(vector<int>& nums) {
this->num.assign(nums.begin(),nums.end());
this->ori.assign(nums.begin(),nums.end());
}
//洗牌算法:保证洗牌之后,牌足够乱,产生的结果要有n!中可能
/** Resets the array to its original configuration and return it. */
vector<int> reset() {
return ori;
}
/** Returns a random shuffling of the array. */
vector<int> shuffle() {
//洗牌算法1,必须保证有n!中可能
int n = num.size();
for(int i = 0; i<n; ++i){
int r = rand()%(n-i)+i; //rand()%100 表示取得[0,100)之间的随机整数
swap(num[i],num[r]); //交换
}
//洗牌算法如何验证? 蒙特卡洛近似
//将所有可能的顺序都表示出来,然后执行shuffle函数100w次,统计每种数出现的次数,如果差不多,说明是正确的
return num;
}
};
97th: Z 字形变换
class Solution {
public:
string convert(string s, int numRows) {
if (numRows == 1) return s;
string ret;
int n = s.size();
//步长为k
int cycleLen = 2 * numRows - 2;
//找规律:如果不是第一行或者最后一行,那么每次追加字符串的时候,除了要按照步长追加,还要追加
//中间的部分的字符, j+cycleLen-2*i 就是其索引
for (int i = 0; i < numRows; i++) {
for (int j = i; j < s.size(); j += cycleLen) {
ret += s[j];
if (i != 0 && i != numRows - 1 && j + cycleLen - 2*i < n)
ret += s[j + cycleLen - 2*i];
}
}
return ret;
}
};
98th:560.和为k的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
class Solution {
public:
int subarraySum(vector<int>& nums, int k) {
//前缀和技巧
int n = nums.size();
//哈希表,记录 前缀和 -> 该前缀和出现的次数 的映射
unordered_map<int,int> mem;
//base case
mem[0] = 1;
int sum_i = 0;
int cnt = 0;
for(int i = 0; i<n; ++i){
sum_i += nums[i];
//sum_i计算的是前i个元素的和,将sum(nums[i...j]) == k等价为 k == sum_i-sum_j
int sum_j = sum_i-k;
if(mem.count(sum_j))
cnt += mem[sum_j];
mem[sum_i] = mem[sum_i]+1;
}
return cnt;
}
};
99th: