LeetCode刷题记录表

坚持刷题,争取每周天更新

剑指 Offer 03. 数组中重复的数字

解法1:使用unordered_map

​ 要找到任意一个,没有限定第一个,或者第k个,直接遍历一边数组,每次遍历时将元素插入到map中,直到map中出现重复值代表找到

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
    unordered_map<int , bool> map;//构造时bool默认为false
    for(int num:nums){//遍历
        if(map[num]) //如果在map中已经存在该元素
        {
            return num;
        }
        map[num] = true;//添加进去的元素设置为true
    }
    return -1;
    }
};

时空复杂度都为O(n)

解法二:

​ 题目中的数字在一个长度为n的数组中,且数字也在0到n-1这个范围内,可以肯定的是一定有重复,且考虑将数组中每个元素的位置与下标对应,那么一定有两个元素的下标对应到了一起(因为有重复的)

如下2应该被映射到num[2],而num[4]存储的2页会被映射到num[2],此时就发生了冲突即num[i]==num[num[i]],即找到了该元素

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        int i = 0;
        while(i < nums.size()) {
            if(nums[i] == i) {//如果该元素已经在正确的位置,继续向后
                i++;
                continue;
            }
            if(nums[nums[i]] == nums[i])//如果该元素需要归位的下标已经有同值元素,结束
                return nums[i];
            swap(nums[i],nums[nums[i]]);//交换到正确位置
        }
        return -1;
    }
};
//作者:jyd

此时时间O(n),空间O(1)

剑指 Offer 04. 二维数组中的查找

行递增,列递增,最小元素在左上角,最大元素在右下角,可以这样解决,从右上角出发向左,当该位置值大于target时向左,当该位置小于target时向下,为什么这么想呢?因为将该矩阵旋转45°就变成了一颗二叉搜索树了。

当然从左上角也可以走,就是稍微麻烦点

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(!matrix.empty()){//判断vector是否为空
		
        int row = matrix.size()-1;//行数
        int col = matrix[0].size();//列数
        int i = 0;
        int j = col-1;
        while(i <= row && j >= 0){
            if(target==matrix[i][j]){
                return true;
            }
            else if(matrix[i][j] > target){//向左
                --j;
            }
            else{	//向下
                ++i;
            }
        }
        }
        return false;
    } 
};

时间复杂度O(m+n)m为行数,n为列数,空间复杂度O(1)

剑指 Offer 05. 替换空格

解法一:

​ 再开辟一个string来保存结果,将原字符串值依次判断再添加到新串中

class Solution {
public:
    string replaceSpace(string s) {
        string res;
        for(char c:s){
            if(c==' '){
                res+="%20";
            }else{
                res+=c;
            }
        }
        return res;
    }
};

时间上遍历一次O(n),空间上开辟了一个新的串O(n)

解法二:

​ 不用开辟一个新的string,而是在已有的string后面再加上需要扩充的空间

​ 需要扩充的空间为:空格数*2

​ 然后再利用双指针,从后向前扫,普通字符直接修改,空格则连续修改3个位置

class Solution {
public:
    string replaceSpace(string s) {
    int length = s.length();
    int count = 0;//替换之后的长度
    for(int i=0;i<length;i++){
        if(s[i]==' ')   count++;
    }
    int new_length = length + count*2;
    s.resize(new_length);//重新扩容
    int low = length;
    int high = new_length;  //指向最高位
    while(low >= 0 && high > low)
    {
        if(s[low] == ' '){//如果是空格的话
            s[high--] = '0';
            s[high--] = '2';
            s[high--] = '%';
        }else{
            s[high--] = s[low];
        }
        --low;
    }
    return s;
    }
};

该方法应为没有开辟新的串所以空间上为O(1)

剑指 Offer 06. 从尾到头打印链表

方法一:

使用vector的insert方法,可以直接实现链表的头插法,但是缺点是运行的时间复杂度有点高,因为每次都要实现数组元素的移动

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int>res;
        while(head!=NULL){
            res.insert(res.begin(),head->val);//头插法
            head = head->next;
        }
        return res;
    }
};

时间O(n^2)空间O(n)

方法二:

使用了C++算法库中的reverse方法,直接实现逆置

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        vector<int> res;
        while(head) {
            res.push_back(head->val);
            head = head->next;
        }
        reverse(res.rbegin(), res.rend());
        return res;
    }
};

复杂度:reverse()函数无返回值,时间复杂度O(n)

方法三:

只要设计到逆序,FILO这种问题自然想到用桟或者递归,本题将元素依次插入桟中,再出栈即实现了逆置

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        stack<int>s;//定义一个桟
        while(head){
            s.push(head->val);//元素入栈
            head = head->next;
        }
        vector<int>res(s.size());//避免动态扩容的开销
        for(auto it=res.begin();it!=res.end();it++){
            *it = s.top();
            s.pop();
        }
        return res;
    }
};

出入桟都是O(1),总时间复杂度为O(n),空间上开辟了桟和vector也是O(n)

剑指 Offer 09. 用两个栈实现队列

桟是先进后出,队列是先进先出,利用两个桟分别完成一次入栈即可实现队列,如ABCD进SA,再DCBA进SB,此时SB的出栈顺序已经是一个队列的顺序了,需要注意的是,只有出栈时我们才需要将SA移动至SB

class CQueue {
public:
    stack<int> stack1;
    stack<int> stack2;
    CQueue() {}
    
    void appendTail(int value) {
        stack1.push(value);
    }
    
    int deleteHead() {
        if (stack1.empty()) return -1;
        
        while (!stack1.empty()){ // SA -> SB	模拟队列
            int tmp = stack1.top();
            stack1.pop();
            stack2.push(tmp);
        }
        // 删除栈顶元素(即队首出队)
        int res = stack2.top();
        stack2.pop();
        while (!stack2.empty()){ // SA <- SB	放回SA
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        return res;
    }
};

当碰到连续删除时,上面的代码每次都需要再把SB拷贝回SA,这时我们可以在插入时再考回SA

class CQueue {
public:
    stack<int> stack1;
    stack<int> stack2;
    CQueue() {}
    
    void appendTail(int value) {
        while (!stack2.empty()){ // 1 <- 2
            int temp = stack2.top();
            stack2.pop();
            stack1.push(temp);
        }
        stack1.push(value);
    }
    
