LeetCode hot 100(复习c++) 1-15

题目一

首先先看数组

一、 std::vector :动态数组容器

vector 是 C++ 标准模板库(STL)中最常用的容器之一,本质上是一个可以动态改变大小的数组biancheng.net+1。

1. 核心特点
  • 动态性:可以在运行时动态地增加或减少元素,自动管理内存biancheng.net+1。
  • 连续内存 :元素在内存中连续存储,支持随机访问(通过下标访问元素效率高,O(1)时间复杂度)biancheng.net
  • 类型安全 :声明时需指定元素类型,如 vector<int> 存整数,vector<string> 存字符串cnblogs.com+1。
2. 常用操作一览

& 符号:引用的奥秘

& 在 C++ 中有两个截然不同的用途,初学者容易混淆。

1. 在声明 中:定义引用

这是你之前代码 vector<int>& nums 中的用法。引用是某个变量的别名(别名)

  • 语法类型 &引用名 = 变量名;
  • 核心特性
    • 必须初始化:定义引用时必须立即指向一个有效变量

    • 不能为空:不存在"空引用"。

    • 一旦绑定,无法改变:引用初始化后,就不能再让它引用其他变量

    • 操作即操作原变量 :对引用的任何操作,都直接作用于它所引用的那个变量

      cpp 复制代码
      int a = 10;
      int &ref = a; // ref是a的引用,即别名
      
      ref = 20;     // 通过引用修改,a的值变为20
      cout << a;    // 输出 20
      2. 在表达式中:取地址运算

      这是C语言遗留的用法,用于获取变量的内存地址。

cpp 复制代码
int a = 10;
int *ptr = &a; // ptr是一个指针,存储了变量a的地址
3. 关键对比:引用 vs 指针

1. 两数之和

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

你可以假设每种输入只会对应一个答案,并且你不能使用两次相同的元素。

你可以按任意顺序返回答案。

示例 1:

复制代码
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。

一 使用暴力法

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
         for(int i = 0; i < nums.size(); i++)
         {
            for(int n = 0; n < nums.size(); n++)
            {
                if(i != n)
                {
                    if(nums[i] + nums[n] == target)
                    {
                        return {i, n};
                    }
                }
            }
         }
         return {};
        
    }
};

反思:把nums.size()赋值后再循环会减少时执行间

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

二 哈希表

第一部分:算法思想("查字典"逻辑)

哪怕换了语言,核心思想是不变的:不要每次都回头找,而是记下来直接查。

想象你在玩拼图,手里拿着一块拼图编号是 num,你需要找另一块编号为 target - num 的拼图。

  • 暴力法:把地上的拼图从头到尾翻一遍,看有没有能配对的。累死你。
  • 哈希表法 :你手边有个小本本(哈希表)。
    1. 捡起一块新拼图 num
    2. 先查小本本:"小本本里有没有写着 target - num 的记录?"
    3. 如果有 -> 恭喜,配对成功!
    4. 如果没有 -> 把这块拼图记在小本本上(记录它的值和位置),然后捡下一块。

核心优化:查小本本(哈希表)的速度极快,几乎不花时间。

