leecode[二]

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,并且对左节点和右节点进行相应的判断求解。

扫描二维码关注公众号,回复: 11441459 查看本文章
/**
 * 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;
        
        
    }
};

猜你喜欢

转载自blog.csdn.net/huanghaihui_123/article/details/87345031