【LeetCode】练习习题集【4月 - 7 月】

news2024/11/29 0:36:11

LEETCODE习题集【4月-7月总结】

简单

数组部分

1.重复数

题目:
在一个长度u为 n 的数组 nums 里的所有数字都在 0~n-1 的范围内。数组中某些数字是重复的,但不知道有几个数字重复了,也不知道每个数字重复了几次。请找出数组中任意一个重复的数字
输入:
[2, 3, 1, 0, 2, 5, 3]
输出:2 或 3 
代码:

class Solution {
public:
    int findRepeatNumber(vector<int>& nums) {
        unordered_map<int,int> mp;
        for(int i=0;i<nums.size();i++){
            if(mp.find(nums[i]) != mp.end()) return nums[i]; 
            else mp[nums[i]] ++;
        }
        return -1;

    }
};

9.回文数

题目:
给你一个整数 x ,如果 x 是一个回文整数,返回 true ;否则,返回 false 。

回文数是指正序(从左向右)和倒序(从右向左)读都是一样的整数

image-20230223092628765

思路:
  1. 如果是负数一定不是回文数 直接返回false
  2. 如果是正数,则将其倒序数值计算出来,然后比较和原数值是否相等
  3. 如果是回文数相等返回true 不相等返回false
代码:
class Solution {
public:
    bool isPalindrome(int x) {
        if(x < 0)
            return false;
        long cursor = 0;
        long number = x;
        while(number != 0){
            cursor = cursor * 10 + number % 10;
            number /= 10;
        }
        return (cursor == x);

    }
};

13. 罗马数字转整数

