代码随想录

一、数组

二分查找

题目704. 二分查找

给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target  ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。

示例:

输入: nums= [-1,0,3,5,9,12],target= 9
输出: 4
解释: 9 出现在 nums中并且下标为 4

题目分析:可以看我之前总结的套路模板

class Solution {
public:
    int search(vector& nums, int target) {

    int left = 0;
    int right = nums.size() -1;

    while(left < right){
        int mid = left + (right - left) / 2;
          
          if(nums[mid] >= target)
          {
                       right = mid;   //满足要求的落在[left,mid],然后区间不断缩小,最后只有一个数
          }
          else
              left = mid +1;
    }
   
         return nums[left] == target ? left: -1;//要是找到了就返回下标,不然就返回-1

    }
};

移除元素

题目:27. 移除元素

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

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

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

示例:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

思路:

1、暴力解法,两层for循环,一个人for循环遍历数组元素,第二个for循环更新数组

class Solution {
public:
    int removeElement(vector& nums, int val) {
        int size = nums.size();
        for(int i = 0; i 

2、双指针法

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
  • 在一个数组上面执行
//快指针找到我们需要的元素,然后将它赋值给慢指针
//新数组的下标就是slow
//在一个数组进行操作
class Solution {
public:
    int removeElement(vector& nums, int val) {

        if(nums.empty()) return 0;
       
        int slowindex = 0;
        for(int fastindex = 0; fastindex 

有序数组的平方

题目:977. 有序数组的平方

给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序

示例

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

1、暴力解法就是直接把直接相乘然后放在原来位置,最后来一个排序算法sort

class Solution {
public:
    vector sortedSquares(vector& A) {
        for (int i = 0; i < A.size(); i++) {
            A[i] *= A[i];
        }
        sort(A.begin(), A.end()); // 快速排序
        return A;
    }
};

2、双指针法,最大的数据一定在两端,然后通过比较两端指针指向的平方看看谁大,就存放到新数组中

//双指针法
class Solution {
public:
    vector sortedSquares(vector& nums) {
         vector result(nums.size(),0);//存放新的数组
        if(nums.empty()) return result;
       
       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[i]*nums[i];//每次存放完一个数组,新容器的下标就要改变一下
              i++;//首端指针后移

          }
          else{
              result[k--] = nums[j] *nums[j];
               j--;//尾端指针前移
          }
          
        }
        return result;
    }
};

长度最小的子数组

1、暴力解法

这道题目暴力解法当然是 两个for循环,然后不断的寻找符合条件的子序列,时间复杂度很明显是

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int sum = 0;//总和
        int length = INT32_MAX;//长度
        int len = INT32_MAX;
        int i = 0;//起点
        for (int i = 0; i < nums.size(); i++) {
            int sum = 0;//总和,每一层大循环还要清理一次

            for (int j = i; j < nums.size(); j++)
            {

                 sum += nums[j];
                 if(sum >= target) {

                    length = j - i + 1;
                    len = min(len, length);
                    break;//再移越来越长
                    

                }

            }
        }
        return len == INT32_MAX ? 0 : len;

    }
};

2、滑动窗口法

起始指针先不动,移动终止指针增加sum,等sum超了移动起始指针去减少我们窗口的sum

关键在什么时候区移动

在本题中实现滑动窗口,主要确定如下三点:

  • 窗口内是什么?
  • 如何移动窗口的起始位置?
  • 如何移动窗口的结束位置?

窗口就是 满足其和 ≥ s 的长度最小的 连续 子数组。

窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(也就是该缩小了)。

窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引。

解题的关键在于 窗口的起始位置如何移动

class Solution {
public:
    int minSubArrayLen(int target, vector& nums) {
        int sum = 0;//总和
        int length =INT32_MAX;//长度,先设置是最大值,因为我们后面是比较谁小
        int len = INT32_MAX;
        int i = 0;//起点
        for(int j = 0;j=target){
               length =  j -i +1;
                len = min(len,length);
                sum -=nums[i];
                i++;

            }


        }
      
        return len == INT32_MAX ? 0:len;

    }
};

螺旋矩阵II

题目:力扣题目链接 (opens new window)

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

代码随想录_第1张图片

示例:

输入: 3 输出: [ [ 1, 2, 3 ], [ 8, 9, 4 ], [ 7, 6, 5 ] ]

分析:

1、左闭右开,最末端的不管,作为下一次开端来写

2、

模拟顺时针画矩阵的过程:

  • 填充上行从左到右
  • 填充右列从上到下
  • 填充下行从右到左
  • 填充左列从下到上
  • 由外向内一圈一圈这么画下去。
  • class Solution {
    public:
        vector> generateMatrix(int n) {
            vector> res(n,vector(n,0)); // 使用vector定义一个二维数组
            int startx = 0, starty = 0; // 定义每循环一个圈的起始位置
            int loop = n / 2; // 每个圈循环几次,例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
            int mid = n / 2; // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
            int count = 1; // 用来给矩阵中每一个空格赋值
            int offset = 1; // 需要控制每一条边遍历的长度,每次循环右边界收缩一位
            int i,j;
            while (loop --) {
                i = startx;
                j = starty;
    
                // 下面开始的四个for就是模拟转了一圈
                // 模拟填充上行从左到右(左闭右开)
                for (j = starty; j < n - offset; j++) {
                    res[startx][j] = count++;
                }
                // 模拟填充右列从上到下(左闭右开)
                for (i = startx; i < n - offset; i++) {
                    res[i][j] = count++;
                }
                // 模拟填充下行从右到左(左闭右开)
                for (; j > starty; j--) {
                    res[i][j] = count++;
                }
                // 模拟填充左列从下到上(左闭右开)
                for (; i > startx; i--) {
                    res[i][j] = count++;
                }
    
                // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
                startx++;
                starty++;
    
                // offset 控制每一圈里每一条边遍历的长度
                offset += 1;
            }
    
            // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
            if (n % 2) {
                res[mid][mid] = count;
            }
            return res;
        }
    };

总结

1、数组的元素只能覆盖不能删除

二、链表

注意事项,空指针没有next

指针next指针改变指向是先断裂,再重新生成,通过next指针寻找下一个地址(本质其实是内存中保存了next节点的地址)

移除链表元素:203. 移除链表元素

给你一个链表的头节点 head 和一个整数 val ,请你删除链表中所有满足 Node.val == val 的节点,并返回 新的头节点

代码随想录_第2张图片

输入:head = [1,2,6,3,4,5,6], val = 6
输出:[1,2,3,4,5]

思路:

链表的移除

移除也就是删除一个节点,需要两个指针一个pre指向要删除的节点之前,一个cur指向要删除的下一个节点,cur ->next来访问下一个节点

//pre指向要删除节点的前一个,cur指向要删除的节点
class Solution {
public:
    ListNode* removeElements(ListNode* head, int val) {
        ListNode* Head = new ListNode(-1);//创建一个虚节点间
        Head->next = head;//在头部创建一个节点
        ListNode* pre = Head;
        ListNode* cur = pre ->next;

        if(cur == NULL) return Head->next;
        while(cur){
            if(cur->val == val){
                
                pre ->next = cur->next;//跳了一格
                cur = cur->next;//p2指下一个节点
            }
            else
            {
                //没找到就全部向前移动一格
                pre = pre->next;
                cur = cur->next;

            }

        }
       return Head->next;

    }
};

设计链表:707. 设计链表

设计链表的实现。您可以选择使用单链表或双链表。单链表中的节点应该具有两个属性:val 和 next。val 是当前节点的值,next 是指向下一个节点的指针/引用。如果要使用双向链表,则还需要一个属性 prev 以指示链表中的上一个节点。假设链表中的所有节点都是 0-index 的。

在链表类中实现这些功能:

get(index):获取链表中第 index 个节点的值。如果索引无效,则返回-1。
addAtHead(val):在链表的第一个元素之前添加一个值为 val 的节点。插入后,新节点将成为链表的第一个节点。
addAtTail(val):将值为 val 的节点追加到链表的最后一个元素。
addAtIndex(index,val):在链表中的第 index 个节点之前添加值为 val  的节点。如果 index 等于链表的长度,则该节点将附加到链表的末尾。如果 index 大于链表长度,则不会插入节点。如果index小于0,则在头部插入节点。
deleteAtIndex(index):如果索引 index 有效,则删除链表中的第 index 个节点。

题目思路:

节点的插入:

关于链表节点的插入,我们需要知道插入位置的前一个节点的位置,cur,再加上cur->next,就把前后节点位置都获取到了

节点的删除:

删除一个节点,需要两个指针一个pre指向要删除的节点之前,一个cur指向要删除的下一个节点,cur ->next来访问下一个节点

class MyLinkedList {
public:
     ListNode* dummpy_head = new ListNode(0);//存放链表的长度,
    
    MyLinkedList() {
  

    }
    
    int get(int index) {
        ListNode* cur = dummpy_head ->next;//cur指向第一个节点
       if(index >dummpy_head ->val -1 || index <0) return -1;//索引无效
        while(index){
            cur = cur ->next;
            index--;
        }
       return cur ->val;

    }
    
    void addAtHead(int val) {
        ListNode* head = new ListNode(val);//创建一个新头节点
        head ->next = dummpy_head->next;
        dummpy_head->next = head;
        dummpy_head ->val ++;
       


    }
    
    void addAtTail(int val) {
         ListNode* tail = new ListNode(val);//创建一个w尾部节点
        
          ListNode* cur= dummpy_head ;
         while(cur ->next)//当cur-》nex为空就不移动,也就是cur指向最后一个节点间
         {
             cur = cur->next;

         }
         cur ->next = tail;
         tail ->next = NULL;
         dummpy_head ->val++;
           

    }
    
