剑指offer——C++打卡

最近开始用c++写算法,浅浅的记录一下打卡题。
之后写的题会持续在里面更新!

栈与队列(简单)4.25

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

在这里插入图片描述
思路:题目需要利用两个栈实现队列,队列是一端进一端出,我们可以将两个栈拼接起来,一个栈作为队尾进元素,将这个栈的元素弹出到另一个栈,另一个栈作为队头输出,就可以实现了。
代码:

class CQueue {
public:
    stack <int> headStack;
    stack <int> frontStack;
    CQueue() {
        
    }
    
    void appendTail(int value) {
        headStack.push(value);
    }
    
    int deleteHead() {
        if (headStack.empty()) {
            return -1;
        } else {
            while (!headStack.empty()) {
                frontStack.push(headStack.top());
                headStack.pop();
            }
            int val = frontStack.top();
            frontStack.pop();
             while (!frontStack.empty()) {
                headStack.push(frontStack.top());
                frontStack.pop();
            }
            return val;
        }
    }
};

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

在这里插入图片描述
思路:首先设置两个栈,第一个栈用来记录输入的元素,第二个栈用于保存数据最小值,这个题的关键在于如何对最小栈进行压入,当栈空时,直接压入第一个元素,并默认此时这个元素为最小值,在压入后续元素时,进行比对,如果小了,就压入栈中,作为新的最小值,如果不是,那么就继续压入一个当前最小值元素,这样做对目的是,在后续两个栈弹出元素时,可以保证最小栈栈顶永远是最小的。

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

    }
    
    void push(int x) {
        myStack.push(x);
        if (minStack.empty()) {
            minStack.push(x);
        } else {
            if (x < minStack.top()) {
                minStack.push(x);
            } else {
                minStack.push(minStack.top());
            }
        }
    }
    
    void pop() {
        myStack.pop();
        minStack.pop();
    }
    
    int top() {
       return (int)myStack.top();
    }
    
    int min() {
        return minStack.top();
    }
};

链表(简单)4.26

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

在这里插入图片描述
很简单可以用个栈记录输入元素,然后给到容器里就行,这里直接调用的数组的函数,让元素一直插入在头部位置。
这里直接调库,循环一遍就行了。

class Solution {
public:
    vector<int> reversePrint(ListNode* head) {
        ListNode *temp = head;
         vector <int> ans;
        while (temp) {
            
            ans.insert(ans.begin(), temp->val);
            temp = temp->next;
        }

        return ans;
    }
};

剑指 Offer 24. 反转链表

在这里插入图片描述
利用三指针原地反转

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *temp;
        ListNode *nextTemp;
        ListNode *p;
        temp = head;
        p = NULL;
        nextTemp = NULL;
        while (temp) {
            nextTemp = temp->next;
            temp->next = p;
            p = temp;
            temp = nextTemp;
        }
        return p;
    }
    
};

剑指 Offer 35. 复杂链表的复制

在这里插入图片描述
思路:这题的难点就在于增加指针是随机的,要做到深拷贝就需要指向新生成的随机结点,这里我的做法是在原来的结点后新生成一个结点,第一遍仅初始化next指针和值,第二遍遍历时,因为新的结点已经生成,根据旧结点的指向给新结点的random赋值,最后将链表新结点取出即可。

class Solution {
public:
    Node* copyRandomList(Node* head) {
        if (head == NULL) {
            return head;
        }
        Node* temp = head;
        while(temp) {
            Node *newNode = new Node(temp->val);
            newNode->next = temp->next;
            temp->next = newNode;
            temp = newNode->next;
            
        }
        temp = head;
        while(temp) {
            if (temp->random != NULL) {
                temp->next->random = temp->random->next;
            }
            temp = temp->next->next;
        }
        Node *ans = head->next;
        temp = head;
        Node *copyNode = head->next;
        while(temp) {
            temp->next = temp->next->next;
            temp = temp->next;
            if (copyNode->next) {
                copyNode->next = copyNode->next->next;
                copyNode = copyNode->next;
            }
        }
        return ans;
    }
};

字符串(简单)4.27

剑指 Offer 05. 替换空格

在这里插入图片描述
思路:找到空格 然后替换就行 很简单

class Solution {
public:
    string replaceSpace(string s) {
        for (int i = 0; i < s.length(); i++) {
            if (s[i] == ' ') {
                
                s.replace(i, 1, "%20");
            }
        }
        return s;
    }
};