(https://leetcode.cn/problems/roman-to-integer/)

题目:

罗马数字包含以下七种字符: IVXLCDM

image-20230228092901433

例如, 罗马数字 2 写做 II ,即为两个并列的 1 。12 写做 XII ,即为 X + II 。 27 写做 XXVII, 即为 XX + V + II 。

通常情况下,罗马数字中小的数字在大的数字的右边。但也存在特例,例如 4 不写做 IIII,而是 IV。数字 1 在数字 5 的左边,所表示的数等于大数 5 减小数 1 得到的数值 4 。同样地,数字 9 表示为 IX。这个特殊的规则只适用于以下六种情况:

I 可以放在 V (5) 和 X (10) 的左边,来表示 4 和 9。
X 可以放在 L (50) 和 C (100) 的左边,来表示 40 和 90。
C 可以放在 D (500) 和 M (1000) 的左边,来表示 400 和 900。
给定一个罗马数字,将其转换成整数。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/roman-to-integer
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

思路:

无序图标记对应的值

每次取两个字母 如果在无序图内有值则直接添加 没有则加上1个字母的值 因为前一次循环中添加了r的值前一个字母的值 所以后面只需要加上减去前一个字母的值 所以无序图是比题目中少给

MXCIV

代码:
class Solution {
public:
    int romanToInt(string s) {
        unordered_map<string,int>  m ={{"I", 1}, {"IV", 3}, {"IX", 8}, {"V", 5}, {"X", 10}, {"XL", 30}, {"XC", 80}, {"L", 50}, {"C", 100}, {"CD", 300}, {"CM", 800}, {"D", 500}, {"M", 1000}};
        int r = m[s.substr(0,1)];
        for(int i = 1; i < s.size(); ++i){
            string two = s.substr(i-1, 2);
            string one = s.substr(i,1);
            r += m[two] ? m[two] : m[one];
        }
        return r;
    }
};

303.区域和检索-数组不可变

题目:

给定一个整数数组 nums,处理以下类型的多个查询:

计算索引 left 和 right (包含 left 和 right)之间的 nums 元素的 和 ,其中 left <= right
实现 NumArray 类:

NumArray(int[] nums) 使用数组 nums 初始化对象
int sumRange(int i, int j) 返回数组 nums 中索引 left 和 right 之间的元素的 总和 ,包含 left 和 right 两点(也就是 nums[left] + nums[left + 1] + … + nums[right] )

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/range-sum-query-immutable
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

image-20230223103405857

思路:

image-20230223103445783

#include <bits/stdc++.h>
using namespace std;

const int N = 100010;
int a[N];//存放读入数据数组
int s[N];//前缀和数组

int main() {
    int n,m;
    int l,r;
    scanf("%d %d",&n,&m);
    for(int i = 1;i<=n;i++){
        scanf("%d",&a[i]);
        s[i] += s[i-1] + a[i];//预处理前缀和
    }
    for(int i = 1;i<=m;i++){
        scanf("%d %d",&l,&r);
        printf("%d\n",s[r] - s[l-1]);//通过前缀和公式直接访问
    }
    system("pause");
    return 0;
}

代码:
class NumArray {
public:
vector<int> sums ;
    NumArray(vector<int>& nums) {
        int n = nums.size();
        sums.resize(n + 1);
        for(int i = 0; i < n; i++){
            sums[i + 1] = sums[i] + nums[i];
        }

    }
    
    int sumRange(int left, int right) {
        return sums[ right + 1 ] - sums[left];
    }
};

差分数组

背景:

频繁对数组中某个区间加减,求最终结果

算法讲解:

https://blog.csdn.net/qq_31601743/article/details/105352885?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-105352885-blog-121647156.pc_relevant_recovery_v2&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-105352885-blog-121647156.pc_relevant_recovery_v2&utm_relevant_index=2

模板题:

1109. 航班预订统计

题目:

这里有 n 个航班,它们分别从 1 到 n 进行编号。

有一份航班预订表 bookings ,表中第 i 条预订记录 bookings[i] = [firsti, lasti, seatsi] 意味着在从 firsti 到 lasti (包含 firsti 和 lasti )的 每个航班 上预订了 seatsi 个座位。

请你返回一个长度为 n 的数组 answer,里面的元素是每个航班预定的座位总数。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/corporate-flight-bookings
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

image-20230302232537910

思路:
image-20230302232701836
代码:
class Solution {
public:
    vector<int> corpFlightBookings(vector<vector<int>>& bookings, int n) {
        vector<int> nums(n);
        for (auto& booking : bookings){
            nums[booking[0]-1] += booking[2];
            if(booking[1]< n){
                nums[booking[1]] -= booking[2];
            }
        }
        for(int i =1; i< n ; i++){
            nums[i] += nums[i-1];
        }
        return nums;

    }
};

1094. 拼车 下标问题

双指针:(数组)

016:最接近三数之和

题目大意 #

给定一个数组,要求在这个数组中找出 3 个数之和离 target 最近。

解题思路 #

这一题看似和第 15 题和第 18 题很像,都是求 3 或者 4 个数之和的问题,但是这一题的做法和 15,18 题完全不同。

这一题的解法是用两个指针夹逼的方法。先对数组进行排序,i 从头开始往后面扫。这里同样需要注意数组中存在多个重复数字的问题。具体处理方法很多,可以用 map 计数去重。这里笔者简单的处理,i 在循环的时候和前一个数进行比较,如果相等,i 继续往后移,直到移到下一个和前一个数字不同的位置。j,k 两个指针开始一前一后夹逼。j 为 i 的下一个数字,k 为数组最后一个数字,由于经过排序,所以 k 的数字最大。j 往后移动,k 往前移动,逐渐夹逼出最接近 target 的值。

这道题还可以用暴力解法,三层循环找到距离 target 最近的组合。

image-20230417232916209

018 4Sum

题目大意 #

给定一个数组,要求在这个数组中找出 4 个数之和为 0 的所有组合。

解题思路 #

用 map 提前计算好任意 3 个数字之和,保存起来,可以将时间复杂度降到 O(n^3)。这一题比较麻烦的一点在于,最后输出解的时候,要求输出不重复的解。数组中同一个数字可能出现多次,同一个数字也可能使用多次,但是最后输出解的时候,不能重复。例如 [-1,1,2, -2] 和 [2, -1, -2, 1]、[-2, 2, -1, 1] 这 3 个解是重复的,即使 -1, -2 可能出现 100 次,每次使用的 -1, -2 的数组下标都是不同的。

这一题是第 15 题的升级版,思路都是完全一致的。这里就需要去重和排序了。map 记录每个数字出现的次数,然后对 map 的 key 数组进行排序,最后在这个排序以后的数组里面扫,找到另外 3 个数字能和自己组成 0 的组合

image-20230418010816021

027 移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1: 给定 nums = [3,2,2,3], val = 3, 函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。 你不需要考虑数组中超出新长度后面的元素。

示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。

你不需要考虑数组中超出新长度后面的元素。

#

思路

image-20230424225225351

/**
* 相向双指针方法,基于元素顺序可以改变的题目描述改变了元素相对位置,确保了移动最少元素
* 时间复杂度:O(n)
* 空间复杂度:O(1)
*/
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }
};
相关题目推荐
  • 26.删除排序数组中的重复项 √ 第一项是不会改变的 不需要更改
  • 283.移动零 √ 思路就是 直接在后面加0
  • 844.比较含退格的字符串 √ 调用一个新的字符串 不是需要的字符就直接弹出
  • 977.有序数组的平方
vector<int> sortedSquares(vector<int>& nums) {
        int left =0 ;
        int right = nums.size()-1;
        int i = nums.size();
        vector<int> res(i,0);
        while(left <= right){
            i--;
            if(nums[left] + nums[right] < 0){
                res[i] = nums[left] * nums[left];
                left++;
            }else{
                res[i] = nums[right] * nums[right];
                right--;
            }
        }
        return res;
        
    }

043 接雨水

题目大意 #

从 x 轴开始,给出一个数组,数组里面的数字代表从 (0,0) 点开始,宽度为 1 个单位,高度为数组元素的值。如果下雨了,问这样一个容器能装多少单位的水?

image-20230420222637984

075 排颜色

题目大意 #

抽象题意其实就是排序。这题可以用快排一次通过。

解题思路 #

题目末尾的 Follow up 提出了一个更高的要求,能否用一次循环解决问题?这题由于数字只会出现 0,1,2 这三个数字,所以用游标移动来控制顺序也是可以的。具体做法:0 是排在最前面的,所以只要添加一个 0,就需要放置 1 和 2。1 排在 2 前面,所以添加 1 的时候也需要放置 2 。至于最后的 2,只用移动游标即可。

这道题可以用计数排序,适合待排序数字很少的题目。用一个 3 个容量的数组分别计数,记录 0,1,2 出现的个数。然后再根据个数排列 0,1,2 即可。时间复杂度 O(n),空间复杂度 O(K)。这一题 K = 3。

