《剑指Offer》笔记题解思路技巧优化——精心编写(1)

和你一起轻松愉快的刷题


一、前言

  • 为了方便阅读,完整笔记分为两篇文章,第(1)篇题目为1-38题,第(2)篇题目为39-75题。
  • 所有题目均来自《剑指 Offer(第 2 版)》。
  • 截止到编写文章时,所有题解代码均可通过LeetCode在线评测,即AC。
  • 笔记中一些题目给出了多种题解和思路,本笔记大多数题解都是较为完美的解法,消耗时间和空间较少。
  • 由于作者水平有限,欢迎大家指教,共同交流学习。
  • 最后祝大家刷题愉快。

二、开始刷题

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

哈希表判重

        遍历数组元素,加入到哈希表中,当出现重复时,返回该元素。

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int, bool> hash;
        for(int num: nums){
            if(hash[num])
                return num;
            hash[num] = true;
        }
        return -1;
    }
};

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

数组元素处理

        从右上角开始查找,若当前值大于待搜索值,向左移动一位;若当前值小于待搜索值,向下移动一位。如果最终移动到左下角时仍不等于待搜索值,则说明待搜索值不存在于矩阵中。

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

剑指 Offer 05. 替换空格

字符串原地修改

        统计空格的个数,修改字符串的长度,双指针倒序遍历修改。

        str.resize(int len);  // 调整字符串的长度

class Solution {
public:
    string replaceSpace(string s) {
        int cnt = 0;
        int len = s.size();
        for(int i=0; i<s.size(); ++i){
            if(s[i] == ' '){
                cnt++;
            }
        }
        int new_len = len + cnt*2;
        s.resize(new_len);
        for(int i=len-1, j=new_len-1; i<j; --i, --j){
            if(s[i] == ' '){
                s[j] = '0';
                s[j - 1] = '2';
                s[j - 2] = '%';
                j -= 2;
            }else{
                s[j] = s[i];
            }
        }
        return s;
    }
};

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

递归思想打印元素

        使用数组保存链表中从前到后的元素,reverse数组或者直接返回一个逆序的数组。

        reverse(arr.begin(), arr.end()); // 反转数组中的元素

        arr.rbegin() <==> arr.end()

        arr.rend() <==> arr.begin()

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

剑指 Offer 07. 重建二叉树

前序遍历 + 中序遍历 => 后序遍历

        前序遍历的形式总是:
[ 根节点, [左子树的前序遍历结果], [右子树的前序遍历结果] ]
        中序遍历的形式总是:
[ [左子树的中序遍历结果], 根节点, [右子树的中序遍历结果] ]

        利用递归的方法建立二叉树,利用前序遍历和中序遍历的形式特点来确定当前子树的根节点。另外用哈希表预处理中序遍历的结果加速优化,可以更快速查找数字的位置。

/**
 * 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 {
    unordered_map<int, int> index;
public:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        int n = preorder.size();
        // 构造哈希映射,快速定位根节点在中序遍历的位置
        for(int i=0; i<n; ++i){
            index[inorder[i]] = i;
        }
        return buildTreeHelper(preorder, inorder, 0, n-1, 0, n-1);
    }
    TreeNode* buildTreeHelper(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
        // 不存在子树,返回空
        if(preorder_left > preorder_right){
            return NULL;
        }
        // 根节点在前序遍历中的位置
        int preorder_root = preorder_left;
        // 根节点在中序遍历中的位置
        int inorder_root = index[preorder[preorder_root]];
        // 建立根节点
        TreeNode* root = new TreeNode(preorder[preorder_root]);
        // 左子树的节点数量
        int size_left_subtree = inorder_root - inorder_left;
        // 递归构造左子树
        // 先序遍历:[左边界+1,左边界+左子树节点数目]
        // 中序遍历:[左边界,根节点定位-1的元素]
        root->left = buildTreeHelper(preorder, inorder, preorder_left+1, preorder_left+size_left_subtree, inorder_left, inorder_root-1);
        // 递归构造右子树
        // 先序遍历:[左边界+1+左子树节点数目,右边界]
        // 中序遍历:[左边界,根节点定位-1]
        root->right = buildTreeHelper(preorder, inorder, preorder_left+size_left_subtree+1, preorder_right, inorder_root+1, inorder_right);
        
        return root;
    }
};

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

栈实现队列

        队列先进先出,栈先进后出,所以还需要另外一个栈来反转数组中的元素,实现先进先出。这个翻转过程既可以在插入时完成,也可以在取值时完成。

class CQueue {
    stack<int> in, out;
public:
    CQueue() {

    }
    
    void appendTail(int value) {
        in.push(value);
    }
    
    int deleteHead() {
        in2out();
        if(!out.empty()){
            int x = out.top();
            out.pop();
            return x;
        }
        return -1;
    }

    void in2out(){
        if(out.empty()){
            while(!in.empty()){
                int x = in.top();
                in.pop();
                out.push(x);
            }
        }
    }
};

/**
 * Your CQueue object will be instantiated and called as such:
 * CQueue* obj = new CQueue();
 * obj->appendTail(value);
 * int param_2 = obj->deleteHead();
 */

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

斐波那契问题

        动态规划思想,三个变量保存值。

class Solution {
public:
    int fib(int n) {
        if(n == 0)  return 0;
        if(n == 1)  return 1;
        int ans = 0;
        int first = 0, second = 1;
        for(int i=2; i<=n; ++i){
            ans = (first + second) % 1000000007;
            first = second;
            second = ans;
        }
        return ans;
    }
};

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

斐波那契问题

        青蛙一次可以跳上1级台阶或2级台阶,F(n) = F(n-1) + F(n-2)。   

        动态规划思想,三个变量保存值。

class Solution {
public:
    int numWays(int n) {
        if(n == 0)  return 1;
        if(n == 1)  return 1;
        int ans = 0;
        int first = 1, second = 1;
        for(int i=2; i<=n; ++i){
            ans = (first + second) % 1000000007;
            first = second;
            second = ans;
        }
        return ans;
    }
};

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