    int deleteHead() {
        if (stack1.empty()&&stack2.empty()) return -1;
        while (!stack1.empty()){ // 1 -> 2
            int tmp = stack1.top();
            stack1.pop();
            stack2.push(tmp);
        }
        // delete head
        int res = stack2.top();
        stack2.pop();
        return res;
    }
};

时间复杂度:对于插入和删除操作,时间复杂度均为 O(1)O(1)。插入不多说,对于删除操作,虽然看起来是 O(n)的时间复杂度,但是仔细考虑下每个元素只会「至多被插入和弹出 stack2 一次」,因此均摊下来每个元素被删除的时间复杂度仍为 O(1)O(1)。

空间复杂度:O(n)O(n)。需要使用两个栈存储已有的元素。

剑指 Offer 10- I. 斐波那契数列

老生常谈的一道题了属于是,后一次的结果由前两次结果得出

解法一:我的解法

class Solution {
public:
    int fib(int n) {
        //递归
        if(n==0) return 0;
        if(n==1) return 1;
        int f1=0,f2=1,res;
        for(int i=2;i<=n;i++){
            res = (f1 + f2)% 1000000007;//后一次结果
            f1 = f2;	//分别更新前两次结果
            f2 = res;
        }
        return res ;
    }
};

!!!解法二:记忆化递归

一点理解:如果不使用记忆数组,那么我们上一次计算得到的值计算机其实并不会保留,再往下算时又需要再计算一次,这样就增加了许多重复的工作

当我们引入记忆数组后,上一次的值保存在cache[n-1],cache[n-2]中,再计算n时,直接根据n-1和n-2中的值可以直接得出结论

//递归解决子问题。但要注意的是递归时会产生大量重复计算,可能会超时,所以使用一个记忆数组避免重复计算。
class Solution {
public:
    int cache[101];
    int fib(int n) {
        if(n < 2){
            return n;
        }
        if(cache[n] != 0) return cache[n];
        cache[n] = (fib(n-1) + fib(n-2)) % (int)(1e9 + 7);
        return cache[n];
    }
};

时空复杂度都是O(n)

剑指 Offer 10- II. 青蛙跳台阶问题

和上面的fib基本一模一样

class Solution {
public:
    int numWays(int n) {
        if(n==0||n==1)    return 1;
        int res,step1=1,step2=1;
        for(int i=2;i<=n;i++){
        res = (step1+step2)%1000000007;
        step1 = step2;
        step2 = res;
        }
        return res;
    }
};

时间上O(n),空间上O(1)

剑指 Offer 11. 旋转数组的最小数字

本题主要想考察的就是二分的方法

解法一:直接for循环O(n)

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int min = -2147483647;
        min = numbers.front();
        for(auto it=numbers.begin();it!=numbers.end();++it){
            if((*it)<min)   min = (*it);
        }
        return min;
    }

};

解法二:二分法

如果mid大于high代表mid的右边是从左边旋转过去的,最小的在右边故 low = mid +1,否则在左边

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int low=0 , high = numbers.size()-1;
        while(low<high){
            int mid = (low+high)/2;
            if(numbers[mid]>numbers[high]) low = mid + 1;
            else if (numbers[mid]<numbers[high]) high = mid;
            else high--;
        }
        return numbers[low];
    }
};

剑指 Offer 15. 二进制中1的个数

解法一:逐位想与再统计次数

理解:用C++的位运算,每次将该数右移一位(保证无符号),然后和1相与,结果为1代表该位为1,再统计个数

class Solution {
public:
    int hammingWeight(uint32_t n) {
        int res=0;
        for(int i=0;i<=31;i++){
            if( ((n>>i)&1)==1)  res++;
        }
        return res;
    }
};

解法二:

用n不断&n-1,直到为0即可统计出0的个数

为何可以这样算呢?原因很简单,每减一之后最右边的1就会被抵消,比如1在最低位,减1后变0,相与后抵消了最低位的1,假如1在次低位:10减1后为01 01与10相与后为00这样次低位的1页被抵消掉了。

这让我想起了补码的快速计算:从右向左找到第一个1,左侧取反右侧不变,因为这个1一定是低位进上来的

count=0
while(k){
        k=k&(k-1);
        count++;
}

流程大概是这样的

231. 2 的幂(和上面类似的一题)

解法一:

​ 问题还是要全面的考虑,一开始的思路错的太离谱了,边界条件都没搞明白,好不容易整明白了,又没仔细看数据范围,测试数据中有负数的,所以一定要转成unsigned int才好使

class Solution {
public:
    bool isPowerOfTwo(int n) {
        int count=0;
        n = (unsigned)n;
        if(n>0){
            while(n){
                n = n&(n-1);//算出1的个数
                count++;
            }
        }
        if(count==1)    return true;
        return false;
    }
};

解法二:继续优化

解法一存在一个小问题,就是进行了太多次的判断,其实没必要,只需要进行一次判断就可以得出结论,只要n&(n-1)不全为0,则代表一定不是2的整数次幂

class Solution {
public:
    bool isPowerOfTwo(int n) {

    return n > 0 && (n & (n - 1)) == 0;

    }
};

为什么一次就能判断呢?因为如果你是二的次幂,那么你必定只有1个1,即与n-1相与一定为0

剑指 Offer 17. 打印从1到最大的n位数

解法一:

缺点:未考虑到大数的情况,当数字较大时,int不够用

class Solution {
public:
    vector<int> printNumbers(int n) {
        //1.先判断n为几位数
        n = pow(10,n);
        vector<int>v;
        for(int i=1;i<n;i++){
            v.push_back(i);
        }
        return v;
    }
};

解法二:考虑大数的情况

大数加法的模板

string Add(string a,string b) {
        string ans;
        int numA[MAX_SIZE]={0},numB[MAX_SIZE]={0};//用来保存数字
        int lenA = a.size();
        int lenB = b.size();

        for(int i=0;i<lenA;i++){
            //字符串转数字
            numA[lenA-1-i] = a[i] - '0';//从个位开始转换
        }
        for(int i=0;i<lenB;i++){
            numB[lenB-1-i] = b[i] - '0';
        }
        int lenMax = lenA>lenB ? lenA:lenB;
        // 从个位开始计算
        for(int i=0;i<lenMax;i++){
            numA[i]+=numB[i];
            numA[i+1]+=numA[i]/10;//判断是否有进位
            numA[i]%=10;
        }
        //去除前置0
        if(!numA[lenMax])   lenMax--;	
        for(int i= lenMax;i>=0;i--){
            ans+=numA[i]+'0';	//保存到结果中
        }
        return ans; 
    }

