剑指offer刷题笔记

文章目录

第一章 栈与队列

简单

20. 用两个栈实现队列

在这里插入图片描述

class MyQueue {
public:
    /** Initialize your data structure here. */
    stack<int> stk, cache;
    MyQueue() {

    }

    /** Push element x to the back of queue. */
    void push(int x) {
        stk.push(x);
    }

    void copy(stack<int> &a, stack<int> &b) {
        while (a.size()) {
            b.push(a.top());
            a.pop();
        }
    }

    /** Removes the element from in front of queue and returns that element. */
    int pop() {
        copy(stk, cache);
        int res = cache.top();
        cache.pop();
        copy(cache, stk);
        return res;
    }

    /** Get the front element. */
    int peek() {
        copy(stk, cache);
        int res = cache.top();
        copy(cache, stk);
        return res;
    }

    /** Returns whether the queue is empty. */
    bool empty() {
        return stk.empty();
    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue obj = MyQueue();
 * obj.push(x);
 * int param_2 = obj.pop();
 * int param_3 = obj.peek();
 * bool param_4 = obj.empty();
 */

单调栈

单调栈:具有单调性的栈

830. 单调栈*

#include<iostream>
using namespace std;
#include<stack>

int main(){
    int n;
    stack<int> sta;
    cin>>n;
    int num;
    
    while(n--){
        cin>>num;
        while(sta.size() != 0){
            if(sta.top() < num){
                cout<<sta.top()<<" ";
                break;
            }
            else sta.pop();
        }
        
        if(sta.size()==0){
            cout<<-1<<" ";
        }
        //这里的单调栈是单调上升的
        //所以输入的每一个数都要在栈中找到栈顶元素比自己小的
        //然后把自己放到栈中,这个栈依然单调上升
        sta.push(num);
        
    }
    return 0;
}

41. 包含min函数的栈

在这里插入图片描述

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> stackValue;
    stack<int> stackMin;
    MinStack() {

    }

    void push(int x) {
        stackValue.push(x);
        // 这里有一个等于,最小数的可以相同不止一个
        if (stackMin.empty() || stackMin.top() >= x)
            stackMin.push(x);
    }

    void pop() {
        if (stackMin.top() == stackValue.top()) stackMin.pop();
        stackValue.pop();
    }

    int top() {
        return stackValue.top();
    }

    int getMin() {
        return stackMin.top();
    }
};

/**
 * Your MinStack object will be instantiated and called as such:
 * MinStack obj = new MinStack();
 * obj.push(x);
 * obj.pop();
 * int param_3 = obj.top();
 * int param_4 = obj.getMin();
 */

第二章 双指针

简单

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

在这里插入图片描述

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode * h = new ListNode(-1 , head);
        ListNode * befPtr = h;
        ListNode * ptr = h->next;

        while(ptr){
            if(ptr->val == val){
                befPtr->next = ptr->next;
            }
            befPtr = ptr;
            ptr = ptr->next;
        }

        return h->next;
    }
};

解法2:

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode p(-1);
        p.next = head;

        ListNode* ptr = &p;
        while(ptr && ptr->next){
            if(ptr->next->val == val)
                ptr->next = ptr->next->next;

            ptr = ptr->next;
        }

        return p.next;
    }
};

[19. 删除链表的倒数第 N 个结点]

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。(在没有提到循环链表的时候,默认都是单链表)

  • 删除节点一般要找删除节点的前一个节点
  • 删除节点的前一个节点和NULL之间有n个节点,我们就可以利用这个性质
  • 定义两个指针,先让快指针走n步,使两个指针之间有n个节点,然后保持这个性质,让两个指针每次走一步,直到快指针指向NULL,此时慢指针指向删除节点的前一个节点
  • 此时进行删除操作

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TBI7g5PR-1670056769416)(D:\冲击offer\博客\数据结构\assets\72efa83b60a5c878240f9bb08e11bc1.jpg)]

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode ret(0,head);
        //这里不是指向同一个起点,这里只是保证了两个指针之间的节点数为0
        ListNode * fast = head , * slow = & ret;
        //使快指针先走n步,
        while(n--)fast = fast->next;

        while(fast)
        {
            fast = fast->next;
            slow = slow->next;
        };
        
        //此时slow指向删除节点的前一个节点
        slow->next = slow->next->next;
        return ret.next;
    }
};

47. 二叉树中和为某一值的路径

在这里插入图片描述

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> temp;
    
    vector<vector<int>> findPath(TreeNode* root, int sum) {
        int total = 0;
        dfs(root,sum,total);
        return ans;
    }
    
    void dfs(TreeNode* root, int sum , int total){
        if(root == NULL) return;
        total += root->val;
        temp.push_back(root->val);
        //必须是叶子节点,而且total == sum
        if(!root->left && !root->right && total == sum) ans.push_back(temp);
        
        if(root->left) dfs(root->left , sum , total);
        if(root->right) dfs(root->right , sum , total);
        
        temp.pop_back();
    }
};