旋转数组

        二分法,即使数组被旋转过,仍然可以利用这个数组的递增性,使用二分查找。

        左排序数组任一元素 >= 右排序数组任一元素,旋转点就是右排序数组的第1个元素。

        当 nums[mid] > nums[r] 时: mid 一定在 左排序数组 中,即旋转点 x 一定在 [mid + 1, r] 闭区间内,因此执行 l = mid + 1;
        当 nums[mid] < nums[r] 时: mid 一定在 右排序数组 中,即旋转点 x 一定在[l, mid] 闭区间内,因此执行 r = mid;
        当 nums[mid] = nums[r] 时: 无法判断 mid 在哪个排序数组中,即无法判断旋转点 x 在 [l,mid] 还是 [mid + 1, r] 区间中。解决方案:--r,缩小判断范围,继续二分。


        为什么不用 nums[mid]和 nums[l] 作比较?

        二分目的是判断 mid 在哪个排序数组中,从而缩小区间。而在 nums[mid] > nums[l] 情况下,无法判断 mid 在哪个排序数组中。本质上是由于 r 初始值肯定在右排序数组中; l 初始值无法确定在哪个排序数组中。

        示例,当 l = 0, r = 4, mid = 2 时,有 nums[mid] > nums[l] ,而结果不同。
        [1, 2, 3, 4 ,5] 旋转点 x = 0 : mid 在右排序数组;
        [3, 4, 5, 1 ,2] 旋转点 x = 3 : mid 在左排序数组。

class Solution {
public:
    int minArray(vector<int>& numbers) {
        int l = 0, r = numbers.size() - 1;
        while(l < r){
            int mid = l + (r - l)/2;
            if(numbers[mid] == numbers[r]){
                // 无法判断
                --r;
            }else if(numbers[mid] < numbers[r]){
                // 向左部分二分
                r = mid;
            }else{
                // 向右部分二分
                l = mid + 1;
            }
        }
        return numbers[l];
    }
};

剑指 Offer 12. 矩阵中的路径

矩阵搜索问题

        DFS + 回溯 + 剪枝

class Solution {
public:
    bool exist(vector<vector<char>>& board, string word) {
        int m = board.size(), n = board[0].size();
        for(int i=0; i<m; ++i){
            for(int j=0; j<n; ++j){
                if(dfs(board, word, i, j, 0)){
                    return true;
                }
            }
        }
        return false;
    }
    // 方向数组
    int dx[4] = {0, 1, 0, -1}, dy[4] = {1, 0, -1, 0};
    bool dfs(vector<vector<char>>& board, string word, int x, int y, int k){
        int m = board.size(), n = board[0].size();
        if(x < 0 || x >= m || y < 0 || y >= n || board[x][y] != word[k]){
            return false;
        }
        if(k == word.size() - 1){
            return true;
        }
        board[x][y] = '.'; // 标记该位置已走过
        bool res = false;
        for(int i=0; i<4; ++i){
            int nx = x + dx[i];
            int ny = y + dy[i];
            if(dfs(board, word, nx, ny, k+1)){
                return true;
            }
        }
        board[x][y] = word[k]; // 回溯
        return false;
    }
};

剑指 Offer 14- I. 剪绳子

整数拆分

        1、数学推导

  • 4 : 2*2
  • 5 : 2*3
  • 6 : 3*3
  • 7 : 2*2*3 或 4*3
  • 8 : 2*3*3
  • 9 : 3*3*3
  • 10:2*2*3*3 或 4*3*3
  • 11:2*3*3*3
  • 12:3*3*3*3
  • 13:2*2*3*3*3 或 4*3*3*3

        k[0] 到 k[m] 只可能是 2 或者 3,当然也可能有 4,但是 4 = 2*2,也可以不考虑。5 < 2*3,6 < 3*3,比 6 更大的数字我们就更不用考虑了,肯定要继续拆分。

        2 的数量肯定小于 3 的数量,因为 2*2*2 < 3*3。

        由于题目规定 m > 1,所以 2 只能是 1,3 只能是2,这两个特殊情况直接返回就行了。

        2、动态规划

        dp[n] = max(dp[n], dp[i] * dp[n-i]);     

        j <= i/2 是因为 f(5) = f(1)*f(4),f(5) = f(2)*f(3),f(5) = f(3)*f(2),f(5) = f(4)*f(1) ,故遍历一半即可,必须是小于等于。

// 数论
class Solution {
public:
    int cuttingRope(int n) {
        if(n == 2 || n == 3){
            return n - 1;
        }
        int mmax = 1;
        while(n > 4){
            mmax *= 3;
            n -= 3;
        }
        mmax *= n;
        return mmax;
    }
};
// 动态规划
class Solution {
public:
    int cuttingRope(int n) {
        if(n == 2 || n == 3){
            return n - 1;
        }
        vector<int> dp(n + 1, 0);
        dp[1] = 1;
        dp[2] = 2;
        dp[3] = 3;
        // 2、3不能再拆分了,拆了会更小
        for(int i=4; i<=n; ++i){
            for(int j=1; j<=(i/2); ++j){
                dp[i] = max(dp[i], dp[j] * dp[i-j]);
            }
        }
        return dp[n];
    }
};

剑指 Offer 14- II. 剪绳子 II

整数拆分

        和 I 相比,II 增大了整数 n 的范围,涉及了 “大数越界情况下的求余问题” 。

        本题只能用数学推导,不能用动态规划了,动态规划的取余之后 max 函数就不能用来比大小了,比如取余后 2000000014(0) 会小于 1000000020(13)。

        可以将保存结果的变量设置为 long 或 long long 类型,防止溢出,也可以采用快速幂。

class Solution {
public:
    int cuttingRope(int n) {
        if(n == 2 || n == 3){
            return n - 1;
        }
        long mmax = 1;
        int mod = 1000000007;
        while(n > 4){
            mmax *= 3;
            mmax %= mod;
            n -= 3;
        }
        mmax *= n;
        mmax %= mod;
        return (int)mmax;
    }
};

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

二进制处理

        1、bitset 类库

        bitset<位数> foo; // 无参构造,默认每一位为0

        bitset<位数> foo(二进制数); // 构造函数,前面用0补充

        foo.count(); //返回1的个数 

        foo.size(); //返回位数 

        2、取余,%

        利用 % 来判断最后一位是不是2,也就是二进制中的1。

        3、位运算,&1

        每次 &1,如果最后一位是1,则计数+1,n 右移1位。

        4、位运算,n & (n - 1)

        把一个整数减去1,再和原整数做与运算,会把该整数最右边一个1变成0,那么一个整数的二进制有多少个1,就可以进行多少次这样的操作。