剑指 Offer 58 - II. 左旋转字符串

在这里插入图片描述
左边的拼到右边 把左边删了

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        
        s += s.substr(0, n);
        s.erase(0,n);
        return s;
    }
};

还有一种方法开辟两个新空间,return两个拼接,但是很占内存。

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        
        string ch1 = s.substr(0, n);
        string ch2 = s.substr(n, s.length());
        return ch2 + ch1;
    }
};

查找算法(简单)4.28

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

在这里插入图片描述
很容易想到两种思路,第一种就是先排序,然后找有没有相邻相同的,这种方法不开新空间,但时间复杂度较高.

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        for (int i = 0; i < nums.size(); i++) {
            if (nums[i] == nums[i + 1]) {
                return nums[i];
            }
        }
        return 0;
    }
};

另外一种就是利用哈希表统计元素个数,时间复杂度低,但是需要开辟空间,

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int, int> map;
        for(int num : nums){
            map[num]++;
            if(map[num] >= 2) {
            	return num;
        	}
        return nums[nums.size() - 1];
    }
};

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

在这里插入图片描述

可以用哈希也可以用二分查找,感觉这题二分查找比较合适

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

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

在这里插入图片描述
第一思路是遍历:

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int icount = 0;
        for (int num : nums) {
            if (num != icount) {
                break;
            }
            icount++;
        }
        return icount;
    }
};

不过对于有序数组最优解应该还是二分查找。
通过二分判断前面少了数还是后面少了数
不过这个题的测试样例有点问题,我的评价是不如遍历

class Solution {
public:
    int missingNumber(vector<int>& nums) {
        int left = 0;
        int right = nums.size() - 1;
        while (left < right) {
            int mid = (left + right) / 2;
            if (mid == nums[mid]) {
                left = mid + 1;
            } else {
                right = mid;
            }
        }
        return left == nums.size() - 1 && nums[left] == left ? left + 1 : left;
    }
};

查找算法(中等)4.28

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

在这里插入图片描述
思路;这个题可以把二维数组当作一个矩形,然后找个数去查找,然后就可以排除区域去寻找,但这样会有重复的地方,这里再看看题的规律随机选取一个数,他的左边一定比他小,下面一定比他大,这里就想到了二叉排序树。可以从右上角开始查找,如果比他大,就找他的下一行,如果比他小,就找他的左一列,这样很快就能找到了。

lass Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if(matrix.size() == 0 || matrix[0].size() == 0) {
            return false;
        }
        int column = matrix[0].size() - 1;
        int row = 0;
        int ans = 0;
        while (row < matrix.size() && column >= 0) {
            if (target == matrix[row][column]) {
                ans = 1;
                break;
            }
            if (target < matrix[row][column]) {
                column--;
            } else if (target > matrix[row][column]) {
                row++;
            } 
        }
        if (ans == 1) {
            return true;
        } else {
            return false;
        }
    }
};

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

在这里插入图片描述
这个题很简单,排序遍历二分都可以

class Solution {
public:
    int minArray(vector<int>& numbers) {
        sort(numbers.begin(), numbers.end());
        return numbers[0];
    }
};
class Solution {
public:
    int minArray(vector<int>& numbers) {
        for (int i = 1; i < numbers.size(); i++) {
            if (numbers[i] < numbers[i - 1]) {
                return numbers[i];
            }
        }
        return numbers[0];
    }
};
cclass Solution {
public:
    int minArray(vector<int>& numbers) {
         int i = 0, j = numbers.size() - 1;
        while (i < j) {
            int m = (i + j) / 2;
            if (numbers[m] > numbers[j]) {
                i = m + 1;
            } else if (numbers[m] < numbers[j]) {
                j = m;
            }
            else {
                j--;
            }
        }
        return numbers[i];
    }
};

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

在这里插入图片描述
利用hash表映射频率

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

搜索与回溯算法(简单)4.29

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

在这里插入图片描述
思路:层次遍历,可以利用队列去做,先将根部压入对列,之后压入左和右,然后弹出队首元素到数组,循环条件为对列不为空。

class Solution {
public:
    queue<TreeNode *> testQueue;
    vector<int> ansVector;
    vector<int> levelOrder(TreeNode* root) {
       if (root == NULL) {
           return ansVector;
       } else {
           testQueue.push(root);
           while (testQueue.size()) {
               TreeNode* temp = testQueue.front();
               ansVector.push_back(temp->val);
               if (temp->left) {
                   testQueue.push(temp->left);
               }
               if (temp->right) {
                   testQueue.push(temp->right);
               }
               testQueue.pop();
           }
       }
       return ansVector;
    }
};

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