这道题也可以用一次三路快排。数组分为 3 部分,第一个部分都是 0,中间部分都是 1,最后部分都是

image-20230420230709887

DFS暴力枚举

题目大意 #

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。说明:解集不能包含重复的子集。

解题思路 #

  • 找出一个集合中的所有子集,空集也算是子集。且数组中的数字不会出现重复。用 DFS 暴力枚举即可。
  • 这一题和第 90 题,第 491 题类似,可以一起解答和复习。

image-20230420235339078

image-20230420235833331

滑动窗口

给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。

示例:

输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 解释:子数组 [4,3] 是该条件下的长度最小的子数组。

提示:

  • 1 <= target <= 10^9
  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^5

image-20230425221214232

螺旋矩阵

排序算法

选择排序

  • 算法思想 1:贪心算法

每一次决策只看当前,当前最优,则全局最优。注意:这种思想不是任何时候都适用。

  • 算法思想 2:减治思想

外层循环每一次都能排定一个元素,问题的规模逐渐减少,直到全部解决,即「大而化小,小而化了」。运用「减治思想」很典型的算法就是大名鼎鼎的「二分查找」。

  • 优点:交换次数最少

「选择排序」看起来好像最没有用,但是如果在交换成本较高的排序任务中,就可以使用「选择排序」(《算法 4》相关章节课后练习题)。

依然是建议大家不要对算法带有个人色彩,在面试回答问题的时候和看待一个人和事物的时候,可以参考的回答模式是「具体问题具体分析,在什么什么情况下,用什么什么算法」。

class Solution {
public:
    vector<int> sortArray(vector<int>& nums) {
        int length = nums.size();
        int swap=0;
        for(int i=0;i<length;i++){
            int min_tag = i;
            for(int j=i+1;j<length; j++){
                if(nums[j] <= nums[min_tag]){
                    min_tag = j;
                }
            }
            if(min_tag != i){
                swap = nums[i];
                nums[i] = nums[min_tag];
                nums[min_tag]= swap;
            }        
        }
        return nums;
    }  
};

双指针法解决三数之和

  1. 第一步将数组排序
  2. i left right
  3. 和 > 0 right 左移 和 < 0 left 右移 直到left和right相遇为止
  4. class Solution {
    public:
        vector<vector<int>> threeSum(vector<int>& nums) {
            vector<vector<int>> result;
            sort(nums.begin(), nums.end());
            // for(int i = 0; i < nums.size(); i++){
            //     cout << nums[i];
            // }
            
            for(int i = 0; i < nums.size(); i++){
                // 排序之后的第一个数组>0 的情况下 那么之后一定是没办法满足
                if(nums[i] > 0) return result;
                // 不理解为什么后面的匹配前面的值 就是正确的去重
                // if(nums[i] == nums[i+1]){
                //     continue;
                // }
             	// 因为这样前面还是执行 后面一步判断就去重
                if(i > 0 && nums[i] == nums[i - 1]){
                    continue;
                }
                for(int left = i+1,right = nums.size() - 1; left < right;){
                    if(nums[i] + nums[left] + nums[right] > 0){
                        right--;
                    }else if(nums[i] + nums[left] + nums[right] < 0){
                        left++;
                    }else{
                        result.push_back(vector<int>{nums[i] , nums[left], nums[right]});
                        while(right >  left && nums[right] == nums[right - 1]) right--;
                        while(right >  left && nums[left] == nums[left + 1]) left++;
                        right--;
                        left++;
                    }
                }
                
            }
            return result;
        }
    
    };
    

二分查找

题型

二分查找

条件

int a[] = {1,2,3,4,5,6,7,8,9,10}

int a[] = {5,6,7,8,9,10,1,2,3,4}

// 1、数组有序,局部有序

// 2、logn

普通二分查找

1、查target

查第一个target

查最后一个target

2、查大于target的第一个

3、查小于target的第一个

#include <iostream>
#include <vector>
using namespace std;

//二分模板,查找target是否存在
int search(vector<int>&nums, int target)
{
    //step 1
    int l = 0;
    int r = nums.size()-1;
    int mid = 0;//返回值

    //step 2
    while(l<=r)
    {
        mid = l + (r - l)/2;
        // 结果
        if(nums[mid] == target)
        {
            // 结果
            return mid;
        }
        else if(nums[mid] < target)
        {
            //
            l = mid+1;
        }
        else{
            //
            r = mid - 1;
        }
    }
    return -1;
}