    void addAtIndex(int index, int val) 
    {

        if (index > dummpy_head ->val ) {
            return;//
        }
        if(index < 0) 
        {
            addAtHead(val);
            return ;//必须退出不然会循环执行

        }
         if(index == dummpy_head ->val)
          {
             addAtTail(val);
             return ;//必须跳出不然后面会循环执行
          }
        
        ListNode* newNode = new ListNode(val);
        ListNode* cur = dummpy_head;
        //当找到索引节点时,cur指针指向要插索引的左边一个节点,要想插入节点,要有一个指针指向左边节点,右边利用cur->next这样就有可以找到两边节点
        int i = 0;

        while(i < index)
        {
            i++;
            cur = cur->next;
        }
        newNode->next = cur->next;
        cur->next = newNode;
        dummpy_head ->val ++;
        
    }
    
    void deleteAtIndex(int index) {
        if(index >dummpy_head ->val -1 || index <0) return ;//索引无效
         ListNode* cur= dummpy_head ->next;
         ListNode* pre = dummpy_head;
        while(index){
            cur =cur ->next;
            pre = pre->next;
            index--;

        }
        pre ->next = cur ->next;
        dummpy_head ->val --;
    }
};

反转链表:206. 反转链表

题目分析:双指针法

节点的反转 

需要使用三个指针,一个pre指针指向反转的前一个节点,cur指向要反转的节点,然后再设置一个temp指针指向需要反转的下一个节点,用来使得cur指针移动,因为我们cur反转之后。无法利用next指针访问到后一个节点

代码随想录_第3张图片

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head ==NULL) return  NULL;
        ListNode* p1 = NULL;
        ListNode* p2 = head;

         while(p2)
         { 
             ListNode* temp = p2->next;
             p2 ->next = p1;
             p1 = p2;
             p2 = temp;
              
         }

         
          return p1;
    }
    
};

两两交换链表中的节点:24. 两两交换链表中的节点

给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题(即,只能进行节点交换)

代码随想录_第4张图片

分析

注意:在我们修改过程中,指针一直在变。cur->next不一定可以访问到我们要的节点

cur指针两两指向交换的后一个节点

终止条件(cur的后一个或者两个为空,这斗凑不到一对,怎么换)

class Solution {
public:
    ListNode* swapPairs(ListNode* head) {
        ListNode* dummpy_head = new ListNode(-1);//重新创建新链表
        dummpy_head ->next = head;//虚头节点加入链表
        ListNode* cur = dummpy_head;
        while(cur->next && cur->next->next)
        {
             ListNode* temp = cur->next;//1号
             ListNode* temp1 = cur->next->next->next;//3号

             cur ->next = cur ->next ->next;//虚节点连接到2号
             cur ->next ->next  = temp;//2号连接到1号
             temp ->next = temp1;//1号连接到3号
             
             cur = cur ->next ->next;//cur指针移动两格
        }
        return dummpy_head ->next;        
    }
};

删除链表的倒数第N个节点 :19. 删除链表的倒数第 N 个结点

给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点

代码随想录_第5张图片

快指针先跑n。然后快慢指针一起运行。等块指针指向空,慢指针指向就是倒数第n个节点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* Head = new ListNode(-1);
        Head ->next = head;
        ListNode* p3 = head;
        ListNode* p2 = head;
        ListNode* p1 = Head;
      
        for(int i = 0; inext;//p3先走n-1,p3与p2的长度就是n
        while(p3){
        p3 = p3->next;
        p2 = p2->next;//倒数第n个
        p1 = p1->next;
        }
        ListNode* temp = p2 ->next;
        p1->next = temp;
         return Head->next;
    }
  
};

链表相交 :面试题 02.07. 链表相交

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

图示两个链表在节点 c1 开始相交

代码随想录_第6张图片

分析:

你走过我的 + 我走过你的 = 我们的人生

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        
        auto a =  headA;
        auto b = headB;

        while(a != b){
            if(a) a = a ->next;
            else a = headB;

            if(b) b = b ->next;
            else b = headA;

        }
        return a;
    }
};

环形链表|| :142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

代码随想录_第7张图片

分析:

当fast指针一次移动两格,slow一次移动一格,当要是有换的话就会在环中遇到

环的终止条件是(快指针的下一步存在)

怎么找到入口处

从相遇点出发一个指针,从头部出发一个指针,会在入口处相遇

class Solution {
public:
    ListNode *detectCycle(ListNode *head)  {

       if(head == nullptr) return nullptr;
        ListNode* fast = head;
        auto slow = head;
        //没有环指针就会走到尽头
        while(fast->next &&  fast->next ->next)
        {
            fast = fast ->next ->next;
            slow = slow ->next;
            if(slow == fast)//相遇
            {
                auto p = head;
                while(p != slow){
                    p = p ->next;
                    slow = slow ->next;
                }
                return p;
            }
        }

        return nullptr;
    }
};

总结

1、单链表每个节点next指针都要指向

2、要改变链表节点的话,都要设置一个虚节点,这样便于对头节点操作

代码随想录_第8张图片

三、哈希表

 有效的字母异位词 : 242. 有效的字母异位词

给定两个字符串 s 和 t ,编写一个函数来判断 t 是否是 s 的字母异位词。

注意:若 s 和 t 中每个字符出现的次数都相同,则称 s 和 t 互为字母异位词。

示例 1:

输入: s = "anagram", t = "nagaram"
输出: true

分析:这种与次数有关系的都是map

class Solution {
public:
    bool isAnagram(string s, string t) {
    if(s.size() != t.size()) 
      return false;
    unordered_maphash;
    //一加一减,要是一样就全是零
    
    for(int i = 0; i

两个数组的交集:349. 两个数组的交集

给定两个数组 nums1 和 nums2 ,返回 它们的交集 。输出结果中的每个元素一定是 唯一 的。我们可以 不考虑输出结果的顺序 。

示例 1:

输入:nums1 = [1,2,2,1], nums2 = [2,2]
输出:[2]

分析:找交集就是查看一个集合里面是否存在这个元素,使用set容器,因为输出的结过是不可以有重复的,我们将交集存放到set中,最后返回的时候凑set中提取

class Solution {
public:
    vector intersection(vector& nums1, vector& nums2) {
      unordered_set hash1(nums1.begin(),nums1.end()); //直接在初始化的时候插入整个,重复的不会插入
       unordered_set hash2;//将相交叉的元素存放在这里面,会自动去除重复的
    
     
    //将nums2中与hash1一样的元素插入hash2中
    for(auto x : nums2)
    {
       if(hash1.find(x) != hash1.end())
              hash2.insert(x);

    }
    return vector(hash2.begin(),hash2.end());//直接返回,不需要创建变量
    }
};

快乐数 :202. 快乐数

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

分析:1、个位数的操作

/ 10 是去掉个位

% 10是只留下个位

2、要是出现1就直接就对了,要是无限循环别的数就是措。使用set容器将出现的存放起来,要是出现一样

class Solution {
public:

     int Getsun(int n)

     {
           int sum = 0;
           while(n)
           {
              sum += (n%10)*(n%10);//n%10的作用是取得个数的值
              n = n/10;//去掉个数
           }  
           return sum;       
     }
    bool isHappy(int n) {
        unordered_set set;
       
        while(1)
        {
            //看看是否等于1
            n= Getsun(n);//运行一次更新一个n
            
            if(n==1)
                return true;

          //看看有没有循环的
            if(set.find(n) == set.end())//n在set中找不到
                set.insert(n);

            else
                  return false;
            
        }     
     }       
};

两数之和:1. 两数之和

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

你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。

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

示例 1:

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

示例 2:

输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:

输入:nums = [3,3], target = 6
输出:[0,1]

分析

set容器只能返回那个值,要想返回下标只能使用map容器

数组中的同一元素只能出现一次,一个元素只能用一次

我们从数组左边遍历到右边,将遍历过的数组放在map中,数组中遍历的时候,在map查找是否有符号条件的值

//将遍历完了的元素存放在map容器中,这样的话我们可以直接查找是否存在某个元素
class Solution {
public:
    vector twoSum(vector& nums, int target) {
        unordered_map map;//元素,元素的下标
        vectorres;
       for(int i = 0; i < nums.size();i++)
       {
           auto iter = map.find(target - nums[i]);//查找遍历的map中是否有与当前匹配的元素
           if(iter != map.end())//找到
           {
               res.push_back(iter ->second);
               res.push_back(i);
               return res;
           }
            
           else
              map.insert(pair(nums[i],i));

       }
       return res;
       
    }
};

四数相加|| :454. 四数相加 II

给你四个整数数组 nums1、nums2、nums3 和 nums4 ,数组长度都是 n ,请你计算有多少个元组 (i, j, k, l) 能满足:

0 <= i, j, k, l < n
nums1[i] + nums2[j] + nums3[k] + nums4[l] == 0
 

示例 1:

输入:nums1 = [1,2], nums2 = [-2,-1], nums3 = [-1,2], nums4 = [0,2]
输出:2
解释:
两个元组如下:
1. (0, 0, 0, 1) -> nums1[0] + nums2[0] + nums3[0] + nums4[1] = 1 + (-2) + (-1) + 2 = 0
2. (1, 1, 0, 0) -> nums1[1] + nums2[1] + nums3[0] + nums4[0] = 2 + (-1) + (-1) + 0 = 0
示例 2:

输入:nums1 = [0], nums2 = [0], nums3 = [0], nums4 = [0]
输出:1

分析:本题重要点在可以重复上面

//将四个分成 2 + 2

//我们先算出a+b 有多少个结果重复也算,使用一个map容器存放a+b,次数

// 然后将在map中找有没有 0 - (c + d),找到一个就加上之前map中的次数

//将四个分成 2 + 2
//我们先算出a+b 有多少个结果重复也算,使用一个map容器存放a+b,次数
// 然后将在map中找有没有 0 - (c + d),找到一个就加上之前map中的次数

class Solution {
public:
    int fourSumCount(vector& nums1, vector& nums2, vector& nums3, vector& nums4)
     {
         unordered_mapmap;//存放a + b的各种可能
         int cout = 0;//存放元组的个数
         for(auto a : nums1)
         {
             for(auto b :nums2)
             {
                 map[a + b]++;//value存放重复次数

             }
         }
         for(auto c:nums3)
         {
             for(auto d :nums4)
             {
                 if(map.find(0 -(c + d)) != map.end())  cout += map[0 -(c + d)];
             }
         }
        return cout;
    }
};

赎金信:383. 赎金信

给你两个字符串:ransomNote 和 magazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false 。

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

输入:ransomNote = "a", magazine = "b"
输出:false
示例 2:

输入:ransomNote = "aa", magazine = "ab"
输出:false
示例 3:

输入:ransomNote = "aa", magazine = "aab"
输出:true

分析:

magzine就是材料库,看看能不能组成ransomNote

和我们之前的异位词是一个意思,使用map容器一加一减

class Solution {
public:
    bool canConstruct(string ransomNote, string magazine) {
        unordered_map map;//将素材放到set中
        for(auto  x : magazine)
        {
            map[x]++;         
        }
        for(auto  y : ransomNote)
        {
            map[y]--;
            if(map[y] < 0) //从材料库拿多了
               return false;         
        }
         return true;   
    }
};

三数之和:15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

 

 

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

分析:怎么使得答案中没有重复的元素是本题的关键,我们设置三个指针

什么叫做不重复的三元素 ,有了 1,2,3 就不能有另外的1,2,3,但是三元组里面的元素是可以重复的

a = nums[i], b = nums[left], c = nums[right]

题目要使得 a + b + c = 0,我们当遍历三个指针元素大于零,就right右边移,反之左移,知道left与right相遇

class Solution {
public:
    vector> threeSum(vector& nums) {
       vector>result;
       sort(nums.begin(),nums.end());//排序
       // 三个数字, a=nums[i] , b = nums[left] , c = nums (right)

       for(int i = 0; i < nums.size(); i++)
       {
            if(nums[i] > 0)  return result;//要是排序的第一个就是0,那么就无法实现

            if( i > 0 && nums[i] == nums[i-1])  continue;//三元素a去重,因为我们排序过,相同的话就会在后面一个,z直接跳过

            int left = i +1;
            int right = nums.size() -1;
            while(right > left)
            {
              
              if(nums[i] + nums[left] + nums[right] > 0)//三个数太大了
                  right--;
              else if (nums[i] + nums[left] + nums[right] < 0)//三个数太小了
                 left++;
              else//等于0
              {
                  result.push_back(vector{nums[i],nums[left],nums[right]});//插入数据
                  while(right > left && nums[right] == nums[right - 1]) right --;//找到与b一样的元素直接跳过
                  while( right > left && nums[left] == nums[left + 1]) left++;//找到与c一样的元素直接跳过
                  
                  //找到答案,指针收缩
                    right--;
                    left++;


              }


            }

       }
      return result;

    }
};

代码随想录_第9张图片

四数之和

总结

代码随想录_第10张图片

四、字符串

反转字符串:344. 反转字符串

分析:我们最好不要使用reverse函数,交换的第一时间想到双指针法,我们要原地修改。

要是使用库函数swap方便,不使用库函数就手动交换,设置一个temp变量

标准答案

void reverseString(vector& s) {
    for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
        swap(s[i],s[j]);
    }
}

没使用swap

class Solution {
public:
    void reverseString(vector& s) {
        int  p1 = 0,p2 = s.size() -1, temp = 0;//创建两个指针,来交换,一个temp存放变量,防止被覆盖
        while(p1 <= p2)
        {
            temp = s[p2];
            s[p2] =  s[p1];
            s[p1]  = temp;
            p1++;
            p2--;
        }
    return ;
    }
};

反转字符||:541. 反转字符串 II

分析:利用reverse函数,记得区间是左闭右开

class Solution {
public:
    string reverseStr(string s, int k) {
        //一次跳2k
      for(int i = 0; i < s.size();i+=(2*k))
      {
          if(i+k <= s.size())//剩下大于k
          {
              reverse(s.begin()+i,s.begin()+i+k);//左闭右开

          }
          else//也就是剩下得小于k
             reverse(s.begin() + i,s.end());
      }
      return s;
    }
};

替换空格:剑指 Offer 05. 替换空格

分析:两种方向一种是创建一个新数组

另外一种是先扩展string在从后往前重新在原地修改,双指针法,i指向未扩展的最后面,j指向扩展的最后面,当i遍历到字符传给j,遍历到空格,j就在附近三个填充,然后循环

class Solution {
public:
    string replaceSpace(string s) {
        string s2;//存放新的字符串
        string s3 = "%20";
    for(int i = 0; i< s.size();i++)
    {
        if(s[i]==' ') s2+=s3;
        else   
            s2 += s[i];
    }
     return s2;
    }
};
class Solution {
public:
    string replaceSpace(string s) {
        int count = 0; // 统计空格的个数
        int sOldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        int sNewSize = s.size();
        // 从后先前将空格替换为"%20"
        for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }
};

翻转字符串中的单词:151. 反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"
示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

分析:

1、删除空格

快慢指针,块指针获取我们需要需要的元素,然后赋值给我买的慢指针

注意我们这样需要手动添加单词之间的空格,

单词一个一个录入,等快指针遍历到空格就一个单词结束,等下轮开始找到一个元素进行开始,我们也在这里添加一个空格

 

2、大反转 使用reverse(s.begin(),s.end())

 

3、单词反转,两个指针交换单词首尾

 

//fast指针遍历原来的数组,slow获取我们需要的元素

class Solution {
public:
    //单词小反转
    void Reverse(string& s, int start,int end){
        for(int i = start, j = end; i 

左旋转字符串:剑指 Offer 58 - II. 左旋转字符串

字符串的左旋转操作是把字符串前面的若干个字符转移到字符串的尾部。请定义一个函数实现字符串左旋转操作的功能。比如,输入字符串"abcdefg"和数字2,该函数将返回左旋转两位得到的结果"cdefgab"。

 

示例 1:

输入: s = "abcdefg", k = 2
输出: "cdefgab"
示例 2:

输入: s = "lrloseumgh", k = 6
输出: "umghlrlose"

分析:

1、新建一个字符串,将两个拼接在一起

2、在原地修改,使用reverse函数,颠三倒四

class Solution {
public:
    string reverseLeftWords(string s, int n) {
        string s1;  //创建一个字符串来存放要移位的字符
        s1 = s.substr(0,n);//获取字符
        s = s.erase(0,n);//将原来字符剪切部分
        s  = s + s1;//合并字符串


       return s ;
    }
};

五、双指针法

移除元素:27. 移除元素

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

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

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

分析:

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置
  • 在一个数组上面执行
//快指针找到我们需要的元素,然后将它赋值给慢指针
//新数组的下标就是slow
//在一个数组进行操作
//从左往右拷贝覆盖
class Solution {
public:
    int removeElement(vector& nums, int val) {

        if(nums.empty()) return 0;
       
        int slowindex = 0;
        for(int fastindex = 0; fastindex 

反转字符串:344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

示例 1:

输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]
示例 2:

输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]

来源:力扣(LeetCode)

分析:我们最好不要使用reverse函数,交换的第一时间想到双指针法,我们要原地修改

class Solution {
public:
    void reverseString(vector& s) {
        for (int i = 0, j = s.size() - 1; i < s.size()/2; i++, j--) {
            swap(s[i],s[j]);
        }
    }
};

替换空格:剑指 Offer 05. 替换空格

请实现一个函数,把字符串 s 中的每个空格替换成"%20"。

示例 1:

输入:s = "We are happy."
输出:"We%20are%20happy."

分析

一种是先扩展string在从后往前重新在原地修改,双指针法,i指向未扩展的最后面,j指向扩展的最后面,当i遍历到字符传给j,遍历到空格,j就在附近三个填充,然后循环

class Solution {
public:
    string replaceSpace(string s) {
        int count = 0; // 统计空格的个数
        int sOldSize = s.size();
        for (int i = 0; i < s.size(); i++) {
            if (s[i] == ' ') {
                count++;
            }
        }
        // 扩充字符串s的大小,也就是每个空格替换成"%20"之后的大小
        s.resize(s.size() + count * 2);
        int sNewSize = s.size();
        // 从后先前将空格替换为"%20"
        for (int i = sNewSize - 1, j = sOldSize - 1; j < i; i--, j--) {
            if (s[j] != ' ') {
                s[i] = s[j];
            } else {
                s[i] = '0';
                s[i - 1] = '2';
                s[i - 2] = '%';
                i -= 2;
            }
        }
        return s;
    }
};

 翻转字符串中的单词:151. 反转字符串中的单词

给你一个字符串 s ,请你反转字符串中 单词 的顺序。

单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。

返回 单词 顺序颠倒且 单词 之间用单个空格连接的结果字符串。

注意:输入字符串 s中可能会存在前导空格、尾随空格或者单词间的多个空格。返回的结果字符串中,单词间应当仅用单个空格分隔,且不包含任何额外的空格。

示例 1:

输入:s = "the sky is blue"
输出:"blue is sky the"
示例 2:

输入:s = "  hello world  "
输出:"world hello"
解释:反转后的字符串中不能存在前导空格和尾随空格。
示例 3:

输入:s = "a good   example"
输出:"example good a"
解释:如果两个单词间有多余的空格,反转后的字符串需要将单词间的空格减少到仅有一个。

分析:

1、删除空格

快慢指针,块指针获取我们需要需要的元素,然后赋值给我买的慢指针

注意我们这样需要手动添加单词之间的空格,

单词一个一个录入,等快指针遍历到空格就一个单词结束,等下轮开始找到一个元素进行开始,我们也在这里添加一个空格

 

2、大反转 使用reverse(s.begin(),s.end())

 

3、单词反转,两个指针交换单词首尾

 

//fast指针遍历原来的数组,slow获取我们需要的元素

class Solution {
public:
    //单词小反转
    void Reverse(string& s, int start,int end){
        for(int i = start, j = end; i 

反转链表:206. 反转链表

代码随想录_第11张图片

输入:head = [1,2,3,4,5]
输出:[5,4,3,2,1]

分析

节点的反转 

需要使用三个指针,一个pre指针指向反转的前一个节点,cur指向要反转的节点,然后再设置一个temp指针指向需要反转的下一个节点,用来使得cur指针移动,因为我们cur反转之后。无法利用next指针访问到后一个节点

代码随想录_第12张图片

class Solution {
public:
    ListNode* reverseList(ListNode* head) {
        if(head ==NULL) return  NULL;
        ListNode* p1 = NULL;
        ListNode* p2 = head;
 
         while(p2)
         { 
             ListNode* temp = p2->next;
             p2 ->next = p1;
             p1 = p2;
             p2 = temp;
              
         }
 
         
          return p1;
    }
    
};

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

代码随想录_第13张图片

输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]

分析

快指针先跑n。然后快慢指针一起运行。等块指针指向空,慢指针指向就是倒数第n个节点

class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        ListNode* Head = new ListNode(-1);
        Head ->next = head;
        ListNode* p3 = head;
        ListNode* p2 = head;
        ListNode* p1 = Head;
      
        for(int i = 0; inext;//p3先走n-1,p3与p2的长度就是n
        while(p3){
        p3 = p3->next;
        p2 = p2->next;//倒数第n个
        p1 = p1->next;
        }
        ListNode* temp = p2 ->next;
        p1->next = temp;
         return Head->next;
    }
  
};

链表相交:面试题 02.07. 链表相交

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

图示两个链表在节点 c1 开始相交:

题目数据 保证 整个链式结构中不存在环。

注意,函数返回结果后,链表必须 保持其原始结构 。

分析:

你走过我的 + 我走过你的 = 我们的人生

class Solution {
public:
    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        
        auto a =  headA;
        auto b = headB;
 
        while(a != b){
            if(a) a = a ->next;
            else a = headB;
 
            if(b) b = b ->next;
            else b = headA;
 
        }
        return a;
    }
};

环型链表||:142. 环形链表 II

给定一个链表的头节点  head ,返回链表开始入环的第一个节点。 如果链表无环,则返回 null。

如果链表中有某个节点,可以通过连续跟踪 next 指针再次到达,则链表中存在环。 为了表示给定链表中的环,评测系统内部使用整数 pos 来表示链表尾连接到链表中的位置(索引从 0 开始)。如果 pos 是 -1,则在该链表中没有环。注意:pos 不作为参数进行传递,仅仅是为了标识链表的实际情况。

不允许修改 链表。

代码随想录_第14张图片

分析:

当fast指针一次移动两格,slow一次移动一格,当要是有换的话就会在环中遇到

环的终止条件是(快指针的下一步存在)

怎么找到入口处

从相遇点出发一个指针,从头部出发一个指针,会在入口处相遇

class Solution {
public:
    ListNode *detectCycle(ListNode *head)  {
 
       if(head == nullptr) return nullptr;
        ListNode* fast = head;
        auto slow = head;
        //没有环指针就会走到尽头
        while(fast->next &&  fast->next ->next)
        {
            fast = fast ->next ->next;
            slow = slow ->next;
            if(slow == fast)//相遇
            {
                auto p = head;
                while(p != slow){
                    p = p ->next;
                    slow = slow ->next;
                }
                return p;
            }
        }
 
        return nullptr;
    }
};

六、栈与队列

用栈实现队列:232. 用栈实现队列

请你仅使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(push、pop、peek、empty):

实现 MyQueue 类:

void push(int x) 将元素 x 推到队列的末尾
int pop() 从队列的开头移除并返回元素
int peek() 返回队列开头的元素
boolean empty() 如果队列为空,返回 true ;否则,返回 false

分析:

push :直接入栈

 pop :将栈出到另一个栈,然后出最后一个元素,这样就满足先进先出

注意我们栈2要想进行放数据要先为空,只有等某一次存放的全出完才能放入,不然会乱掉

class MyQueue {
public:
    stacksta1;//栈1
    stacksta2;//栈2,//只有当sta2为空的时候才从sta1中导入数据
    MyQueue() {

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


    }
    
    int pop() {
        if(sta2.empty())//当栈2为空
        {

            while(!sta1.empty())//将栈1里面的数据全部导入栈2
            {
            sta2.push(sta1.top());
            sta1.pop();
            }

        }
        //将最先进入栈的出去
        int result = sta2.top();
        sta2.pop();
        return result;
       
    }
    
    int peek() {
        int result = this->pop();//调用函数
        sta2.push(result);//将pop函数出栈的加回去
        return result;


    }
    
    bool empty() 
    {
        if(sta1.empty() && sta2.empty())
           return true;
        
        return false;

    }
};

用队列实现栈:225. 用队列实现栈

请你仅使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

实现 MyStack 类:

void push(int x) 将元素 x 压入栈顶。
int pop() 移除并返回栈顶元素。
int top() 返回栈顶元素。
boolean empty() 如果栈是空的,返回 true ;否则,返回 false 。

分析

一个队列在模拟弹出元素的时候只要将队列头部元素(除去最后一个元素)重新添加到队列尾部,此时在去弹出元素就是栈的顺序了

class MyStack {
public:
   queue que;
    MyStack() {

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


    }
    
    int pop() //先确定除去尾部有多少个数据,然后将他们全部插到后面
    {
        int size = que.size();
        size--;
        while(size--)
        {
            que.push(que.front());//将头部元素往后面拷贝插入
            que.pop();//将队列头部元素出去
        }
        int result = que.front();//这弹出的就是原来队列的最后一个元素
        que.pop();
        return result;
        

    }
    
    int top()//也就是最后加入的元素
     {
     return que.back();
    }
    
    bool empty() {
      return   que.empty();

    }
};

有效的括号:20. 有效的括号

给定一个只包括 '(',')','{','}','[',']' 的字符串 s ,判断字符串是否有效。

有效字符串需满足:

左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
每个右括号都有一个对应的相同类型的左括号。
 

示例 1:

输入:s = "()"
输出:true
示例 2:

输入:s = "()[]{}"
输出:true
示例 3:

输入:s = "(]"
输出:false

分析:

消消乐,将遍历到的括号对应的另一半加入栈里面,

注意一点就是,我们要是开始匹配的话,栈顶一定时先匹配的

1、左边括号多余

2、类型匹配失败

3、右边括号多余

//注意一点就是,我们要是开始匹配的话,栈顶一定时先匹配的
class Solution {
public:
    bool isValid(string s) {
        if(s.size() % 2 != 0) return false;//长度为奇就一定不匹配
        stack st;
        for(int i = 0; i < s.size(); i++)//变量字符串
        {
            //将遍历到的字符对应的匹配字符串存放到栈里面
           for(int i = 0; i < s.size(); i++)
            {
                if(s[i] =='(') st.push(')');
                else if (s[i] == '{') st.push('}');
                else if (s[i] == '[') st.push(']');
                //下面是处理右边的符号
                else if(st.empty() || st.top() != s[i])//1、多的消不掉,左边括号少了,2、符号类型不同无法匹配
                     return false;
                else st.pop();//st.top()与s[i] 相等,弹出,消去

            }
            return st.empty();//栈还有多余的无法匹配,左边符号多了

        }
       return  true; 
    }
};

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

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

class Solution {
public:
    string removeDuplicates(string s) {
        stack sta;
        string s1;
       
        for(int  i = 0; i 

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

给出由小写字母组成的字符串 S,重复项删除操作会选择两个相邻且相同的字母,并删除它们。

在 S 上反复执行重复项删除操作,直到无法继续删除。

在完成所有重复项删除操作后返回最终的字符串。答案保证唯一。

示例:

输入:"abbaca"
输出:"ca"
解释:
例如,在 "abbaca" 中,我们可以删除 "bb" 由于两字母相邻且相同,这是此时唯一可以执行删除操作的重复项。之后我们得到字符串 "aaca",其中又只有 "aa" 可以执行重复项删除操作,所以最后的字符串为 "ca"。

分析:

将他们放到栈里面,要是顶部的与遍历的一样就消消乐,不然就插入栈里面

class Solution {
public:
    string removeDuplicates(string s) {
        stack sta;
        string s1;
       
        for(int  i = 0; i 

逆波兰表达式求值:150. 逆波兰表达式求值

根据 逆波兰表示法,求表达式的值。

有效的算符包括 +、-、*、/ 。每个运算对象可以是整数,也可以是另一个逆波兰表达式。

注意 两个整数之间的除法只保留整数部分。

可以保证给定的逆波兰表达式总是有效的。换句话说,表达式总会得出有效数值且不存在除数为 0 的情况。

示例 1:

输入:tokens = ["2","1","+","3","*"]
输出:9
解释:该算式转化为常见的中缀算术表达式为:((2 + 1) * 3) = 9
示例 2:

输入:tokens = ["4","13","5","/","+"]
输出:6
解释:该算式转化为常见的中缀算术表达式为:(4 + (13 / 5)) = 6
示例 3:

输入:tokens = ["10","6","9","3","+","-11","*","/","*","17","+","5","+"]
输出:22
解释:该算式转化为常见的中缀算术表达式为:
  ((10 * (6 / ((9 + 3) * -11))) + 17) + 5
= ((10 * (6 / (12 * -11))) + 17) + 5
= ((10 * (6 / -132)) + 17) + 5
= ((10 * 0) + 17) + 5
= (0 + 17) + 5
= 17 + 5
= 22

分析:

遇到数字就加入到栈里面,遇到操作符就取出进行运算,运算结果是数字也加入到栈里面

class Solution {
public:
    int evalRPN(vector& tokens) {
        stack st;
        for (int i = 0; i < tokens.size(); i++) {
            if (tokens[i] == "+" || tokens[i] == "-" || tokens[i] == "*" || tokens[i] == "/") {
                int num1 = st.top();
                st.pop();
                int 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(stoi(tokens[i]));
            }
        }
        int result = st.top();
        st.pop(); // 把栈里最后一个元素弹出(其实不弹出也没事)
        return result;
    }
}

七、二叉树

八、回溯算法

九、贪心算法

10、动态规划

你可能感兴趣的:(#,代码随想录,c++,leetcode)