用字符串模拟数字加法:流程为,先判断是否有溢出,没有溢出就加1并保存该值

class Solution {
public:
    vector<int> output;
    vector<int> printNumbers(int n) {
        // 以下注释的前提:假设 n = 3
        if(n <= 0) return vector<int>(0);
        string s(n, '0'); // s最大会等于999,即s的长度为n
        while(!overflow(s)) inputNumbers(s);// 当没有溢出999时
        return output;	
    }
    bool overflow(string& s)	//判断溢出情况
    {
        // 本函数用于模拟数字的累加过程,并判断是否越界(即 999 + 1 = 1000,就是越界情况)
        bool isOverFlow = false;
        int carry = 0; // carry表示进位
        for(int i=s.length()-1; i>=0; --i)	//从高到低
        {
            int current = s[i] - '0' + carry; // current表示当前这次的操作
            if(i == s.length() - 1) current ++; // 如果i此时在个位,current执行 +1 操作
            if(current >= 10)	//有进位了
            {
                // 假如i已经在最大的那一位了,而current++之后>=10,说明循环到头了,即999 + 1 = 1000
                if(i == 0) isOverFlow = true;
                else
                {
                    // 只是普通进位,比如current从9变成10
                    carry = 1;
                    s[i] = current - 10 + '0'; 
                }
            }
            else
            {
                // 如果没有进位,更新s[i]的值,然后直接跳出循环,这样就可以回去执行inputNumbers函数了,即往output里添加元素
                s[i] = current + '0';
                break;
            }
        }
        return isOverFlow;
    }
    void inputNumbers(string s)
    {
        // 本函数用于循环往output中添加符合传统阅读习惯的元素。比如001,我们会添加1而不是001。
        bool isZero = true; // 判断是否是0,比如001前面的两个0
        string temp = "";
        for(int i=0; i<s.length(); ++i)
        {
            if(isZero && s[i] != '0') isZero = false;
            if(!isZero) temp += s[i];   //去除0后,将该值保存
        }
        output.push_back(stoi(temp));//stoi	string to integer
    }
};

剑指 Offer 18. 删除链表的节点

//主要是要考虑到头结点也有可能要被删除这种情况,所有再开辟一个新结点指向头结点,这样循环向后一个判断即可可以方便不少
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode *pre = new ListNode(0);
        pre->next = head;
        head = pre;
        while(pre->next!=NULL){
            if(pre->next->val==val){
                pre->next = pre->next->next;
                return head->next;
            }
            pre = pre->next;
        }
        return head->next;
    }
};

剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

第一时间我想到的是快速排序中的类似partition函数这样做分割,但关键地方有点卡住了,就先暴力过了一边

法一:暴力法

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        vector<int>res;
        for(auto it=nums.begin();it!=nums.end();++it){
            if( (*it)%2!=0 ){
                res.push_back((*it));
            }
        }
        for(auto it=nums.begin();it!=nums.end();++it){
            if( (*it)%2==0 ){
                res.push_back((*it));
            }
        }
        return res;
    }
};

法二:双指针法

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int i=0;
        int j=nums.size()-1;
        while(i < j){
            while(i<j && (nums[i] & 1) == 1) i++;	//奇数
            while(i<j && (nums[j] & 1) == 0) j--;	//偶数
            swap(nums[i],nums[j]);
        }
        return nums;
    }
};

法三.快慢指针

思路是让一个先走,而每当快指针指向的是奇数时,将其与low交换(即向左边靠)

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int low = 0, fast = 0;
        while (fast < nums.size()) {
            if (nums[fast] & 1) {	//当快指针指向的是奇数时
                swap(nums[low], nums[fast]);
                low ++;
            }
            fast ++;
        }
        return nums;
    }
};

剑指 Offer 22. 链表中倒数第k个节点

没什么难度,直接双指针就出来了,倒数第K个,就让一个先走K步,再两个指针一起走,当先走的到达链尾时,后走的正好到达倒数第K个

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *rear = head;
        for(int i = 0;i<k;++i){
            rear = rear->next;
            if(rear==NULL){//代表K超出范围
                return head;
            }
        }
        while(rear){
            rear = rear->next;
            head = head->next;
        }
        return head;
    }
};

剑指 Offer 24. 反转链表

相当简单,就是画个图,分析一下断链的情况和指针的情况就行了。

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head==NULL)  return head;
            ListNode *p,*q;
            p = head->next;
            head->next = NULL;
            while(p!=NULL){
                q = p->next;
                p->next = head;
                head = p;
                p = q;
            }
            return head;
    }
};

方案二:递归实现

链表的逆序可以自然想到递归,桟

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        return recur(head, nullptr);           // 调用递归并返回
    }
private:
    ListNode* recur(ListNode* cur, ListNode* pre) {
        if (cur == nullptr) return pre;        // 终止条件,开始回溯
        
        //该语句会一直向下指到5
        ListNode* res = recur(cur->next, cur); // 递归后继节点
        //开始回溯时修改指针指向	5->next = 4  4->next = 3....
        cur->next = pre;                       // 修改节点引用指向
        return res;                            // 返回反转链表的头节点
    }
};

剑指 Offer 25. 合并两个排序的链表

解法一
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* list_1, ListNode* list_2) {
        ListNode * list = new ListNode(0),*cur =list;
        while(list_1 && list_2){
            //按递增排序
            if(list_1->val<list_2->val){
                cur->next = list_1;
                list_1 = list_1->next;
            }else{
                cur->next = list_2;
                list_2 = list_2->next;
            }
            cur = cur ->next;
        }
        cur->next = list_1 ? list_1 : list_2 ;//处理剩余的结点,因为前一个while循环一定会让一个链表到尾
        return list->next;//为什么要return list->next呢?因为一开始申请的时候申请了一个0当头结点
    }
};
解法二

递归写法,貌似有关链表的题目,都可以用递归来写,但是有一点难理解