//查找第一个target
int searchL(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size() - 1;
    int res = -1; //结果

    while(l<=r)
    {
        int mid = l + (r - l)/2;
        
        
        if(nums[mid] == target)
        {
            res = mid;
            r = mid - 1;
        }
        
        
        else if(nums[mid] < target)
        {
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }
    return res;
}

//查找最后一个target
int searchR(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size();
    int res = -1;

    while(l<=r)
    {
        int mid = l + (r - l)/2;
        if(nums[mid] == target)
        {
            res = mid;
            l = mid + 1;
        }
        else if(nums[mid] < target)
        {
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }
    return res;

}

//查找大于target的第一个
int searchR1(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size() - 1;
    int res = -1;

    while(l <= r)
    {
        int mid = l + (r - l)/2;
        if(nums[mid] > target)
        {
            res = mid;
            r = mid - 1;
        }
        else
        {
            l = mid + 1;
        }
    }

    return res;
}

//查找小于target的第一个
int searchL1(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size() - 1;
    int res = -1;

    while(l <= r)
    {
        int mid = l + (r - l)/2;
        if(nums[mid] < target)
        {
            res = mid;
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }

    return res;
}

//查找小于等于target的第一个
int searchL2(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size() - 1;
    int res = -1;

    while(l <= r)
    {
        int mid = l + (r - l)/2;
        if(nums[mid] <= target)
        {
            res = mid;
            l = mid + 1;
        }
        else
        {
            r = mid - 1;
        }
    }

    return res;
}


//查找大于等于target的第一个
int searchR2(vector<int>&nums, int target)
{
    int l = 0;
    int r = nums.size() - 1;
    int res = -1;

    while(l <= r)
    {
        int mid = l + (r - l)/2;
        if(nums[mid] >= target)
        {
            res = mid;
            r = mid - 1;
        }
        else
        {
            l = mid + 1;
        }
    }

    return res;
}
int main() {
    vector<int>nums{1,2,3,4,5,5,5,5,5,7,9};//
    int target = 5;
    cout<<searchL1(nums,target)<<endl;
    return 0;
}

二分

大于、小于、等于、时间复杂度不能为N^2 、时间复杂度为logn 考虑二分

最大值最小、最小值最大 用二分答案

lower_bound(nums.begin(),nums.end(),val) - nums.begin();//查找 大于等于 val的第一个坐标

upper_bound(nums.begin(),nums.end(),val) - nums.end();//查找 大于 val的第一个坐标

基础模板:

704. 二分查找 √

35. 搜索插入位置 √

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

367. 有效的完全平方数 √

剑指 Offer II 072. 求平方根 √

374. 猜数字大小 √

进阶:

162. 寻找峰值 l < r写法 √

[6355. 统计公平数对的数目]( 没找到

链表部分

链表基础

翻转链表

题意:反转一个单链表。

示例: 输入: 1->2->3->4->5->NULL 输出: 5->4->3->2->1->NULL

思路:

image-20230612111933190

image-20230612111950684

参考题目

206 反转链表

两两交换链表中的节点

思路:

image-20230612212350520

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummyHead = new ListNode(0); // 设置一个虚拟头结点
        dummyHead->next = head; // 将虚拟头结点指向head,这样方面后面做删除操作
        ListNode* cur = dummyHead;
        while(cur->next != nullptr && cur->next->next != nullptr) {
            ListNode* tmp = cur->next; // 记录临时节点
            ListNode* tmp1 = cur->next->next->next; // 记录临时节点

            cur->next = cur->next->next;    // 步骤一
            cur->next->next = tmp;          // 步骤二
            cur->next->next->next = tmp1;   // 步骤三

            cur = cur->next->next; // cur移动两位,准备下一轮交换
        }
        return dummyHead->next;
    }
};

参考题目:

  1. 两两交换链表中的节点

删除链表的倒数第N个节点

思路:

双指针方法 先循环然后再删除

第一步初始化头结点 然后遍历之后的节点 然后fast前移一个指针 为了方便slow可以指向slow前面的节点 可以直接删除

/**
 * 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* removeNthFromEnd(ListNode* head, int n) {
        // 初始化头结点
        ListNode* dummyHead = new ListNode(0);
        dummyHead -> next = head;
        // 当前虚拟头结点指向头结点指针head
        ListNode* fast = dummyHead;
        ListNode* slow = dummyHead;
        // 这里判断当前fast节点必须要在slow节点的n个值前面
        while(n-- && fast!= nullptr){
            fast = fast -> next;
        }
        // 为什么要fast节点往前一个步骤呢? 我不理解
        fast = fast -> next;
        while(fast != nullptr){
            fast = fast -> next;
            slow = slow -> next;
        }
        // 这里是指当前节点自动执行到最后一个节点的位置
        slow -> next = slow -> next -> next;
        ListNode* temp = slow -> next;
        slow -> next = temp -> next;
        delete temp;
        // C++语言必须要释放内存 只有释放了内存才不会内存溢出oom
        

    }
    
};

链表相交

思路

先比较大小长度 然后 对齐全部的长度 判断当前位置是否相等 如果相等 直接返回 不相等 就直接返回

image-20230613194932906

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        // 判断两个指针头部 以及判断两个链表长度
        ListNode* curA = headA;
        ListNode* curB = headB;
        int lenA = 0, lenB =0;
        // 求出链表A的长度
        while(curA != NULL){
            lenA++;
            curA = curA -> next;
        }
        // 求出链表B的长度
        while(curB != NULL){
            lenB++;
            curB = curB -> next;
        }
        // 这一步可以求出链表AB的长度 当前指针指向头部
        curA = headA;
        curB = headB;
        if(lenB > lenA){
            swap(lenA, lenB);
            swap(curA, curB);
        }
        int gap = lenA - lenB;
        while(gap--){
            curA = curA -> next;
        }
        // 这时候curA和curB 在同一起点 (末尾位置对齐)
        while(curA != NULL){
            if(curA == curB){
                return curA;
            }
            curA = curA -> next;
            curB = curB -> next;
        }
        return NULL;   
    }
};

环形链表

双指针: 使用一个 一个快指针 一个慢指针 慢指针走一步 快指针走两步 进入环形链表之后 快指针相当于走到慢指针追着一步一步 当追上的时候 就是确定有环 此时链表开始从头开始 环内节点开始跳动 然后当两者相遇的时候就可以确定此时就是环形链表相遇的入口

class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        ListNode* fast = head;
        ListNode* slow = head;
        while(fast != NULL && fast->next != NULL) {
            slow = slow->next;
            fast = fast->next->next;
            // 快慢指针相遇,此时从head 和 相遇点,同时查找直至相遇
            if (slow == fast) {
                ListNode* index1 = fast;
                ListNode* index2 = head;
                while (index1 != index2) {
                    index1 = index1->next;
                    index2 = index2->next;
                }
                return index2; // 返回环的入口
            }
        }
        return NULL;
    }
};

哈希

总结一下,当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法

但是哈希法也是牺牲了空间换取了时间,因为我们要使用额外的数组,set或者是map来存放数据,才能实现快速的查找

判断两个字符的位置

两个数组的交集

思路: 判断数组有交集 主要是需要判定一种无序结构 unordered_set 输出的每一个元素是唯一的 也就是说输出的结果是去重的

std::set和std::multiset底层实现都是红黑树,std::unordered_set的底层实现是哈希表, 使用unordered_set 读写效率是最高的,并不需要对数据进行排序,而且还不要让数据重复,所以选择unordered_set

哈希表的效率比红黑树的效率高

class Solution {
public:
    vector<int> intersection(vector<int>& nums1, vector<int>& nums2) {
        unordered_set<int> result_set; // 存放结果,之所以用set是为了给结果集去重
        unordered_set<int> nums_set(nums1.begin(), nums1.end());
        for (int num : nums2) {
            // 发现nums2的元素 在nums_set里又出现过
            if (nums_set.find(num) != nums_set.end()) {
                result_set.insert(num);
            }
        }
        return vector<int>(result_set.begin(), result_set.end());
    }
};

快乐数

题目 202

题目中说了会 无限循环,那么也就是说求和的过程中,sum会重复出现,这对解题很重要!

当我们遇到了要快速判断一个元素是否出现集合里的时候,就要考虑哈希法了。

思路:第一步需要求这个数的平方和

求平方和就是除以对应的元素然后直接进行对应的相加

第二部 判断这个sum总和是不是 == 1 如果是等于1的话就是结束的出口

第三部 判断这个数字是不是原来出现过 如果出现过就是无线循环 如果没有出现过 就继续在无序哈希表插入

class Solution {
public:
    int getSum(int n){
        int sum = 0;
        while(n){
            sum += (n % 10) *(n % 10);
            n /= 10;
        }
        return sum;
    }    
    bool isHappy(int n) {
        unordered_set<int> result_set;
        while(true){
            int answer = getSum(n);
            if(answer == 1){
                return true;
            }
            // 如果这个sum曾经出现过 就说明进入无线循环了 并且 无序哈希表记录的不是单个 而是整个数字
            if(result_set.find(answer) != result_set.end()){
                return false;
            }else{
                result_set.insert(answer);
            }
            n = answer;  //这里将n赋予给answer是直接
        }

    }
};

两数之和

image-20230614222808891

Q:为什么需要两边遍历:

A:因为这样的时间复杂度是O^2的 而不是三的

然后对应的

赎金信

题目: https://leetcode.cn/problems/ransom-note/

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        // map数组的空间消耗是比数组大一些的 map需要维护红黑树 以及哈希表 而且还需要做哈希函数
        // 使用record数组记录当前的值
        // 每次n循环判断当前数组是否存在 直接减去 判断小于0 返回false
        int record[26] = {0};
        // 如果ransomNote 的 长度比magzine的长度长的话 直接返回false
        if(ransomNote.length() > magazine.length()){
            return false;
        }
        for(int i = 0; i< magazine.length(); i++){
            record[magazine[i] - 'a']++;
        }
        for(int j = 0 ; j < ransomNote.length(); j++){
            record[ransomNote[j] - 'a']--;
            if(record[ransomNote[j] - 'a'] < 0){
                return false;
            }
        }
        return true;
    }
};

栈与队列

栈与队列的基础操作 用栈实现队列

class MyQueue {
public:
    stack<int> stIn;
    stack<int> stOut;
    MyQueue() {
        // 初始化需要的队列

    }
    
    void push(int x) {
        stIn.push(x);

    }
    
    int pop() {
        if(stOut.empty()){
            while(!stIn.empty()){
                stOut.push(stIn.top());
                stIn.pop();
            }
        }
        int result = stOut.top();
        stOut.pop();
        return result;

    }
    
    int peek() {
        // 返回当前队列的第一个元素
        int res = this-> pop();
        stOut.push(res);
        return res;
    }
    
    bool empty() {
        return stIn.empty() &&  stOut.empty();

    }
};

/**
 * Your MyQueue object will be instantiated and called as such:
 * MyQueue* obj = new MyQueue();
 * obj->push(x);
 * int param_2 = obj->pop();
 * int param_3 = obj->peek();
 * bool param_4 = obj->empty();
 */

一定要学会复用 因为功能相近的函数要抽象出来 不要大量的复制粘贴 很容易出问题

1047删除字符串所有相邻的重复项

class Solution {
public:
    string removeDuplicates(string s) {
        // 入栈比较栈顶和当前元素的值 相等就直接出栈 并且自增
        stack<char> st;
        for(char t:s){
            if(st.empty() || t != st.top()){
                st.push(t);
            }else{
                st.pop();
            }
        }
        // 现在不能直接返回st 因为st是栈值的意思 不能使用
        string result = "";
        // 需要把result的值 把栈里面的值
        while(!st.empty()){
            result += st.top();
            st.pop();   
        }
        // 此时放到result的值是反着的 需要进行翻转一下 c++翻转需要使用reverse
        reverse(result.begin(), result.end());
        return result;
    }
};

做题步骤

第一步: String数组 使用char进行遍历 删除栈顶元素 
第二步: 因为使用stack 但是返回的类型是String的类型
				需要初始化一个string类型 的使用 栈的相关操作 将stack赋予给string
第三步:因为赋予的string类型是反方向的 所以需要进行反转 这样才能使得返回的string类型是对应的
class Solution {
public:
    string removeDuplicates(string s) {
        // 第二种方法直接使用string作为stack 省去第二步骤 和第三步骤的区别
        string result;
        for(char t : s){
            if(result.empty() || result.back() != t){
              result.push_back(t);  
            }else{
                result.pop_back();
            }
        }
        return result;

    }
};

150 逆波兰表达式求值

第一步: 使用longlong 类型 的stack 去处理 需要操作的数据
第二步: 循环判断 如果是字符串 弹出两个数 进行处理
第三步:返回需要的栈顶值
class Solution {
public:
    int evalRPN(vector<string>& tokens) {
        stack<long long> st;
        for(int i = 0; i < tokens.size(); i++){
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                long long num1 = st.top();
                st.pop();
                long long num2 = st.top();
                st.pop();
                if (tokens[i] == "+") st.push(num2 + num1);
                if (tokens[i] == "-") st.push(num2 - num1);
                if (tokens[i] == "*") st.push(num2 * num1);
                if (tokens[i] == "/") st.push(num2 / num1);
            }else{
                st.push(stoll(tokens[i]));
            }

        }
        return st.top();
        

    }
};

二叉树

层次遍历

    void order(TreeNode* cur, vector<vector<int>>& result, int depth){
        if(cur == nullptr) return;
        if(result.size() == depth) result.push_back(vector<int>());
        result[depth].push_back(cur -> val);
        order(cur -> left, result, depth+1);
        order(cur -> right, result, depth+1);
    }


    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>> result;
        int depth = 0;
        order(root, result, depth);
        return result;
    }