class Solution {
public:
    int hammingWeight(uint32_t n) {
        return bitset<32>(n).count();
    }
};
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int cnt = 0;
        while(n != 0){
            cnt += n % 2;
            n /= 2;
        }
        return cnt;
    }
};
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int cnt = 0;
        while(n != 0){
            if(n&1 != 0){
                ++cnt;       
            }
            n >>= 1;
        }
        return cnt;
    }
};
class Solution {
public:
    int hammingWeight(uint32_t n) {
        int cnt = 0;
        while(n != 0){
            n = n & (n - 1);
            ++cnt;
        }
        return cnt;
    }
};

剑指 Offer 16. 数值的整数次方

快速幂算法

        1、分治法

        2、倍增法(基于位运算):标准的快速幂算法

class Solution {
public:
    double myPow(double x, long long  n) {
        return n > 0? myPowHelper(x, n): 1.0 / myPowHelper(x, -n);
    }
    double myPowHelper(double x, long long n){
        if(n == 0)  return 1;
        if(n == 1)  return x;
        double tmp = myPowHelper(x, n/2);
        if(n%2 == 0){
            return tmp * tmp;
        }else{
            return tmp * tmp * x;
        }
    }
};
class Solution {
public:
    double myPow(double x, long long n) {
        return n > 0? fastPow(x, n): 1.0 / fastPow(x, -n);
    }
    double fastPow(double x, long long n){
        double ans = 1.0;
        while(n){
            if(n & 1)
                ans *= x;
            x *= x;
            n >>= 1;
        }
        return ans;
    }
};

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

最大 n 位数

        最大的 n 位数(记为 end )和位数 n 的关系:end = 10^{n} - 1

        如果 n 的数据很大的情况下,end 会溢出,无法正常存储,需要考虑大数越界问题。

class Solution {
public:
    vector<int> printNumbers(int n) {
        vector<int> ans;
        int end = pow(10, n) - 1;
        for(int i=1; i<=end; ++i){
            ans.push_back(i);
        }
        return ans;
    }
};

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

链表删除节点

        1、单指针:找到要删除节点的前一个节点,注意头节点就是要删除的节点的情况。

        2、双指针:找到要删除节点和要删除节点的前一个节点和,注意头节点就是要删除的节点的情况。

        3、单指针/双指针 + 虚拟头节点:这样就不用考虑头节点就是要删除的节点的情况了。

        4、递归:找到删除的节点时返回该节点的下一个,否则返回当前节点。

        5、栈或数组等数据结构:遍历链表,将值不等于 val 的节点依次压入栈中;再将链表的节点重新连接。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val == val){
            return head->next;
        }
        ListNode *pre = head;
        while((pre->next != NULL) && (pre->next->val != val)){
            pre = pre->next;
        }
        if(pre->next != NULL){
            pre->next = pre->next->next;
        }
        return head;
    }
};
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head->val == val){
            return head->next;
        }
        ListNode *pre = head;
        ListNode *cur = head;
        while((cur != NULL) && (cur->val != val)){
            pre = cur;
            cur = cur->next;
        }
        if(cur != NULL){
            pre->next = cur->next;
        }
        return head;
    }
};
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        ListNode *dummy = new ListNode(0, head);
        ListNode *pre = dummy;
        while((pre->next != NULL) && (pre->next->val != val)){
            pre = pre->next;
        }
        if(pre->next != NULL){
            pre->next = pre->next->next;
        }
        return dummy->next;
    }
};
class Solution {
public:
    ListNode* deleteNode(ListNode* head, int val) {
        if(head == NULL){
            return head;
        }
        head->next = deleteNode(head->next, val);
        return head->val == val? head->next: head; 
    }
};

剑指 Offer 19. 正则表达式匹配

字符串编辑

        动态规划

        二维数组 dp[i][j] 表示:s[0] ~ s[i - 1] 和 p[0] ~ p [j - 1]是否匹配。根据字符、星号,点号,分情况讨论来更新 dp 数组。

class Solution {
public:
    bool isMatch(string s, string p) {
        int m = s.size(), n = p.size();
        vector<vector<bool>> dp(m + 1, vector<bool>(n + 1, false));
        // 初始化 空串情况
        dp[0][0] = true;
        // 初始化第一行,如果有*,考虑匹配结果
        for(int j=1; j<n+1; ++j){
            // 按题意p第一个字符不可能为*,所以不会越界
            if(p[j-1] == '*'){
                dp[0][j] = dp[0][j-2];
            }
        }
        for(int i=1; i<m+1; ++i){
            for(int j=1; j<n+1; ++j){
                if(s[i-1] == p[j-1] || p[j-1] == '.'){
                    dp[i][j] = dp[i-1][j-1];
                }else if(s[i-1] != p[j-1] && p[j-1] != '*' && p[j-1] != '.'){
                    dp[i][j] = false;
                }else if(p[j-1] == '*'){
                    if(s[i-1] == p[j-2] || p[j-2] == '.'){
                        dp[i][j] = dp[i-1][j] || dp[i][j-2];
                    } else{
                        dp[i][j] = dp[i][j-2];
                    }
                }
            }
        }
        return dp[m][n];
    }
};

剑指 Offer 20. 表示数值的字符串

字符串编辑

        1、模拟:根据题目要求,模拟字符串是数值的条件

        2、确定有限状态自动机(DFA)

        3、正则表达式

class Solution {
public:
    bool isNumber(string s) {
        if(s.size() == 0){
            return false;
        }
        int index = 0;
        // 空格遍历下一个
        while(s[index] == ' '){
            ++index;
        }
        bool numeric = scanInteger(s, index);
        // 判断小数部分:.123 或 123. 或 123.456
        if(s[index] == '.'){
            ++index;
            numeric = scanUnsignedInteger(s, index) || numeric;
            // 注意不能是 numeric = numeric || scanUnsignedInteger(s, index);
            // 因为如果 numeric == true,就不会扫描.后面的部分,也就不会改变index的值了
        }
        // 判断指数部分:1e5 或 1e+5
        if(s[index] == 'e' || s[index] == 'E'){
            ++index;
            numeric = scanInteger(s, index) && numeric;
        }
        // 空格遍历下一个
        while(s[index] == ' '){
            ++index;
        }
        return numeric && index == s.size();
    }
    // 判断是否是整数:[+|-]A
    bool scanInteger(const string s, int& index){   
        if(s[index] == '+' || s[index] == '-'){
            ++index;
        }
        return scanUnsignedInteger(s, index);
    }
    // 判断是否是无符号整数:A
    bool scanUnsignedInteger(const string s, int& index){
        int temp = index;
        while(index != s.size() && s[index] >= '0' && s[index] <= '9'){
            ++index;
        }
        return index > temp;
    }
};

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