在这里插入图片描述
思路:也是层次遍历只不过加了一个行和行剩余元素判断,这里注意给二维数组添加时只能添加vector。

class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode *> testQueue;
        vector<vector<int>> ansVector;
        int number = 1;
        int row = 0;
        if (!root) {
           return ansVector;
       } else {
           testQueue.push(root);
            vector<int> tempVector;
           while (testQueue.size()) {
               TreeNode* temp = testQueue.front();
                tempVector.push_back(temp->val);
               if (temp->left) {
                   testQueue.push(temp->left);
               }
               if (temp->right) {
                    testQueue.push(temp->right);
                }
                testQueue.pop();
                number--;
                if (number == 0) {
                    number = testQueue.size();
                    row++;
                    ansVector.push_back(tempVector);
                    tempVector.clear();
                }
               }  
           }
       return ansVector;
    }
};

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

在这里插入图片描述
加一个数组反转

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        queue<TreeNode *> testQueue;
        vector<vector<int>> ansVector;
        int number = 1;
        int row = 0;

        if (!root) {
           return ansVector;
       } else {
           testQueue.push(root);
            vector<int> tempVector;
           while (testQueue.size()) {
               TreeNode* temp = testQueue.front();
                tempVector.push_back(temp->val);
               if (temp->left) {
                   testQueue.push(temp->left);
               }
               if (temp->right) {
                    testQueue.push(temp->right);
                }
                testQueue.pop();
                number--;
                if (number == 0) {
                    number = testQueue.size();
                    row++;
                    if (row % 2 == 0) {
                        reverse(tempVector.begin(), tempVector.end());
                    }
                    ansVector.push_back(tempVector);
                    tempVector.clear();
                }
               }  
           }
       return ansVector;
    }
};

搜索与回溯算法(简单)5.1

剑指 Offer 26. 树的子结构

在这里插入图片描述
一开始看见这个题的时候有点懵,看了一下评论建议全文背诵默写,理了一遍思路写完了。
这里使用的是递归的思想,首先要针对传入的指针地址是否为空作特殊处理,无论A空还是B空我们都得返回false。
接下来我们现在A数中找到和B根结点值相同的结点,然后开始对比他的左子树和右子树是否相等,结束条件为A空,B空,以及值不相等,对应了false,true,false。
这里提及一个点题上的val是int值,如果是double的时候,因为精度存在差异,所以我们不能直接用==判断,这个时候我们应该写一个Equal去判断,如果精度在0.000001之内,我们就默认他俩相等。下面是代码:

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if (A == NULL || B == NULL) {
            return false;
        }
        return searchSubTreeNode(A, B);
    }
    bool compareTreeNode(TreeNode* A, TreeNode* B) {
        if (B == NULL) {
            return true;
        }
        if (A == NULL) {
            return false;
        }
        return A->val == B->val && compareTreeNode(A->left, B->left) && compareTreeNode(A->right, B->right);
    }
    bool searchSubTreeNode(TreeNode* A, TreeNode* B) {
        if (A == NULL) {
            return false;
        }
        if (A->val == B->val && compareTreeNode(A, B)) {
            return true;
        }
        return searchSubTreeNode(A->left, B) || searchSubTreeNode(A->right, B);
    }
};

这个是第一次的代码,看了评论区大神短短六行,尝试优化了一下:

bool searchSubTreeNode(TreeNode* A, TreeNode* B) {
        if (A == NULL) {
            return false;
        }
        if (A->val == B->val && compareTreeNode(A, B)) {
            return true;
        }// 可以发现这里值对比是多余的,所以可以整合到最后
        return searchSubTreeNode(A->left, B) || searchSubTreeNode(A->right, B);
        // 这个函数也就是遍历寻找结点,我们可以直接在主函数中遍历
    }

于是可以写成下面的高质量代码:

class Solution {
public:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if (A == NULL || B == NULL) {
            return false;
        }
        return compareTreeNode(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }
    bool compareTreeNode(TreeNode* A, TreeNode* B) {
        if (B == NULL) {
            return true;
        }
        if (A == NULL) {
            return false;
        }
        return A->val == B->val && compareTreeNode(A->left, B->left) && compareTreeNode(A->right, B->right);
    }
};