class Solution {
public:
         ListNode* mergeTwoLists(ListNode *l1, ListNode *l2) {
        
        if(l1 == NULL) return l2;   //l1到头了
        if(l2 == NULL) return l1;   //l2到头了
        if(l1->val <= l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);	//l1向后
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);	//l2向后
            return l2;
        }
    }

};

剑指 Offer 27. 二叉树的镜像

解法一法:

class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == nullptr) return nullptr;	//这里是边界条件
        TreeNode * temp = root->left;
        root->left=
            mirrorTree(root->right);//根节点的左结点应该向右递归
        root->right=
            mirrorTree(temp); 	//右结点向左递归
        return root;
    }
};
解法二:

用栈来处理

//桟的写法
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == nullptr) return nullptr;//一个特殊情况的判断
        stack<TreeNode *>s;
        s.push(root);
        while(!s.empty()){//非空
            TreeNode *node = s.top();//根结点出栈	处理顺序为4,7,9,6,2,3,1
            s.pop();
            if(node->left!=NULL)    s.push(node->left);//左进桟
            if(node->right!=NULL)   s.push(node->right);
            //实现左右指针的一个交换
            TreeNode *temp = node->left;
            node->left = node ->right;
            node->right = temp;
        }
        return root;
    }
};

总结:这几天写了下这种有类似反转啊,逆置啊,感觉一般首先可以想递归,然后想桟,这种FILO的结构来处理

剑指 Offer 28. 对称的二叉树

大佬的写法

class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        // 如果是空树
        if(!root)
            return true;
        else
            return isSymmetric(root->left, root->right);	//判断左子树和右子树是否对称
    }
    // 此函数比较二叉树中位置对称的两个节点
    bool isSymmetric(TreeNode* left, TreeNode* right){
        // 结束条件1:如果对称两个节点都为空,则返回true
        if(!left && !right){
            return true;
        }
        // 结束条件2:如果单独一个节点为空,另一个节点不为空,又或者是对称节点间的val值不等,则返回false
        if(!left || !right || left->val != right->val)
            return false;
        // 该层符合对称二叉树的要求,开始比较下一层
        //左结点的左结点,右结点的右结点
        return isSymmetric(left->left, right->right) && isSymmetric(left->right, right->left);      
    }
};

补一下递归:

递归,一定有1.递推关系2.递推出口

  • 递归计算数组的前你项和

  • 实现一个字符串的逆序输出
    void reverse_print(int index,string str){
        if(str[index]==NULL){
            return;			//1
        }
        else{
            reverse_print(index+1,str);	//2
            cout<<str[index];		//3
        }
    }
    执行流程是: 2 2 2 2 2..... 1 3 3 3 3 3.....
    
  • 杨辉三角

最核心的也是找出递推关系:

class Solution {
public:
    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> ret(numRows);
        for (int i = 0; i < numRows; ++i) {
            ret[i].resize(i + 1);	//空间的初始化 
            ret[i][0] = ret[i][i] = 1;
            for (int j = 1; j < i; ++j) {
                ret[i][j] = ret[i - 1][j] + ret[i - 1][j - 1];	//最核心的递推关系
            }
        }
        return ret;
    }
};

217. 存在重复元素

这是用集合的特性来做的,集合本身就具有排同性O(nlog2n),还有一种解法就是先排序,之后再判断相邻元素是否相等来判断O(n)

class Solution {
public:
    bool containsDuplicate(vector<int>& nums) {
        unordered_set<int>s;
        for(int x:nums){
            if(s.find(x)!=s.end()){
                return true;
            }
            s.insert(x);
        }
        return false;
    }
};

类似的287. 寻找重复数

和上篇文章剑指 Offer 03. 数组中重复的数字的解题思路一样,详情见上篇文章

class Solution {
public:
    int findDuplicate(vector<int>& nums) {
        int res;
        int i=0;
        while(i<nums.size()){
            if(nums[i]==i){ //已经归位
                i++;
                continue;
            }
            else if(nums[i]==nums[nums[i]])
                return nums[i];
                swap(nums[i],nums[nums[i]]);
        }
        return res;
    }
};

剑指 Offer 29. 顺时针打印矩阵

模拟这个顺时针的过程,一开始向右走到右边界,走完上边界++,向下走到下边界,走完右边界--,向左走到左边界,走完下边界--,向上走上边界,走完左边界++

class Solution 
{
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) 
    {
        if (matrix.empty()) return {};
        vector<int> res;
        int l = 0;                      //左边界
        int r = matrix[0].size() - 1;   //右边界
        int t = 0;                      //上边界
        int b = matrix.size() - 1;      //下边界
        while (true)
        {
            //left -> right
            for (int i = l; i <= r; i++) res.push_back(matrix[t][i]);
            if (++t > b) break;
            //top -> bottom
            for (int i = t; i <= b; i++) res.push_back(matrix[i][r]);
            if (--r < l) break;
            //right -> left
            for (int i = r; i >= l; i--) res.push_back(matrix[b][i]);
            if (--b < t) break;
            //bottom -> top
            for (int i = b; i >= t; i--) res.push_back(matrix[i][l]);
            if (++l > r) break;
        }
        return res;
    }
};

剑指 Offer 30. 包含min函数的栈

利用辅助桟s2来存放最小值,如果有比s2更小的则让更小的进s2,当原s1中最小值出栈时,判断如果同s2值相当,代表需要更新最小值,则s2栈顶出栈

class MinStack {
public:
    stack<int>s1;
    stack<int>s2;
    
    /** initialize your data structure here. */
    MinStack() {
        s2.push(INT_MAX);
    }
    
    void push(int x) {
        s1.push(x);
        if(x<=s2.top()){	
            s2.push(x);
        }
    }
    void pop() {
        if(s1.top()==s2.top())  s2.pop(); //s1出栈元素同s2栈顶保存的最小值相等
        s1.pop();        
    }
    int top() {
        return s1.top();
    }   
    int min() {
        return s2.top();	//s2的栈顶始终保存最小值
    }
};

面试题32 - I. 从上到下打印二叉树

二叉树的层次遍历 3,9,20,15,7依次入队

class Solution {
public:
    vector<int> levelOrder(TreeNode* root) {
        vector<int>v;
        if(root == NULL)    return v;
        queue<TreeNode*>q;	//初始化队列
        q.push(root);	    //根结点入队
        while(!q.empty()){
            TreeNode * node = q.front();	//队首出队
            q.pop();
            v.push_back(node->val);			
            if(node->left)  q.push(node->left);		//该结点的左孩子入队
            if(node->right)  q.push(node->right);	//右孩子入队
        }
        return v;
    }
};