对称二叉树

class Solution {
public:
    bool compare(TreeNode* left , TreeNode* right){
        // 第一次判断 判断左右节点是否为空 
        if(left == NULL && right != NULL) return false;
        else if(right == NULL && left != NULL) return false;
        else if(left == NULL && right == NULL) return true;
        // 排除了空节点  再排除 数值不相同的情况
        else if(left -> val != right -> val) return false;
        
        //目前是左右节点相同 此时确定递归下一层的判断
        bool outside = compare(left -> left, right -> right);
        bool inside = compare(left -> right, right -> left);
        bool issame = outside && inside;
        return issame; 
    }
    bool isSymmetric(TreeNode* root) {
        if(root == NULL) return true;
        return compare(root -> left, root -> right);
    }
};

获取树的最大深度

step 1 判断是不是空的 如果是的话 就0

step 2 获取左边的 和 获取右边的

step 3 总 = max(左右 )+1

获取树的最小深度

image-20230724100134569

左叶子之和


class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        // 第一步判断终止条件 左叶子的节点就是在判断当前节点不为0 且当前节点的左孩子和右孩子都是空值
        // 如果根节点是空值的话 那么直接返回return 因为根节点如果是空的值的话 就可以确定当前可以直接返回0
        if(root == NULL) return 0;
        // 第二步骤 判断 如果根节点 既没有左边的叶子结点 也没有右边的叶子结点 那么 判断他的左叶子结点之和 那么根节点的判断也一定是0
        if(root -> left == NULL && root -> right == NULL) return 0;
        // 这里要确定一下 单层递归 的逻辑
        // 这里说明当前的子节点的值不是空值 但是确认了 当前节点的左孩子节点是空值并且右孩子节点也是空值 判断到了左叶子节点 
        // 以下开始进行左叶子结点累加和的操作
        int leftValue =  sumOfLeftLeaves(root -> left);
        if(root -> left && !root -> left -> left && !root -> left -> right){
            leftValue = root -> left -> val;
        }

        int rightValue = sumOfLeftLeaves(root -> right);
        // 这里分别使用递归法 判断出了 左边叶子节点 和 右边叶子结点的值 
        // 那么 需要 判断一下 总和 即可
        int sum = leftValue + rightValue;
        return sum;



    