这样的话看起来会整洁许多。
写了一天树感觉自己很拉。

剑指 Offer 27. 二叉树的镜像

在这里插入图片描述
递归遍历换结点就行

class Solution {
public:
    void swapNode(TreeNode* root) {
        if (!root) {
            return;
        }
        if (root->left || root->right) {
            TreeNode* temp = root->left;
            root->left = root->right;
            root->right = temp;
        }
        if (root->left) {
            swapNode(root->left);
        }
        if (root->right) {
            swapNode(root->right);
        }
    }
    TreeNode* mirrorTree(TreeNode* root) {
        if (!root) {
            return NULL;
        }
        swapNode(root);
        return root;
    }
};

剑指 Offer 28. 对称的二叉树

在这里插入图片描述
镜像分析其实就是每个结点的左子树和右子树相等,所以用递归很合适,递归出口左右为空(遍历完相等),左或右为空(一个还没遍历完就空不相等)。

class Solution {
public:
    bool isEqual(TreeNode* p, TreeNode* q) {
        if (!p && !q) {
            return true;
        }
        if (!p || !q) {
            return false;
        }
        return p->val == q->val && isEqual(p->left, q->right) && isEqual(p->right, q->left);
    }

    bool isSymmetric(TreeNode* root) {
        return isEqual(root, root);
    }
};

动态规划(简单)5.2

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

在这里插入图片描述
看见直接递归,结果超时了,我是小丑,这边可以利用滚动数组,循环一遍就够了

class Solution {
public:
    int fib(int n) {
       if (n < 2) {
           return n;
       }
       int p = 0;
       int q = 0;
       int r = 1;
       for (int i = 2; i <= n; i++) {
           p = q;
           q = r;
           r = (q + p) % 1000000007;
       }
       return r;
    }
};

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

在这里插入图片描述
也是个斐波那契,只不过求和而已。

class Solution {
public:
    int numWays(int n) {
        if (n < 2) {
            return 1;
        }
        int q = 1;
        int p = 1;
        for (int i = 2; i <=n; i++) {
            p = q + p;
            q = p - q;
            p = p % 1000000007;
        }
        return p;
    }
};

剑指 Offer 63. 股票的最大利润

在这里插入图片描述记录前面的最小数字,维护一个最大利益,返回就行

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int Min = INT_MAX;
        int ans = 0;
        for (int num : prices) {
            Min = min(Min, num);
            ans = max(num - Min, ans);
        }
        return ans;
    }
};

动态规划(中等)

5.3

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

在这里插入图片描述
动态规划真的拉的一批。
这个状态就是找直到nums[i]之前和的最大值,所以需要比对当前数字和之前和大小,最后比较之前的最大值

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0;
        int ans = nums[0];
        for (int num : nums) {
            pre = max(num + pre, num);
            ans = max(ans, pre);
        }
        return ans;
    }
};

剑指 Offer 47. 礼物的最大价值

在这里插入图片描述
比较右边和下边那个值大,直接加上当前值,最后返回数组右下角。

class Solution {
public:
    int maxValue(vector<vector<int>>& grid) {
        if (grid.size() == 0 || grid[0].size() == 0) {
            return 0;
        }
        int m = grid.size();
        int n = grid[0].size();
        for(int i = 0; i < m; i++){
            for(int j = 0; j < n; j++){
                if(i == 0 && j > 0) {
                    grid[i][j] += grid[i][j - 1];
                } else if(j == 0 && i > 0) {
                    grid[i][j] += grid[i - 1][j];
                } else if(i > 0 && j > 0) {
                    grid[i][j] += max(grid[i - 1][j], grid[i][j - 1]);
                }
            }
        }
        return grid[m - 1][n - 1];
    }
};

5.4

剑指 Offer 46. 把数字翻译成字符串

在这里插入图片描述
刚开始毫无思路,可以具体画一画,然后就会发现规律,其实和青蛙跳台阶很像,只不过需要加一个判断,判断当前的数字是否可以和前面的结合成组成字母的数字,也就是10-25。如果可以就把i - 1和i - 2的最大排列数加起来,如果不可以那么就说明最大排列数没变,这里使用滚动数组存最大值可以节省空间。