剑指 Offer 32 - II. 从上到下打印二叉树 II

和上一题差不多一模一样,就是多了一个分层输出,怎么分层:根结点入队,队列size()为1,根结点左右孩子入队,队列size()为2,假设为满二叉树,则再向下,size()为4.......一直这样,每层有几个结点就i=q.size(),再i--把每个结点输出

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        // 层次遍历问题
        vector<vector<int>> v;
        if(root==NULL){
            return v;
        }
        queue<TreeNode *>q;
        q.push(root);
        while(!q.empty()){	//队列非空时
        vector<int>temp;
            for(int i=q.size();i>0;i--)	//关键的分层的办法
            {
                TreeNode * node = q.front();
                q.pop();
                temp.push_back(node->val);
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            v.push_back(temp);	//保存这一层的结果
        }
        return v;
    }
};

剑指 Offer 32 - III. 从上到下打印二叉树 III![image-20220306113134809]

方法一:和上一题一样,但是用一个数字来记录层数,然后奇数层正常,偶数层反转一下就可以了
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        // 层次遍历问题
        vector<vector<int>> v;
        if(root==NULL){
            return v;
        }
        queue<TreeNode *>q;
        q.push(root);
        int deep = 1;
        while(!q.empty()){//队列非空时
        vector<int>temp;
        
            for(int i=q.size();i>0;i--)//关键的分层的办法
            {
                TreeNode * node = q.front();
                q.pop();
                temp.push_back(node->val);
                if(node->left) q.push(node->left);
                if(node->right) q.push(node->right);
            }
            if(deep%2 ==0){
                reverse(temp.begin(),temp.end());
            }
            v.push_back(temp);
            deep++;
        }
        return v;
    }
};
还可以用双端队列

// 使用双端队列 (树的偶层: 尾入(先左子结点再右子结点)头出; 树的奇层: 头入(先右子结点再左子结点)尾出)
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        if (root == nullptr) {
            return {};
        }

        vector<vector<int>> vec;
        deque<TreeNode*>dqe;           

        int level = 0;
        dqe.push_back(root);                   // root结点在第0层(偶层),所以从队列尾入
        while (!dqe.empty()) {
            int level_nodes = dqe.size();
            vec.push_back({});

            if (level % 2 != 0) {              // 奇层: 从队列头入(先右子结点再左子结点),队列尾出
                while (level_nodes) {
                    TreeNode* p_node = dqe.back();
                    if (p_node->right != nullptr) dqe.push_front(p_node->right);
                    if (p_node->left != nullptr) dqe.push_front(p_node->left);

                    vec[level].push_back(p_node->val);
                    dqe.pop_back();
                    --level_nodes;
                }
                ++level;
            }
            else {                             // 偶层: 从队列尾入(先左子结点再右子结点),队列头出
                while (level_nodes) {
                    TreeNode* p_node = dqe.front();
                    if (p_node->left != nullptr) dqe.push_back(p_node->left);
                    if (p_node->right != nullptr) dqe.push_back(p_node->right);

                    vec[level].push_back(p_node->val);
                    dqe.pop_front();
                    --level_nodes;
                }
                ++level;
            }
        }

        return vec;
    }
};

88. 合并两个有序数组

就是说给两个非递减的数组,然后让你把第一个数组的前n位和第二个数组的前m位合并到一个数组1中(数组1的长度为M+N)

逆序双指针(双指针的思想常用)

class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
    int i = nums1.size() - 1;
    m--;	 //下标从0开始,所以先减1
    n--;
    while (n >= 0) {
        while (m >= 0 && nums1[m] > nums2[n]) {
            nums1[i--]=nums1[m--];
        }
        nums1[i--]=nums2[n--];
    }
}

剑指 Offer 39. 数组中出现次数超过一半的数字

这题是408出现过的原题,最好想的是做统计,排序,但这题用的是投票法

因为一定出现众数,每次出现众数就+1,不是就-1,最后一定大于0

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int res = nums[0];	//先设nums[0]为众数
        int count = 1;
        for(int i = 1;i<nums.size();i++){
            if(nums[i]==res){	//值相等++
                count++;
            }else{	//值不等进一步判断
                if(count==1){	//仅剩1次,那么将众数更新
                    res = nums[i];
                }else{
                    count--;	//出现次数--
                }
            }
        }
        return res;
    }
};

大佬的写法:本题具有特殊性,即不用判断是否为众数,最好还是可以加上一个判断

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int x = 0, votes = 0, count = 0;
        for(int num : nums){
            if(votes == 0) x = num;
            votes += num == x ? 1 : -1;
        }
        // 验证 x 是否为众数
        for(int num : nums)
            if(num == x) count++;
        return count > nums.size() / 2 ? x : 0; // 当无众数时返回 0
    }
};

剑指 Offer 40. 最小的k个数

4种解法秒杀TopK(快排/堆/二叉搜索树/计数排序)❤️ - 最小的k个数 - 力扣(LeetCode) (leetcode-cn.com)

剑指 Offer 40. 最小的 k 个数(基于快速排序的数组划分,清晰图解) - 最小的k个数 - 力扣(LeetCode) (leetcode-cn.com)

1.快速选择算法

优化了的快速排序,因为只需要找出最小的k个,并不关心他们的顺序

同快排一样使用频率很高

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {

        if(k>=arr.size())    return arr;
        return quick_select(arr, k, 0, arr.size() - 1);
    }
    vector<int>quick_select(vector<int>&arr,int k,int low,int high){
        int i = low;
        int j = high;
        int pivot = arr[i];//选定枢轴
        while(i<j){
            while(i<j && arr[j]>=pivot)  --j;
            arr[i] = arr[j] ;
            while(i<j && arr[i]<=pivot)  ++i;
            arr[j] = arr[i];
        }
        arr[i] = pivot;    //枢轴归位
        // 再判断是否需要继续,如果i>k,代表范围大了,减小范围
        //若果i<k代表我们的范围小了,需要再次划分
        if (i > k) return quick_select(arr, k, low, i - 1);
        if (i < k) return quick_select(arr, k, i + 1, high);
        vector<int> res;
        res.assign(arr.begin(), arr.begin() + k);   //返回前K个
        return res;
    }
};

