Leetcode hot100 题解(题型分类,包含思路)

数组

121.买卖股票的最佳时机(√)

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0 。

思路:一次遍历

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int maxprice=0;
        int minprice=INT_MAX;
        for(int i=0;i<prices.size();i++){
            if(prices[i]<minprice) minprice=prices[i];
            else maxprice=max(maxprice,prices[i]-minprice);
        }
        return maxprice;
    }
};

1.两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target  的那两个整数,并返回它们的数组下标。你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。你可以按任意顺序返回答案。

思路:哈希表,对于数x,查找数target-x,可用哈希表查找是否在数组中出现过和下标,在哈希表存在,返回下标,在哈希表不存在,把当前数和索引加入哈希表,供后续查找,时间复杂度O(n)

class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        unordered_map<int,int> map;
        for(int i=0;i<nums.size();i++){
            int a=target-nums[i];
            if(map.count(a)) return {map[a],i};    //在哈希表存在,返回下标
            //哈希表不存在,把当前数和索引加入哈希表,供后续查找。
            map[nums[i]]=i;        
        }
        return {};    //记得这个
    }
};

3.无重复字符的最长子串

给定一个字符串 s ,请你找出其中不含有重复字符的 最长子串(字符连续) 的长度。

输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。

思路:滑动窗口 ,每次右端点向右移动时判断是否重复,如果重复则左端点向右移动,数组记录是否出现

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //初始化
        int ref[128]= {0}; // ASCII 编码表一共有 128 个字符
        int a=0,b=0,re=0;
        //滑动窗口
        while(b<s.size()){
            if(ref[s[b]]>0){    //重复
                ref[s[a++]]--;  // 把窗口左边的字符去掉,a++
            }else{
                ref[s[b++]]++;
            }
            re=max(re,b-a);
        }
        return re;
    }
};

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

给你一个含 n 个整数的数组 nums ,其中 nums[i] 在区间 [1, n] 内。请你找出所有在 [1, n] 范围内但没有出现在 nums 中的数字,并以数组的形式返回结果。

思路:原地交换法,通过交换,把数字x放到下标 x-1的位置,即检查nums[i]和nums[nums[i]-1],然后再遍历数组,哪个位置数字不对(nums[i] != i+1),那个位置对应的数字 i+1 就是缺失的。

class Solution {
public:
    vector<int> findDisappearedNumbers(vector<int>& nums) {
        for(int i=0;i<nums.size();i++){
            while(nums[i]!=nums[nums[i]-1]) swap(nums[i],nums[nums[i]-1]);
        }
        vector<int> re;
        for(int i=0;i<nums.size();i++){
            if(nums[i]!=i+1) re.push_back(i+1);
        }
        return re;
    }
};

排序

215.数组中第k个最大元素(√)

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

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

169.多数元素(√)

给定一个大小为 n 的数组 nums ,返回其中的多数元素。多数元素是指在数组中出现次数 大于 ⌊ n/2 ⌋ 的元素。你可以假设数组是非空的,并且给定的数组总是存在多数元素。

思路:排序后多数元素肯定占据中间位置(因为它数量超过一半),直接返回中间那个数即可。

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

位运算

136.只出现一次的数字(√)

给你一个非空整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。你必须设计并实现线性时间复杂度和常数空间复杂度的算法来解决此问题。

思路:异或运算,两个数某一位不同则为1,相同则为0。对所有数执行一次异或操作:成对的数会互相抵消,剩下的那个只出现一次的数就是最终答案。

设一个变量re初始化为0,依次对数组元素做异或操作,最后re的值即为只出现依次的数字

class Solution {
public:
    int singleNumber(vector<int>& nums) {
        int re=0;
        for(int i=0;i<nums.size();i++){
            re^=nums[i];
        }
        return re;
    }
};

461.汉明距离(√)

两个整数之间的 汉明距离 指的是两个数字对应二进制位不同位置的数目。给你两个整数 x 和 y,计算并返回它们之间的汉明距离。