class Solution {
public:
    int translateNum(int num) {
        vector<int> tempNumber;
        while (num) {
            int i =  num % 10;
            tempNumber.push_back(i);
            num = num / 10;
        }
        reverse(tempNumber.begin(), tempNumber.end());
        int p = 1;
        int q = 0;
        int ans = 1;
        if (tempNumber.size() > 1) {
            int temp = tempNumber[0] * 10 + tempNumber[1];
            if (temp > 9. && temp < 26) {
                q = 2;    
                ans = q;            
            } else {
                q = 1;

            }
        }
        for (int i = 2; i < tempNumber.size(); i++) {
            int temp = tempNumber[i - 1] * 10 + tempNumber[i];
            if (temp > 9. && temp < 26) {
                ans = p + q;
            }
            p = q;
            q = ans;
        }
        return ans;
    }
};

剑指 Offer 48. 最长不含重复字符的子字符串

在这里插入图片描述
本意是用哈希记录元素,维护哈希,莫名其名写成了暴力循环,我的评价是寄.
在这里插入图片描述

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        if (!s.length()) {
            return 0;
        }
        int start = 0;
        int end = 0;
        int ans = 1;
        int temp = 0;
        unordered_map<char, int> tempMap;
        while (end < s.length()) {
            tempMap[s[end]]++;
            if (tempMap[s[end]] > 1) {
                ans = max(end - start, ans);
                start++;
                end = start;
                tempMap.clear();
                tempMap[s[end]]++;
                temp = 0;
            } 
            temp++;
            end++;  
        }
        return ans > temp ? ans : temp;
    }
};

学了学别的解法:
这个是理想的样子。

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        unordered_map<char,int> map;
        int i = 0,j = 0,ans = 0;
        while(j < s.size())
        {
            if (map.find(s[j]) != map.end()&&i <= map[s[j]]) // 如果这个窗口存在相同元素
                i = map[s[j]] + 1; // 缩小窗口
            if (j - i + 1 > ans) // 判断窗口的长度是否大于结果集
                ans = j - i + 1; // 记录结果
            map[s[j]] = j; // 覆盖元素位置
            j++;
        }
        return ans;
    }
};

双指针(简单)5.5.

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

在这里插入图片描述

class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if (!head) {
            return head;
        }
        if (head->val == val) {
            return head->next;
        }
        ListNode* temp = head;
        while (temp->next) {
            if (temp->next->val == val) {
                temp->next = temp->next->next;
                break;
            }
            temp = temp->next;
        }
        return head;
    }
};

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

在这里插入图片描述
两个指针,一个快一个慢,快指针指到k+1的位置,然后两个同步走,fast走到末尾,slow就到了倒数第k个。

class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode* fast = head;
        ListNode* slow = head;

        while (fast && k > 0) {
            fast = fast->next;
            k--;
        }
        while (fast) {
            fast = fast->next;
            slow = slow->next;
        }

        return slow;
    }
};

双指针(简单)5.6

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

在这里插入图片描述
递归就行

class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if (l1 == nullptr) {
            return l2;
        } else if (l2 == nullptr) {
            return l1;
        } else if (l1->val < l2->val) {
            l1->next = mergeTwoLists(l1->next, l2);
            return l1;
        } else {
            l2->next = mergeTwoLists(l1, l2->next);
            return l2;
        }
    }
};

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

在这里插入图片描述
在这里插入图片描述
这个哥们很深情,下面代码就参考这个哥们的。

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if (headA == NULL || headB == NULL) {
            return NULL;
        }
        ListNode* you = headA;
        ListNode* me = headB;
        while (you != me) {
            you = you == NULL ? headB : you->next;
            me = me == NULL ? headA : me->next;
        }
        return you;
    }
};

5.7

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

在这里插入图片描述
两个指针遍历交换就行

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] % 2 == 1) {
                i++;
            } 
            while (i < j && nums[j] % 2 == 0) {
                j--;
            }
            swap(nums[i], nums[j]);
        }
        return nums;
};

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

在这里插入图片描述
第一题两数之和,梦开始的地方,左右指针,比较大小移动。

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        vector<int> ans;
        while (left < right) {
            if (nums[left] + nums[right] < target) {
                left++;
            } else if (nums[left] + nums[right] > target) {
                right--;
            } else {
                ans.push_back(nums[left]);
                ans.push_back(nums[right]);
                break;
            }
        }
        return ans;
    }
};

搜索与回溯算法(中等)

前几天忙着考试断更了,回来继续打卡

剑指 Offer 12. 矩阵中的路径 5.23