第二部分:C++ 核心知识(unordered_map

在 C++ 里,这个"小本本"叫做 unordered_map。这是做这道题必须要会的容器。

1. 头文件

使用它之前,必须包含头文件:

复制代码
#include <unordered_map>

2. 定义与结构

复制代码
unordered_map<int, int> map;

这里有两个 int,是什么意思?

  • 第一个 int (Key) :对应我们题目里的数字。因为我们需要通过"数值"来查找,所以数值做 Key。
  • 第二个 int (Value) :对应我们题目里的下标。因为题目最后要求返回下标,所以把下标存起来。

3. 关键操作(划重点)

  • 存数据
cpp 复制代码
    map[数值] = 下标;
    // 比如:map[7] = 1; 表示数字7在下标1出现过。
  • 查数据
    这是 C++ 最大的坑点。你不能直接问 map[7],因为如果 7 不存在,C++ 会自动创建 一个 7,这会干扰我们的判断。
    必须使用 .find() 函数:
cpp 复制代码
    // 查找 key 为 complement 的元素
    if (map.find(complement) != map.end()) {
        // 找到了!
    }

这里的逻辑是:.find() 返回一个指针(迭代器)。如果这个指针指向了 .end()(末尾),说明没找到;如果没指向末尾,说明找到了。

第三部分:完整代码与逐行解析

cpp 复制代码
class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        // 第一步:构建那个"小本本"
        // Key存数值,Value存下标
        unordered_map<int, int> map;

        // 第二步:遍历数组
        // 这里的 i 就是当前元素的下标
        for (int i = 0; i < nums.size(); i++) {
            int num = nums[i];              // 当前手里的数字
            int complement = target - num;  // 我要找的那个"另一半"

            // 第三步:查小本本
            // map.find(complement) 查找键
            // 如果不等于 map.end(),说明之前有人把自己记下来了,正好是我要找的
            if (map.find(complement) != map.end()) {
                // 找到了!
                // map[complement] 是之前那个"另一半"的下标
                // i 是我自己的下标
                return { map[complement], i };
            }

            // 第四步:没找到,把自己记在小本本上
            // 等以后有人来找 target - num 时,就能找到我了
            // 这一步必须是"先查后存",防止自己匹配自己
            map[num] = i;
        }

        // 兜底返回,虽然题目说一定有解,不写这行编译器可能会报错
        return {};
    }
};

题目二 字母异位词分组

字符串就是动态数组

哈希表相关知识

auto类型

新的for循环

1. for (string& str : strs) vs for (int i = 0; i < n; i++)

这是最明显的变化,也是现代 C++ 的一大亮点。

  • 你的写法(传统)

    复制代码
      for (int i = 0; i < n; i++) {
          string s = strs[i];
          // ...
      }

* 原理 :通过一个索引 i 来遍历数组 strs。你需要手动管理索引 i 的初始化、判断和递增。

* 优点 :非常直观,容易理解。

* 缺点 :代码稍长,容易出错(比如 i 越界)。

  • 新写法(范围 for 循环)

    复制代码
      for (string& str : strs) {
          // ...
      }

* 原理 :这是 C++11 引入的"范围 for 循环"。它会自动遍历 strs 中的每一个元素,并把每个元素依次赋值给 str

* string& str :这里的 &引用 。意思是 str 不是 strs 中元素的副本,而是直接指向 strs 中的那个元素本身。这样做的好处是效率高 ,因为避免了不必要的拷贝。

* 优点 :代码简洁、安全、不易出错。你无需关心数组的长度和索引。

* 注意 :如果不需要修改 strs 中的元素,也可以用 const string& str,这样更安全。

总结for (auto& str : strs) 是现代 C++ 中遍历容器(如 vectorarray)的标准写法,强烈推荐使用。

题目三

给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。

示例 1

输入: strs = ["eat", "tea", "tan", "ate", "nat", "bat"]

输出:[["bat"],["nat","tan"],["ate","eat","tea"]]

解释:

  • 在 strs 中没有字符串可以通过重新排列来形成 "bat"
  • 字符串 "nat""tan" 是字母异位词,因为它们可以重新排列以形成彼此。
  • 字符串 "ate""eat""tea" 是字母异位词,因为它们可以重新排列以形成彼此。
cpp 复制代码
class Solution {
public:
    vector<vector<string>> groupAnagrams(vector<string>& strs) {
        int n = strs.size();
        unordered_map<string, vector<string>> map;
        for(int i = 0; i < n; i++ )
        {
            string s = strs[i];
            string a = s;
            sort(a.begin(),a.end());
            map[a].push_back(s);
            
        }
        vector<vector<string>> result;
        for (auto it = map.begin(); it != map.end(); ++it)
        {
            result.push_back(it -> second);
        }
        return result;

    }
};

