diama训练营

DAY1

数组在内从空间的地址是连续的。

二维数组在地址空间上时连续的。

int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2

------------------------------------------------------------------------------------------------------------------

erase时间复杂度O(n)

数组的元素是不能删除的,只能覆盖。相当于删除一个元素,就要将后面每一个元素向前进行覆盖

双指针法:快指针指向新数组所需要的元素。慢指针 新数组的下标值。

外层for循环完成快指针移动的操作。快指针寻找到新数组所需要的元素之后,要给新数组对应的下标(慢指针)进行赋值。

当快指针所指向的元素不等于所查找的元素时,进行赋值。赋值后慢指针也应向后移动一个位置,即slow++。

1、二分

注意点:1.mid的初始化,int mid = l+(r-l)/2;

               2.第一次更新边界时 r = mid -1的原因;

               3.在整个数组访问完后未找到才return -1;

               4.容器的定义,vector<数据类型>容器名;

               5.闭区间写法时间复杂度O(log n),空间复杂度O(1);

               6.在C++中,将容器(如vector<int>)作为函数参数时,如果不使用引用传递,容器会被拷贝一份传给函数。对于大型容器,这种拷贝操作可能非常昂贵,既消耗大量内存也影响程序执行效率。通过使用引用传递,函数内部使用的将是原始容器的引用,而不是其副本。这样,无论容器有多大,传递的成本都是恒定的,大大提高了效率。 在二分查找的参数列表中的vector<int>& nums意味着nums是对原始vector的引用。这样做可以避免了将整个vector复制给函数,特别是当vector很大时,这种效率的提高尤为明显;不需要额外的内存来存储容器的副本,这对于内存使用较敏感的应用尤其重要;直接作用于原始数据,如果函数内部需要修改容器,这些修改会直接反映到原始容器上。虽然在二分查找的场景下不需要修改容器,但这一点在其他使用引用传递的情景下非常重要。


class Solution {
public:
    int search(vector<int>& nums, int target) {
        int left = 0;
        int right = nums.size() - 1; // 定义target在左闭右闭的区间里,[left, right]
        while (left <= right) { // 当left==right,区间[left, right]依然有效,所以用 <=
            int middle = left + ((right - left) / 2);// 防止溢出 等同于(left + right)/2
            if (nums[middle] > target) {
                right = middle - 1; // target 在左区间,所以[left, middle - 1]
            } else if (nums[middle] < target) {
                left = middle + 1; // target 在右区间,所以[middle + 1, right]
            } else { // nums[middle] == target
                return middle; // 数组中找到目标值,直接返回下标
            }
        }
        // 未找到目标值
        return -1;
    }
};


2、删除元素

方法一:暴力解法

erase时间复杂度O(n)

// 时间复杂度:O(n^2)
// 空间复杂度:O(1)
class Solution {
public:
    int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for (int i = 0; i < size; i++) {
            if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
                for (int j = i + 1; j < size; j++) {
                    nums[j - 1] = nums[j];
                }
                i--; // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位
                size--; // 此时数组的大小-1
            }
        }
        return size;

    }
};

方法二:双指针法

先定义一个快指针、慢指针。快指针寻找新数组的元素,慢指针指向更新新数组下标的位置