数组元素修改

        1、单指针 + 两次遍历

        2、首尾双指针 + 一次遍历

        3、原地交换 + 一次遍历

        定义 i,j 首尾双指针,i 从左侧往右开始遍历,如果遇到的是奇数,就表示这个元素已经调整完成了,继续从左往右遍历,直到遇到一个偶数。j 从右侧往左开始遍历,如果遇到的是偶数,就表示这个元素已经调整完成了,继续从右往左遍历,直到遇到一个奇数。交换这个偶数和奇数的位置,并且重复两边的遍历,直到在中间相遇。

class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        vector<int> ans;
        for(auto num: nums){
            if(num % 2 != 0){
                ans.push_back(num);
            }
        }
        for(auto num: nums){
            if(num % 2 == 0){
                ans.push_back(num);
            }
        }
        return ans;
    }
};
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int n = nums.size();
        vector<int> ans(n, 0);
        int i = 0, j = n - 1;
        for(auto num: nums){
            if(num % 2 != 0){
                ans[i++] = num;
            }else{
                ans[j--] = num;
            }
        }
        return ans;
    }
};
class Solution {
public:
    vector<int> exchange(vector<int>& nums) {
        int n = nums.size();
        int i = 0, j = n - 1;
        while(i < j){
            while(i < j && nums[i]%2 == 1){
                ++i;
            }
            while(i < j && nums[j]%2 == 0){
                --j;
            }
            if(i < j){
                swap(nums[i], nums[j]);
            }
        }
        return nums;
    }
};

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

链表遍历

        1、遍历,记录链表元素个数

        2、快慢指针,快指针先走 k 步,再和慢指针一起走,一起走时,两个指针共走了全部的元素。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        int cnt = 0;
        ListNode *node = head;
        while(node != NULL){
            ++cnt;
            node = node->next;
        }
        cnt = cnt - k;
        while(cnt--){
            head = head->next;
        }
        return head;
    }
};
class Solution {
public:
    ListNode* getKthFromEnd(ListNode* head, int k) {
        ListNode *fast = head;
        ListNode *slow = head;
        while(fast != NULL && k > 0){
            fast = fast->next;
            --k;
        }
        while(fast != NULL){
            fast = fast->next;
            slow = slow->next;
        }
        return slow;
    }
};

剑指 Offer 24. 反转链表

链表反转

        1、三个变量迭代(头插法):链表指针转向,变量指针移动

        2、递归

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode *prev = NULL, *next = NULL;
        while(head != NULL){
            next = head->next;
            head->next = prev;
            prev = head;
            head = next;
        }
        return prev;
    }
};
class Solution {
public:
    ListNode* reverseList(ListNode* head, ListNode* prev = nullptr) {
        if(!head){
            return prev;
        }
        ListNode* next = head->next;
        head->next = prev;
        return reverseList(next, head);
    }   
};

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

合并链表

        遍历两个链表,元素比较,将小的元素添加到新的链表中,直到其中一个链表遍历到末尾,将另一个链表的剩余部分添加到新链表中。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* node = dummy;
        while(l1 && l2){
            if(l1->val > l2->val){
                node->next = l2;
                l2 = l2->next;
            }else{
                node->next = l1;
                l1 = l1->next;
            }
            node = node->next;
        }
        node->next = l1? l1: l2;
        return dummy->next;
    }
};

剑指 Offer 26. 树的子结构

树的子结构

        递归

/**
 * 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:
    bool isSubStructure(TreeNode* A, TreeNode* B) {
        if(A == NULL || B == NULL){
            return false;
        }
        // 当前节点开始比较 或 左子树 或 右子树
        return isSubStructureHelper(A, B) || isSubStructure(A->left, B) || isSubStructure(A->right, B);
    }

    bool isSubStructureHelper(TreeNode* A, TreeNode* B) {
        if(B == NULL)   return true;
        if(A == NULL)   return false;
        // 当前节点相同继续比较左子树和右子树
        if(A->val == B->val){
            return isSubStructureHelper(A->left, B->left) && isSubStructureHelper(A->right, B->right);
        }else{
            return false;
        }
    }
};

剑指 Offer 27. 二叉树的镜像

翻转二叉树

        1、递归:交换左右子树的元素

        2、借助队列或者栈:类似于层序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == NULL){
            return NULL;
        }
        swap(root->left, root->right);
        mirrorTree(root->left);
        mirrorTree(root->right);
        return root;
    }
};
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == NULL){
            return NULL;
        }
        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            TreeNode* node = q.front();
            q.pop();
            if(node){
                q.push(node->left);
                q.push(node->right);
                swap(node->left, node->right);
            }
        }
        return root;
    }
};
class Solution {
public:
    TreeNode* mirrorTree(TreeNode* root) {
        if(root == NULL){
            return NULL;
        }
        stack<TreeNode*> s;
        s.push(root);
        while(!s.empty()){
            TreeNode* node = s.top();
            s.pop();
            if(node){
                s.push(node->left);
                s.push(node->right);
                swap(node->left, node->right);
            }
        }
        return root;
    }
};

剑指 Offer 28. 对称的二叉树

 对称二叉树

        1、递归:左的右 == 右的左

        2、借助队列或者栈:类似于层序遍历

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root == NULL){
            return true;
        }
        return isSymmetricHelper(root->left, root->right);
    }

    bool isSymmetricHelper(TreeNode* l, TreeNode* r) {
        if(l == NULL && r == NULL){
            return true;
        }
        if(l == NULL || r == NULL || l->val != r->val){
            return false;
        }
        return isSymmetricHelper(l->left, r->right) && isSymmetricHelper(l->right, r->left);
    }
};
class Solution {
public:
    bool isSymmetric(TreeNode* root) {
        if(root == NULL){
            return true;
        }
        return isSymmetricHelper(root->left, root->right);
    }

    bool isSymmetricHelper(TreeNode* l, TreeNode* r) {
        queue<TreeNode*> q;
        q.push(l); q.push(r);
        while(!q.empty()){
            TreeNode* node1 = q.front(); q.pop();
            TreeNode* node2 = q.front(); q.pop();
            if(node1 == NULL && node2 == NULL){
                continue;
            }
            if(node1 == NULL || node2 == NULL){
                return false;
            }
            if(node1->val != node2->val){
                return false;
            }
            q.push(node1->left);  q.push(node2->right);
            q.push(node1->right); q.push(node2->left);
        }
        return true;
    }
};

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

矩阵遍历

        模拟:根据题意,定义四个指针变量,不断移动移动,将矩阵元素顺时针添加到新数组中。

class Solution {
public:
    vector<int> spiralOrder(vector<vector<int>>& matrix) {
        if(matrix.size() == 0){
            return vector<int>();
        }
        int m = matrix.size(), n = matrix[0].size();
        vector<int> ans;
        int left = 0, right = n - 1;
        int top = 0, bottom = m - 1;
        while(left <= right && top <= bottom){
            // 向右
            for(int j=left; j<=right; ++j){
                ans.push_back(matrix[top][j]);
            }
            if(++top > bottom)  break;
            // 向下
            for(int i=top; i<=bottom; ++i){
                ans.push_back(matrix[i][right]);
            }
            // 向左
            if(--right < left)  break;
            for(int j=right; j>=left; --j){
                ans.push_back(matrix[bottom][j]);
            }
            // 向上
            if(--bottom < top)  break;
            for(int i=bottom; i>=top; --i){
                ans.push_back(matrix[i][left]);
            }
            if(++left > right)  break;
        }
        return ans;
    }
};

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

最小栈

        1、辅助栈:栈顶表示当前原栈里所有值的最小值

        2、不使用额外栈:每次添加元素时,压入最小值和压入新元素,栈顶下一个元素表示最小值。

class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s, min_s;

    MinStack() {
        //min_s.push(INT_MAX);
    }
    
    void push(int x) {
        s.push(x);
        if(min_s.empty() || min_s.top() >= x){
            min_s.push(x);
        }
    }
    
    void pop() {
        if(!min_s.empty() && min_s.top() == s.top()){
            min_s.pop();
        }
        s.pop();
    }
    
    int top() {
        return s.top();
    }
    
    int min() {
        return min_s.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->min();
 */