逻辑不难,主要是对于哈希图的使用,以及sort函数,先把字符统一化并存储,最后放在一起输出

最长连续序列

第一次写完

cpp 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> map(nums.begin(), nums.end());

        
        for(int i = 0; i < n; i++)
        {
            
            int num = nums[i];
            int a = 0;
            if(!map.count(num - 1))
            {
                while(map.count(num))
                {
                    a++; 
                    num++;
                    if(a > max)
                        max = a;
                }
                
            }
            
        }
        return max;
    }
};

在第80个案例后出错

set会自动帮我筛选掉重复元素,所有不能循环数组而是循环map

cpp 复制代码
class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        unordered_set<int> map(nums.begin(), nums.end());

        int max = 0;
        for(int num : map)
        {
            int a = 0;
            if(!map.count(num - 1))
            {
                while(map.count(num))
                {
                    a++; 
                    num++;
                    if(a > max)
                        max = a;
                }
                
            }
            
        }
        return max;
    }
};

题目一二一 买股票的最佳时间

贪心算法

给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。

你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。

返回你可以从这笔交易中获取的最大利润。如果你不能获取任何利润,返回 0

暴力时间过了

cpp 复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int max = 0;
        for (int i = 0; i < prices.size(); ++i) 
        {
            for (int j = i; j < prices.size(); ++j) 
            {
                if ((prices[j] > prices[i]) && (max < prices[j] - prices [i]))
                {
                        max = prices[j] - prices [i];
                }
            }
        }
        return max;
    }
};

本质是找低点,找到低点就算赚的钱,

cpp 复制代码
class Solution {
public:
    int maxProfit(vector<int>& prices) {
        if (prices.empty()) return 0;
        int minPrice = prices[0];
        int maxProfit = 0;
        for (int i = 1; i < prices.size(); ++i) {
            if (prices[i] < minPrice) {
                minPrice = prices[i];  // 更新历史最低价
            } else {
                maxProfit = max(maxProfit, prices[i] - minPrice);
            }
        }
        return maxProfit;
    }
};

题目五十五 跳跃游戏

给你一个非负整数数组 nums ,你最初位于数组的 第一个下标 。数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个下标,如果可以,返回 true ;否则,返回 false

示例 1:

复制代码
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。

示例 2:

复制代码
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
cpp 复制代码
class Solution {
public:
    bool canJump(vector<int>& nums) {
        int max = nums[0];
        for(int i = 0; i < nums.size() - 1 ; i++)
        {
            if(nums[i] > max)
                max = nums[i];       

            if(max == 0)
                return false;

            max--;   
        }
        return true;
    }
};

贪心算法只需要计算每次能走的步数max即可

题目四十五 跳跃游戏 II

给定一个长度为 n0 索引 整数数组 nums。初始位置在下标 0。

每个元素 nums[i] 表示从索引 i 向后跳转的最大长度。换句话说,如果你在索引 i 处,你可以跳转到任意 (i + j) 处:

  • 0 <= j <= nums[i]
  • i + j < n

返回到达 n - 1 的最小跳跃次数。测试用例保证可以到达 n - 1

示例 1:

复制代码
输入: nums = [2,3,1,1,4]
输出: 2

示例 2:

复制代码
输入: nums = [2,3,0,1,4]
输出: 2
cs 复制代码
class Solution {
public:
    int jump(vector<int>& nums) {
        if(nums.size() == 1)
            return 0;
        int n = 1;
        int i = 0;
        while(nums[i] < nums.size() - i - 1)
        {
            int max = 0;
            int a = nums[i] + i;
            for(int j = i + 1; j <= a; j++)
            {
                if(max < j + nums[j])
                {
                    i = j;
                    max = j + nums[j];
                }
            }
            n++;
        }
        return n;
    }
};

关键点是这个j + nums【j】就是找到max步长里面步长最大的就行

题目763