路径总和

image-20230808110340707

class Solution {
private:
    bool traversal(TreeNode* cur, int count) {
        if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
        if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回

        if (cur->left) { // 左
            count -= cur->left->val; // 递归,处理节点;
            if (traversal(cur->left, count)) return true;
            count += cur->left->val; // 回溯,撤销处理结果
        }
        if (cur->right) { // 右
            count -= cur->right->val; // 递归,处理节点;
            if (traversal(cur->right, count)) return true;
            count += cur->right->val; // 回溯,撤销处理结果
        }
        return false;
    }

public:
    bool hasPathSum(TreeNode* root, int sum) {
        if (root == NULL) return false;
        return traversal(root, sum - root->val);
    }
};

困难

双端队列239. 滑动窗口最大值

单调队列的解析:

https://blog.csdn.net/qq_53268869/article/details/122870945

image-20230703231132304

题解

  1. 第一步 滑动窗口 需要保证双端队列 C++里面有直接使用的deque
    			每次pop 先判断里面的值是不是空的 如果空的 就不弹出 如果不是空的 就比较当前弹出的数值是否等于队列出口元素的数值 如果相等就弹出
    			实现双端队列 一共分为三步
    			第一步: push 确定一下当前value的值和队列尾端的值大小 如果是小的值 就弹出来 如果当前没有逼他小的直接入栈
    			第二步:pop 去顶当前value等于 双端队列的队首 只弹队首 也就是最大值
    			第三步:front 返回当前的最大值
    			
    			
    