class MinStack {
public:
    /** initialize your data structure here. */
    stack<int> s;
    int minNum = INT_MAX;

    MinStack() {
        
    }
    
    void push(int x) {
        minNum = std::min(minNum, x);
        s.push(minNum);
        s.push(x);
    }
    
    void pop() {
        s.pop();
        s.pop();
        if(!s.empty()){
            int tmp = s.top();
            s.pop();
            minNum = s.top();
            s.push(tmp);
        }else{ // 注意要恢复初始值
            minNum = INT_MAX;
        }
    }
    
    int top() {
        return s.top();
    }
    
    int min() {
        return minNum;
    }
};

剑指 Offer 31. 栈的压入、弹出序列

验证栈序列

        辅助栈:将压入序列中的元素压入新栈中,当元素和弹出序列中的元素相同时开始出栈,index++,最终栈为空则证明栈序列正确。

class Solution {
public:
    bool validateStackSequences(vector<int>& pushed, vector<int>& popped) {
        if(pushed.size() != popped.size()){
            return false;
        }
        if(pushed.size() == 0 && popped.size() == 0){
            return true;
        }
        int len = pushed.size();
        stack<int> s;
        int popIndex = 0;
        for(int i=0; i<len; ++i){
            s.push(pushed[i]);
            while(!s.empty() && popIndex < len && s.top() == popped[popIndex]){
                ++popIndex;
                s.pop();
            }
        }
        return s.empty();
    }
};

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

层序遍历

        最基础的层序遍历,借助队列即可。