给你一个字符串 s 。我们要把这个字符串划分为尽可能多的片段,同一字母最多出现在一个片段中。例如,字符串 "ababcc" 能够被分为 ["abab", "cc"],但类似 ["aba", "bcc"]["ab", "ab", "cc"] 的划分是非法的。

注意,划分结果需要满足:将所有划分结果按顺序连接,得到的字符串仍然是 s

返回一个表示每个字符串片段的长度的列表。

示例 1:

复制代码
输入:s = "ababcbacadefegdehijhklij"
输出:[9,7,8]
解释:
划分结果为 "ababcbaca"、"defegde"、"hijhklij" 。
每个字母最多出现在一个片段中。
像 "ababcbacadefegde", "hijhklij" 这样的划分是错误的,因为划分的片段数较少。 
cpp 复制代码
class Solution {
public:
    vector<int> partitionLabels(string s) {
        int last[26];
        int length = s.size();
        for (int i = 0; i < length; i++) {
            last[s[i] - 'a'] = i;
        }
        vector<int> partition;
        int start = 0, end = 0;
        for (int i = 0; i < length; i++) {
            end = max(end, last[s[i] - 'a']);
            if (i == end) {
                partition.push_back(end - start + 1);
                start = end + 1;
            }
        }
        return partition;
    }
};

首先这道题做了很久,因为纠缠于暴力,但其实这题并不难,有的时候没思路不能硬做

这题的关键是找到对应字母的最大end,然后使用max来取最大值,最后通过push增加

基本位运算有 6 种:按位与、按位或、按位异或、按位取反(不常考)、左移和右移,下面将首先介绍5种常考的运算的定义,接着介绍有助于我们做题的相关性质与一些结论:

知识点题单-位运算

常考位运算的定义

  1. 与、或、异或运算
    • 这三种运算都是两数间运算,将两个整数作为二进制数,对每一位逐一运算。
    • 与运算(&):对应位都为 1 时结果为 1,如3 & 5 = 1011 & 101 = 001)。
    • 或运算(|):对应位有一个为 1 时结果为 1,如3 | 5 = 7011 | 101 = 111)。
    • 异或运算(^):对应位不同时结果为 1,如3 ^ 5 = 6011 ^ 101 = 110),异或运算的逆运算是它本身,即a ^ b ^ b = a
  2. 左移和右移运算
    • num << i表示将num的二进制表示向左移动i位,右侧补 0。如1 << 2 = 40001左移 2 位变为0100)。
    • num >> i表示将num的二进制表示向右移动i位,正数左侧补 0,负数左侧补 1(符号位不变)。如4 >> 1 = 20100右移 1 位变为0010)。
    • 移位运算中,右操作数为负值或大于等于左操作数的位数时,行为未定义,如int类型变量aa << -1a << 32都是未定义的。

知识点题单-滑动窗口

算法步骤

  1. 初始化:将窗口的起点设置为数组的第一个元素,然后根据具体问题来初始化窗口。(如果题目是定长窗口的题型,比如给定是长度为k的窗口,那直接取[1,k]作为初始窗口即可)
  2. 枚举右指针:对于每一次枚举,让右指针右移一次,同时维护对应信息(比如窗口和)。
  3. 调整左指针:在初始窗口的基础上,每次右指针右移一次,此时一旦窗口满足条件,就不断右移窗口的左指针,同时维护对应信息(比如窗口和),直到窗口内的元素不再满足题目条件。(对于定长窗口题型,左指针一定只右移一次)
  4. 重复第2步和第3步,直到窗口的右指针超过数组的最后一个元素,统计结果。

算法复杂度证明

由于该算法的左右指针都只会右移,因此从1不断右移移动到n,最多移动2n次,且每次指针移动,都保证对于维护的信息是O(1)更新,因此总复杂度是O(n)。

题型分类