414. 第三大的数

方法一:使用集合(默认排序),且仅保存3个数
class Solution {
public:
    int thirdMax(vector<int> &nums) {
        set<int> s;
        for (int num : nums) {
            s.insert(num);
            if (s.size() > 3) {	//超过3个元素就删除其中最小的
                s.erase(s.begin());
            }
        }
        return s.size() == 3 ? *s.begin() : *s.rbegin();	//不足3个数就返回最大的(例二的情况)
    }
};
解法二:用三个指针,仅用一轮扫描就可以找到
class Solution {
public:
    int thirdMax(vector<int> &nums) {
        int *a = nullptr, *b = nullptr, *c = nullptr;
        for (int &num : nums) {
            if (a == nullptr || num > *a) {
                c = b;
                b = a;
                a = &num;
            } else if (*a > num && (b == nullptr || num > *b)) {
                c = b;
                b = &num;
            } else if (b != nullptr && *b > num && (c == nullptr || num > *c)) {
                c = &num;
            }
        }
        return c == nullptr ? *a : *c;
    }
};

215. 数组中的第K个最大元素(这个条件又比上面的苛刻一些了)

解法一:还是直接快排
解法二:快速选择
class Solution {
public:
    int quickSelect(vector<int>& a, int l, int r, int index) {
        int q = randomPartition(a, l, r);
        if (q == index) {
            return a[q];
        } else {
            return q < index ? quickSelect(a, q + 1, r, index) : quickSelect(a, l, q - 1, index);
        }
    }
    inline int randomPartition(vector<int>& a, int l, int r) {
        int i = rand() % (r - l + 1) + l;//引入随机化	快排的时间复杂度取决于划分
        swap(a[i], a[r]);
        return partition(a, l, r);
    }
    inline int partition(vector<int>& a, int l, int r) {//划分
        int x = a[r], i = l - 1;
        for (int j = l; j < r; ++j) {
            if (a[j] <= x) {
                swap(a[++i], a[j]);
            }
        }
        swap(a[i + 1], a[r]);
        return i + 1;
    }
    int findKthLargest(vector<int>& nums, int k) {
        srand(time(0));
        return quickSelect(nums, 0, nums.size() - 1, nums.size() - k);
    }
};
解法三:利用大根堆
class Solution {
public:
    void maxHeapify(vector<int>& a, int i, int heapSize) {
        int l = i * 2 + 1, r = i * 2 + 2, largest = i;
        if (l < heapSize && a[l] > a[largest]) {
            largest = l;
        } 
        if (r < heapSize && a[r] > a[largest]) {
            largest = r;
        }
        if (largest != i) {
            swap(a[i], a[largest]);
            maxHeapify(a, largest, heapSize);
        }
    }

​    void buildMaxHeap(vector<int>& a, int heapSize) {	//建堆操作
​        for (int i = heapSize / 2; i >= 0; --i) {
​            maxHeapify(a, i, heapSize);
​        } 
​    }

​    int findKthLargest(vector<int>& nums, int k) {
​        int heapSize = nums.size();
​        buildMaxHeap(nums, heapSize);
​        for (int i = nums.size() - 1; i >= nums.size() - k + 1; --i) {
​            swap(nums[0], nums[i]);
​            --heapSize;
​            maxHeapify(nums, 0, heapSize);
​        }
​        return nums[0];
​    }
};

剑指 Offer 42. 连续子数组的最大和(dp)

求连续子数组的最大和,要最大,即加上后的值变大,所以我们用一个数组dp[]来记录该连续和,如果加上num[i]变大代表需要这个数,变小代表不需要这个数就不加,流程如下:

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        vector<int>dp;
        dp.resize(nums.size());
        dp[0] = nums[0];
        int res = nums[0];
        for(int i = 1;i<nums.size();i++){
            //如果当前前缀和加上该结点值变大就加上,否则不加
            dp[i] = dp[i-1]+nums[i]>nums[i] ? dp[i-1]+nums[i] : nums[i];
            res = res>dp[i] ? res : dp[i];//更新最大值
        }
        return res;
    }
};

上面的代码的空间复杂度为O(n),还可以进一步优化

当数组很大时,使用滚动数组可以节约相当空间
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int currentMax = nums[0];
        int res = nums[0];
        for(int i = 1;i<nums.size();i++){
            int preMax = currentMax;//将当前的最大值赋给preMax
            //当前的最大值为
            currentMax = currentMax+nums[i]>nums[i] ? currentMax+nums[i] : nums[i];
            //判断是否更新结果最大值
            res = res>currentMax ? res : currentMax;
        }
        return res;
    }
};

剑指 Offer 50. 第一个只出现一次的字符

解法一:用一个map存对应关系
class Solution {
public:
    vector<map<char,int>>v;
    char firstUniqChar(string s) {
        if(s.length()==0)	return ' ';
        
        map<char,int>m;
        for(char ch:s){
            if(m.find(ch)==m.end()){
                //还未加入,则加入
                m.insert(make_pair(ch,1));
                // v.push_back(m);
            }else{//在map中已经存在,则记数值++
                m[ch]++;                
            }
        }
        char res = ' ';
        for( char c:s ){
            if(m[c]==1){
                res = c;
                break;
            }
        }
        return res;
    }
};
高级写法
class Solution {
public:
    char firstUniqChar(string s) {
        unordered_map<char, bool> dic;
        for(char c : s)//取出每一个字符
            //如果c字符未找到,将true赋值给dic[c],如果找到了代表出现不止一次,将false赋值给dic[c]
            dic[c] = dic.find(c) == dic.end();
        for(char c : s)
            if(dic[c]) return c;//找到第一个为true的c返回
        return ' ';
    }
};
方法二:有序哈希表

class Solution {
public:
    char firstUniqChar(string s) {
        vector<char> keys;
        unordered_map<char, bool> dic;
        for(char c : s) {
            if(dic.find(c) == dic.end())
                keys.push_back(c);
            dic[c] = dic.find(c) == dic.end();
        }
        for(char c : keys) {
            if(dic[c]) return c;
        }
        return ' ';
    }
};

剑指 Offer 52. 两个链表的第一个公共节点

大佬的写法