/**
 * 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<int> levelOrder(TreeNode* root) {
        if(root == NULL){
            return vector<int>();
        }
        vector<int> ans;

        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            root = q.front();
            q.pop();
            ans.push_back(root->val);
            if(root->left){
                q.push(root->left);
            }
            if(root->right){
                q.push(root->right);
            }
        }
        return ans;
    }
};

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

层序遍历——按层打印

        与 I 相比,II 要求按层打印,也就是将每一层的元素分别保存到一维数组中。

        方法1:利用队列的.size(),来确定每一层元素的个数。

        ret.push_back(vector <int> ());  在结果数组(ret, 二维)中添加一个新的数组(一维,空)用于保存每一层的节点。

        ret.back().push_back(node->val);  获取结果数组(ret, 二维)中最后一个数组(一维,保存当前层节点的数组),并在该数组中添加节点。

/**
 * 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) {
        if(root == NULL){
            return vector<vector<int>>();
        }
        vector<vector<int>> ans;

        queue<TreeNode*> q;
        q.push(root);
        while(!q.empty()){
            int cnt = q.size();
            ans.push_back(vector<int>());
            for(int i=0; i<cnt; ++i){
                TreeNode* node = q.front();
                q.pop();
                ans.back().push_back(node->val);
                if(node->left){
                    q.push(node->left);
                }
                if(node->right){
                    q.push(node->right);
                }
            }
        }
        return ans;
    }
};

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

层序遍历——Z型打印

        与 II 相比,III 要求按照 Z型/锯齿型 打印,在选择每一层从左向右还是从右向左时,定义一个变量 isOrderLeft 来保证隔行打印方向是一样的,使用双端队列 deque 更加方便的添加元素,最终再加入到 ans 结果数组中。

/**
 * 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) {
        if(root == NULL){
            return vector<vector<int>>();
        }
        vector<vector<int>> ans;
        
        queue<TreeNode*> q;
        q.push(root);
        bool isOrderLeft = true;
        while(!q.empty()){
            int cnt = q.size();
            deque<int> levelList;
            for(int i=0; i<cnt; ++i){
                TreeNode* node = q.front();
                q.pop();
                if(isOrderLeft){
                    levelList.push_back(node->val);
                }else{
                    levelList.push_front(node->val);
                }
                if(node->left){
                    q.push(node->left);
                }
                if(node->right){
                    q.push(node->right);
                }
            }
            ans.emplace_back(levelList.begin(), levelList.end());
            isOrderLeft = !isOrderLeft;
        }
        return ans;
    }
};

剑指 Offer 33. 二叉搜索树的后序遍历序列

验证后序遍历

        递归

        后序遍历最后一个元素就是根,由于是题目是二叉搜索树,找到第一个大于根的元素,这个元素前面是左子树,均小于根,后面是右子树,均大于根。判断是否符合上述要求,如果符合递归的验证左右子树部分。

class Solution {
public:
    bool verifyPostorder(vector<int>& postorder) {
        if(postorder.empty() || postorder.size() == 1){
            return true;
        }
        return verifyPostorderHelper(postorder, 0, postorder.size()-1);
    }

    bool verifyPostorderHelper(vector<int>& postorder, int low, int high) {
        if(low >= high){
            return true;
        }
        int start = low;
        while(start < high && postorder[start] < postorder[high]){
            ++start;
        }
        for(int i=start; i<high; ++i){
            if(postorder[i] <= postorder[high]){
                return false;
            }
        }
        return verifyPostorderHelper(postorder, low, start-1) && verifyPostorderHelper(postorder, start, high-1);
    }
};

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

路径总和

        递归、回溯

/**
 * 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>> res;
    vector<int> path;

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

    void pathSumHelper(TreeNode* root, int tar) {
        if(root == nullptr){
            return;
        }
        path.push_back(root->val);
        tar -= root->val;
        if(tar == 0 && root->left == nullptr && root->right == nullptr){
            res.push_back(path);
        }
        pathSumHelper(root->left, tar);
        pathSumHelper(root->right, tar);
        path.pop_back(); // 回溯:说明当前节点不满足要求,pop掉,返回其父亲节点
    }
};

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

复制带随机指针的链表

        深拷贝

        1、哈希表 + 回溯  O(n)、O(n)

        哈希表记录每一个节点对应新节点的创建情况。遍历该链表的过程中,检查「当前节点的后继节点」和「当前节点的随机指针指向的节点」的创建情况。

        如果这两个节点中的任何一个节点的新节点没有被创建,我们都立刻递归地进行创建。当我们拷贝完成,回溯到当前层时,我们即可完成当前节点的指针赋值。注意一个节点可能被多个其他节点指向,因此我们可能递归地多次尝试拷贝某个节点,为了防止重复拷贝,我们需要首先检查当前节点是否被拷贝过,如果已经拷贝过,我们可以直接从哈希表中取出拷贝后的节点的指针并返回即可。

        2、迭代:新节点插入再拆分  O(n)、O(1)

        把复制的节点插到原结点后面,复杂指针的指向去原结点找,复制节点的随机指针指向原节点的随机指针的下一个。

class Solution {
public:
    unordered_map<Node*, Node*> cachedNode;
    Node* copyRandomList(Node* head) {
        if(head == NULL){
            return NULL;
        }
        if(!cachedNode.count(head)){ // 当前节点未拷贝
            Node* newNode = new Node(head->val);
            cachedNode[head] = newNode;
            newNode->next = copyRandomList(head->next);
            newNode->random = copyRandomList(head->random);
        }
        return cachedNode[head];
    }
};
class Solution {
public:
    Node* copyRandomList(Node* head) {
        if(head == NULL){
            return NULL;
        }
        // 将拷贝节点放到原节点后面,例如1->2->3变成了1->1'->2->2'->3->3'
        for(Node* node = head; node != NULL; node = node->next->next){
            Node* newNode = new Node(node->val);
            newNode->next = node->next;
            node->next = newNode;
        }
        // 处理拷贝节点的random指针
        for(Node* node = head; node != NULL; node = node->next->next){
            Node* newNode = node->next;
            if(node->random == NULL){
                newNode->random = NULL;
            }else{
                // 注意是指向拷贝节点,不是源节点的node->random,否则拆分时会出现问题
                newNode->random = node->random->next;
            }
        }
        // 拆分拷贝节点和原节点,变成1->2->3和1'->2'->3'两个链表
        Node* newHead = head->next;
        for(Node* node = head; node != NULL; node = node->next){
            Node* newNode = node->next;
            node->next = node->next->next;
            if(newNode->next == NULL){
                newNode->next = NULL;
            }else{
                newNode->next = newNode->next->next;
            }
        }
        return newHead;
    }
};

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

二叉搜索树与双向链表

        中序遍历、递归

        中序遍历的结果就是链表元素的顺序,设置头节点、前驱节点和当前节点,连接节点。

/*
// Definition for a Node.
class Node {
public:
    int val;
    Node* left;
    Node* right;

    Node() {}

    Node(int _val) {
        val = _val;
        left = NULL;
        right = NULL;
    }

    Node(int _val, Node* _left, Node* _right) {
        val = _val;
        left = _left;
        right = _right;
    }
};
*/
class Solution {
public:
    Node *head = NULL;
    Node *pre = NULL;
    Node* treeToDoublyList(Node* root) {
        if(root == NULL)    return NULL;
        dfs(root);
        // 首位连接
        head->left = pre;
        pre->right = head;

        return head;
    }

    void dfs(Node* cur){
        if(cur == NULL) return;
        // 左 根 右
        dfs(cur->left);
        if(pre == NULL){ // 当前节点是第一个节点
            head = cur;
            pre = cur;
        }else{
            cur->left = pre;
            pre->right = cur;
            pre = cur;
        }
        dfs(cur->right);
    }
};

剑指 Offer 37. 序列化二叉树

二叉树序列化与反序列化

        加密与解密        

        1、bfs,层序遍历

        2、dfs,前序遍历

       可以使用 istringstream(string str) 来读取字符。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == NULL){
            return "";
        }
        string data;
        queue<TreeNode*> q;
        q.push(root);
        while (!q.empty()) {
            TreeNode* node = q.front();
            q.pop();
            // null用#表示,元素间用空格分隔
            if (node) {
                data += to_string(node->val) + ' ';
                q.push(node->left);
                q.push(node->right);
            } else {
                data += "# ";
            }
        }
        // 删除最后一个空格
        if (!data.empty()){
            data.pop_back();
        }

        return data;
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        if(data.empty()){
            return NULL;
        }
        vector<TreeNode*> nodes; // 保存节点
        for(int i=0; i<data.size(); ++i){
            if(data[i] == ' ')   continue;
            if(data[i] == '#'){
                nodes.push_back(NULL);
                continue;
            }
            // 找到以空格分隔的子串转换为int元素
            int begin = i, end = i;
            while(data[end] != ' '){
                ++end;
            }
            string sub = data.substr(begin, end-begin);
            nodes.push_back(new TreeNode(atoi(sub.c_str())));
            i = end;
        }
        // 将节点连接,返回头节点即可
        int pos = 1;
        for(int i=0; i<nodes.size(); ++i){
            if(nodes[i] == NULL){
                continue;
            }else{
                nodes[i]->left = nodes[pos++];
                nodes[i]->right = nodes[pos++];
            }
        }
        
        return nodes[0];
    }
};

// Your Codec object will be instantiated and called as such:
// Codec codec;
// codec.deserialize(codec.serialize(root));
class Codec {
public:
    // Encodes a tree to a single string.
    string serialize(TreeNode* root) {
        if(root == NULL){
            return "#";
        }
        return to_string(root->val) + " " + serialize(root->left) + " " + serialize(root->right);
    }