class Solution {
            // 第一步: push 确定一下当前value的值和队列尾端的值大小 如果是小的值 就弹出来 如果当前没有逼他小的直接入栈
			// 第二步:pop 去顶当前value等于 双端队列的队首 只弹队首 也就是最大值
			// 第三步:front 返回当前的最大值
private:
    class SinQueue{
        // 单调队列 从小的值到大的值
        public: //不写public默认是private
        deque<int> que;
        void pop(int value){
            if(!que.empty() && que.front() == value){
                que.pop_front();
            }

        }
        void push(int value){
            while(!que.empty() && value > que.back()){
                que.pop_back();
            }
            que.push_back(value);

        }
        int front(){
            return que.front();
        }
    };


public:
    vector<int> maxSlidingWindow(vector<int>& nums, int k) {
        // 第一步: 初始一个单调队列 然后初始化一个结果向量
        // 第二步: 把所有的k值放入单调队列里面进去
        SinQueue que;
        vector<int> result;
        for(int i = 0; i < k; i++){
            que.push(nums[i]);
        }
        // 这里需要先存一下 最大值
        // 第四步 循环移出窗口最前面的元素 然后添加当前的值 每做一步都需要确认当前的最大值
        result.push_back(que.front());
        for(int i = k ; i < nums.size(); i++){
            que.pop(nums[i - k]);
            que.push(nums[i]);
            result.push_back(que.front());
        }
        return result;
    }
}

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络安全—黑客技术【自学】

一、黑客是什么 原是指热心于计算机技术&#xff0c;水平高超的电脑专家&#xff0c;尤其是程序设计人员。但后来&#xff0c;黑客一词已被用于泛指那些专门利用电脑网络搞破坏或者恶作剧的家伙。 二、学习黑客技术的原因 其实&#xff0c;网络信息空间安全已经成为海陆空之…

Linux驱动——input子系统

一、input子系统基本框架 Linux内核为了两个目的&#xff1a; 简化纯输入类外设&#xff08;如&#xff1a;键盘、鼠标、游戏杆、轨迹球、触摸屏。。。等等&#xff09;的驱动开发 统一输入类外设产生的数据格式&#xff08;struct input_event&#xff09;&#xff0c;更加方…

将你的计算机变身为果园守护者:与本地树莓派建立连结的乐趣探索!!

如何连接本地树莓派 文章目录 如何连接本地树莓派前言1. 操作流程2. 打开树莓派SSH功能3. 确认树莓派信息后 安装相应SSH客户端 &#x1f341; 小结 &#x1f341; 前言 树莓派作为一款以教育为目的推出的硬件系统&#xff0c;也是超低功耗的微型“准系统”&#xff0c;能够提…

Redis的RDB持久化

Redis是一个键值对数据库服务器&#xff0c;服务器中通常包含着任意个非空数据库&#xff0c;而每个非空数据库中又可以包含任意个键值对&#xff0c;为了方便起见&#xff0c;我们将服务器中的非空数据库以及它们的键值对统称为数据库状态。 举个例子&#xff0c;下图展示了一…

模仿学习(行为克隆,逆强化学习,生成式对抗模仿学习)

目录 1.模仿学习 1.1先说强化学习 1.2再说逆强化学习 1.3最后说生成对抗模仿学习 1.3.1先说GAN 1.3.2再说生成对抗模仿学习 1.4逆强化学习常用方法 参考文献 1.模仿学习 定义&#xff1a;当我们想训练机器人时&#xff0c;通过复制人类的动作&#xff0c;对机器人进行训…

ssm新生报到系统源码和论文PPT

ssm新生报到系统源码和论文PPT004 开发环境 开发工具&#xff1a;idea 数据库mysql5.7(mysql5.7最佳) 数据库链接工具&#xff1a;navcat,小海豚等 开发技术&#xff1a;java ssm tomcat8.5 选题依据及意义 每学年九月份随着新生的入校&#xff0c;学校工作中的重点都会集…

Endnote中导入和显示中文文献国标格式具体实现步骤

Endnote中导入和显示中文文献国标格式具体实现步骤 目录 Endnote中导入和显示中文文献国标格式具体实现步骤一、下载中文文献引用标准格式二、将下载的两种格式复制到Endnote安装文件下的Styles子文件夹中三、对导入的样式进行标记&#xff0c;以便在word中方便显示和使用四、在…

领航优配:美联储货币政策风向或已生变 私行纵论下半年黄金配置价值