从滑动窗口的类型上区分,主要有定长窗口和不定长窗口两种,二者的区别主要在于:

  1. 对于定长窗口,随着右指针右移一次,左指针一定只右移一次;
  2. 对于不定长窗口,随着右指针右移一次,左指针可能右移多次,需要在满足题意的情况下尽可能的右移;

下面分别举例并且给出模板。

定长窗口场景举例

考虑这样一个题目背景,给定一个整数数组 nums 和一个整数 k,找到一个连续的子数组,其长度为 k,并且其平均值最大。输出最大平均值。(k<len(nums)<1e5)

复制代码
输入
6 4
1 12 -5 -6 50 3
输出
12.75

解释: 最大平均值的子数组是 [12, -5, -6, 50],平均值为 12.75。

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

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

示例 1:

复制代码
输入: s = "abcabcbb"
输出: 3 
解释: 因为无重复字符的最长子串是 "abc",所以其长度为 3。注意 "bca" 和 "cab" 也是正确答案。

示例 2:

复制代码
输入: s = "bbbbb"
输出: 1
解释: 因为无重复字符的最长子串是 "b",所以其长度为 1。
cpp 复制代码
class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        vector<int> S(128, -1);
        int l =0;
        int r = 0;
        int Max = 0;
        while (r < s.size())
        {
            if(S[s[r]] >= l)
                l = S[s[r]] + 1;
            S[s[r]] = r;

            Max = max(Max , r - l + 1);
            r++;
        }
        return Max;
    }
};

注意边界问题

438 找到字符串中所有字母异位词

给定两个字符串 sp,找到 s中所有 p异位词的子串,返回这些子串的起始索引。不考虑答案输出的顺序。

示例 1:

复制代码
输入: s = "cbaebabacd", p = "abc"
输出: [0,6]
解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的异位词。

示例 2:

复制代码
输入: s = "abab", p = "ab"
输出: [0,1,2]
解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的异位词
cpp 复制代码
class Solution {
public:
    vector<int> findAnagrams(string s, string p) {
        if (s.size() < p.size()) return {};
        vector<int> need(26, 0), have(26, 0);
        for (char c : p) need[c - 'a']++;
        vector<int> v;
        int l = 0;
        for(int i = 0; i< p.size(); i++)
        {
            char c = s[i];
            have[c - 'a']++;
        }
        for(int r = p.size() ; r <= s.size(); r++)
        {
            if(need == have)
                v.push_back(l);
            if(r != s.size())
                have[s[r] - 'a']++;
            have[s[l] - 'a']--;
            l++;
        }
        return v;
    }
};

刚开始想使用sort排序,后面感觉不太合适,主要能想到使用数组-a这种就行

知识点题单-二分

二分查找

二分查找,其实就是把查找区间长度从n不断收敛到1的过程,每次区间长度都会收敛一半。

以在一个升序数组a中查找一个数tar为例。每次考察待查找区间的中间元素a[mid],如果a[mid]<tar,那么只需到右侧进一步查找;如果a[mid]>tar,那么只需到左侧进一步查找,如果a[mid]=tar,那么根据题意决定是要结束还是进一步收敛区间。

下面分别以"查得到"和"查不到"举具体的例子:

查得到

a=[1,2,3,4,5],tar=2

经过三轮,L和mid和R聚合到一起,此时已经判断得出是否查找成功。

查不到

a=[1,3,4,5,6],tar=2

在找不到的例子中,可以发现出现了R<L的情况,这肯定是不合理的,因此数组中找不到tar,二分查找的结束条件也就是L<=R

时间复杂度

由于每次查找,都会使得查找区间缩减为原来的一半,即如果查找区间为1024,只需要10次查找即可得到结果。

即不论查找是否成功,该算法的查找次数一定是O(logn)级别,因此时间复杂度为O(logn)。

模板

关于二分查找的模板有很多种,多为区分不同情况来使用不同的模板,个人感觉很容易用错。这里只推荐下面这一种,只有这一套模板,完全不需要担心用错或者记错,相关细节都标注在算法代码中。