    // Decodes your encoded data to tree.
    TreeNode* deserialize(string data) {
        istringstream temp(data);
        return des(temp);
    }

    TreeNode *des(istringstream& ss){
        string sub;
        ss >> sub;
        if(sub == "#"){
            return NULL;
        }
        TreeNode* node = new TreeNode(stoi(sub));
        node->left = des(ss);
        node->right = des(ss);
        return node;
    }
};

剑指 Offer 38. 字符串的排列

全排列

        1、next_permutation()

 C++ 的 STL 提供求「下一个」排列组合的函数 next_permutation()

  • 函数 next_permutation() 的定义有两种形式
    • bool next_permutation (BidirectionalIterator first, BidirectionalIterator last);
    • bool next_permutation (BidirectionalIterator first, BidirectionalIterator last, Compare comp);
  • 返回值如果没有下一个排列组合,返回 false,否则返回 true。每执行 next_permutation() 一次,会把新的排列放到原来的空间里。
  • 注意它排列的范围是 [first,last),包括 first,不包括 last
  • 补充:next_permutation() 是从当前的全排列开始,逐个输出更大的全排列,而不是输出所有的全排列。如果要得到所有的全排列,先使用 sort 排序,得到最小排列后,然后再使用 next_permutation() 就可以了。

        2、自写全排列函数:dfs + 回溯 + 去重

class Solution {
public:
    vector<string> permutation(string s) {
        vector<string> res;
        sort(s.begin(), s.end());
        do{
            res.push_back(s);
        }while(next_permutation(s.begin(), s.end()));

        return res;
    }
};
class Solution {
public:
    vector<string> res;
    vector<string> permutation(string s) {
        dfs(s, 0, s.size()-1);
        return res;
    }

    set<string> st; // set去重
    void dfs(string& str, int s, int t){ // 从第s个数开始到第t个结束的全排列
        if(s == t){ // 递归结束,产生一个全排列
            if(st.find(str) == st.end()){
                st.insert(str);
                res.push_back(str);
            }
            return;
        }
        for(int i=s; i<=t; ++i){
            swap(str[s], str[i]); // 把当前第1个数与后面所有数交换位置
            dfs(str, s+1, t);
            swap(str[s], str[i]); // 恢复,用于下一次交换
        }
    }
};
class Solution {
public:
    vector<string> res;
    vector<string> permutation(string s) {
        if(s.size() == 0){
            return vector<string>();
        }
        dfs(s, 0, s.size()-1);
        sort(res.begin(), res.end());
        return res;
    }

    void dfs(string& str, int s, int t){
        if(s == t){
            res.push_back(str);
            return;
        }
        unordered_map<int, bool> vis;
        // set<int> st;
        for(int i=s; i<=t; ++i){
            if(vis[str[i]] == true){
                continue;
            }
            // if(st.find(str[i]) != st.end()){
            //     continue;
            // }
            vis[str[i]] = true;
            // st.insert(str[i]);
            swap(str[s], str[i]);
            dfs(str, s+1, t);
            swap(str[s], str[i]);            
        }
    }
};

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

多数元素

        1、排序  O(nlogn)、O(logn)

        如果将数组 nums 中的所有元素按照单调的顺序排序,那么下标为 n/2 的元素(下标从 0 开始)一定是众数。

        2、哈希表  O(n)、O(n)

        遍历数组 nums ,用 HashMap 统计各数字的数量,即可找出 众数 。

        3、摩尔投票法(Boyer-Moore)  O(n)、O(1)

        摩尔投票法,核心理念为 票数正负抵消,成立前提就是有出现超过一半的元素。

        设输入数组 nums 的众数为 x ,数组长度为 n 。

        推论一: 若记 众数 的票数为 +1 ,非众数 的票数为 -1 ,则一定有所有数字的票数和 > 0 。

        推论二: 若数组的前 a 个数字的 票数和 = 0,则 数组剩余 (n-a) 个数字的票数和一定仍 >0 ,即后 (n-a) 个数字的 众数 仍为 x 。

        根据以上推论,记数组首个元素为 n1,众数为 x ,遍历并统计票数。当发生 票数和 = 0 时,剩余数组的众数一定不变 ,这是由于:

        当 n1 = x :抵消的所有数字中,有一半是众数 x 。
        当 n1 != x:抵消的所有数字中,众数 x 的数量最少为 0 个,最多为一半。
        利用此特性,每轮假设发生 票数和 = 0 都可以缩小剩余数组区间 。当遍历完成时,最后一轮假设的数字即为众数。

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size() / 2];
    }
};
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        unordered_map<int, int> hash;
        for(int i=0; i<nums.size(); ++i){
            hash[nums[i]]++;
            if(hash[nums[i]] > nums.size()/2){
                return nums[i];
            }
        }
        return 0;
    }
};
class Solution {
public:
    int majorityElement(vector<int>& nums) {
        int votes = 0, x = 0;
        for(int num: nums){
            if(votes == 0){
                x = num;
            }
            votes += num == x? 1: -1;
        }
        // 验证x是否为众数
        int cnt = 0;
        for(int num: nums){
            if(num == x){
                cnt++;
            }
        }
        return cnt > nums.size()/2? x: 0;
    }
};

觉得文章还不错,可以点个小赞赞,支持一下哈!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.kler.cn/a/2151.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

什么是推挽输出,开漏输出?

这篇文章是看B站“工科男孙老师”这个视频的笔记推挽 开漏 高阻 这都是谁想出来的词&#xff1f;&#xff1f; 我觉得讲的很好&#xff0c;做一下笔记 1.什么是IO输出三态 一共有&#xff1a;高电平, 低电平&#xff0c;浮空/高阻态 三种IO态 2.推挽输出 推挽输出能够表示高、…

【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程

【嵌入式硬件芯片开发笔记】HART协议调制解调芯片AD5700配置流程 XTAL_EN接地&#xff0c;CLK_CFG的两个引脚由同一个GPIO控制 初始时HART_CLK_CFG输出低电平 由RTS引脚控制调制/解调。当RTS处于高电平时&#xff0c;为解调&#xff08;输入&#xff09;&#xff1b;否则为调…

Qt优秀开源项目之十七:QtPromise

QtPromise是Promises/A规范的Qt/C实现。该规范的译文见附录。 QtPromise基于Qt5.6及以上版本&#xff0c;当然也包括Qt6。 github地址&#xff1a;https://github.com/simonbrunel/qtpromise 新手导航&#xff1a;Getting Started | QtPromise API手册&#xff1a;API Referenc…

第十七天 JavaScript、Vue详细总结

目录 JavaScript、Vue 1. JavaScript常用对象 1.1 Array对象 1.2 String对象 1.3 自定义对象 1.4 JSON 1.5 BOM 1.6 DOM 2. 事件监听 2.1 事件绑定 2.2 常见事件 2.3 案例 3. Vue 3.1 概述 3.2 快速入门 3.3 常用指令 3.4 生命周期 JavaScript、Vue 今日目标&…

队列实现及leetcode相关OJ题

上一篇写的是栈这一篇分享队列实现及其与队列相关OJ题 文章目录一、队列概念及实现二、队列源码三、leetcode相关OJ一、队列概念及实现 1、队列概念 队列同栈一样也是一种特殊的数据结构&#xff0c;遵循先进先出的原则&#xff0c;例如&#xff1a;想象在独木桥上走着的人&am…

Redis 如何实现库存扣减操作和防止被超卖?

本文已经收录到Github仓库&#xff0c;该仓库包含计算机基础、Java基础、多线程、JVM、数据库、Redis、Spring、Mybatis、SpringMVC、SpringBoot、分布式、微服务、设计模式、架构、校招社招分享等核心知识点&#xff0c;欢迎star~ Github地址&#xff1a;https://github.com/…

【业务安全-02】业务逻辑漏洞之越权操作

越权越权即越权查看被人的信息&#xff0c;又分为水平越权和垂直越权&#xff0c;但是两者的本质都是一样的&#xff0c;只是越权的身份权限不一样而已水平越权&#xff1a;相同级别的用户&#xff0c;如用户A访问用户B垂直越权&#xff1a;普通用户到管理员&#xff0c;普通用…

测试老鸟都在用的接口抓包常用工具以及接口测试工具都有哪些?

目录 接口 接口测试的重要性 常用抓包工具 常用接口测试工具 接口 接口测试是测试系统组件间接口的一种测试。接口测试主要用于检测外部系统与系统之间以及内部各个子系统之间的交互点。测试的重点是要检查数据的交换&#xff0c;传递和控制管理过程&#xff0c;以及系统间…

【C++】二叉搜索树

文章目录一、二叉搜索树的概念二、二叉搜索树的优点三、二叉搜索树的操作及实现1、二叉搜索树的查找2、二叉树的插入3、二叉搜索树的删除4、二叉搜索树的递归实现5、模拟实现完整代码四、二叉搜索树的应用五、二叉树进阶面试题一、二叉搜索树的概念 二叉搜索树又称二叉排序树&…

WebService简单入门

1. JAX-WS发布WebService 创建web工程 创建simple包&#xff0c;和server、client两个子包。正常情况下server和client应该是两个项目&#xff0c;这里我们只是演示效果&#xff0c;所以简化写到一个项目中&#xff1a; 1.1 创建服务类Server package simple.server;import ja…

python+django+vue全家桶鲜花商城售卖系统

重点&#xff1a; &#xff08;1&#xff09; 网上花店网站中各模块功能之间的的串联。 &#xff08;2&#xff09; 网上花店网站前台与后台的连接与同步。 &#xff08;3&#xff09; 鲜花信息管理模块中鲜花的发布、更新与删除。 &#xff08;4&#xff09; 订单…

机器学习——无监督学习

机器学习的分类一般分为下面几种类别&#xff1a;监督学习( supervised Learning )无监督学习( Unsupervised Learning )强化学习( Reinforcement Learning&#xff0c;增强学习)半监督学习( Semi-supervised Learning )深度学习(Deep Learning)Python Scikit-learn. http: // …

基于java下的Springboot框架实现幼儿园管理系统

基于java下的Springboot框架实现幼儿园管理系统开发语言&#xff1a;Java 框架&#xff1a;springboot JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven…

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(6)

目录 写在前面&#xff1a; 题目&#xff1a;P1683 入门 - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述&#xff1a; 输入格式&#xff1a; 输出格式&#xff1a; 输入样例&#xff1a; 输出样例&#xff1a; 解题思路&#xff1a; 代码&#xff1a; AC &a…

使用GPT-4生成QT代码

一、概述最近ChatGPT火爆起来了&#xff0c;ChatGPT是一种基于GPT的自然语言处理模型&#xff0c;可以用于生成自然语言文本&#xff0c;例如对话、文章等。最近又发现了一个优秀且免费的代码生成工具Cursor.so &#xff0c;Cursor.so集成了 GPT-4 &#xff0c;可以帮助你快速编…

使用 Python 从点云生成 3D 网格

从点云生成 3D 网格的最快方法 已经用 Python 编写了几个实现来从点云中获取网格。它们中的大多数的问题在于它们意味着设置许多难以调整的参数&#xff0c;尤其是在不是 3D 数据处理专家的情况下。在这个简短的指南中&#xff0c;我想展示从点云生成网格的最快和最简单的过程。…

flex布局优化(两端对齐,从左至右)

文章目录前言方式一 nth-child方式二 gap属性方式三 设置margin左右两边为负值总结前言 flex布局是前端常用的布局方式之一&#xff0c;但在使用过程中&#xff0c;我们总是感觉不太方便&#xff0c;因为日常开发中&#xff0c;大多数时候&#xff0c;我们想要的效果是这样的 …

我的 System Verilog 学习记录(11)

引言 本文简单介绍 SystemVerilog 的其他程序结构。 前文链接&#xff1a; 我的 System Verilog 学习记录&#xff08;1&#xff09; 我的 System Verilog 学习记录&#xff08;2&#xff09; 我的 System Verilog 学习记录&#xff08;3&#xff09; 我的 System Verilo…

C语言—文件操作

1.为什么使用文件使用文件可以直接将数据存放到电脑硬盘上&#xff0c;做到数据的持久化2.什么是文件硬盘上的文件是文件但在程序中&#xff0c;我们一般谈的文件有两种&#xff1a;程序文件和数据文件&#xff08;从文件功能角度来分类的&#xff09;2.1程序文件包括源程序文件…

STM32之点亮一个LED小灯(轮询法)

目录 一、初始化GPIO口 二、按键点亮LED灯&#xff08;轮询法&#xff09; 一、初始化GPIO口 1、点亮LED小灯前&#xff0c;需要先初始化GPIO口 HAL_GPIO_Init(GPIO_TypeDef *GPIOx, GPIO_InitTypeDef *GPIO_Init) GPIO_TypeDef *GPIOx&#xff1a; //指初始化GPIO…
最新文章