769. 最多能完成排序的块
数组arr是[0, 1, …, arr.length - 1]的一种排列,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
我们最多能将数组分成多少块?
方法一:
思路:遍历数组从0到size-1,当遍历到的左边的数组的最大数等于当前的序号时候,就可以拆分成一块独立的模块。
即当一个块可以独立剥离出来的时候,该块的最大序号与该块的最大值应该是相等的。
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
int i=0;
int imax=0;
int result=0;
while(i<=arr.size()-1){
imax=arr[i]; //开启新的一块内容
for(int j=i;j=imax;j++) //查看当前块的最大值是否小于等于当前的序号
//在更新j的同时,也不断更新imax
{
if(arr[j]>imax)
imax=arr[j];
}
i=imax+1; //开始进行更新新的一块
result++;
}
return result;
}
};
方法二:
思路:
arr[] : 0 2 1 3 4
rmin[]: 0 1 1 3 4
lmax[]: 0 2 2 3 4 (可选)
维护一个数组rmin,代表从该序号出发向右方向看的时候,右边数组中最小的元素。该数组的目的在于:当左边的数组的最大值小于右边数组的最小值得时候,可以进行剥离。
因此程序分为两部分:
1.创建rmin数组
2.从下标1开始比较左边数组最大值lmaxnum是否小于右边数组的最小值,如果小于,就拆分成独立的块。在此过程中不断更新lmaxnum的值。(这一步骤也可以用创建一个lmax数组来替换)
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
vector<int> rmin(arr.size());
int rminnum=arr[arr.size()-1];
for(int i=arr.size()-1;i>=0;i--)
{
if(arr[i]<rminnum){
rmin[i]=arr[i];
rminnum=arr[i];
}else{
rmin[i]=rminnum;
}
}
int lmaxnum=arr[0];
int result=1;
for(int i=1;i<arr.size();i++){
if(lmaxnum<rmin[i]){
result++;
lmaxnum=arr[i];
}else{
lmaxnum=(arr[i]>lmaxnum?arr[i]:lmaxnum);
}
}
return result;
}
};
方法二的高效版:
class Solution {
public:
//保证左边的数是最小的,之后不断右推
//维护一个右边最小数数组和左边最大数,本题的排序方式简单来说就是 左边块里的最大数 小于 右边块里的最小数
int maxChunksToSorted(vector<int>& arr) {
int n=arr.size();
int ans=1,lmax=arr[0];
vector<int> rmin(n);
rmin[n-1]=arr[n-1];
for(int i=n-2;i>=0;i--)
rmin[i]=min(rmin[i+1],arr[i]);
for(int i=1;i<n;i++){
if(rmin[i]>=lmax){
ans++;
lmax=arr[i];
}
else{
lmax=max(lmax,arr[i]);
}
}
return ans;
}
};
222. 完全二叉树的节点个数
给出一个完全二叉树,求出该树的节点个数。
说明:完全二叉树的定义如下:在完全二叉树中,除了最底层节点可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
直接想到两种方案:
1.直接用二叉树的遍历来进行求解:
2.利用完全二叉树的性质来进行求解:
1.第一种方案:
对二叉树进行遍历迭代。当该节点不为空时,最终结果自增1,并且对左节点和右节点进行相应的判断求解。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int a=0;
int countNodes(TreeNode* root) {
if(root==NULL){
return 0;
}
else{
a++;
countNodes(root->left);
countNodes(root->right);
}
return a;
}
};
2.第二种方案:
断裂的节点:从上到下,从左到右的最后一个节点。
利用完全二叉树的性质:
(1)当一个节点的左子树和右子树的高度不一致的时候,我们可以知道断裂的节点出现在左子树上面。
(2)当一个节点的左子树和右子树的高度一致的时候,我们可以知道断裂的节点出现在右子树上面。
因此 ,我们可以采用迭代求解节点的方式,当出现第(1)中情况的时候,右子树一定是一颗满二叉树。因此可以直接计算出右子树节点的数量。同理(2)也是。因此迭代到最后,就会剩余一些叶子节点,左子树右子树都为空,迭代就从下到上进行返回。
其中树的深度可以一直迭代遍历左子树来进行。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int countNodes(TreeNode* root) {
if(root==NULL){
return 0;
}
int leD=GetDepth(root->left);
int riD=GetDepth(root->right);
if(leD==riD)
return 1+((1<<leD)-1) +countNodes(root->right);
//1代表当前的根节点
//第二项代表左子树,肯定是一颗满二叉树
//第三项对右子树进行相同的迭代求解。
//因为右子树中我们还没办法确认出断裂的节点具体是在哪个位置
else
return 1+((1<<riD)-1) +countNodes(root->left) ;
}
int GetDepth(TreeNode* root){
int i=0;
while(root){
i++;
root=root->left;
}
return i;
}
};
61. 旋转链表
给定一个链表,旋转链表,将链表每个节点向右移动 k 个位置,其中 k 是非负数。
示例一:
输入: 1->2->3->4->5->NULL, k = 2
输出: 4->5->1->2->3->NULL
解释:
向右旋转 1 步: 5->1->2->3->4->NULL
向右旋转 2 步: 4->5->1->2->3->NULL
思路一:把链表转化为队列,先进先出,容易做右移
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
queue<int> q1;
if(head==NULL){
return 0;
} //特例一定要单独列举出来
ListNode* p=head;
while(p!=NULL){
q1.push(p->val);
p=p->next;
}
int temp=0;
int num=q1.size()-k%q1.size(); //右移k,相当于左移n个数-k
for(int i=0;i<num;i++){
temp= q1.front();
q1.pop();
q1.push(temp);
}
p=head;
while(p!=NULL){
p->val=q1.front();
q1.pop();
p=p->next;
}
return head;
}
};
思路二:把链表转化为循环链表先,先缝合再从特定位置拆开。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if (head == NULL)
return NULL;
ListNode* p= head;
int sizeoflist=1;
while((p) && (p->next)){
sizeoflist++;
p=p->next;
}
if(k==0){
return head;
}
k%=sizeoflist; //实际等效右移的数,在0到sizeoflist-1闭区间之间徘徊
if(k==0){
return head;
}
p->next=head; //特别小心!当自己指向自己的时候
//不能提前放在k==0判断的前面,不然就都会输出一个循环链表
//如果一个链表为:[1,2,3,4,5,6,7],右移k位
//即倒数第k位是首元素
//现在要将head移动到链表右移后的最后一位
//head的next就是新链表的第一位
//head要移动sizeoflist-k位(head移动到倒数第一位需要移动sizeoflist-1次,现在需要移动到倒数k+1位)
for(int i =sizeoflist-k-1;i>0;i--)
head=head->next;
p=head->next;
head->next=NULL;
return p;
}
};
一个序列有n个数,倒数第i位,等于正数第n+1-i位。
480. 滑动窗口中位数
思路1:维护一个指向中位数的指针(迭代器)
关键思想:
当新插入的值小于中位数,指针减一
当要去掉的值小于等于中位数,指针加一
class Solution {
public:
vector<double> medianSlidingWindow(vector<int>& nums, int k) {
vector<double> res;
multiset<double> ms(nums.begin(), nums.begin() + k); //k是k个数,包括前面,不包括后面
auto mid = next(ms.begin(), k / 2);
for (int i = k; ; ++i) {
res.push_back((*mid + *prev(mid, 1 - k % 2)) / 2);
if (i == nums.size()) return res;
ms.insert(nums[i]);
if (nums[i] < *mid) --mid;
if (nums[i - k] <= *mid) ++mid;
ms.erase(ms.lower_bound(nums[i - k]));
}
}
};
注意点:
1.ms.lower_bound(nums[i - k])与lower_bound(ms.begin(),ms.end(),nums[i - k]))时间效果差别很大,因为后面这个函数每次都要计算ms.end(),开销巨大?
2.next(),prev()使用比较陌生,迭代器使用比较生疏。auto 比较万能
思路2:维护两个堆,
最大堆存放最小的一半的数(+1)
最小堆存放最大的一半的数
(奇数的情况下)每次从最大堆取一个数
(偶数的情况)每次从最大堆和最小堆取一个数求平均
维护:
1.新进来的数如果小于最大堆的最大值,放进最大堆,否则放进最小堆
2.找到要丢的数是在大堆还是小堆
3.比较大堆小堆的数量,要维持大堆始终=小堆+1
547. 朋友圈
班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N * N 的矩阵 M,表示班级中学生之间的朋友关系。如果M[i][j] = 1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
方法一:
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
ios::sync_with_stdio(false);
int result=0;
int N= M.size();
set<int> left; //维护一个集合,用这个集合来表示还未搜寻的人的序号
for(int i=0;i<N;i++){
left.insert(i);
}
while(!left.empty()){
findrlt(left,M,*left.begin()); //flag代表下一个要查找的对象
result++;
}
return result;
}
void findrlt(set<int> &left,vector<vector<int>>& M,int flag){
left.erase(flag);
for(int i=0;i<M.size();i++){
if(i!=flag && M[flag][i]==1 && left.find(i)!=left.end()){
findrlt(left,M,i);
}
}
}
};
方法二:
也是属于并查集的思想
class Solution {
public:
int findCircleNum(vector<vector<int> >& M) {
ios::sync_with_stdio(false);
int len=M.size();
// cout<<len;
int pre[len];
for(int i=0;i<len;i++) pre[i]=i;
for(int i=0;i<M.size();i++){
for(int j=i+1;j<M[0].size();j++){
if(M[i][j]==1){
//用非递归法找到集合标识
int k1=j;
while(k1!=pre[k1]) k1=pre[k1];
int k2=i;
while(k2!=pre[k2]) k2=pre[k2];
pre[k1]=k2;
}
}
}
int count=0;
for(int i=0;i<len;i++){
if(pre[i]==i) count++;
}
return count;
}
};
通过并查集来进行:
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
if (M.empty())
return 0;
vector<int> pre(M.size());
for(int i=0; i<M.size(); i++)
pre[i] = i;//先各自为组,组名也为自己的序号
int group = M.size();//一开始有多少人就有多少个朋友圈,当每出现一对朋友时就减1,最后就是总的朋友圈数量了。
for(int i=0; i<M.size(); i++)
{
for(int j=0; j<M.size(); j++)
{
if (i != j && M[i][j] == 1)
{
int x1 = find(i, pre);//x1为i所属的组
int x2 = find(j, pre);//x2为j所属的组
if (x1 != x2)
{
//如果不属于同个朋友圈的话就把i归为j的组
pre[x1] = x2;
group--;
}
}
}
}
return group;
}
private:
int find(int x, vector<int>& pre)
{
return pre[x]==x ? x : pre[x] = find(pre[x], pre);//“pre[x] = ”这句为路径压缩,直接指向组的根节点,下次查询时就快很多了。
}
};
2. 两数相加
给出两个 非空 的链表用来表示两个非负的整数。其中,它们各自的位数是按照 逆序 的方式存储的,并且它们的每个节点只能存储 一位 数字。
如果,我们将这两个数相加起来,则会返回一个新的链表来表示它们的和。
您可以假设除了数字 0 之外,这两个数都不会以 0 开头。
示例:
输入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
输出:7 -> 0 -> 8
原因:342 + 465 = 807
思路:有一个进位标志,当两个链表有一个不为空的时候,就进行加法。注意:只有在这两个链表有一个不为空的时候才能res->next=new ListNode(0); 不能提前new出一个ListNode出来,否则会多出一个数位。
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
class Solution {
public:
ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
ListNode* res1=new ListNode(0);
ListNode* res=res1;
ListNode* p=l1;
ListNode* q=l2;
int sum=0;
int carry=0;
while(p!=NULL || q!=NULL){
res->next=new ListNode(0);
res = res->next;
if(p!=NULL){
sum+=p->val;
p=p->next;
}
if(q!=NULL){
sum+=q->val;
q=q->next;
}
sum+=carry;
carry=sum/10;
res->val=sum%10;
sum=0;
}
if(carry==1){
res->next=new ListNode(1);
}
return res1->next;
}
};