cpp 复制代码
int search(int tar) {
    int l=0, r=n - 1, mid, ans = -1;//四个固定变量,其中ans先赋值为非法值
    while(l<=r) {// 模板内固定写法,不需要变
        mid = (l+r)/2;// 模板内固定写法,不需要变
        if(a[mid] == tar) ans = mid;// 找到答案后更新答案,根据具体题目决定是return,还是更改L或R后继续查找
        else if(a[mid] < tar) L = mid+1;// 在L=mid+1或R=mid-1中根据题意选一个,并根据题目自行决定是否需要记录ans
        else R = mid-1;// 在L=mid+1或R=mid-1中根据题意选一个,并根据题目自行决定是否需要记录ans
    }
    return ans;
}

35. 搜索插入位置

给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。

请必须使用时间复杂度为 O(log n) 的算法。

示例 1:

复制代码
输入: nums = [1,3,5,6], target = 5
输出: 2

示例 2:

复制代码
输入: nums = [1,3,5,6], target = 2
输出: 1

示例 3:

复制代码
输入: nums = [1,3,5,6], target = 7
输出: 4
cpp 复制代码
class Solution {
public:
    int searchInsert(vector<int>& nums, int target) {
        int l = 0;
        int r = nums.size() - 1;
        int mid = 0;
        
        while(r >= l)
        {
            mid = (l + r) / 2;
            if(nums[mid] == target)
                return mid;
            else if(nums[mid] > target)
                r = mid - 1;
            else 
                l = mid + 1;
        }

        
        if(nums[mid] > target)
            return mid;
        else
            return mid + 1;
    }
};

标准二分查找,注意nums[mid] > target的时候不是mid-1

74. 搜索二维矩阵

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

给你一个整数 target ,如果 target 在矩阵中,返回 true ;否则,返回 false

示例 1:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true
cpp 复制代码
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int l = 0;
        int mid,a,b;
        int n = matrix[0].size();
        int r = n * matrix.size() - 1;
        
        while(r >= l)
        {
            mid = (r + l) / 2;
            a = (mid) / n;
            b = (mid) % n;
            if(matrix[a][b] == target)
                return true;
            else if(matrix[a][b] > target)
                r = mid - 1;
            else
                l = mid + 1;
            
        }
        return false;
    }
};

除和取余要使用列而不是行

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

给你一个按照非递减顺序排列的整数数组 nums,和一个目标值 target。请你找出给定目标值在数组中的开始位置和结束位置。

如果数组中不存在目标值 target,返回 [-1, -1]

你必须设计并实现时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]

示例 2:

复制代码
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]

示例 3:

复制代码
输入:nums = [], target = 0
输出:[-1,-1]
cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int l = 0;
        int r = nums.size() - 1;
        int mid;
        int first = -1;
        int end  = -1; 

        while(l <= r)
        {
            mid = (l + r) / 2;
            if(nums[mid] == target)
            {
                first = mid;
                r = mid - 1;
            }
            else if(nums[mid] > target)
                r = mid - 1;
            else
                l = mid + 1;
        }
        
        l = 0;
        r = nums.size() - 1;
        while(l <= r)
        {
            mid = (l + r) / 2;
            if(nums[mid] == target)
            {
                end = mid;
                l = mid + 1;

            }
            else if(nums[mid] > target)
                r = mid - 1;
            else
                l = mid + 1;
        }
        int v[] = {first, end};
        return {v[0], v[1]};
    }
};

这道题很夸张,想了会都没思路,最后还是看题解,两次二分,一次找首一次找尾,我看还有找到一个tar然后再扩写的

33. 搜索旋转排序数组

整数数组 nums 按升序排列,数组中的值 互不相同