在这里插入图片描述
思路:根据题干的意思,我们第一步应该找到单词的开头位置,然后从这个位置开始展开dfs,bool dfs(vector<vector<char>>& board, string& word, int k, int x, int y), 这是定义的函数声明,这里的k是对应word的位置,x,y是行和列,递归结束条件是什么呢,1,当前位置的字母和单词k位置的不一样。2,已经对比到了单词的末尾。
根据题意,移动方向只有上下左右四个位置,并且已经参与过对比的字母不能重复使用,所以我们每次比对的时候都需要将这个字母修改成特殊值,如果移动后的位置没有越界,就展开dfs,搜索完四个位置后,如果对比失败,在回溯时把刚刚我们修改的特殊值再改回来。重新找单词的开头重新搜索。

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        for (int i = 0; i < board.size(); i++) {
            for (int j = 0; j < board[i].size(); j++) {
                if (dfs(board, word, 0, i, j)) {
                    return true;
                }
            }
        }  
        return false;      
    }
    int dx[4] = {-1, 0, 1, 0}, dy[4] = {0, 1, 0, -1}; //方向数组
    bool dfs(vector<vector<char>>& board, string& word, int k, int x, int y) {
        // 递归结束条件,当前矩阵位置字母和单词k位置不相同
        if (board[x][y] != word[k]) {
            return false;
        }
        // 对比单词的最后一位,说明网格内存在完整的单词
        if (k == word.size() - 1) {
            return true;
        }
        // 取出当前位置的字符之后修改它,表示已经搜索过此位置,对应题目中字母只能使用一次
        char t = board[x][y];
        board[x][y] = '!';
        for(int i = 0; i < 4; i++) {
            int a = x + dx[i], b = y + dy[i];
            // 判读出界或者走到已经搜索过的位置
            if(a < 0 || a >= board.size() || b < 0 || b >= board[0].size() || board[a][b] == '!') {
                continue;
            }
            if(dfs(board, word, k + 1, a, b)) {
                return true;
            }
        }
        board[x][y] = t;
        return false;
    }
};

剑指 Offer 13. 机器人的运动范围

在这里插入图片描述
思路:这里就是判断矩阵中数的数位之和是否大于k,机器人可以往四个方向行走,很容易想到使用dfs,这里用一个二维数组来维护矩阵元素是否走过,这里的递归结束条件就是越界或着该位置遍历过以及数位和大于k,下面是代码:

class Solution {
public:
    int numberSum(int x) {
        int sum = 0;
        while  (x > 0) {
            sum += x % 10;
            x /= 10;
        } 
        return sum;
    }
    int dfs(int m, int n, vector<vector<bool>> &temp, int k, int i, int j) {
        if (i > m - 1 || j > n - 1 || i < 0 || j < 0 || numberSum(i) + numberSum(j) > k || temp[i][j] == true) {
            return 0;
        }
        temp[i][j] = true;
        return 1 + dfs(m, n, temp, k, i + 1, j) + dfs(m, n, temp, k, i - 1, j) + dfs(m, n, temp, k, i, j + 1) + dfs(m, n, temp, k, i, j - 1);
    }
    int movingCount(int m, int n, int k) {
        vector<vector<bool>> test (m, vector<bool>(n));
        return dfs (m, n, test, k, 0, 0);
    }
};

剑指 Offer 34. 二叉树中和为某一值的路径 5.24

在这里插入图片描述
思路:这里就是需要一个二维数组来保存满足题意的数组来,利用先序遍历递归实现去遍历元素的每一个节点,记录路径的和,和target比较,如果满足则将当前数组存入二维数组,在每一层返回根节点时弹出数组的最后一位元素。