思路是这样的:因为两个要找到两个链表的第一个公共结点(一定存在),但是两个链表在公共结点之前的结点数目又不一样,所有让两个链表分别遍历自己和对方,则一定会一起走到相同结点。

两个链表长度分别为L1+C、L2+C, C为公共部分的长度, 第一个人走了L1+C步后,回到第二个人起点走L2步;第2个人走了L2+C步后,回到第一个人起点走L1步。 当两个人走的步数都为L1+L2+C时就走到了公共结点

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        ListNode * node1 = headA;
        ListNode * node2 = headB;
        while(node1 != node2){
            //判断1的路走完没有,走完就走2的路,同理2
            node1 = node1!=NULL ? node1->next : headB;
            node2 = node2!=NULL ? node2->next : headA;
        }           
        return node1; 
    }
};

剑指 Offer 53 - I. 在排序数组中查找数字 I

这道题,面试中一般是想考察你的二分查找

class Solution {
public:
    int search(vector<int>& nums, int target) {
        int count = 0;
        for(int num :nums){
            if(num==target){
                count++;
            }
        }
        return count;
    }
};
二分写法:

二分的条件是该序列是有序排列的

这里的二分思想是这样的,1.去找traget的右边界,即第一个不为target的值

2.去找target-1的右边界,即第一个target出现的位置

3.然后将两个位置下标相减,得到最终结果

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //return 右边界-左边界	就可以得到个数
        return bin_search(nums,target) - bin_search(nums,target-1);
    }
    int bin_search(vector<int>&nums ,int target){
        int low =0,high = nums.size()-1;
        while(low <= high){
            int mid = (low + high)/2;
            if(nums[mid]<=target)
            {
                low = mid+1;
            }else{
                high = mid - 1;
            }
        }
        return low;
    }
};

剑指 Offer 53 - II. 0~n-1中缺失的数字

解法一:无脑解法
class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int res = 0;
        for(int num:nums){
            if(num!=res){
                break;
            }
            res ++;
        }
        return res;
    }
};
解法二:二叉查找

思路:如果下标数等于mid代表左边的都已经有序了(因为如果不等于代表一定有缺失的值)此时向右边搜索,否则代表左边是无序的向右边搜索

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int low =0,high = nums.size()-1;
        while(low<=high){
            int  mid = (low+high)/2;
            if(mid==nums[mid]){	//左边未出现缺失
                low = mid+1;
            }else{
                high = mid-1;
            }
        }
        return low;
    }
};

剑指 Offer 54. 二叉搜索树的第k大节点

如上图按照右根左的遍历序列可以直接得到第K大的数
class Solution {
public:
    int k,res;
    int kthLargest(TreeNode* root, int k) {
        this->k = k;
        dfs(root);
        return res;
    }
    void  dfs(TreeNode *root){
        if(root==nullptr)   return ; //边界条件
        dfs(root->right);	//向右遍历
        if(k==0)    return ;	
        if(--k==0)  res =  root->val;	
        dfs(root->left);	//向左遍历
    }
};
树的中序遍历(左根右)

中序遍历的结果为从小到大的排序,要找到第K大的数即为倒数第K个即v[count-k]

class Solution {
public:
    int count=0,res,k;
    vector<int>v;
    int kthLargest(TreeNode* root, int k) {
        this->k = k;
        dfs(root);
        return v[count-k];
    }
    void  dfs(TreeNode *root){
        // 左根右
        if(root==nullptr)   return;
        if(root->left)  dfs(root->left);
        v.push_back(root->val);
        count++;
        if(root->right) dfs(root->right);
    }
};

剑指 Offer 65. 不用加减乘除做加法

考察异或,位运算这类的问题要对计算机组成原理有一定的了解,其实在底层乘法器和除法器都是依靠加法器和移位来实现的,而加法器的实现靠的是本位和和进位(Cin)来计算的

class Solution {
public:
    int add(int a, int b) {
//因为不允许用+号,所以求出异或部分和进位部分依然不能用+ 号,所以只能循环到没有进位为止        
        while(b!=0)
        {
//保存进位值,下次循环用
            int c=(unsigned int)(a&b)<<1;//C++中负数不支持左移位,因为结果是不定的
//保存不进位值,下次循环用,
            a^=b;
//如果还有进位,再循环,如果没有,则直接输出没有进位部分即可。
            b=c;   
        }
        return a;
    }
};

剑指 Offer II 024. 反转链表

链表的原地逆置
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == nullptr) return head;
        // 链表的原地逆置
        ListNode *p,*q = head;
        p = head ->next;
        head->next = NULL;
        while(p){
            q = p->next;
            p->next = head;
            head = p;
            p = q;
        }
        return head;

    }
};

面试题 01.02. 判定是否互为字符重排

就是两个串,判断B串是否为A串的重新排列,那只用判断B串中是否仅含A串的字母,和上面的剑指offer50类似用一个map解决

class Solution {
public:
    bool CheckPermutation(string s1, string s2) {
        //仅需要判断是不是A中的字符B中都有就可以了
        map<char,int>m;
        bool res = true;
        for(char each:s1){
            if(m.find(each)==m.end()){
                m.insert(make_pair(each,1));
                // m[each]++;
            }else{
                m[each]++;
            }
        }
        for(char each:s2){
            if(m.find(each)==m.end()||m[each]==0){
                return false;
            }else{
                m[each]--;
            }
            
        }
        return res;
    }
};

剑指 Offer 55 - I. 二叉树的深度

解法一:
class Solution {
public:
    int maxDepth(TreeNode* root) {
        if(root==NULL)   return 0;
        int left = maxDepth(root->left);	//左递归
        int right = maxDepth(root->right);	//右递归

        return left>right ? left+1 :right+1;	//返回条件,每次向上返回时,left和right各自加一
    }
};
解法二:

注意,当用层次遍历需要记录层次数时,使用for(int i = q.size();i>0;i--)

class Solution {
    int count = 0;
public:
    int maxDepth(TreeNode* root) {
        if(root == NULL)    return 0;
        queue<TreeNode*> q;     //创建队列q
        q.push(root);           //将root推入队列q
        while(!q.empty()){
            for(int i = q.size(); i; i--){
                auto node = q.front();
                q.pop();
                if(node->left != NULL)  q.push(node->left);
                if(node->right != NULL) q.push(node->right);
            }
            count++;
        }
        return count;
    }
};

