1.leetcode

Leetcode刷题

数组

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<int> 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<<nums[i]<<endl;
           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<<rightone<<endl; //0010
        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<<prev->val<<std::endl;
        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<ListNode *> s;
        while(head)
        {
            std::cout<<head->val<<std::endl;
            if(s.count(head) > 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<int,vector<int>,greater<int>> 小顶堆
        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<<A[even]<<" "<<A[odd]<<std::endl;
                //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<int> preorderTraversal(TreeNode* root) {
        vector<int> res;
        //递归终止条件
        if(root == nullptr) return res;

        res.push_back(root->val); 

        vector<int> 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<int> inorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == nullptr) return res;

        vector<int> 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<<root->val<<std::endl;
            res.push_back(root->val);
            st.pop();
            root = root->right;
        }   

        return res;
    }
};

36th:145. 二叉树的后序遍历

给定一个二叉树,返回它的 后序 遍

 /*二叉树的后续遍历,左右中*/
class Solution {
public:
//1、递归实现
    /*vector<int> postorderTraversal(TreeNode* root) {
        vector<int> res;
        if(root == nullptr) return res;

        vector<int> 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<TreeNode*> 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<int> res = helperFunction(root);
        return res[k-1];
    }

    vector<int> helperFunction(TreeNode *root){
        vector<int> res;
        if(root == nullptr) return res;

        vector<int> 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<right,具体来说是left==right+1
        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<string>(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<pair<int,int>,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<ListNode *> 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<int> 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: "<<i<<" "<<tuple[0]<<" "<<tuple[1]<<std::endl;
                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<int> 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<int> res;
        int cnt = 0;
        for(int i = 0; i<nums.size(); ++i){
            if(nums[i] == target && cnt == 0)
            {
                cnt = 1;
                 res.push_back(i);
            }
            if(cnt == 1 && nums[i] != target)
            {
                res.push_back(i-1);
                break;
            }
        }  
        if(res.size() == 1) res.push_back(nums.size()-1);
        return cnt== 0?vector<int>(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<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<m
          c:代表遇到的字符 0~256
          next:下一个状态

          dp[4]['d'] = 3; 当前状态是4,如果遇到字符d,则转移到状态3  
    */

    void make_dp(string pat, vector<vector<int>> &dp){
        /*  状态推进, 状态回退 */
        int m = pat.size();
        //vector<int> dp(m,vector<int>(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<cstdlib>
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:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值