class Solution{
public:
    int removEelement(vector<int>&nums,int val)
    {
       int slow = 0;
       for(int fast = 0;fast<nums.size();fast++)
       {
           if(nums[fast]!=val)
           {
               nums[slow] = nums[fast];
               slow++;
           }
       }
       return slow;
}

DAY2

sort(A.begin(), A.end()); // 快速排序

vector<int> result(A.size(), 0); 用于创建一个名为 result 的整数型向量(vector),其元素数量与另一个向量 A 的大小相同,并且所有元素的初始值都设定为 0。

for(int i = 0,j = nums.size()-1;i<=j; ) 此处为i<=j而非i<j,因为如果是i<j,即在i=j时循环退出,但是会将i=j处的元素漏掉。

只有取了最大值的操作后,才做i++或j---的操作。所以该部分不写在循环内。

-----------------------------------------------------------------------------------------------

当终止位置固定,集合内元素>=s时,收集该长度后,起始位置就可以向后移动,来缩小目前集合的长度,来看下一个集合是否符合要求

result当前的值和收集的subl取一个最小值,这样result才能持续更新总和>=s中的最小长度。

result初始为最大值int result = INT32_MAX;

sum表示当前滑动窗口里所有元素的集合,起始位置向后移动之后,sum应将目前nums[i]减去,即sum = sum - nums[i];此时,i++,q起始位置向后移动一位

由于起始位置i是持续向后移动的过程,直至滑动窗口内的值小于s停止,所以循环用while

1.有序数组的平方(双指针)

方法一:暴力解法

直接取平方后使用sort排序,时间复杂度O(n+logn)。

class Solution{
public:
    vector<int> sortedsquares(vector<int>& nums){
        int size = nums.size()-1;
        for(int i = 0;i<size;i++){
            nums[i]*=nums[i];
        }
        sort(nums.begin(),nums.end());
        return nums;
    }
};

方法二:双指针法

两个指针向中间移动,将平方较大的结果幅值给新数组

class Solution{
public:
    vector<int> sortedsquares(vector<int>&nums){
        vector<int>result(nums.size(),0);//创建新数组,其大小与nums相同,所有元素初始值都设为0
        int k = nums.size()-1;//新数组的最后一位索引
        for(int i = 0,j = nums.size()-1;i<=j; ){
            if(nums[i] * nums[i] < nums[j] * nums[j]){
                result[k--] = nums[j] * nums[j];
                j--;}
            else{
                result[k--] = nums[i] * nums[i];
                i++;
            }
        }
        return result;
};

2.长度最小的子数组

滑动窗口:

for循环内的j指向滑动窗口的终止位置

result是最终取的最小的长度

sum表示当前滑动窗口内所有元素的集合

起始位置向后移动之后,需要将起始位置的值减去

class Solution {
public:
    int minSubArrayLen(int target, vector<int>& nums) {
        int result = INT32_MAX;
        int sum = 0;//数组之和
        int subl = 0;//滑动窗口的长度
        int i = 0;//滑动窗口的起始位置
        for(int j = 0;j<nums.size();j++)
        {
            sum = sum+nums[j];
            while(sum>=target)
            {
                //取子序列的长度
                subl = j-i+1;

                result = result<subl?result:subl;
                sum = sum-nums[i];
                i++;
            }

        }
        //如果result没有被赋值的话返回0,表示未找到长度大于target的序列
        return result==INT32_MAX?0:result;
    }
};
//每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)。

INT32_MAX 是一个在 C++ 中定义的常量,代表 32 位整型可以表示的最大正数。它是在 <climits><limits.h> 头文件中定义的,具体数值是 2,147,483,647,这个值是因为 32 位整型(int 类型)使用其中一个位来表示正负号,所以其正数范围是从 0 到 2^31 - 1。

在很多算法和函数实现中,INT32_MAX 常被用作一个初始的比较值,特别是当你需要找到一个最小值但事先不知道具体的数值范围时。通过将变量初始化为 INT32_MAX,你可以保证任何合理的、比这个值小的数在比较时都会被识别和更新。

例如,在寻找一个数组中的最小元素时,如果你将比较变量初始化为 INT32_MAX,则可以确保任何数组元素都会小于这个初始值,从而在第一次比较时就将变量更新为那个较小的值。相同的逻辑也适用于你的滑动窗口问题,其中你寻找的是长度最小的子数组,所以开始时将结果设为最大可能的整数值可以简化逻辑和编码过程。

DAY3

1、螺旋矩阵

力扣题目链接

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。示例:输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

循环不变量:每循环一次转一圈,不变量即对每条边的处理规则。

左闭右开,不处理最后一个节点,留给下一条边处理。

while(loop--)//结果不为0时执行循环体,每次循环处理矩阵的一圈,当loop为0时循环结束。

ij的赋值i = startx; j = starty;是在每一圈的循环开始时进行的。这样做的目的是确保在每一圈循环开始时,ij都被设置为当前圈的起始位置。这个赋值操作确保了每一圈循环都从正确的位置开始填充矩阵。因为在每一圈循环中,ij会随着循环的进行而改变,因此需要在循环开始时将它们重置为当前圈的起始位置,以确保填充矩阵的正确性。

class Solution{
public:
       vector<vector<int>> generateMatrix(int n){
           vector<vector<int>> res(n,vector<int>(n,0));
           int startx = 0;
           int starty = 0;
           int loop = n/2;
           int count = 1;
           int i ,j;
           int offset = 1;
           while(loop--){
               i = startx;
               j = starty;
               for(j;j<n-offset;j++){
                   res[i][j] = count++;
               }
               for(i;i<n-offset;i++){
                   res[i][j] = count++;
               }
               for(j;j>starty;j--){
                   res[i][j] = count++;
               }
               for(i;i>startx;i--){
                   res[i][j] = count++;
               }
               startx++;
               starty++;
               offset++;
          }
          if(n%2){
              res[n/2][n/2] = count;
          }
}

-----------------------------------------------------------------------------------------------------------------------------

第一章总结:

数组是存放在连续内存空间上相同数据元素的集合。

数组的元素不能删除,只能覆盖。

--------------------------------------------------------------------------------------------------------------------------------

2、移除链表元素

链表中的节点 在内存中不是连续分布的,而是散乱分布在内存中的某地址上,分配机制取决于操作系统的内存管理。

//单链表
struct ListNode{
    int val;//节点上存储的元素
    ListNode * next;//指向下一个节点的指针
    ListNode(int x):val(x),next(Null){};//节点的构造函数
}
//初始化节点
ListNode * head = new ListNode(5);
//如果不定义构造函数使用默认构造函数的话,在初始化的时候就不能直接给变量赋值!

方法一:原链表删除元素

移除头结点是持续移除的过程,使用while而不能是if。

遍历链表时,要定义一个临时的指针遍历链表。

方法二:使用虚拟头结点

while(cur->next!=NULL) 判断cur的next是否为空,非空的话一直遍历链表

DAY4

1、设计链表

2、翻转链表

双指针解法,注意在移动指针时,先移动pre后移动cur。

class Solution{
pubilc:
      ListNode * reverseList(ListNode * head){
          ListNode * pre = nullptr;
          Listnode * cur = head;
          ListNode * tmp ;
          while(cur!=nullptr){
             tmp = cur->next;
             cur->next = pre;
             pre = cur;
             cur = tmp;
          }
          delete cur;
          delete tmp;
          return pre;
};

             

DAY5

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

利用虚拟头结点,cur指向dummyhead

循环条件一定要先为判断cur->next!=null再判断cur->next->next!=null,如果顺序反过来则会空指针异常。偶数判断cur->next为空,奇数则为cur->next->next为空。

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

双指针(快慢指针起始都指向虚拟头结点),快指针移动n个后,再移动一位。此时快慢指针同时移动,直至快指针到null。

此时,slow的next即是需要删除的元素。

DAY6

1、链表相交

力扣题目链接(opens new window)

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

思路:求出两个链表的长度,并求出两个链表长度的差值。然后移动长链表的cur,使cura和curb再同一起点(末尾对齐)。遍历cura和curb遇到相同则直接返回。

curA = headA;curB = headB; 这两行代码的作用是重置 curAcurB 指针到各自链表的头节点。这是必要的步骤,因为在此之前的代码中,这两个指针被用于遍历各自的链表以计算链表的长度,导致它们移动到了链表的末端。

2、环形链表

DAY7

1、有效的字母异位词

2、两个数组的交集

DAY8

1、快乐数

2、两数之和

我们不仅要知道元素有没有遍历过,还要知道这个元素对应的下标,需要使用 key value结构来存放,key来存元素,value来存下标,那么使用map正合适

map是一种key value的存储结构,可以用key保存数值,用value再保存数值所在的下标。

std::unordered_map 底层实现为哈希表

map目的用来存放我们访问过的元素。

map中的存储结构为 {key:数据元素,value:数组元素对应的下标}。在遍历数组的时候,只需要向map去查询是否有和目前遍历元素匹配的数值,如果有,就找到的匹配对,如果没有,就把目前遍历的元素放进map中,因为map存放的就是我们访问过的元素。


class Solution {
public:
    vector<int> twoSum(vector<int>& nums, int target) {
        std::unordered_map <int,int> map;
        for(int i = 0; i < nums.size(); i++) {
            // 遍历当前元素,并在map中寻找是否有匹配的key
            auto iter = map.find(target - nums[i]); 
            if(iter != map.end()) {
                return {iter->second, i};
            }
            // 如果没找到匹配对,就把访问过的元素和下标加入到map中
            map.insert(pair<int, int>(nums[i], i)); 
        }
        return {};
    }
};

3、四数相加

  • 19
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值