/**
 * 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<vector<int>> ans;
    vector<int> temp;

    void dfs(TreeNode* root, int target) {
        if (!root) {
            return;
        }
        temp.emplace_back(root->val);
        target -= root->val;
        if (!root->left && !root->right && target == 0) {
            ans.emplace_back(temp);
        }
        dfs(root->left, target);
        dfs(root->right, target);
        // 每一层返回时弹出临时数组的最后一个元素
        temp.pop_back();
    }

    vector<vector<int>> pathSum(TreeNode* root, int target) {
        dfs(root, target);
        return ans;
    }
};


emplace_back()

这里利用到了emplace_back()方法,对比一下其和push_back()的区别。
emplace_back()是c++11的新特性。
和push_back()的区别在于:
push_back()方法要调用构造函数和复制构造函数,这也就代表着要先构造一个临时对象,然后把临时的copy构造函数拷贝或者移动到容器最后面。
而emplace_back()在实现时,则是直接在容器的尾部创建这个元素,省去了拷贝或移动元素的过程。

剑指 Offer 36. 二叉搜索树与双向链表

在这里插入图片描述
思路:这个题看着挺唬人的,仔细看一下,其实就是利用中序遍历遍历搜索树,(中序遍历就可以得到一个递增序列),改变指针关系,画图就可以明白,每次都是保存上一个结点,到第二个结点时,让上一个结点的右指向当前结点,当点结点的左指向上一个尾结点,最后遍历完之后首位相连接。

class Solution {
public:
    Node* treeToDoublyList(Node* root) {
        if(root == nullptr) {
            return nullptr;
        }
        dfs(root);
        // 遍历完之后特殊处理,让头指向尾,尾指向头
        head->left = pre;
        pre->right = head;
        return head;
    }
    Node *pre, *head;
    void dfs(Node* temp) {
        if(temp == nullptr) {
            return;
        }
        dfs(temp->left);
        if(pre != nullptr) {
            // 保存的尾结点的右指针指向当前
            pre->right = temp;
        } else {
            // 对第一次遍历到底部特殊处理,令head指向树中最小的
            head = temp;
        }
        // 当前结点的左结点为尾结点
        temp->left = pre;
        // 更新尾结点
        pre = temp;
        dfs(temp->right);
    }
};

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

在这里插入图片描述

思路:一个中序遍历,第k个return

class Solution {
public:
    int i = 0;
    int val = 0;
    int kthLargest(TreeNode* root, int k) {
        if (!root) {
            return 0;
        }
        kthLargest(root->right, k);
        i++;
        if (i == k) {
            val = root->val;
            return val;
        }
        kthLargest(root->left, k);
        return val;
    }
};

排序 5.25

剑指 Offer 45. 把数组排成最小的数

在这里插入图片描述
这个就是利用排序:

class Solution {
public:
    string minNumber(vector<int>& nums) {
        vector<string> strs;
        string res;
        for(int i = 0; i < nums.size(); i++) {
            strs.push_back(to_string(nums[i]));
        }
        sort(strs.begin(), strs.end(), [](string& x, string& y){ return x + y < y + x; });
        for(int i = 0; i < strs.size(); i++) {
            res.append(strs[i]);
        }
        return res;
    }
};

剑指 Offer 61. 扑克牌中的顺子

在这里插入图片描述

class Solution {
public:
    bool isStraight(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        int count_0 = 0;
    
        for(int i = 0; i < 4; i++){
            if(nums[i] == 0){
                count_0++;
            } 
            // 出现非0重复牌
            else if(nums[i] == nums[i + 1]){
                return false;
            }
        }
        return nums[4] - nums[count_0] < 5;
    }
};

剑指 Offer 40. 最小的k个数

在这里插入图片描述
开摆,sort加前k个

class Solution {
public:
    vector<int> getLeastNumbers(vector<int>& arr, int k) {
        sort(arr.begin(), arr.end());
        vector<int> ans;
        for (int i = 0; i < k; i++) {
            ans.emplace_back(arr[i]);
        }
        return ans;
    }
};

搜索与回溯算法(中等)5.26

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

在这里插入图片描述

递归

class Solution {
public:
    int maxDepth(TreeNode* root) {
        if (!root) {
            return 0;
        }
        return max(maxDepth(root->left) + 1, maxDepth(root->right) + 1);
    }
};

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

在这里插入图片描述
思路:从上到下对比左右子树是否平衡

class Solution {
public:
    int height(TreeNode* root) {
        if (root == NULL) {
            return 0;
        } else {
            return max(height(root->left), height(root->right)) + 1;
        }
    }

    bool isBalanced(TreeNode* root) {
        if (!root) {
            return true;
        } else {
            return abs(height(root->left) - height(root->right)) > 1 ? false :true && isBalanced(root->left) && isBalanced(root->right);
        }
    }
};

还有一种从下到上的递归办法,这样可以只调用一次height函数,类似于后序遍历,如果底部有不符合条件的那么一直返回-1.

class Solution {
public:
    int height(TreeNode* root) {
        if (root == NULL) {
            return 0;
        }
        int leftHeight = height(root->left);
        int rightHeight = height(root->right);
        if (leftHeight == -1 || rightHeight == -1 || abs(leftHeight - rightHeight) > 1) {
            return -1;
        } else {
            return max(leftHeight, rightHeight) + 1;
        }
    }

    bool isBalanced(TreeNode* root) {
        return height(root) >= 0;
    }
};

5.27

剑指 Offer 64. 求1+2+…+n

在这里插入图片描述
思路:这里可以用递归

class Solution {
public:
    int sumNums(int n) {
        return n == 0 ? 0 : n + sumNums(n - 1);
    }
};

剑指 Offer 68 - I. 二叉搜索树的最近公共祖先

在这里插入图片描述
思路:这里利用一下二叉搜索树的性质,比当前结点小的结点永远在这个结点的左子树,大的永远在右子树,如果题目给的两个结点一个在左,一个在右,或者说一个为本身,那么说明出现分叉,就是最近的祖先。

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        TreeNode* ancestor = root;
        while (ancestor) {
            if (p->val < ancestor->val && q->val < ancestor->val) {
                ancestor = ancestor->left;
            } else if (p->val > ancestor->val && q->val > ancestor->val) {
                ancestor = ancestor->right;
            } else {
                break;
            }
        }
        return ancestor;
    }

剑指 Offer 68 - II. 二叉树的最近公共祖先

在这里插入图片描述
思路:首先判断是根节点的几种可能,目标节点在根节点的两侧,其中一个目标节点为根节点,然后递归左子树和右子树,如果左右同时为空,说明目标节点不在左右两侧,所以最近公共祖先为root,当找到目标节点时,直接返回root,继续向上返回,如果左右都不会空,返回当前结点,如果一个为空返回另一个指,返回到上一次继续递归。
总结就是先找到当前结点,然后逐级返回,如果返回时在某个节点的左右就返回这个结点,知道返回到最后一层,如果找到不或者越界会像上一级返回NULL

class Solution {
public:
    TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
        if (root == NULL || p == root || q == root) {
            return root;
        }
        TreeNode* l = lowestCommonAncestor(root->left, p, q);
        TreeNode* r = lowestCommonAncestor(root->right, p, q);
        return l == NULL ? r : (r == NULL ? l : root);
    }
};

剑指 Offer 07. 重建二叉树 5.29

在这里插入图片描述
思路:利用前序中序遍历的性质,首先在前序中找到根节点,那么在中序遍历中,根节点的左边一定是他的左子树,右边是右子树,然后根据这个特点,对左和右子树继续进行分治。最后就可以得到返回结点,这里的核心方法肯定还是递归。
在中序遍历中对根节点进行定位时,一种简单的方法是直接扫描整个中序遍历的结果并找出根节点,但这样做的时间复杂度较高。我们可以考虑使用哈希表来帮助我们快速地定位根节点。对于哈希映射中的每个键值对,键表示一个元素(节点的值),值表示其在中序遍历中的出现位置。在构造二叉树的过程之前,我们可以对中序遍历的列表进行一遍扫描,就可以构造出这个哈希映射。在此后构造二叉树的过程中,我们就只需要 O(1) 的时间对根节点进行定位了。

class Solution {
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        this->preorder = preorder;
        // 利用hashMap映射索引,可以在每一次取的时候时间复杂度为o(1)
        for(int i = 0; i < inorder.size(); i++) {
             dic[inorder[i]] = i;
        }
        return recur(0, 0, inorder.size() - 1);
    }
    // 全局变量,方便在后续递归时使用。
    vector<int> preorder;
    unordered_map<int, int> dic;
    // 通过下面参数锁定遍历划分的范围,含义下面会讲。
    TreeNode* recur(int root, int left, int right) { 
        if(left > right) { //  已经越过叶子结点.
            return nullptr; 
        }                       
        TreeNode* node = new TreeNode(preorder[root]);          // 建立根节点
        int i = dic[preorder[root]];                            // 获取当前结点在中序的index
        // 三个参数的含意
        // root:是根节点在前序遍历的位置
        // left:在中序遍历中左边界
        // 递归左子树时,左边界限是确定的,i - 1是右边界,因为i是中序的索引,他的左子树的右边界一定在他的上一位。
        node->left = recur(root + 1, left, i - 1);              // 开启左子树递归
        // 递归右子树时,右边界确定,i + 1是左边界,理由同上,这里的root计算实际就是在前序中加上左子树的长度加一找到右子树根结点。
        node->right = recur(root + i - left + 1, i + 1, right); // 开启右子树递归
        return node;                                            // 回溯返回根节点
    }
};

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值