如何快速找到奇数节点链表的中间值

  • 设置两个指针都指向单链表头节点
  • 第一个指针的速度是第二个指针的两倍
  • 当第一个指针指向末尾的时候,第二个就指向了链表的中间值

简单

66. 两个链表的第一个公共结点

在这里插入图片描述

  • 使用的方法十分的巧妙,看不明白就看Y总的讲解
class Solution {
public:
    ListNode *findFirstCommonNode(ListNode *headA, ListNode *headB) {
        if(!headA || !headB) return NULL;
        auto pa = headA;
        auto pb = headB;
        
        // 没有else的时候,有时候一个指针在一个循环中执行两步
        while(pa != pb){
            if(pa) pa = pa->next;
            else pa = headB;
            if(pb) pb = pb->next;
            else pb = headA;
        }
        
        return pa; //这里返回 pa pb 都可以
    }
};

简单

32. 调整数组顺序使奇数位于偶数前面

在这里插入图片描述

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        if(nums.empty()) return nums;

        int  l = -1 , r = nums.size();

        while(r > l){
            do l++; while(r > l && nums[l] %2 == 1);
            do r--; while(r > l && nums[r] %2 == 0);
            if(r>l) swap(nums[l] , nums[r]);
        }

        return nums;
    }
};

剑指 Offer 58 - I. 翻转单词顺序

![在这里插入图片描述](https://img-blog.csdnimg.cn/e1f4de4cb21b43369bbf873574afe104.png

class Solution {
public:
    string reverseWords(string s) {
        int n = s.size();
        if(n == 0) return s;

        string ans = "";
        for(int i = n - 1 ; i >= 0 ; i--){
            if(s[i] != ' '){
                int end = i;
                //首先不可以越界,再判断不能等于空格
                while(i >= 0 && s[i] != ' ') i--;
                ans += s.substr(i + 1 , end - i) + ' ';
            }
        }

        //最后一位不能有空格
        return ans.substr(0 , ans.size() - 1);
    }
};

70. 二叉搜索树的第k个结点

在这里插入图片描述

中序遍历输出二叉搜索树:1 2 3 -------单调递增

先输出的元素就是先回溯的元素,即也是先执行语句的元素

class Solution {
public:
    vector<TreeNode*> ans;
    TreeNode* kthNode(TreeNode* root, int k) {
        dfs(root,k);
        return ans[k - 1];
    }
    
    void dfs(TreeNode* root, int k){
        if(root == NULL) return;
        
        if(root->left) dfs(root->left, k);
        ans.push_back(root);
        if(root->right) dfs(root->right, k);
    }
};

第三章 链表

简单

17. 从尾到头打印链表

在这里插入图片描述

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    vector<int> printListReversingly(ListNode* head) {
        vector<int> res;
        while (head) {
            res.push_back(head->val);
            head = head->next;
        }
        return vector<int>(res.rbegin(), res.rend());
    }
};

反转链表

[206. 反转链表]

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head == NULL || head->next == NULL)return head;

        //使用头插法实现链表翻转
        //把旧链表的每一个头结点,从前往后头插到新链表中
        auto h = new ListNode(-1 , NULL);
        ListNode * curh = h , * cur = head , * curNext = NULL;

        while(cur){
            curNext = cur->next;
            cur->next = curh->next;
            curh->next = cur;

            cur = curNext;
        } 

        return h->next;
    }
};

第二种做法:

思路:

  1. 先把每个节点的下一个节点保存一下,
  2. 递归到最深层,返回最后一个节点作为头结点,
  3. 然后局部翻转,之前 head -> tail 翻转后tail -> head->NULL
  4. 函数可以实现翻转链表,并且返回头结点.

代码实现:

/**
 * 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* reverseList(ListNode* head) {
       if(head == nullptr||head->next==nullptr)return head;

       //记录当前头结点后面一个节点,方便稍后翻转两个节点
       ListNode * tail = head->next;

       //递归函数放在执行任务之前,意味着先操作后面的链表
       //因为是翻转,所以最后一个节点翻转为头结点即p指向头结点(在最深一层完成头结点指向)
       ListNode * p = reverseList(head->next);

       //局部翻转两个节点 执行任务
       head->next = tail->next;//这里也可以改为NULL,因为链表的开头最后变为结尾,结尾最后指向NULL
       tail->next = head;
       return p;
    }
};

[92. 反转链表 II]

  • 给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。

实现代码:

/**
 * 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:
    //此函数实现,反转head前numCode个节点
    ListNode* reverseN(ListNode* head ,int numCode){
        if(numCode==1)return head;//当head是最后一个要反转的节点的时候,直接返回执行反转任务

        ListNode * tail = head->next;
        ListNode * p = reverseN(head->next , numCode - 1);

        head->next = tail->next;
        tail->next = head;

        return p;
    }

    ListNode* reverseBetween(ListNode* head, int left, int right) {
        ListNode ret(0,head);//创建虚拟头节点,指向传入链表的第一个节点

        ListNode * p = &ret;
        int numCode = right - left + 1;

        while(--left)p = p->next;//使指针指向翻转链表的前一个节点
        p->next = reverseN(p->next , numCode);


        return ret.next;//此时不可以返回head,因为第一个节点也有可能被反转 

    }
};

48. 复杂链表的复刻

在这里插入图片描述

class Solution {
public:
    ListNode *copyRandomList(ListNode *head) {
        auto cur = head;
        
        while(cur){
            auto temp = new ListNode(cur->val);
            temp->next = cur->next;
            cur->next = temp;
            
            cur = cur->next->next;
        }


        cur = head;
        while(cur){
            if(cur->random){
                cur->next->random = cur->random->next;
            }
            cur = cur->next->next;
        }


        auto h = new ListNode(-1);
        auto hcur = h;
        cur = head;
        while(cur){
            hcur->next = cur->next;
            hcur = hcur->next;
            cur->next=cur->next->next;
            cur=cur->next;//不可以修改初始链表
        }
        return h->next;
    }
};

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

在这里插入图片描述

class Solution {
public:

    int kthLargest(TreeNode* root, int k) 
    {
        int res = 0;
        dfs(root, k, res);
        return res;
    }

    void dfs(TreeNode* root, int& k , int& res)
    {
    //遍历顺序为:右中左
        if (root == nullptr)return;
//这里写成dfs(root->right , k - 1 , res)不行
//这里我们暂且理解为,这是执行语句,执行语句根据进栈顺序依次执行
        dfs(root->right, k, res);
        k--;
        if (k == 0) res = root->val;
        dfs(root->left,  k, res);
    }
};

删除链表重复元素

[83. 删除排序链表中的重复元素]

  • 给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
  • 解题思路比较简单,直接上代码
class Solution {
public:
    ListNode* deleteDuplicates(ListNode* head) {
        // 在链表中删除重复元素

        if(!head || !head->next) return head;

        ListNode *ptr = head;

        while(ptr && ptr->next){
            while(ptr->next && ptr->val == ptr->next->val) ptr->next = ptr->next->next;

            ptr = ptr->next;
        }

        return head;
    }
};

[82. 删除排序链表中的重复元素 II]

  • 给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表 。

  • 这个题目有点绕,但是还不好画图,必须结合实际的节点来看

  • 可以假设 1 2 2 2 3 3 3 4 4 6

  • 仔细看看注释

/**
 * 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* deleteDuplicates(ListNode* head) {
        if(head==NULL)return NULL;
        ListNode ret(0,head);

        //str:始终指向值不重复的节点,用于找出值相同的节点
        //temp:用于跳过值相同的节点,找到与之前值不相同的节点
        ListNode * str = &ret , * temp = NULL;

        //经过下面变化 str->next 有可能为NULL
        //当为空的时候,str就指向最后一个节点,也不需要往后面遍历了
        while(str->next)
        {
            //str指向的是前一个没有重复val的节点
            //至少后面有两个节点才有可能发生重复,所以需要判断str->next->next
            if(str->next->next && str->next->val == str->next->next->val)
            {
                //可以假设 1 2 2 2 3 3 3 4 4 6
                temp = str->next->next;
                while(temp!=NULL && str->next->val==temp->val)temp = temp->next;

                //这里不直接用str接收是为了,防止temp中保存的值和后面的重复,例如上面第一个3节点
                str->next = temp;
            }
            else
            {
                str = str->next;
            }
        };
        return ret.next;
    }
};

第四章 字符串

简单

16. 替换空格

在这里插入图片描述

class Solution {
public:
    string replaceSpaces(string &str) {
        string res;
        for (auto x : str)
            if (x == ' ')
                res += "%20";
            else
                res += x;
        return res;
    }
};

78. 左旋转字符串

在这里插入图片描述

class Solution {
public:
    string leftRotateString(string str, int n) {
        if(str.size() == 0 || str.size() == 1) return str;
        
        string ans = "";
        int len = str.size();
        
        for(int i = n; i < str.size(); i++) ans += str[i];
        for(int i = 0; i < n; i++) ans += str[i];
        return ans;
    }
};

71. 二叉树的深度

在这里插入图片描述

class Solution {
public:
    int treeDepth(TreeNode* root) {
        if(!root) return 0;
        //后序遍历
        int left = treeDepth(root->left);
        int right = treeDepth(root->right);
        
        int height = max(left , right) + 1;
        
        return height;
    }
};

第五章 查找算法

查找算法(简单)

13. 找出数组中重复的数字

在这里插入图片描述

class Solution {
public:
    int duplicateInArray(vector<int>& nums) {
        unordered_map<int,int> mm;
        int ans = -1;
        for(auto num:nums){
            if(num < 0 || num > nums.size()-1)return -1;
            
            mm[num]++;
            if(mm[num] == 2)ans = num;
        }
        return ans;
    }
};

67. 数字在排序数组中出现的次数

在这里插入图片描述

class Solution {
public:
    int getNumberOfK(vector<int>& nums , int k) {
        if(nums.size() == 0) return 0;
        
        int l = 0 , r = nums.size() - 1;
        while(r > l){
            int mid = (l + r)/2 ;
            if(nums[mid] >= k)r=mid;
            else l = mid + 1;
        }
        
        if(nums[l] != k)return 0;
        
        int ll=0,rr=nums.size()-1;
        while(rr > ll){
            int mid = (ll + rr + 1)/2;
            if(nums[mid] <= k)ll = mid;
            else rr = mid - 1;
        }
        
        return ll - l + 1;
    }
};

68. 0到n-1中缺失的数字

在这里插入图片描述

class Solution {
public:
    //  0 1 2 3 4 5
    //  0 1 3 4 5 6

    int getMissingNumber(vector<int>& nums) {
        if(nums.empty()) return 0;
        
        // 如果缺少最后一个数字就直接返回
        if(nums[nums.size() - 1] == nums.size() - 1) return nums.size();
        
        int l = 0 , r = nums.size() - 1;
        while(r > l){
            int mid = (r + l) / 2;
            if(nums[mid] != mid) r = mid;
            else l = mid + 1;
        }
        

        return r;
    }
};

查找算法(中等)

15. 二维数组中的查找

在这里插入图片描述

class Solution {
public:
    bool searchArray(vector<vector<int>> array, int target) {
        if (array.empty() || array[0].empty()) return false;
        int i = 0, j = array[0].size() - 1;
        while (i < array.size() && j >= 0) {
            if (array[i][j] == target) return true;
            if (array[i][j] > target) j -- ;
            else i ++ ;
        }
        return false;
    }
};

72. 平衡二叉树

在这里插入图片描述

class Solution {
public:
    bool ans = true;

    bool isBalanced(TreeNode* root) {
        dfs(root);
        return ans;
    }
    
    int dfs(TreeNode * root){
        if(!root) return 0;
        int left = dfs(root->left);
        
        int right = dfs(root->right);
        
        if(abs(left - right) > 1) ans = false;
        int height = max(left , right) + 1;
        
        return height;
    }
};

22. 旋转数组的最小数字*

在这里插入图片描述

  • 分析:去重后,前面的数字一定小于后面的数字,若遇到前面的数字大于后面的数字,则这个较小的数字就是答案
  • 在没有重复数字的情况下
    在这里插入图片描述
    在这里插入图片描述
  • 去重

因为mid与right指向的值相同,不能确定分界点
在这里插入图片描述
先把right的指向向前移动一位,然后更新mid的值
这时候发现right指向的值大于mid指向的值又可以移动了
在这里插入图片描述

class Solution {
public:
    int findMin(vector<int>& nums) {
        int l = 0  , r = nums.size() - 1 ;
        if(r == -1) return -1;
        
        while(r > l){
            int mid = (l + r)/2;
            if(nums[mid] < nums[r]) r = mid ;
            else if(nums[mid] > nums[r]) l = mid + 1;
            else if(nums[mid] == nums[r]) r--;
        }
        return nums[l];
    }
};

63. 字符串中第一个只出现一次的字符

在这里插入图片描述

class Solution {
public:
    char firstNotRepeatingChar(string s) {
        char ch = '#';
        
        unordered_map<char,int> mm;
        for(char chh : s) mm[chh]++;
        
        for(int i = 0 ; i < s.size() ; i++){
            if(mm[s[i]] == 1) return s[i];
        }
        
        return ch;
    }
};

第七章 分治算法

18. 重建二叉树*

在这里插入图片描述

class Solution {
public:

    unordered_map<int,int> pos;

    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        for (int i = 0; i < n; i ++ )
            pos[inorder[i]] = i;//把元素全部加入哈希表中,方便查找根节点
            
            
        return dfs(preorder, inorder, 0, n - 1, 0, n - 1);
    }

    TreeNode* dfs(vector<int>&pre, vector<int>&in, int pl, int pr, int il, int ir)
    {
        if (pl > pr) return NULL;
        TreeNode* root = new TreeNode(pre[pl]);
        
        
        int k = pos[pre[pl]] - il;//左子树元素个数 = 中序根节点下标 - 中序首节点下标
        
        //这两个左右子树,要对照着写,先都写完前序下标范围,再.....
        root->left = dfs(pre, in, pl + 1, pl + k, il, pos[pre[pl]] - 1);
        root->right = dfs(pre, in, pl + k + 1, pr, pos[pre[pl]] + 1, ir);
        return root;
    }
};

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

在这里插入图片描述

class Solution {
public:
    vector<int> printNumbers(int n) {
        int sum = 1;
        while(n--) sum *= 10;

        vector<int> ans;
        for(int i = 1; i < sum; i++) ans.push_back(i);

        return ans; 
    }
};

27. 数值的整数次方*

这个题主要采用了快速幂

  • 例如:计算n^k

如果是按照朴素做法的话,则需要循环k次时间复杂度为 O ( k ) ,则这个复杂度就比较高了,而如果是 采用快速幂时间复杂度是 O(logn)。
​快速幂的核心思路是:反复平方法(思想上有点类似逆向二分。二分是每次在当前基础上减一半,快速幂是每次在当前基础上扩大一倍)。

例如
在这里插入图片描述
快速幂讲解视频

在这里插入图片描述

class Solution {
public:
    double Power(double base, int exponent) {
        if(base == 0) return 0;
        
        double ans = 1 , temp = base;
        
        //-2的31次方 <= n <= 2的31次方-1
        //所以int的最小数无法转化为正数
        long y = exponent;
        
        if(exponent < 0) y = - y  , temp = 1 / temp;

        
        while(y){
            
             //若二进制最后一位为1
             // 010111101 & 1 = 1;
             if(y & 1 == 1) ans *= temp;
             
             //为下一次所准备元素
             temp = temp * temp;
             y = y >> 1; // 位运算
        }
        
        return ans;
    }
};

第八章 搜索与回溯算法

简单

84. 求1+2+…+n

在这里插入图片描述

class Solution {
public:
    int getSum(int n) {
        int ans = n;
        
        //当n = -1的时候,就return回溯
        (n > 0) && (ans += getSum(n-1));
        return ans;
    }
};

23. 矩阵中的路径

在这里插入图片描述

如果不在遍历节点前加上if (!ans) 那么就会出现运行超时

class Solution {
public:
    bool ans = false;

    bool hasPath(vector<vector<char>>& matrix, string &str) {
        for(int i = 0 ; i < matrix.size() ; i++)
            for(int j = 0 ; j < matrix[0].size() ; j++)
                if(!ans) (dfs(matrix , str , 0 , i , j));
        return ans;
    }
    
    int dir[5] = {1,0,-1,0,1};
    void dfs(vector<vector<char>>& matrix, string &str , int u , int x , int y){
        if(matrix[x][y] != str[u]) return;
        
        if(u == str.size() - 1) {
            ans = true;
            return;
        }
        
        //层层深度的时候,标记这个点已经被访问
        char ch = matrix[x][y];
        matrix[x][y] = '*';
        
        for(int i  = 0 ; i < 4 ; i++){
            int a = x + dir[i] , b = y + dir[i+1];
            if(a >= 0 && a < matrix.size() && b >= 0 && b < matrix[0].size())
                if(!ans) (dfs(matrix , str , u + 1 , a , b));
        }
        //当回溯的时候,把标记点去掉
        matrix[x][y] = ch;
    }
};

24. 机器人的运动范围

在这里插入图片描述

class Solution {
public:
    int ans = 0 , dir[5] = {1,0,-1,0,1};

    int movingCount(int threshold, int rows, int cols)
    {
        if(rows == 0 || cols == 0) return 0;
        
        bfs(threshold , rows , cols);
        return ans;
    }
    
    void bfs(int k, int rows, int cols){
        vector<vector<bool>> v(rows , vector<bool>(cols , false));//用于标记已经访问
        
        queue<pair<int , int>> qu; //用于广度优先遍历
        qu.push({0,0});
        
        while(qu.size()){
            auto t = qu.front();
            qu.pop();
            if(v[t.first][t.second] == true || k < getNum(t)) continue;
            
            ans++;
            v[t.first][t.second] = true;
            
            for(int i = 0 ; i < 4 ; i++){
                int x = t.first + dir[i] , y = t.second + dir[i+1];
                if(x >= 0 && x < rows && y >= 0 && y < cols) qu.push({x , y});
            }
        }
    }
    
    int getNum(pair<int , int> p){
        int num = 0;
        while(p.first){
            num += p.first % 10;
            p.first /= 10;
        }
        while(p.second){
            num += p.second % 10;
            p.second /= 10;
        }
        return num;
    }
};

第九章 动态规划

简单

21. 斐波那契数列

在这里插入图片描述

class Solution {
public:
    int Fibonacci(int n) {
        if (n == 0) return 0;
        //n 是所要求的相数
        vector<int> v(n + 1 , 0);
        
        v[1] = 1 , v[2] = 1;
        
        for(int i = 3 ; i <= n ; i++){
            v[i] = v[i - 1] + v[i - 2];
        }
        
        return v[n];
    }
};

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

在这里插入图片描述

class Solution {
public:
	int numWays(int n) {
		int MOD = 1000000007;
		if (n <= 1) return 1;
		vector<int>dp(n + 1);
		dp[1] = 1;
		dp[2] = 2;
		for (int i = 3; i <= n; ++i)
		{
			dp[i] = (dp[i - 1] + dp[i - 2]) % MOD;
		}
		return dp[n];
	}
};

83. 股票的最大利润

在这里插入图片描述

class Solution {
public:
    int maxDiff(vector<int>& nums) {
        if(nums.size() == 0) return 0;
        
        int minNum = nums[0] , ans = 0;
        for(int i = 1 ; i < nums.size() ; i++){
            //找出下标比i小的最小值
            minNum = min(minNum , nums[i - 1]);
            //用下标为i的值减去,他之前的最小值,并与ans比较取较大的值
            ans = max(ans , nums[i] - minNum);
        }
        
        return ans;
    }
};

88. 树中两个结点的最低公共祖先

在这里插入图片描述

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if(!root) return NULL;
        if(root == p || root == q) return root;
        //若节点非空,且没有想要的元素那就接着递归
        
        auto left = lowestCommonAncestor(root->left , p , q);
        auto right = lowestCommonAncestor(root->right , p , q);
        
        //代表root是p和q的最低公共祖先
        if(left != NULL && right != NULL) return root;
        
        //代表left接收到公共祖先或者接收到与p或q相同的节点
        if(left != NULL && right == NULL) return left;
        if(left == NULL && right != NULL) return right;
        
        return NULL; //两个都为空的时候返回空
    }
};

中等

55. 连续子数组的最大和

在这里插入图片描述

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int s = 0;
        int res = -300;   
        for(auto x : nums)
        {
            if(s > 0) s = x + s; //如果前面的序列大于0则加上
            else s = s = x;      //如果前面的序列小于0就舍去    

            res = max(res, s);  // 取出子数组的和的最大值
        }

        return res;
    }
};

60. 礼物的最大价值

在这里插入图片描述

不会就根据代码画图

class Solution {
public:
    int getMaxValue(vector<vector<int>>& grid) {
        if(!grid.size()) return 0;
        int x = grid.size() , y = grid[0].size();
        vector<vector<int>> f(x + 1 , vector<int>(y + 1));
        
        for(int i =1 ; i <= x ; i++){
            for(int j = 1 ; j <= y ; j++){
            //相对f[i - 1][j],f[i][j]是向下移动
            //相对f[i][j - 1],f[i][j]是向右移动
                f[i][j] = max(f[i - 1][j] , f[i][j - 1]) + grid[i - 1][j - 1];
            }
        }
        
        return f[x][y];
    }
};

中等

59. 把数字翻译成字符串

在这里插入图片描述

class Solution {
public:
    int translateNum(int num) {

        string s = to_string(num);

        int n = s.size();
        if(n == 0 ) return 0;
        
        vector<int> f(n);
        f[0] = 1;
        //当至少有两个元素的时候,数组中第二个元素为1或者2
        if(n >= 2 ) {
            int t = s[1] - '0' + (s[0] - '0') * 10;
            //06  26之类的数字都不行  0是a所以z是25
            if(t >= 10 && t <= 25 ) f[1] = 2;
            else f[1] = 1;
        }
        
        //vector数组从下标为1开始计数
        for (int i = 2 ; i < n; i ++ ) {
            f[i] = f[i - 1];
            int t = s[i] - '0' + (s[i - 1] - '0') * 10;
            if (t >= 10 && t <= 25) f[i] += f[i - 2];
        }
        return f[n - 1];
    }
};

62. 丑数

在这里插入图片描述

class Solution {
public:
    int getUglyNumber(int n) {
        vector<int> ans(n , 0);
        
        //注意这里不可以使用push_back,使用后会把1加入到0的后面
        ans[0] = 1; //加入第一个丑数
        int a = 0 , b = 0 , c = 0;
        
        //丑数的因子只可以是 2,3,5所以丑数一定是这三个相乘得到的
        //每次取最小值,是防止漏掉较小的丑数
        
        for(int i = 1 ; i < n ; i++){
            ans[i] = min(min(ans[a] * 2 , ans[b] * 3) , ans[c] * 5);
            if(ans[i] == ans[a] * 2) a++;
            if(ans[i] == ans[b] * 3) b++;
            if(ans[i] == ans[c] * 5) c++;
        }
        
        return ans[n - 1];
    }
};

第十章 排序

简单

58. 把数组排成最小的数

在这里插入图片描述

class Solution {
public:
    string printMinNumber(vector<int>& nums) {
        vector<string> v;
        string ans = "";
        for(auto & t : nums) v.push_back(to_string(t));
        
        //return a + b < b + a;本题灵魂  若不符合就交换两个数
        sort(v.begin() , v.end() , [](string & a , string & b){ return a + b < b + a;});
        
        for(auto & t : v) ans += t;
        
        return ans;
    }
};

81. 扑克牌的顺子

在这里插入图片描述

class Solution {
public:
    bool isContinuous( vector<int> numbers ) {
        if(numbers.size() != 5) return false;
        
        // 排序
        sort(numbers.begin() , numbers.end());
        
        // i 为0的个数
        int i = 0;
        while(numbers[i] == 0) i++;
        
        // 最大数 - 最小数(除0)
        int n = numbers.size() - 1;
        if(numbers[n] - numbers[i] > 4) return false;
        
        // 不能有相等的数
        for(int j = i ; j < numbers.size() - 1 ; j++){
            if (numbers[i] == numbers[i + 1]) return false;
        }
        
        return true;
    }
};

53. 最小的k个数

在这里插入图片描述

class Solution {
public:
    vector<int> getLeastNumbers_Solution(vector<int> input, int k) {
        priority_queue<int> temp;
        vector<int> ans;
        
        //优先队列,存放k个最小数.
        for(auto & t : input){
            temp.push(t);
            if(temp.size() > k) temp.pop();
        }
        
        while(temp.size()){
            ans.push_back(temp.top());
            temp.pop();
        }
        
        return vector<int>(ans.rbegin() , ans.rend());
    }
};

46. 二叉搜索树的后序遍历序列

在这里插入图片描述

class Solution {
public:
    bool verifySequenceOfBST(vector<int> sequence) {
        
       return dfs(sequence,0,sequence.size() - 1);
    }
    
    //[4, 8, 6, 12, 16, 14, 10] 二叉搜索树后序遍历
    //最后一个元素为根节点,特性根节点大于左子树所有元素,小于右子树所有元素
    //所有 使用根节点可以分出左子树(4,8,6),右子树(12,15,14)
    //当后序遍历不符合,用最后一个元素分出左右子树的时候,就不是二叉搜索树
    //例如 [4, 18, 6, 12, 16, 14, 10]

    
    bool dfs(vector<int> sequence , int l , int r){
        if(l >= r) return true;
        int head = sequence[r];// 取出根节点
        
        // l为左子树第一个元素对应的索引
        int k = l , kk = 0;
        
        while(k < r && sequence[k] < head) k++;
        //此时k指向右子树对应的第一个索引,判断右子树所有元素是否小于根节点,若是则符合特性
        
        kk = k;
        while(kk < r && sequence[kk] > head) kk++;
        if(kk != r) return false;
        
        //左右子树也必须是二叉搜索树
        return dfs(sequence , l , k - 1) && dfs(sequence , k , r - 1);
    }
};

第十一章 位运算

简单

[801. 二进制中1的个数 - AcWing题库]

#include<iostream>
using namespace std;

const int N = 1e5 + 10;
int n;

//获取二进制数中的最后一个1 , 例如 01100 得到 00100
int lowbit(int x) {
    return x & -x;
}

int main() {
    scanf("%d", &n);
    while(n--) {
        int x;
        scanf("%d", &x);
        int c = 0;
        while(x > 0) {
            x -= lowbit(x);
            c++;
        }
        printf("%d ", c);
    }
    return 0;
}

中等

73. 数组中只出现一次的两个数字

在这里插入图片描述

两个相同的数异或为0,那么
2 ^ 2 ^ 3 ^3 ^ 5 = 5;
2 ^ 2 ^ 3 ^3 ^ 5 ^ 6 = 5 ^ 6;

class Solution {
public:
    vector<int> findNumsAppearOnce(vector<int>& nums) {
        int sum = 0;
        
        //把所有数进行异或,得到答案两个数的异或的值
        for(auto & t : nums) sum ^= t;
        int k = 0;
        while((sum >> k & 1) != 1) k++; //若(sum >> k & 1)=1;则找到两个数二进制不同的点
        
        //依据这个不同点,我们把数组分成两个部分,则两个数据必不可能在一个部分
        int first = 0;
        for(auto & t : nums)
            if(t >> k & 1) first ^= t;
        int second = sum ^ first;//第二个数就用这种办法
        
        return vector<int>{first , second};
    }
};

第十二章 数学

简单

52. 数组中出现次数超过一半的数字

在这里插入图片描述

class Solution {
public:
    int moreThanHalfNum_Solution(vector<int>& nums) {
        int val = -1 , count = 0;
        for(auto & t : nums){
            if(count == 0) val = t;
                
            if(t == val) count ++;
            else count--;
        }
        return val;
    }
};

86. 构建乘积数组

在这里插入图片描述

题解 : 例如 ans[2] = A[0] x A[1] x A[3] x A[4]
第一步计算左边的乘积 A[0] x A[1]
第二步计算右边的乘积 A[3] x A[4]

class Solution {
public:
    vector<int> multiply(const vector<int>& A) {
        if(!A.size()) return vector<int>{};
        
        vector<int> ans(A.size() , 0);
        
        //从左到右把每个B[i]左边所有数乘积算出来,存放到对应位置
        //第一个元素左边没有任何数,赋值为1
        for(int i = 0 , nextValue = 1 ; i < A.size() ; i++){
            ans[i] = nextValue;
            nextValue = nextValue * A[i];
        }
        
        //从左到右把每个B[i]右边所有数乘积算出来,存放到对应位置
        //最后一个元素右边没有任何数,乘1,保持不变
        for(int i = A.size() - 1 , preValue = 1 ; i >= 0 ; i--){
            ans[i] = ans[i] * preValue;
            preValue = preValue * A[i];
        }
        
        return ans;
    }
};

补充:二叉树遍历基础

//前序遍历
void praTree(TreeCode * root)
{
	if (root == NULL)
	{
		return;
	}
	cout << root->data << "  ";
	praTree(root->lchild);
	praTree(root->rchild);
}

//中序遍历
void medTree(TreeCode * root)
{
	if (root == NULL)
	{
		return;
	}

	medTree(root->lchild);
	cout << root->data << "  ";
	medTree(root->rchild);
}

//后序遍历
void subTree(TreeCode * root)
{
	if (root == NULL)
	{
		return;
	}

	subTree(root->lchild);
	subTree(root->rchild);
	cout << root->data << "  ";
}

中等

25. 剪绳子

在这里插入图片描述

class Solution {
public:
    int maxProductAfterCutting(int length) {
        if(length <= 3) return 1 * (length - 1);
        
        int ans = 1;
        if(length % 3 == 2) ans *= 2 , length -= 2;
        if(length % 3 == 1) ans *= 4 , length -= 4;
        
        while(length){
            ans *= 3;
            length -=3 ;
        }
        
        return ans;
    }
};

76. 和为S的连续正数序列

在这里插入图片描述

class Solution {
public:
    vector<vector<int> > findContinuousSequence(int sum) {
        vector<vector<int>> ans;
        vector<int> temp;
        if(sum == 0) return ans;
        
        //滑动窗口:初始两个指针指向第一个元素,且元素的值就是指针的值
        int i = 1 , j = 1 ; 
        int s = 1;//左右指针已经指向第一个元素,所以初始值为1
        
        //滑动窗口:左指针最多可以移动到 sum/2 的位置
        
        while(i <= sum/2){
            if(s < sum){
                j++;
                s += j;
            } 
            if(s > sum){
                s -= i;
                i++;
            }
            if(s == sum){
                for(int a = i ; a <= j ; a++) temp.push_back(a);
                ans.push_back(temp);
                temp.clear();
                
                //这个答案已经保存,所以左指针向后移动一位
                s -= i;
                i++;
            }
        }
        return ans;
    }
};

第十三章 模拟

简单

40. 顺时针打印矩阵

在这里插入图片描述

class Solution {
public:
    vector<int> printMatrix(vector<vector<int> > matrix) {
        
        vector<int> ans;
        if(matrix.empty()) return ans;
        
        int n = matrix.size() , m = matrix[0].size();
        int diy[5] = {0,1,0,-1,0};
        
        vector<vector<bool>> visited(n , vector<bool>(m,false));//判断是否已经遍历过
        int x= 0 , y = 0 , d = 0;
        
        for(int i = 0 ; i < n * m ; i++){
            ans.push_back(matrix[x][y]);
            visited[x][y] = true;
            
            int a = x + diy[d] , b = y + diy[d + 1];
            
            if(a < 0 || a >= n || b < 0 || b >= m || visited[a][b]){
                d = (d + 1) % 4;
                a = x + diy[d] , b = y + diy[d + 1];
            }
            x = a , y = b;
        }
        return ans;
    }
};

42. 栈的压入、弹出序列

在这里插入图片描述

class Solution {
public:
    bool isPopOrder(vector<int> pushV,vector<int> popV) {
        if(pushV.size()!=popV.size()) return false;
        stack<int> sta;
        int j = 0 ;
        
        for(int i = 0 ; i < pushV.size() ; i++){
            sta.push(pushV[i]);
            //注意sta.empty()条件必须在前面,因为如果为空就不能访问栈顶
            while(sta.empty() == false && sta.top() == popV[j]) sta.pop() , j++;
        }
        
        return sta.empty();
    }
};

第十四章 全排列

模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

    for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
        处理节点;
        backtracking(路径,选择列表); // 递归
        回溯,撤销处理结果
    }
}

78. 子集

在这里插入图片描述
在这里插入图片描述

class Solution {
public:
    vector<int> path;
    vector<vector<int>> ans;
    void dfs(vector<int>& nums , int startIndex){
        //放在结束的前,以防漏掉子集,第一个子集为[]
        ans.push_back(path);
        if(startIndex >= nums.size()) return; //终止条件可以不加,如果不加后面的循环也不会执行

        for(int i = startIndex ; i < nums.size() ; i++){
            path.push_back(nums[i]);
            dfs(nums , i + 1);
            path.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(nums , 0);
        return ans;
    }
};

46. 全排列

在这里插入图片描述在这里插入图片描述

class Solution {
public:
    vector<vector<int>> ans;
    vector<int> path;

    vector<vector<int>> permute(vector<int>& nums) {
        vector<bool> used(nums.size() , false);
        dfs(nums , used);

        return ans;
    }

    void dfs(vector<int>& nums , vector<bool>& used){
        if(nums.size() == path.size()) {
            ans.push_back(path) ;
            return;
        }

        for(int i = 0 ; i < nums.size() ; i++){
            if(used[i] == true) continue;
            used[i] = true;
            path.push_back(nums[i]);
            dfs(nums , used);

            //回溯
            used[i] = false;
            path.pop_back();
        }
    }
};

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值