在传递给函数之前,nums 在预先未知的某个下标 k0 <= k < nums.length)上进行了 向左旋转 ,使数组变为 [nums[k], nums[k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下标 从 0 开始 计数)。例如, [0,1,2,4,5,6,7] 下标 3 上向左旋转后可能变为 [4,5,6,7,0,1,2]

给你 旋转后 的数组 nums 和一个整数 target ,如果 nums 中存在这个目标值 target ,则返回它的下标,否则返回 -1

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [4,5,6,7,0,1,2], target = 0
输出:4
cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int l = 0;
        int r = nums.size() - 1;
        int mid;

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


          if (nums[l] <= nums[mid]) { 
        if (nums[l] <= target && target < nums[mid]) {
            r = mid - 1;
        } else {
            l = mid + 1;
        }
    } else {
       
        if (nums[mid] < target && target <= nums[r]) {
            l = mid + 1;
        } else {
            r = mid - 1;
        }
    }
}

        return -1;
    }
};

开始寻找mid左右两边那边合法(必有一边是按顺序排的),然后从合法的那边裁剪

153. 寻找旋转排序数组中的最小值

已知一个长度为 n 的数组,预先按照升序排列,经由 1n旋转 后,得到输入数组。例如,原数组 nums = [0,1,2,4,5,6,7] 在变化后可能得到:

  • 若旋转 4 次,则可以得到 [4,5,6,7,0,1,2]
  • 若旋转 7 次,则可以得到 [0,1,2,4,5,6,7]

注意,数组 [a[0], a[1], a[2], ..., a[n-1]] 旋转一次 的结果为数组 [a[n-1], a[0], a[1], a[2], ..., a[n-2]]

给你一个元素值 互不相同 的数组 nums ,它原来是一个升序排列的数组,并按上述情形进行了多次旋转。请你找出并返回数组中的 最小元素

你必须设计一个时间复杂度为 O(log n) 的算法解决此问题。

示例 1:

复制代码
输入:nums = [3,4,5,1,2]
输出:1
解释:原数组为 [1,2,3,4,5] ,旋转 3 次得到输入数组。
cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
         int low = 0, high = nums.size() - 1;
        int x = nums[high];
        while (low < high) { // 注意这里没有=了,=的时候直接退出循环得到答案
            int mid = (low + high) / 2;
            if (nums[mid] < x)
                high = mid;  //这里不是常规的 mid-1 是因为此时的mid有可能就是我们要找的m
            else
                low = mid + 1;  //这里和常规一样是 mid+1 是因为此时的mid不可能是m
        }
        return nums[low];
    }
};

更具旋转方式可以看出找到关键最右端数字,因为只要比他大或小就能锁定mid到底在哪,再最小值之左还是右,最后调整mid范围,给出最小值

知识点题单-差分

三、应用场景

差分十分适合多次区间操作,然后单点查询的场景。

又是也会结合前缀和,进行区间操作+离线区间查询的场景

知识点题单-前缀和

应用场景

  1. 需要高效的区间查询场景(包括区间和,区间乘积,区间异或等)
  2. 需要前缀、后缀等维护的情况
相关推荐
艾莉丝努力练剑2 小时前
确保多进程命名管道权限一致的方法
java·linux·运维·服务器·开发语言·网络·c++
alphaTao2 小时前
LeetCode 每日一题 2026/3/9-2026/3/15
算法·leetcode·职场和发展
Kiyra2 小时前
[特殊字符] LeetCode 做题笔记(二):678. 有效的括号字符串
笔记·算法·leetcode
Fcy6482 小时前
与队列有关练习题
算法
TracyCoder1232 小时前
LeetCode Hot100(71/100)——152. 乘积最大子数组
算法·leetcode·职场和发展
Z9fish2 小时前
sse哈工大C语言编程练习44
c语言·c++·算法
特种加菲猫2 小时前
透过源码看本质:list 的模拟实现与核心原理
开发语言·c++
李日灐2 小时前
改造红黑树实现封装 map/set:感受C++ 标准容器的精妙设计与底层实现
开发语言·数据结构·c++·后端·算法·红黑树
李日灐2 小时前
【优选算法1】双指针经典算法题
数据结构·c++·后端·算法·刷题·双指针