leetcode热题100(持续更新中)


一、两数之和

难度:简单
题目:
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
https://leetcode-cn.com/problems/two-sum/

我的题解:

点击查看代码
//时间复杂度为O(N^2)
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
    vector<int> a;
    int i,j;
    int n=nums.size();
    for(i=0;i<n;i++){
        for(j=1;j<n;j++){//优化:for(j=i+1;j<n;j++){ //排除了i==j的情况,减少遍历次数
            if(nums[i]+nums[j]==target&&i!=j){
                a.push_back(i);
                a.push_back(j);
                return a;
            }
    }
    }
    return a;
    }
};
时间复杂度更小的官方题解:
点击查看代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int, int> hashtable;
        for (int i = 0; i < nums.size(); ++i) {
            auto it = hashtable.find(target - nums[i]);//find()返回一个指向target - nums[i]的迭代器
            if (it != hashtable.end()) {
                return {it->second, i};//返回特定键所对的值
            }
            hashtable[nums[i]] = i;//使用[ ]进行单个插入,括号内为特定键,若已存在键值,则赋值修改,若无则插入。
        }
        return {};
    }
};
总结: 1.break只能跳出一层循环。

2.使用哈希表,可以将查找的时间复杂度降低到从O(N)降低到O(1)。

3.C++中有关Map的操作。
map: #include < map >
unordered_map: #include < unordered_map >
map: map内部实现了一个红黑树(红黑树是非严格平衡二叉搜索树,而AVL是严格平衡二叉搜索树),红黑树具有自动排序的功能,因此map内部的所有元素都是有序的,红黑树的每一个节点都代表着map的一个元素。因此,对于map进行的查找,删除,添加等一系列的操作都相当于是对红黑树进行的操作。map中的元素是按照二叉搜索树(又名二叉查找树、二叉排序树,特点就是左子树上所有节点的键值都小于根节点的键值,右子树所有节点的键值都大于根节点的键值)存储的,使用中序遍历可将键值按照从小到大遍历出来。
unordered_map: unordered_map内部实现了一个哈希表(也叫散列表,通过把关键码值映射到Hash表中一个位置来访问记录,查找的时间复杂度可达到O(1),其在海量数据处理中有着广泛应用)。因此,其元素的排列顺序是无序的。

C++ Map常见用法说明:

点击查看代码
#include <iostream>  
#include <unordered_map>  
#include <map>
#include <string>  
using namespace std;  
int main()  
{  
	//注意:C++11才开始支持括号初始化
    unordered_map<int, string> myMap={{ 5, "张大" },{ 6, "李五" }};//使用{}赋值
    myMap[2] = "李四";  //使用[ ]进行单个插入,若已存在键值2,则赋值修改,若无则插入。
    myMap.insert(pair<int, string>(3, "陈二"));//使用insert和pair插入
  
	//遍历输出+迭代器的使用
    auto iter = myMap.begin();//auto自动识别为迭代器类型unordered_map<int,string>::iterator
    while (iter!= myMap.end())
    {  
        cout << iter->first << "," << iter->second << endl;  
        ++iter;  
    }  
	
	//查找元素并输出+迭代器的使用
    auto iterator = myMap.find(2);//find()返回一个指向2的迭代器
    if (iterator != myMap.end())
	    cout << endl<< iterator->first << "," << iterator->second << endl;  
    system("pause");  
    return 0;  
}  

二、合并两个有序链表

难度:简单
题目:将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
https://leetcode-cn.com/problems/merge-two-sorted-lists/
递归解法:
之所以适合用到递归是因为题目的思想是找到最小的节点,连接次小的节点;这个不断寻找最小值的过程适合用递归去实现。