美联储钱银方针风向或已生变&#xff0c;黄金的拥趸开端跃跃欲试。 费城联储主席帕特里克哈克周二标明&#xff0c;美国有一条通往经济软着陆的路径&#xff0c;失业率或许有所上升&#xff0c;但起伏不会太大&#xff1b;不希望美联储过度收紧钱银方针&#xff0c;估计明年某时…

springBoot整合RabbitMq实现手动确认消息

如何保证消息的可靠性投递&#xff1f; 1.保证生产者向broke可靠性投递&#xff0c;开启ack投递成功确认&#xff0c;如果失败的话进行消息补偿 /*** author yueF_L* date 2023-08-10 01:32* ConfirmCallback&#xff1a;消息只要被 RabbitMQ broker 接收到就会触发confirm方…

OpenAI-Translator 实战总结

最近在极客时间学习《AI 大模型应用开发实战营》&#xff0c;自己一边跟着学一边开发了一个进阶版本的 OpenAI-Translator&#xff0c;在这里简单记录下开发过程和心得体会&#xff0c;供有兴趣的同学参考 功能概览 通过openai的chat API&#xff0c;实现一个pdf翻译器实现一个…

牛客小白月赛75C题题解

文章目录 方豆子问题建模问题分析代码 方豆子 问题建模 给定一个整数n&#xff0c;要求输出n级好豆豆&#xff0c;n级好豆豆由3个n-1级好豆豆和1个n-1级坏豆豆组成&#xff0c;已经给出了1级好豆豆和1级坏豆豆。 问题分析 由于最终的豆豆是由其上一级的豆豆产生的&#xff0…

将应用设置成系统App/获取Android设备SN号

1&#xff0c;和系统签名一致&#xff1b;&#xff08;签名设置签名文件&#xff09; 2&#xff0c;配置Manifest 至此你的App就是一个系统App了&#xff0c;可以执行一些系统App才能有的操作&#xff0c;如获取机器SN号&#xff1a; public String getSerialNumber() {Strin…

如何系统的学习单片机?

一、学习单片机需要的一些基础知识 &#xff08;1&#xff09;要具有一些模电、数电的知识&#xff08;不一定要精通&#xff0c;但基本至少要知道&#xff09; &#xff08;2&#xff09;具备C语言基础&#xff0c;有基础就可以入门了。数据结构、设计模式、汇编这些&#x…

IAR目标代码4字节对齐

向工程添加文件 eof.c : // 文件头 #if defined(__CC_ARM) // MDK // uint32_t g_update_flag[2] __attribute__((zero_init, at(0x1000FFF0)));const unsigned long gc_eof __attribute__((used)) 0xFFFFFFFFul; #elif defined(__ICCARM__) // IAR__root const unsigned…

【ur3机械臂配置夹爪Robotiq2F-85】

【ur3机械臂配置夹爪Robotiq2F-85】 1. 前言2. 按照官网要求清点安装设备3. 在示教器上配置夹爪3.1 另一头连接好夹爪和缆线3.2 下载官网驱动 5. 总结 1. 前言 欢迎大家阅读2345VOR的博客【ur3机械臂配置夹爪Robotiq2F-85】&#x1f973;&#x1f973;&#x1f973;2345VOR鹏鹏…

【100天精通python】Day31:使用python操作数据库_数据库编程接口,连接对象和游标对象,数据库连接配置

专栏导读 专栏订阅地址&#xff1a;https://blog.csdn.net/qq_35831906/category_12375510.html 数据库编程接口是用于连接、操作和管理数据库的一组函数、类和方法。不同类型的数据库可能有不同的编程接口。以下是一些常见的数据库编程接口&#xff1a; 一、数据库编程接口 …

Android AOSP源码编译——AOSP下载(一)

一、电脑配置 Ubuntu16.04 16G&#xff0c;硬盘的大小最好大于300G (我这边是找了个win电脑装了双系统 没有使用虚拟机的方式) 二、基础环境配置 1、安装git sudo apt install git配置git email和name git config --global user.email "youexample.com" git conf…

大环境下,企业如何找到大数据传输解决方案

随着网络技术的进步和智能设备的普及&#xff0c;大数据成为了当下社会的热点话题。但是&#xff0c;大数据的传输和处理仍然存在着很多的困难和挑战。在大数据传输中&#xff0c;如何解决数据量巨大、网络带宽不足、传输时间过长和数据安全等问题&#xff0c;是当前亟待破解的…

zmibra ssl证书替换

一、阿里云上申请证书 选择其他证书 二、证书上传及制作 首先当然要去阿里云申请的ssl证书下载其他类型证书,解压缩后有两个文件上传到相应目录, 文件名类似18131-mail.xxx.comkey和181111-mail.xxx.com.pem 创建目录 /opt/zimbra/ssl/aliyunssl/ 上传这两个文件 以上是…

JVM 查看配置 jinfo 及使用 jstat,查看堆栈jstack及GC

1. Jinfo 查看正在运行的Java应用程序的扩展参数: 包含 JVM 参数与 java 系统参数 命令&#xff1a; jinfo pid 2 jstat 查看堆内存使用情况及 GC 回收频率等&#xff1a; jstat [-命令选项] [vmid] [间隔时间(毫秒)] [查询次数] 2.1 jstat -gc pid 最常用&#xff0c;可…