剑指 Offer 55 - II. 平衡二叉树

判断一颗树是不是平衡二叉树,可以用上面的解法一的思路,对每一个结点的左右都判断深度差,当所有结点左右子树深度差小于2时代表该树是一颗平衡二叉树

解法一:
class Solution {
public:
    bool isBalanced(TreeNode* root) {
        return dfs(root)>0 ? true :false;
    }
    int dfs(TreeNode *root){
        if(root==NULL)  return true;

        int left  = dfs(root->left);//向左遍历
        if(left==-1)    return -1;
        int right = dfs(root->right);//向右遍历
        if(right==-1)   return -1;

        return abs(left-right)<2 ? max(left,right)+1 :-1;//判断左右之差是否小于2
    }
};
解法二:
/* 先序遍历+判断深度 (从左到右,自顶向下。因为对每个节点 都进行了dfs,所以会产生大量重复计算,时间复杂度较高)*/
class Solution {
private:
    int dfs(TreeNode* root);

public:
    bool isBalanced(TreeNode* root) {     
        if (nullptr == root) return true;   

        int left_deep = dfs(root->left);        // 以该root为根节点的树 的左子树深度
        int right_deep = dfs(root->right);      // 以该root为根节点的树 的右子树深度
        if (std::abs(left_deep - right_deep) > 1) return false;      // 0<=左右子树的深度差<=1 才满足条件,否则返回false

        return isBalanced(root->left) && isBalanced(root->right);    // 继续遍历该树的其它节点,并检查 以每个节点为root的子树 是否为平衡二叉树
    }
};

int Solution::dfs(TreeNode* root) {       // 以该root为根节点的树 的深度      
    if (nullptr == root) return 0;
    return std::max(dfs(root->left), dfs(root->right)) + 1;
}

剑指 Offer 56 - I. 数组中数字出现的次数

仅两个数字仅出现一次,其余都出现两次,思路如下注释

class Solution {
public:
    vector<int> singleNumbers(vector<int>& nums) {
        int x = 0, y = 0, n = 0, m = 1;
        // 以{4,1,4,6,3,3}为例
        for(int num : nums)         // 1. 遍历异或
            n ^= num;               //最终为1^6 = 0111= 7

        while((n & m) == 0)         // 2. 循环左移,计算 m
            m <<= 1;                //获取首位1 m=0001
            // 再用这个首位1为分界线将其分为两组
            // 为何?1 --> 0001
            //       6 --> 0110
            // 即找到他们有差别的那一位数字
            // 那么我们再在nums中取数,1&m=0001一定不为0   6&m=0000一定为0
            // 其他数字都出现两次,一定抵消,则结果一定为1和6

        for(int num : nums) {       // 3. 遍历 nums 分组
            if(num & m) x ^= num;   // 4. 当 num & m != 0
            else y ^= num;          // 4. 当 num & m == 0
        }
        return vector<int> {x, y};  // 5. 返回出现一次的数字
    }
};

剑指 Offer 56 - II. 数组中数字出现的次数 II

没有复杂度要求,可以考虑计数这样是双O(n)

解法一:

利用map映射,该数字出现就计数

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        if(nums.size()==0)  return 0;
        // 法一:map映射
        map<int,int>m;
        // 1.是数字 2.是count数量
        for(int num:nums){
            if(m.find(num)!=m.end()) m[num]++;
            else{
                m.insert(make_pair(num,1));
            }
        }
        int res;
        for(pair<int,int>a:m){
            if(a.second!=3){
                res = a.first;
                break;
            }
        }
        return res;
    }
};
解法二:将每位上的二进制位数都统计一遍,然后再对3取余,则剩下的为所求的二进制位数

原理就是其余数字都出现3次,则转换为2进制后每一位都会出现3次,取余后相当于消去了出现3次的数,则剩下的为所求

// 位运算 + 遍历统计
class Solution {
public:
    int singleNumber(vector<int>& nums) {
        vector<int> vec(32);     // 记录所有数字的各二进制位的 1的出现次数

        for (int i = 0; i < nums.size(); ++i) {
            unsigned int m = 1;
            for (int j = 0; j < 32; ++j) {
                if ((m & nums[i]) != 0) ++vec[j];    // 如果第j位上为1,即(m & nums[i])!= 0,则对应vec[j]+1
                m <<= 1;
            }
        }
        unsigned int res = 0;
        for (int i = 31; i >= 0; --i) {     // 将vec各元素对3求余,结果为"只出现一次的数字"的各二进制位上的数。
            res <<= 1;
            res |= vec[i] % 3;    // 恢复第i位的值到res(从高位到底位)  
        }

        return res;
    }
};

剑指 Offer 57. 和为s的两个数字

题目中给的递增(有序)直接可以想到二分

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        vector<int>res;
        if(nums.size()==0)  return res;
        
        int left = 0, right = nums.size()-1;
        while(left<right){
            if(nums[left]+nums[right]==target){//当找到和为S的数时
                res.resize(2);
                res[0] = nums[left];
                res[1] = nums[right];
                break;
            }
             if(nums[left]+nums[right]>target)   right--;//大于S时范围大了向左收缩
            else if(nums[left+nums[right]<target])  left++;
        }
        return res;
    }
};

剑指 Offer 57 - II. 和为s的连续正数序列

要输出所有和为S的,思路就是利用一个左边界和右边界,当小于目标值时左边界向右滑动,当大于目标值时,右边界向左滑动

滑动窗口
class Solution {
public:
    vector<vector<int>> findContinuousSequence(int target) {
    int i = 1; // 滑动窗口的左边界
    int j = 1; // 滑动窗口的右边界
    int sum = 0; // 滑动窗口中数字的和
    vector<vector<int>> res;

    while (i <= target / 2) {//左边界一定小于等于target的一半,不然一定大于target
        if (sum < target) {
            // 右边界向右移动
            sum += j;
            j++;
        } else if (sum > target) {
            // 左边界向右移动
            sum -= i;
            i++;
        } else {
            // 记录结果
            vector<int> arr;
            for (int k = i; k < j; k++) {
                arr.push_back(k);
            }
            res.push_back(arr);
            // 左边界向右移动
            sum -= i;
            i++;
        }
    }
    return res;
}
};
posted @ 2022-02-20 14:27  TrueDZ  阅读(56)  评论(0编辑  收藏  举报