输入:x = 1, y = 4
输出:2
解释:
1   (0 0 0 1)
4   (0 1 0 0)

思路:异或位运算,1的个数即是汉明距离

首先进行异或运算转化为二进制n,然后当n不为0时,通过按位与运算n & n-1 的值统计1的个数

class Solution {
public:
    int hammingDistance(int x, int y) {
        int ret=x^y;    // 步骤1:异或找出不同位
        int ans=0;
        while(ret){     //ret不为0
            ret&=(ret-1);    //n & n-1 的值是去掉二进制n最右边1的值
            ans++;
        }
        return ans;
    }
};

二分

34.在排序数组查找元素的第一个和最后一个出现位置(√)

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。如果数组中不存在目标值 target,返回 [-1, -1]。你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        auto left=lower_bound(nums.begin(),nums.end(),target);  //第一个 ≥ target 的元素位置
        auto right=upper_bound(nums.begin(),nums.end(),target); //第一个 > target
        if(left==right){   
            return {-1,-1};
        }else{
            return {int(left-nums.begin()),int(right-nums.begin())-1};
        }
    }
};

链表

2.两数相加(√)

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

输入:l1 = [2,4,3], l2 = [5,6,4]
输出:[7,0,8]
解释:342 + 465 = 807.

思路:

  • 从两个数的最低位开始(可能是链表尾、数组末尾,或者字符串末尾)

  • 逐位相加,同时加上上一位的进位 carry

  • 当前位等于相加结果的个位数 sum % 10

  • 进位等于相加结果的十位数 sum / 10

  • 两个数走完后,如果还有进位,也要加到结果

while (A没完 or B没完 or 还有进位) {
    x = A当前位值(没位补0)
    y = B当前位值(没位补0)
    sum = x + y + carry
    当前位 = sum % 10
    carry = sum / 10
    结果添加当前位
    A移动到下一位
    B移动到下一位
}

如果carry不为0,添加carry

/**
 * 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* addTwoNumbers(ListNode* l1, ListNode* l2) {
        ListNode*dummy=new ListNode(0);   //创建值为0的头节点
        ListNode*cur=dummy; //创建头指针
        int carry=0;

        while(l1||l2||carry){     //A没完 or B没完 or 还有进位
            int x=(l1?l1->val:0); //la->var是值,条件 ? 条件为真时的值 : 条件为假时的值
            int y=(l2?l2->val:0);
            int sum=x+y+carry;

            carry=sum/10;         
            cur->next=new ListNode(sum%10);
            cur=cur->next;         //carry创建新的

            if(l1) l1=l1->next;    // 如果 l1 还有节点,就往后走
            if(l2) l2=l2->next;
        }
        return dummy->next;
    }
};

21.合并两个有序链表

将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。 

思路:从 l1l2 的头开始,比较两个当前节点的值,谁小,把谁接到结果链表的尾部上,把这个链表的指针往后移动一步,重复上面的过程,直到两个链表都走完

/**
 * 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* list1, ListNode* list2) {
        ListNode*dummy=new ListNode(0);
        ListNode*tail=dummy;        //创建尾指针
        // 当 l1 和 l2 都不为空
        while(list1&&list2){
            if(list1->val<list2->val){
                tail->next=list1;   // 将 l1 当前节点接到 tail 后面
                list1=list1->next;  // l1 指针移动
            }else{
                tail->next=list2;
                list2=list2->next;
            }
            tail=tail->next;        // tail 指针移动
        }
        // 当 l1 或 l2 有不为空
        if(list1) tail->next=list1; // 将 l1 当前节点接到 tail 后面
        if(list2) tail->next=list2;
        return dummy->next;
    }
};

206.反转链表

给你单链表的头节点 head ,请你反转链表,并返回反转后的链表。

思路:双指针

定义两个指针: pre 和 cur ;pre 在前 cur 在后。
每次让 pre 的 next 指向 cur ,实现一次局部反转
局部反转完成之后,pre 和 cur 同时往前移动一个位置
循环上述过程,直至 pre 到达链表尾部

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        ListNode*cur=NULL;      //头节点初始为NULL
        ListNode*pre=head;      //当前正在处理的节点,初始指向链表头
        // 遍历整个链表,直到 pre 为 NULL
        while(pre!=nullptr){
            ListNode*t=pre->next;   //暂存pre的下一个节点
            pre->next=cur;          //将pre的next指向cur
            cur=pre;                //移动cur
            pre=t;                  //移动pre
        }
        return cur;             //返回整个链表的起点
    }
};

160.相交链表

给你两个单链表的头节点 headA 和 headB ,请你找出并返回两个单链表相交的起始节点。如果两个链表不存在相交节点,返回 null 。

思路:双指针,非常巧妙,使用两个指针 pApB 分别从两个链表头开始走,当走到链表末尾时跳到对方的头节点,最终在相交处相遇。

pA 总共走:a + c + b,pB 总共走:b + c + a,它们走的总长度一样,因此会在走完 (a + b + c) 步后,相遇于第一个相交点(如果有),如果没有交点,最终都会走到 NULL,也相等,于是退出循环。

/**
 * 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) {
        ListNode *A = headA;
        ListNode *B = headB;
        while(A!=B){            //不是同一个地址
            if(A!=nullptr){     
                A=A->next;     //如果A不为空,移动A
            }else{
                A=headB;       //如果A为空,跳到链表 B 的开头
            }
            if(B!=nullptr){
                B=B->next;
            }else{
                B=headA;
            }
        }
        return A;      //最后一个到达的节点地址
    }
};

234.回文链表

给你一个单链表的头节点 head ,请你判断该链表是否为回文链表。如果是,返回 true ;否则,返回 false 。

思路:快慢指针找中点 + 原地反转后半段 + 比较两段链表

设置 fast 指针每次走两步,slow 每次走一步,当 fast 到达链表末尾时,slow 正好处于中点。从中点 mid 开始,将后半部分反转;通过两个指针:head 指向原链表头部(前半段),head2 指向反转后的后半段头部比较

动态规划

70.爬楼梯(√)

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

思路:动态规划

class Solution {
public:
    int climbStairs(int n) {
        if(n<=2) return n;      //先判断,避免越界
        vector<int> dp(n+1);    //否则n会报错
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};

647.回文子串(√)

思路:动态规划

1.状态转移

长度 ≤ 2:如果 j - i <= 2(即长度是 1 或 2 或 3)且 s[i] == s[j],那一定是回文

长度 > 2:s[i] == s[j]dp[i+1][j-1] == true

2.递归

因为我们用到 dp[i+1][j-1],所以in-1 到 0(从右往左),ji 到 n-1(从左往右)

class Solution {
public:
    int countSubstrings(string s) {
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));   //初始化
        int ans=0;    //要赋初值
        for(int i=n-1;i>=0;i--){    //i从大到小
            for(int j=i;j<n;j++){   //j大于i
                if(s[i]==s[j]){
                    if(j-i<=2||dp[i+1][j-1]){
                        ans++;
                        dp[i][j]=true;
                    }
                }
            }
        }
        return ans;
    }
};

5.最长回文子串(√)

给你一个字符串 s,找到 s 中最长的回文(字符串向前和向后读都相同)子串(连续)。

思路:和上一个题一样,实时记录子串长度 currLen = j - i + 1,如果 currLen > maxLen,更新最长回文子串起点和长度,用s.substr来截取 s 中最长的回文子串,返回即可。

class Solution {
public:
    string longestPalindrome(string s) {
        int n=s.size();
        vector<vector<bool>> dp(n,vector<bool>(n,false));
        int count=0,maxlen=1,start=0;
        
        for(int i=n-1;i>=0;i--){
            for(int j=i;j<n;j++){
                if(s[i]==s[j]){
                    if(j-i<=2||dp[i+1][j-1]){
                        dp[i][j]=true;
                        count++;

                        int len=j-i+1;
                        if(len>maxlen){
                            maxlen=len;
                            start=i;
                        }
                    }
                }
            }
        }
        return s.substr(start,maxlen);
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值