点击查看代码
/**
 * 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==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(l2->next,l1);
    return l2;
}

    }
};

迭代解法:
思路
使用 dummy->next 来保存需要返回的头节点。
判断 l1 和 l2 哪个更小,就把这个节点接到下一个。
使用指向指针的指针 pp 用来存储更小的一边的指针。
在帮助 dummy 连接之后,还可以控制更小的 l1 或 l2 向后移动。
直到有一边为 nullptr ,即可将另一边剩余的都接上。

点击查看代码
    ListNode* mergeTwoLists(ListNode* l1, ListNode* l2) {
        ListNode* dummy = new ListNode(0);
        ListNode* cur = dummy;
        while (l1 != nullptr && l2 != nullptr) {
            ListNode** pp = (l1->val < l2->val) ? &l1 : &l2;
            cur->next = *pp;
            cur = cur->next;
            *pp = (*pp)->next;
        }
        cur->next = (l1 == nullptr) ? l2 : l1;

        ListNode* ans = dummy->next;
        delete dummy;
        return ans;
    }

总结:注意判空,递归还是很不熟悉,链表指针也很不熟悉。

三、整数反转

难度:简单
题目:你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。
https://leetcode-cn.com/problems/reverse-integer/
我的思路:将整数转换为字符串,再将字符串反转。
更好的解题思路:
每次取末尾数字:x%10
取完后将x去掉末尾数字:x/10

点击查看代码
class Solution {
public:
    int reverse(int x) {
        int res = 0;
        while(x!=0) {
            //每次取末尾数字
            int tmp = x%10;
            //判断是否 大于 最大32位整数
            if (res>214748364 || (res==214748364 && tmp>7)) {
                return 0;
            }
            //判断是否 小于 最小32位整数
            if (res<-214748364 || (res==-214748364 && tmp<-8)) {
                return 0;
            }
            res = res*10 + tmp;
            x /= 10;
        }
        return res;
    }

};

四、有效的括号

难度:简单
题目:给你一个 32 位的有符号整数 x ,返回将 x 中的数字部分反转后的结果。如果反转后整数超过 32 位的有符号整数的范围 [−231,  231 − 1] ,就返回 0。
用到主要的方法:栈,map
思路:
1.若字符数串为单数可直接判断是错误的。
2.括号匹配的查找:运用map形成键值对;
3.将字符串按单个字符进行遍历 for (char ch: s)
4.建立栈: stack<栈元素类型> stk; 若为左括号则压栈,若为右括号,栈空则判断错;栈不空,取栈顶元素看是否与当前字符对应的另一半括号匹配;若匹配,栈顶元素出栈;
5.若栈为空,表达式正确。

点击查看代码
class Solution {
public:
    bool isValid(string s) {
        int n = s.size();
        if (n % 2 == 1) {
            return false;
        }

        unordered_map<char, char> pairs = {
            {')', '('},
            {']', '['},
            {'}', '{'}
        };
        stack<char> stk;
        for (char ch: s) {
            if (pairs.count(ch)) {
                if (stk.empty() || stk.top() != pairs[ch]) {// pairs[ ]:用于通过键查找对应值
                    return false;
                }
                stk.pop();
            }
            else {
                stk.push(ch);
            }
        }
        return stk.empty();
    }
};

四、爬楼梯

难度:简单
题目:假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?注意:给定 n 是一个正整数。
方法:动态规划
我的题解:

点击查看代码
class Solution {
public:
    int climbStairs(int n) {
    int f[n+1];
    f[0]=1;
    f[1]=2;
    for(int i =2;i<n;i++){
        f[i]=f[i-1]+f[i-2];
    }
return f[n-1];
    }
};

五、最大子序和

难度:简单
题目:给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
方法:动态规划
难点在于转移方程的构建:f(i)表示以第i个数结尾的最大子序和;f(i)=max{f(i-1)+nums[i],nums[i]};
我的题解:

点击查看代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n=nums.size();
int f[n];
f[0]=nums[0];
for(int i=1;i<n;i++){
f[i]=max(f[i-1]+nums[i],nums[i]);
}
int answer=-2147483648;
for(int i=0;i<n;i++){
answer=max(f[i],answer);
}
return answer;
    }
};

六、二叉树的中序遍历

难度:简单
题目:给定一个二叉树的根节点 root ,返回它的中序遍历。
方法:递归

点击查看代码
/**
 * 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:
 void inorder(TreeNode* root,vector<int> &answer){
         if(!root){return; }
         inorder(root->left, answer);
        answer.push_back(root->val);
        inorder(root->right, answer);
 }
    vector<int> inorderTraversal(TreeNode* root) {
          vector<int> answer;
          inorder(root,answer);
          return answer;   
    }
    
};

总结:1.这是一个简单基础的算法,必须掌握背诵。
2.C++中vector<int>& nums和vector<int> nums的区别:
当vector当作形参输入到函数时,有两种方法:
vector & a;(这种形式只能出现在形参中)
vector a;
带&表示传入函数的是vector的引用(即物理位置),函数内部对vector改动,vector就会改变;
不带&表示传入的是vector的复制品(开辟了另一块位置),函数内部对其改动,不会影响原本的vector;

七、盛水最多的容器

难度:中等
标签:Array
题目:Given n non-negative integers a1, a2, ..., an , where each represents a point at coordinate (i, ai). n vertical lines are drawn such that the two endpoints of the line i is at (i, ai) and (i, 0). Find two lines, which, together with the x-axis forms a container, such that the container contains the most water.
Notice that you may not slant the container.
Example 1:

Input: height = [1,8,6,2,5,4,8,3,7]
Output: 49
Explanation: The above vertical lines are represented by array [1,8,6,2,5,4,8,3,7]. In this case, the max area of water (blue section) the container can contain is 49.
https://leetcode.com/problems/container-with-most-water/
方法:双指针
我的题解:两层循环(超时了)

点击查看代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int max_area=0;
        int i,j;
        int n=height.size();
        int longth=0;
        for(i=0;i<n;i++){
            for(j=i+1;j<n;j++){
            longth=min(height[i],height[j]);
                if((j-i)*longth>max_area){
                    max_area=(j-i)*longth;
                }
        }
        }
        return max_area;
    }
}

官方题解:

点击查看代码
class Solution {
public:
    int maxArea(vector<int>& height) {
        int l = 0, r = height.size() - 1;
        int ans = 0;
        while (l < r) {//当两指针重合循环结束
            int area = min(height[l], height[r]) * (r - l);//height[l], height[r]:最开始两个指针分别指向数组的两头
            ans = max(ans, area);//取每次以双指针为左右边界(也就是「数组」的左右边界)计算出的容量中的最大值
            if (height[l] <= height[r]) {//若左指针的值比右指针小,将左指针向右移动;若右指针的值比左指针大,将右指针向左移动
                ++l;
            }
            else {
                --r;
            }
        }
        return ans;
    }
};

时间复杂度:O(N)O(N),双指针总计最多遍历整个数组一次。
空间复杂度:O(1)O(1),只需要额外的常数级别的空间。

总结:这道题最优的做法是使用「双指针」,但是怎么能看出用双指针最优呢???
双指针代表的是 可以作为容器边界的所有位置的范围。在一开始,双指针指向数组的左右边界,表示 数组中所有的位置都可以作为容器的边界,因为我们还没有进行过任何尝试。在这之后,我们每次将 对应的数字较小的那个指针 往 另一个指针 的方向移动一个位置,就表示我们认为 这个指针不可能再作为容器的边界了。

七、三数之和

难度:中等
标签:Array
题目:Given an integer array nums, return all the triplets [nums[i], nums[j], nums[k]] such that i != j, i != k, and j != k, and nums[i] + nums[j] + nums[k] == 0.Notice that the solution set must not contain duplicate triplets.
https://leetcode.com/problems/3sum/
方法:双指针
当我们需要枚举数组中的两个元素时,如果我们发现随着第一个元素的递增,第二个元素是递减的,那么就可以使用双指针的方法,将枚举的时间复杂度从O(N^2)减少至O(N)。

解题思路:
1.除去重复:因为不能重复,所以将数组排序,使得枚举出的组合为(a,b,c)(a>=b>=c),保证了只有 (a, b, c)(a,b,c) 这个顺序会被枚举到,而 (b, a, c)(b,a,c)、(c, b, a)(c,b,a) 等等这些不会,这样就减少了重复。
2.跳出三重循环的大框架:
如果我们固定了前两重循环枚举到的元素 a 和 b,那么只有唯一的 c 满足 a+b+c=0。当第二重循环往后枚举一个元素b'时,由于 b' > b ,那么满足 a+b'+c'=0,a+b′+c'=0 的 c' 一定有 c' < c,即 c' 在数组中一定出现在c的左侧。也就是说,我们可以从小到大枚举b,同时从大到小枚举c,即第二重循环和第三重循环实际上是并列的关系。(双指针)有了这样的发现,我们就可以保持第二重循环不变,而将第三重循环变成一个从数组最右端开始向左移动的指针。

点击查看代码
class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        vector<vector<int>> answer;
        int n=nums.size();
        int sum=0;
        int l=0;
        sort(nums.begin(), nums.end());
        //枚举a
        for(int i=0;i<n;i++){
            //减少重复枚举
            if (i > 0 && nums[i] == nums[i - 1]) {
                continue;
            }
            int k=n-1;
            int target = -nums[i];
            for (int j = i + 1; j < n; ++j) {
                // 需要和上一次枚举的数不相同
                if (j > i + 1 && nums[j] == nums[j - 1]) {
                    continue;
                }
                // 需要保证 b 的指针在 c 的指针的左侧
                while (j < k && nums[j] + nums[k] > target) {
                    --k;
                }
                // 如果指针重合,随着 b 后续的增加
                // 就不会有满足 a+b+c=0 并且 b<c 的 c 了,可以退出循环
                if (j == k) {
                    break;
                }
                if (nums[j] + nums[k] == target) {
                    answer.push_back({nums[i], nums[j], nums[k]});
                }
        }
        }
        return answer;
    }
};


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM