【数据结构与算法】LeetCode HOT 100(简单,22道)

本文详细介绍了使用栈解决有效括号问题,位运用来查找数组中只出现一次的数字和多数元素,以及计算汉明距离的不同方法。接着探讨了二叉树的对称性检查、最大深度、翻转、转换为累加树以及直径计算。此外,还涵盖了数组中两数之和、最大子序和、买卖股票最佳时机等经典算法。最后,讲解了合并有序链表、环形链表检测、相交链表以及链表的回文判断和反转。这些内容展示了如何运用数据结构和算法高效地解决各种问题。
摘要由CSDN通过智能技术生成

一、栈

20. 有效的括号

class Solution {
public:
    bool isValid(string s) {
        if(s.size() % 2 == 1){//奇数个字符,不可能有效
            return false;
        }

        map<char,char> map;
        map[')'] = '(';
        map[']'] = '[';
        map['}'] = '{';

        if(s==""){
            return true;
        }
        stack<char> stack;
        for(auto i : s){
            if(i=='(' || i=='[' || i=='{'){
                stack.push(i);
            }
            if(i==')' || i==']' || i=='}'){
                if(stack.empty()){
                    return false;
                }else{
                    if(stack.top()==map[i]){
                        stack.pop();
                        continue;
                    }else{
                        return false;
                    }
                }
            }
        }
        return stack.empty();
    }
};

155. 最小栈 简单

时间复杂度O(1),空间复杂度O(n)

class MinStack {
private:
    stack<int> si, minsi;
public:
    /** initialize your data structure here. */
    MinStack() {
        minsi.push(INT_MAX);
    }
    
    void push(int x) {
        si.push(x);
        minsi.push(x <= minsi.top() ? x : minsi.top());
    }
    
    void pop() {
        si.pop();
        minsi.pop();
    }
    
    int top() {
        return si.top();
    }
    
    int getMin() {
        return minsi.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();
 */

二、位运算

136. 只出现一次的数字

方法1:异或

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int res = 0;
        for(auto i : nums){
            res ^= i;
        }
        return res;
    }
};

169. 多数元素

给定一个大小为 n 的数组,找到其中的多数元素。多数元素是指在数组中出现次数大于 ⌊ n/2 ⌋ 的元素。
方法1:利用标准算法sort排序,排序后,index=mid的位置的元素一定是那个多数元素

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        sort(nums.begin(), nums.end());
        return nums[nums.size()/2];        
    }
};

方法2:利用快排的思想

class Solution {
public:
    int majorityElement(vector<int>& nums) {
        srand(time(NULL));
        // partion出index=mid的位置的元素一定是那个多数元素
        return _majorityElement(nums, 0, nums.size()-1, nums.size()/2);
    }

    int _majorityElement(vector<int>& nums, int left, int right, int k){
        int p = partion(nums, left, right);
        if(k == p){
            return nums[p];
        }
        return k < p ? _majorityElement(nums, left, p-1, k) : _majorityElement(nums, p+1, right, k);
    }

    int partion(vector<int>& nums, int left, int right){
        swap(nums[left], nums[rand() % (right - left + 1) + left]);
        int v = nums[left];
        int i = left + 1, j = right;
        while(true){
            while( i <= right && nums[i] < v){
                ++i;
            }
            while(j >= left+1 && nums[j] > v ){
                --j;
            }
            if(i > j) break;
            swap(nums[i], nums[j]);
            ++i;
            --j;
        }
        swap(nums[left], nums[j]);
        return j;
    }
};

方法3:利用map统计数字出现频率,如果达到数组总长度的一半,就认为找到了

class Solution {
public:
    int majorityElement(vector<int>& nums) {
       unordered_map<int, int> map;
       for(auto i : nums){
            ++map[i];
            if(map[i] > nums.size()/2){
                return i;
            }
       }
       return 0;//纯粹因为LeetCode最外层必须要return点什么
    }
};

461. 汉明距离

两个整数之间的汉明距离指的是这两个数字对应二进制位不同的位置的数目。
给出两个整数 x 和 y,计算它们之间的汉明距离。
方法1:异或后,逐位检查是否是1

class Solution {
public:
    int hammingDistance(int x, int y) {
        int res = 0;
        int xXORy = x ^ y; 
        while(xXORy){
           if(xXORy & 1 == 1){
               ++res;
           }
           xXORy = xXORy >> 1;
        }
        return res;
    }
};

方法2:异或后,快速获得1的个数,有几个1就比较了几次

class Solution {
public:
    int hammingDistance(int x, int y) {
        int res = 0;
        int xXORy = x ^ y; 
        while(xXORy){
           ++res;
           xXORy = xXORy & (xXORy - 1);
        }
        return res;
    }
};

方法3:充分利用轮子,一行搞定

class Solution {
public:
    int hammingDistance(int x, int y) {
        return bitset<32>(x^y).count();
    }
};

三、树

101. 对称二叉树

给定一个二叉树,检查它是否是镜像对称的。
方法1:递归法:

/**
 * 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) {
       return isMirr(root, root);
    }

    bool isMirr(TreeNode* p, TreeNode* q){
        if(p == NULL && q == NULL){
            return true;
        }

        if(p == NULL || q == NULL) {
			return false;
		}

        return (p->val == q->val) && isMirr(p->left, q->right) && isMirr(p->right, q->left);
    }
};

方法2:迭代法:

class Solution {
public:
    bool check(TreeNode *u, TreeNode *v) {
        queue <TreeNode*> q;
        q.push(u); q.push(v);
        while (!q.empty()) {
            u = q.front(); q.pop();
            v = q.front(); q.pop();
            if (!u && !v) continue;
            if ((!u || !v) || (u->val != v->val)) return false;

            q.push(u->left); 
            q.push(v->right);

            q.push(u->right); 
            q.push(v->left);
        }
        return true;
    }

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

104. 二叉树的最大深度

/**
 * 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:
    int maxDepth(TreeNode* root) {
        if(root == NULL){
            return 0;
        }
        int maxDepthLeft = maxDepth(root->left);
        int maxDepthRight = maxDepth(root->right);
        return max(maxDepthLeft, maxDepthRight) + 1;
    }
};

226. 翻转二叉树

/**
 * 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* invertTree(TreeNode* root) {
        if(root == NULL){
            return NULL;
        }
        invertTree(root->left);
        invertTree(root->right);
        
        swap(root->left, root->right);
        return root;
    }
};

538. 把二叉搜索树转换为累加树

给定一个二叉搜索树(Binary Search Tree),把它转换成为累加树(Greater Tree),使得每个节点的值是原来的节点值加上所有大于它的节点值之和。
思路:二叉搜索树特点:左节点 < 根 < 右节点,中序遍历得到升序数组。进行逆向的中序遍历,在根节点位置操作时,进行值的累加。

class Solution {
    int sum = 0;
public:
    TreeNode* convertBST(TreeNode* root) {
        if(root != NULL){
            convertBST(root->right);
            sum += root->val;
            root->val = sum;
            convertBST(root->left);
        }
        return root;
    }
};

543. 二叉树的直径

给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过也可能不穿过根结点。

/**
 * 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 {
private:
    int Max;
    int depth(TreeNode* root){
        if(root == NULL){
            return 0; // 递归结束的出口
        }
        int L, R;
        L = depth(root->left);//对左节点做同样的事
        R = depth(root->right);//对右节点做同样的事
        if(L + R > Max){//每次递归都计算一次
            Max = L + R;//如果当前找到更大的就替换Max
        }
        return max(L, R) + 1;
    }
public:
    int diameterOfBinaryTree(TreeNode* root) {
        Max = 0;
        depth(root);
        return Max;
    }
};

617. 合并二叉树

方法1:递归,修改了原树1:

/**
 * 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* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if(t1 == NULL){
            return t2;
        }
        if(t2 == NULL){
            return t1;
        }
        t1->val += t2->val;
        t1->left = mergeTrees(t1->left, t2->left);
        t1->right = mergeTrees(t1->right, t2->right);
        return t1;        
    }
};

方法2:递归,不修改原树:

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if(t1 == NULL){
            return t2;
        }
        if(t2 == NULL){
            return t1;
        }
        TreeNode * root = new TreeNode(0);
        root->val = t1->val + t2->val;
        root->left = mergeTrees(t1->left, t2->left);
        root->right = mergeTrees(t1->right, t2->right);
        return root;        
    }
};

方法3:迭代

class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        if (t1 == NULL) return t2;
        if (t2 == NULL) return t1;
        queue<TreeNode*> que;
        que.push(t1);
        que.push(t2);
        while(!que.empty()) {
            TreeNode* node1 = que.front(); que.pop();
            TreeNode* node2 = que.front(); que.pop();
            // 此时两个节点一定不为空,val相加
            node1->val += node2->val;
            // 如果左节点都不为空,加入队列
            if (node1->left != NULL && node2->left != NULL) {
                que.push(node1->left);
                que.push(node2->left);
            }
            // 如果右节点都不为空,加入队列
            if (node1->right != NULL && node2->right != NULL) {
                que.push(node1->right);
                que.push(node2->right);
            }
            // 当t1的左节点 为空 t2左节点不为空,就赋值过去
            if (node1->left == NULL && node2->left != NULL) {
                node1->left = node2->left;
            }
            // 当t1的右节点 为空 t2右节点不为空,就赋值过去
            if (node1->right == NULL && node2->right != NULL) {
                node1->right = node2->right;
            }
        }
        return t1;
    }
};

四、数组

1. 两数之和

给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那 两个 整数,并返回他们的数组下标。

方法一:暴力法,双重循环

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        int n = nums.size();
        if(n<=0) return {};
        for(int i=0; i<n-1; ++i){
            for(int j=i+1; j<n; ++j){
                if(nums[i] + nums[j] == target){
                    return {i, j};
                }
            }
        }
        return {};
    }
};

方法二:STL中的find方法

class Solution {
    vector<int> res;
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        for(auto iter = nums.begin(); iter != nums.end(); ++iter){
            auto it = find(iter+1, nums.end(), target-(*iter));//从下一个位置开始找
            if(it != nums.end()){
                res.push_back(iter - nums.begin());
                res.push_back(it - nums.begin());
                return res;
            }else{
                continue;
            }
        }
        return res;
    }
};

53. 最大子序和

方法1:暴力法,两层循环,第1层从头到尾遍历每一个元素;第2层从当前元素i到尾加一遍,每加一个元素都更新当前的sum和max。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int max = INT_MIN;
        for(int i=0; i<nums.size(); ++i){
            int sum = 0;
            for(int j=i; j<nums.size(); ++j){
                sum += nums[j];
                if(max < sum){
                    max = sum;
                } 
            }
        }
        return max;
    }
};

方法2:动态规划法:维护两个量:1,从开始到当前元素的最大和dp,2,从开始到当前元素的所有最大和中的最大值res

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int dp = nums[0];  
        int res = dp;
        for(int i=1; i<nums.size(); ++i){
            dp = max(nums[i], dp + nums[i]);
            res = max(res, dp);
        }
        return res;
    }
};

方法3:贪心算法

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int res = INT_MIN;
        int sum = 0;
        for(int i = 0; i < nums.size(); ++i){
            sum += nums[i];
            res = max(res, sum);
            if(sum < 0){  贪心算法的关键,当发现sum为负的时候说明之前的结果都可以舍弃了,重新去找子元素
                sum = 0;  
            }
        }
        return res;
    }
};

121. 买卖股票的最佳时机

方法1:时间复杂度n平方, 空间复杂度O(1),超时

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size(), profit = 0;
        for(int i=0; i<n; ++i){
            for(int j=i+1; j<n; ++j){
                profit = max(profit, prices[j] - prices[i]);
            }
        }
        return profit;
    }
};

方法2:只看一遍,时间复杂度O(n),空间复杂度O(1)

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int n = prices.size();
        int minPrice = 1e5; //最小值一般要初始化成一个较大的数
        int maxProfit = 0;

        for(int i=0; i<n; ++i){
            // 保持minPrice一直是历史最低点
            if(prices[i] < minPrice){
                minPrice = prices[i];
            }
            // 利润在历史最大利润,和当前利润之间选最大
            maxProfit = max(maxProfit, prices[i] - minPrice);
        }

        return maxProfit;
    }
};

方法3:这个题本质就是要求某个数与其右边最大的数的差值,这符合了单调栈的应用场景 当你需要高效率查询某个位置左右两侧比他大(或小)的数的位置的时候

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int profit = 0;
        vector<int> sv;
        prices.emplace_back(-1);//哨兵,放一个prices数组中不可能出现的最小值进去
        for(int i = 0; i < prices.size(); ++i){
            while(!sv.empty() &&  prices[i] < sv.back()){
                profit = max(profit, sv.back()-sv.front());// 维护当前最大利润
                sv.pop_back();
            }
            sv.emplace_back(prices[i]);
        }
        return profit;
    }
};

448. 找到所有数组中消失的数字

方法1:用set去重,数组中有n个元素,就从0到n-1遍历,检查每个元素对应的值(它本身加1)在set中是否出现,如果没有出现,就把这个元素对应的值加入到res数组中。时间O(n),空间O(n)

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        set<int> mSet(nums.begin(), nums.end());
        vector<int> res;
        for(int i=0; i<nums.size(); ++i){
            if(mSet.find(i+1) == mSet.end()){//没找到
                res.push_back(i+1);
            }
        }
        return res;
    }
};

方法2:我们可以在输入数组本身以某种方式标记已访问过的数字,然后再找到缺失的数字
将数组元素对应为索引的位置加n
遍历加n后的数组,若数组元素值小于等于n,则说明数组下标值不存在,即消失的数字

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        vector<int> res;
        for(int i=0; i<nums.size(); ++i){
            int index = (nums[i]-1) % nums.size();//得到[0,n-1]之间的数
            nums[index] += nums.size();
        }

        for(int i=0; i<nums.size(); ++i){
            if(nums[i] <= nums.size()){
                res.push_back(i + 1);
            }
        }
        return res;
    }
};

五、链表

21. 合并两个有序链表

/**
 * 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* mergeTwoLists(ListNode* l1, ListNode* l2) {
        if(l1 == NULL) return l2;
        if(l2 == NULL) return l1;
        
        // 此时l1和l2都不为null

        ListNode *resHead = new ListNode(-1), *resNode = resHead;

        while(l1 != NULL && l2 != NULL){
            if(l1->val <= l2->val){
                resNode->next = l1;
                l1 = l1->next;
            }else{
                resNode->next = l2;
                l2 = l2->next;
            }
            resNode = resNode->next;
        }
        resNode->next = (l1 == NULL ? l2 : l1);

        return resHead->next;
    }
};

141. 环形链表

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool hasCycle(ListNode *head) {
        ListNode *fast = head, *slow = head;
        while(fast != NULL && fast->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
            if(fast == slow){
                return true;
            }
        }
        return false;
    }
};

160. 相交链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
       if(headA == NULL || headB == NULL){
           return NULL;
       } 
       ListNode * pA = headA, * pB = headB;
       while(pA != pB){
           pA = (pA == NULL ? headB : pA->next);
           pB = (pB == NULL ? headA : pB->next);
       }
       return pA;
    }
};

206. 反转链表

反转一个单链表。

/**
 * 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) {
        if(head == NULL){
            return head;
        }
        ListNode * cur = NULL, *pre = head;//pre在前,cur在后
        while(pre != NULL){
            ListNode * tmp = pre->next;
            pre->next = cur;
            cur = pre;
            pre = tmp;
        }
        return cur;
    }
};

234. 回文链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    bool isPalindrome(ListNode* head) {
        if(head == NULL){
            return true;
        }

        // 得到链表的中间节点,6个节点的链表锁定第3个节点,5个节点的链表也是锁定第3个节点
        ListNode * firstHalfEnd = endOfFirstHalf(head);
        
        // 翻转后半个链表
        ListNode * secondHalfStart = reverseList(firstHalfEnd->next);
        
        // 判断是否是回文链表
        ListNode * p1 = head, * p2 = secondHalfStart;
        bool result = true;
        while(result && p2 != NULL){
            if(p1->val != p2->val){
                result = false;
            }
            p1 = p1->next;
            p2 = p2->next;
        }

        // 恢复链表
        firstHalfEnd->next = reverseList(secondHalfStart);

        return result;        
    }

    // 前半部分最后一个节点
    ListNode * endOfFirstHalf(ListNode * head){
        ListNode * fast = head, * slow = head;
        while(fast->next != NULL && fast->next->next != NULL){
            fast = fast->next->next;
            slow = slow->next;
        }
        return slow;
    }
    

    // 翻转链表
    ListNode * reverseList(ListNode * head){
        ListNode * cur = head, * pre = NULL;
        while(cur != NULL){
            ListNode * tmp = cur->next;
            cur->next = pre;
            pre = cur;
            cur = tmp;
        }
        return pre;
    }
};

六、动态规划

70. 爬楼梯

class Solution {
public:
    int climbStairs(int n) {
        if(n==0)return 1;
        if(n==1)return 1;
        vector<int> dp(n+1, -1);
        dp[0] = 1;
        dp[1] = 1;
        for(int i=2; i<=n; ++i){
            dp[i] = dp[i-1] + dp[i-2];            
        }
        return dp[n];
    }
};

198. 打家劫舍

子问题:
f(k) = 偷 [0…k) 房间中的最大金额

f(0) = 0
f(1) = nums[0]
f(k) = max{ rob(k-1), nums[k-1] + rob(k-2) }

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size() == 0){
            return 0;
        }
        int n = nums.size();
        vector<int> dp(n+1, 0);
        dp[0] = 0;
        dp[1] = nums[0];
        for(int k=2; k<=n; ++k){
            dp[k] = max(dp[k-1], nums[k-1]+dp[k-2]);
        }
        return dp[n];
    }
};

优化:无需使用数组,仅用两个变量即可。

class Solution {
public:
    int rob(vector<int>& nums) {
        if(nums.size() == 0){
            return 0;
        }
        int prev = 0;
        int curr = 0;
        // 每次循环,计算“偷到当前房子为止的最大金额”
        for(auto i : nums){
            // 循环开始时,curr 表示 dp[k-1],prev 表示 dp[k-2]
            // dp[k] = max{ dp[k-1], dp[k-2] + i }
            int temp = max(curr, prev + i);
            prev = curr;
            curr = temp;
            // 循环结束时,curr 表示 dp[k],prev 表示 dp[k-1]
        }
        return curr;
    }
};
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值