LeetCode刷题


本文章主要用于记录在LeetCode-简单难度题刷题过程中的心得与新学习算法的比较

C++ 中,STL容器主要分为两类,序列式容器、关联式容器。
序列式容器主要指的就是数组、链表、队列等只是顺序式的容器。
而关联式容器指的是容器在存储元素值的同时,还会为元素配备一个键值,此操作就能够在查找元素过程中,只需要已知键值,就能够找到元素值,不需要再遍历整个容器。即关联式容器存储元素,是以“键值对”的形式进行存储,即<key, value>的形式进行存储

vector用法题

vector用法
动态数组,一维建立方法:vector<数据类型> 数组名字,
二维建立方法://使用vector的resize函数进行二维数组的定义(注意:此种方法适用于每一行的列数相等或不相等的二维数组,调整for循环内的resize函数的参数即可)
vector<vector> matrix(m); //创建一维数组matirx,这个数组里有m个元素,元素是int型vector。不能省略m。
使用方法:push_back添加元素,pop_back删除尾元素,size()返回元素个数,clear()删除所有元素
数组起始:数组名.begin()
数组末尾:数组名.end()
迭代器: vector::iterator it; 使用方法如下
for(it = a.begin(); it != a.end(); it++)
printf(“%d”, *it);
insert(it, x)向指定位置插入元素x,
erase(it) 即删除迭代器为it处的元素。
erase(first,last)即删除 [ first , last) 内的所有元素。例如:erase(vi.begin(), vi.end());

vector排序方法简要介绍:

1、简单升序排序:

sort(a.begin(), a.end());

2、简单降序排序:

sort(a.rbegin(), a.rend());

3、制定规则排序

    sort(courses.begin(), courses.end(), [](const auto& c0, const auto& c1) {
        return c0[1] < c1[1];
    });

118. 杨辉三角 I

脍炙人口的杨辉三角,在创建二维vector的时候要注意要加上长度

    vector<vector<int>> generate(int numRows) {
        vector<vector<int>> ans(numRows);
        for (int i = 0;i < numRows ; i++)
        {
            ans[i].resize(i + 1);
            ans[i][0] = ans[i][i] = 1;
            for (int j = 1; j < i ; j++)
                ans[i][j] = ans[i - 1][j - 1] + ans[i - 1][j];
        }
        return ans;
    }

119.杨辉三角 II

这道题跟118题其实很像,不同之处就在于他只需要某一特定的行,题解的方法很巧妙…滚动数组,我直接暴力打表返回了

    vector<int> getRow(int rowIndex) {
        vector<vector<int>>ans(35);
        for (int i = 0;i <= rowIndex ; i++)
        {
            ans[i].resize(i + 1);
            ans[i][0] = ans[i][i] = 1;
            for (int j = 1; j < i ; j++)
                ans[i][j] = ans[i - 1][j - 1] + ans[i - 1][j];
        }
        return ans[rowIndex];
    }

2500.删除每行中的最大值

题意:m*n的矩阵,每次操作会删去每行的当前最大值,然后在每一次删除的操作中,要求所有的最大值中的最大值的累加和。
思路:本来以为需要用优先队列维护,于是觉得这也太难了吧,这为啥是Easy啊,想半天没想到好解法,然后联想到之前有个题有个异或解法,或许这也是一条出路?然后暴死了,直接看题解!
题解:这题还不简单🐎,直接排序,排序完了遍历,每一列选个最大值相加

    int deleteGreatestValue(vector<vector<int>>& grid) {
        int m = grid.size(), n = grid[0].size();
        int ans = 0;
        for (int i = 0; i < m; i++)
            sort(grid[i].begin(), grid[i].end());
        for (int i = 0; i < n; i++)
        {
            int maxx = grid[0][i];
            for (int j = 0; j < m ;j ++)
                maxx = max(maxx, grid[j][i]);
            ans += maxx;
        }
        return ans;
    }

好好好,是个好题

2596.检查骑士巡视方案

签到题,思路不太复杂,有点被他绕进去了,自己的做法非常的傻逼且复杂

先贴我的代码:

    bool judge(int nx, int ny, int tx, int ty ){
        int cx[] = {-2, -2, -1, -1, 1,  1, 2,  2},
            cy[] = { 1, -1,  2, -2, 2, -2, 1, -1};
        for (int i = 0; i < 8; i++)
            if(nx + cx[i] == tx && ny + cy[i] == ty)
                return true;
        return false;
    }
    struct node{
    int x,y,num;
    };
    static bool cmp(const node& a1, const node& a2){
        return a1.num < a2.num;
    }
    bool checkValidGrid(vector<vector<int>>& grid) {
        int n = grid.size();

        vector<node>Q;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < n; j++)
                Q.push_back({i,j,grid[i][j]});
        std::sort(Q.begin(), Q.end(), cmp);
        int nx = Q[0].x, ny = Q[0].y;
        if(nx != 0 || ny != 0)
            return false;
        for (int i = 1; i < Q.size(); i++)
        {
            if(judge(nx, ny, Q[i].x, Q[i].y) == false)
                return false;
            nx = Q[i].x;
            ny = Q[i].y;
        }
        return true;
    }

这里我的做法的不太聪明的地方就是专门建了一种新的结构体,然后又给结构体排序,最后才逐个遍历判断答案。

题解的做法就是建立一个新二维数组index,这个数组的一维存储的是棋盘所有点值,二维存储的是坐标信息

例:

	for (int i = 0; i < n; i++)
       	for (int j = 0; j < n; j++)
           	index[grid[i][j]] = {i,j};

(谁是SB,我不说)

Map用法题

Map用法
哈希表,红黑树原理,关键字(key)-值对,key值唯一
初始化方法:map<int,int>mp;
添加对象:insert,例如:mp.insert({2,13});
删除元素:erase,例如:mp.erase(1);
修改元素:可以根据对应的key值修改value,mp[键值] = 新value
查找元素:可以通过key值直接查找,例如:cout<< mp[key值];
也可以通过value找键值,例如:cout<<find(value值) -> first;
其他用法
map_1.clear(); //清除所有元素
map_1.empty(); //如果为空返回1,负责返回0
map_1.size(); //返回容器的元素个数
map_1.max_size; //返回容器可以容纳的最大元素

//可以用过迭代器与first,second访问元素
map_1.begin()->first; //结果为容器的第一个元素的key值
map_1.begin()->second; //结果为容器的第一个元素的value值**

1.两数之和

题意:给定target和数组,需要在数组中找到两个数,这两个数之和为target
做法:
主要利用Map的红黑树特性,即关键值与数字相匹配的特性来进行判断
遍历整个nums数组,如果target - 当前值已经加入map中,则可以认为已经找到了答案,取消匹配过程,以Vecror的形式输出结果,否则将当前值加入map进行匹配

    vector<int> twoSum(vector<int>& nums, int target) {
        map<int,int>mp;
        vector<int>a;
        for (int i = 0; i < nums.size(); i++)
        {
            if(mp.count(target - nums[i])) // find successfully
            {
                a.push_back(mp[target - nums[i]]);
                a.push_back(i);
                break;
            }
            mp[nums[i]] = i;
        }
        return a;
    }

136.只出现一次的数字

题目中给定的条件是给一个非空整数数组,这个数组中只有一个元素出现了一次,其他元素都出现了两次。本来想着用set映射一下,但是好像只能检查一遍是否存在,没存在无论如何也要加进去,好像不太合适,果断放弃,瞄了一眼题解说是要用HashMap,因为HashMap能够更改value,只要让value存储的值为出现次数即可。
这里用到了一个新学的概念:C++11中基于范围的for循环
基于范围的for语句

    int singleNumber(vector<int>& nums) {
        unordered_map<int,int>a;
        for (auto i:nums)
            a[i]++;
        for (auto i:nums)
            if(a[i] == 1)
                return i;
        return -1;
    }

题解里有一个更厉害的思路,就是用异或(初看惊为天人,因为每个数字都可能出现两次,所以用异或,不同为1,每个数字都会被抵消掉,真的震撼)^^^^^^^^^^^^打几个异或符号压压惊

506.相对名词

题意:给定长度为n的数组,为运动员得分,所有得分互不相同

看到所有得分互不相同,然后数据规模在10^4,开始联想能不能用map或者set做这道题,最暴力的解法应该是构建一个结构体,结构体中存两个关键参数,一个是数值一个是下标,先对数值排序,然后再给相应下标的地方判断赋值(总感觉暴力都不是最优暴力,感觉还有更进一步的暴力)

(因为懒不想写暴力,感觉再赋值一遍确实太麻烦了,开始寻求别的思路,然后顺应之前的思路,想想map和set会不会更快,set看似可以,因为set自带排序,但是排完序之后怎么返回原来的顺序又成了一个问题,于是就想到了map,因为map会按照插入的first值从小到大进行排序,然后随之而来的问题就是如何逆序遍历?(学,都可以学)

    vector<string> findRelativeRanks(vector<int>& score) {
    // 重点  所有得分互不相同
    int n = score.size();
    map<int,int>a;
    vector<string>ans(n);
    for (int i = 0; i < n; i++)
        a[score[i]] = i;
    int i = 1;
    for (auto it = a.rbegin(); it != a.rend(); it++)
    {
        score[it -> second] = i;
        i++;
    }
    for (int i = 0; i < n; i++)
        if(score[i] > 3)
            ans[i] = to_string(score[i]);
        else if(score[i] == 1)
            ans[i] = "Gold Medal";
        else if(score[i] == 2)
            ans[i] = "Silver Medal";
        else if(score[i] == 3)
            ans[i] = "Bronze Medal";
    return ans;
    }

Set 用法题

首先要区分:Set和Map存在本质性的区别,Set主要是一个集合,他只能允许一个数字存在一次,主要是用于集合类题目使用的,但Map主要用于hash
set与map用法的不同主要体现在:用set存储<key, value>时,key和value必须相等。因此,若是想要修改set容器中的元素做法是:先删除某个元素,再添加修改后的元素。
SET容器常用方法
要注意的是,在判断set容器中是否含有元素时,判断条件应该是:是否等于迭代器的最末端。

771.宝石与石头

这道题就是非常经典的一个set用法题,因为我只需要判断前后两个字符串中字符的重合性,而不需要进行别的判断,因此没有必要使用map等容器,先将jwels中的元素加入set中,然后再遍历stones。

    int numJewelsInStones(string jewels, string stones) {
        set<char>a;
        int count = 0;
        for(int i = 0; i < jewels.size(); i++)
            a.insert(jewels[i]);
        for (int i = 0;i < stones.size(); i ++)
            if(a.find(stones[i]) != a.end())
                count++;
        return count;
    }

141.环形链表

看到这个题的第一想法就是用HashMap记录每个节点的遍历情况,如果在遍历过程中遇到了已经遍历过了的节点,则把标志变量flag 置为True,认为有环存在。但是实现过程中没想到不同节点可以有相同的val值,直接暴死。
偷偷去看了一眼题解,然后发现题解用的是Set,Set中存储的类型是ListNode类型,这样就可以对整个链表进行查重了。

    bool hasCycle(ListNode *head) {
        if(head == NULL)
            return false;
        unordered_set<ListNode*>a;
        while(head)
        {
            if(a.find(head) == a.end())
                a.insert(head);
            else
                return true;
            head = head -> next;
        }
        return false;
    }

题解的另一种做法:
快慢双指针法,即使用两个指针同时对整个链表进行遍历,如果快指针能追上慢指针,则说明存在回环。

字符串题

28. 找出字符串中第一个匹配项的下标

看到Easy标签,本来想一发strstr函数解决潇洒走人,然后发现离谱的是没有strstr函数。
KMP算法
BF算法就是最朴素的字符串匹配算法,在解题过程中,将源字符串与匹配字符串分别称为原串与模式串,如果使用BF算法,当原串与模式串某一位不同时,将会将模式串整体后移一位,重新进行匹配,但是若是引入前后缀相等的
KMP算法指的是三位大佬共同研发的字符串匹配算法,KMP算法的核心就是要利用模式字符串的前缀后缀相等,来减少匹配的冗余问题。
一个详解:
https://www.zhihu.com/question/21923021/answer/281346746
问题在于:如何处理模式串的next数组?next数组中实际上存储的就是模式串开头到当前字符为止最长相等的前后缀。
这里使用的方法是两个指针,i,j,分别维护的是右端点和左端点。
在KMP算法中存在多种写法,这里选用一种较为容易理解的一种,即在字符串开头插入一个空格,默认相等,这样的话 j可以从0开始

        for(int i = 2, j = 0; i <= m; i++){
            while(j and p[i] != p[j + 1]) j = next[j];
            if(p[i] == p[j + 1]) j++;
            next[i] = j;
        }

解析代码:在更新next数组的过程中,当左右端点的字符不相等时,即当前的前后缀字符串不相等,j向前回溯(因此是p[i]与p[j + 1]进行判断),回溯时才能j = next[j]
但是如果相等的话,j++ 即左端点也往后移动。

    int strStr(string haystack, string needle) {
        int n = haystack.size();
        int m = needle.size();
        haystack.insert(haystack.begin(), ' ');
        needle.insert(needle.begin(), ' ');
        vector<int>nxt(m + 1);
        for (int i = 2, j = 0; i <= m ; i++)
        {
            while(j > 0 && needle[i] != needle[j + 1])
                j = nxt[j];
            if(needle[i] == needle[j + 1])
                j++;
            nxt[i] = j;
        }
        for(int i = 1, j = 0; i <= n; i++){
            while(j && haystack[i] != needle[j + 1])
                j = nxt[j];
            if(haystack[i] == needle[j + 1]) 
                j++;
            if(j == m) 
                return i - m;
        }
        return -1;

    }

9. 回文数

题中说给一个整数x,不能使用字符串函数,尽量使用整数反转。
做法是直接使用了暴力反转

    bool isPalindrome(int x) {
        if(x < 0)
            return false;
        string s = to_string(x);
        int wei = s.size();
        long int n = 0, xx = x, now = 0;
        while(wei)
        {
            n += ((long int) (x / pow(10, wei - 1))) * pow(10, now);
            x %= (long int) (pow(10, wei - 1));
            wei--;
            now++;
        }
        if(n == xx)
            return true;
        return false;
    }

2337. 移动片段得到字符串

暴力解法超时

    bool canChange(string start, string target) {
        int leng = start.size();
        int left = 0, right = leng - 1;
        for (int i = 0; i < leng ; i++)
        {
            if(target[i] == 'L' && start[i] != 'L')
            {
                cout << "i = " << i << endl;
                for (int j = left + 1; j < leng; j++)
                {
                   if(start[j] == 'L' && j >= i)
                    {
                        start[j] = '_';
                        start[i] = 'L';
                        left ++;
                        break;
                    }
                    else if(start[j] == 'R')
                        return false;
                    else if(j == leng - 1)
                        return false;
                }
            }
        }
        cout << start << endl;
        for (int i = leng - 1; i >= 0 ; i--)
        {
            if(target[i] == 'R' && start[i] != 'R')
            {
                cout << "i = " << i << endl;
                for (int j = right - 1; j >= 0; j--)
                {
                    cout << "j = " << j << endl;
                    if(start[j] == 'R' && j <= i)
                    {
                        start[j] = '_';
                        start[i] = 'R';
                        right --;
                        break;
                    }
                    else if(start[j] == 'L')
                        return false;
                    else if(j == 0)
                        return false;
                }
            }
        }
        return start == target;
    }

思路:
从左往右扫字符串,如果 target 为’L’ 而且 start[i] 和 target[i] 在该位置不相等,此时需要去start串中的当前位置的右边找一个L,而且在找到这个L之前只能遇到字符’_’ ,如果在找字符’L’的路上遇到了字符’R’,那么说明已经形成了RL的局面,则无法进行L和R的对调,说明没办法达成目标效果,那么 return false 如果检索到了最右边还一直没找到字符‘L’,也 return false
然后再从右往左扫字符串,找 ‘R’ 逻辑与找‘L’的过程反过来

事实上,这个暴力思路已经非常接近于题解思路了,但是仍然存在一个问题,就是超时,也就是如果题目中恶意将L集中放在字符串的最右边,而且字符串左边放一堆字符’_’ 那么扫描时间将会非常长

要实现这个题目的题解思路,我们需要认识到两个问题,也就是将 start 串和 target 两个串中的所有L和R提取出来,如果两个串中的 L R 顺序不同,则说明无法实现片段的移动,即start中的顺序为LRLL,target串中的顺序为RLLL 局部存在同一位置的L和R不同,也没法实现片段的移动。

即使两个串中的LR顺序相同,也需要考虑以下两种情况,即:

  • start 串中的L 在目标串的左边,此时start串中的L没办法移动到右边去,因此也没办法实现
  • start 串中的R 在目标串的右边,此时start串中的R没办法移动到左边去,因此也没办法实现
    bool canChange(string start, string target) {
        int leng = start.size();
        vector<int>a, b;
        for (int i = 0; i < leng; i++)
        {
            if(start[i] == 'L' || start[i] == 'R')
                a.emplace_back(i);
            if(target[i] == 'L' || target[i] == 'R')
                b.emplace_back(i);
        }
        int alength = a.size(), blength = b.size();
        if(alength != blength)
            return false;
        for (int i = 0; i < alength; i++)
        {
            if(start[a[i]] != target[b[i]])
                return false;
            if(start[a[i]]=='L'&&a[i]<b[i]) 
                return false;
            if(start[a[i]]=='R'&&a[i]>b[i]) 
                return false;
        }
        return true;
    }

链表题

21. 合并两个有序链表

我的原本做法:
遍历两个链表的同时建立一个新链表,进行逐个比较,如果较小,则将较小的节点挂到新链表上,同时,较小的节点需要去向它指向的下一个有序节点。

    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        ListNode* list = new ListNode(-1);
        ListNode* bianli;
        ListNode* list3 = list1;
        ListNode* list4 = list2;
        ListNode* ans = list;
        bianli = list3;
        if(list1 == NULL)
            return list2;
        if(list2 == NULL)
            return list1;
        while(list1 != NULL && list2 != NULL)
        {
            int a1 = list1 -> val;
            int a2 = list2 -> val;
            if(a1 <= a2)
            {
                list -> next = list1;
                list1 = list1 -> next;
            }
            else
            {
                list -> next = list2;
                list2 = list2 -> next;
            }
            list = list -> next;
        }
        if(list1 == NULL)
            list -> next = list2;
        if(list2 == NULL)
            list -> next = list1;
        ans = ans -> next;
        return ans;
    }

该做法的缺陷就是链表遍历的时间太长,而且当其中有空链表时需要多重判断,而且需要创建多个链表节点。
另一种比较神奇的做法就是递归,递归在这道题的神奇之处的应用就在于:

每一次处理,本层都只需要处理应该做的两个节点,只需要针对这两个节点比大小,而这两个节点前面是什么,后面是什么,都不关心,如果list1的节点更小,那么list1后面挂的链就应该是当前的list1 -> next 与当前的list2 两条链合并而来的结果,因为最终结果的链应该要挂在节点值更小的那个结点上,但是我并不关心这条链是怎么来的,自行去下一层处理。

    ListNode* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(list1 == NULL)
            return list2;
        if(list2 == NULL)
            return list1;
        if(list1 -> val < list2 -> val)
        {
            list1 -> next = mergeTwoLists(list1 -> next, list2);
            return list1;
        }
            list2 -> next = mergeTwoLists(list1, list2 -> next);
            return list2;
    }

83.删除排序链表中的重复元素

记录这个题目的原因是遍历链表的写法还没有完全熟悉,而这个题目又是很能反应链表用法的一个题。
发现我经常写链表题写着写着就挂了,分析了一下发现是我很喜欢在链表遍历循环里套链表循环…其实大可不必,一次次遍历过去也是能够解决问题的,不需要急于求成。
本题需要解决的一个问题就是:如何在遍历过程中,将所有重复节点全部删除干净?
思路:当出现重复节点时,当前节点不动,重复节点一直往下next,直到所有的节点都被清除干净。
代码如下

    ListNode* deleteDuplicates(ListNode* head) {
        ListNode* bianli = head;
        if(bianli == NULL)
            return NULL;
        while(bianli -> next)
        {
            if(bianli -> next -> val == bianli -> val)
                bianli -> next = bianli -> next -> next;
            else
                bianli = bianli -> next;
        }
        return head;
    }

141.环形链表

题意:给你一个链表的头节点,判断链表中是否有环的存在。
首先想到的做法是用Set来进行hash判断,即用每个结点的val值来进行判断,然后就被狠狠暴死了,因为题目中并没有说过不能存在两个val值相同的节点,(然后开始抄题解
题解中点明了一种新思路就是建立ListNode*的集合,以此来判断是否重复,用该种集合就能够对整个链表进行查重。
新思路Get

    bool hasCycle(ListNode *head) {
        if(head == NULL)
            return false;
        unordered_set<ListNode*>a;
        while(head)
        {
            if(a.find(head) == a.end())
                a.insert(head);
            else
                return true;
            head = head -> next;
        }
        return false;
    }

然后又跑去抄题解,发现题解还有一种很巧妙的做法,即快慢双指针法,使用两个指针来遍历整个链表,一个指针一次移动两步,一个指针一次移动一步,如果整个链表中存在环的话,这个问题就会变成一个追及问题,就是慢指针迟早会被快指针追上,因为他们一直在绕着环跑跑跑,而且速度不同。
(力扣经常莫名其妙报错空指针,所以加了个判断)

    bool hasCycle(ListNode *head) {
        if(head == NULL)
            return false;
        ListNode* fast;
        ListNode* slow;
        fast = head -> next;
        slow = head;
        while(fast && slow)
        {
            if(fast == slow)
                return true;
            if(fast -> next == NULL)
                break;
            fast = fast -> next -> next;
            slow = slow -> next;
        }
        return false;
    }

160.相交链表

题意:给两个链表,如果两个链表相交,返回第一个相交节点,否则返回NULL
第一思路:HashMap

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA == NULL || headB == NULL)
            return NULL;
        set<ListNode*>a;
        while(headA)
        {
            a.insert(headA);
            headA = headA -> next;
        }
        while(headB)
        {
            if(a.find(headB) != a.end())
                return headB;
            headB = headB -> next;
        }
        return NULL;
    }

题解的妙思路:两个链表,用两个指针,将其演化成了追及运动,利用长短链表的长度差。
两个指针图解

    ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) {
        if(headA == NULL || headB == NULL)
            return NULL;
        ListNode* pA = headA;
        ListNode* pB = headB;
        while(pA != pB)
        {
            if(pA == NULL)
                pA = headB;
            else
                pA = pA -> next;
            if(pB == NULL)
                pB = headA;
            else
                pB = pB -> next;
        }
        return pA;
    }

树题

100. 相同的树

我的思路:分别对两棵树跑一遍中序遍历和前序遍历,比较一下结果,如果结果不同,则说明两棵树不相同,如果结果相同,则说明不相同,
缺陷:中序遍历 + 前序遍历只能确定层序遍历,因此无法确定树的结构。
题解思路:
树形结构最合适的解法就是递归,不断遍历整个子树,然后分别比较。
当时做题时想了这个思路,但是觉得会非常麻烦,实际上比我的思路还更简单一些。

    bool isSameTree(TreeNode* p, TreeNode* q) {
        if(p == NULL && q == NULL)
            return true;
        if(p == NULL || q == NULL)
            return false;
        if(p -> val != q -> val)
            return false;
        return isSameTree(p -> left, q -> left) && isSameTree(p -> right, q -> right);
    }

另一个思路:BFS,用队列拓展树形结构,然后进行比较,整体思路与递归相近,但是实现方法主要依赖于队列结构。队列结构的优点就是空间换取时间,因此会加速时间。

    bool isSameTree(TreeNode* p, TreeNode* q) {
        queue<TreeNode*>Q;
        Q.push(p);
        Q.push(q);
        while(!Q.empty())
        {
            TreeNode* n1 = Q.front();
            Q.pop();
            TreeNode* n2 = Q.front();
            Q.pop();
            if(n1 == NULL && n2 == NULL)
                continue;
            
            if(n1 == NULL || n2 == NULL)
                return false;

            if(n1 -> val != n2 -> val)
                return false;

            Q.push(n1 -> left);
            Q.push(n2 -> left);
            Q.push(n1 -> right);
            Q.push(n2 -> right);
        }
        return true;
    }

101.对称二叉树

题意:要求找一个轴对称的二叉树,很容易想到的一种解法:从根节点开始分,将子树分别分为左右两颗子树,递归判断左子树的最右节点和右子树的最左节点是否相等,又因为要判断轴对称,乍一看题以为节点的值不相等也无所谓,遇到[1,2,3]这样的样例结构才发现要相等才可以,代码如下:

class Solution {
public:
    bool Is_sym(TreeNode* left, TreeNode* right)
    {
        if(left == NULL && right == NULL)
            return true;
        if(left == NULL || right == NULL)
            return false;
        if(left -> val != right -> val)
            return false;
        return Is_sym(left -> right, right -> left) && Is_sym(left -> left, right -> right);
    }
    bool isSymmetric(TreeNode* root) {
        if(root == NULL)
            return true;
        return Is_sym(root -> left, root -> right);
    }
};

题解做法跟101题有些相似之处,仍然是使用队列来维护算法。思想还是一致的,不过是使用队列来进行迭代
代码:

class Solution {
public:
    bool Is_sym(TreeNode* left, TreeNode* right)
    {
        queue<TreeNode*>Q;
        Q.push(left);
        Q.push(right);
        while(!Q.empty())
        {
            auto n1 = Q.front();
            Q.pop();
            auto n2 = Q.front();
            Q.pop();
            if(n1 == NULL && n2 == NULL)
                continue;
            if(n1 == NULL || n2 == NULL)
                return false;
            if(n1 -> val != n2 -> val)
                return false;
            Q.push(n1 -> right);
            Q.push(n2 -> left);
            Q.push(n1 -> left);
            Q.push(n2 -> right);
        }
        return true;
    }
    bool isSymmetric(TreeNode* root) {
        if(root == NULL)
            return true;
        return Is_sym(root -> left, root -> right);
    }
};

104. 二叉树的最大深度

思想:遍历整个二叉树,但是只需要计算深度,因此只需要返回深度,简单的递归题。

    int maxDepth(TreeNode* root) {
        if(root == NULL)
            return 0;
        return max(maxDepth(root -> left), maxDepth(root -> right) ) + 1;
    }

108.将有序数组转换为二叉搜索树

这个题目刚开始做是有些困难,因为并不能够理解什么是二叉搜索树,偷偷看了一下题解,发现二叉搜索树的定义指的是一颗尽可能平衡的树(根节点的左子树和右子树层数差距小于1,而且根节点的左子树的值都小于根节点的值,根节点的右子树的值都大于根节点的值),比较容易想到的算法还是递归建树,递归建树还是要注意区分左右子树。

class Solution {
public:
    TreeNode* BuildTree(vector<int>& nums, int left, int right)
    {
        if(left > right)
            return NULL;
        int mid = left + (right - left) / 2;
        TreeNode* root = new TreeNode(-1);
        root -> val = nums[mid];
        root -> left = BuildTree(nums, left, mid - 1);
        root -> right = BuildTree(nums, mid + 1, right);
        return root;
    }
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        return BuildTree(nums, 0, nums.size() - 1);
    }
};

110.平衡二叉树

经典题目没看全…
给定一个二叉树,判断它是否是高度平衡的二叉树。
本题中,一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
因此,只需要在之前二叉树高度的基础上判断加一个递归,即将左右子树都纳入判断范围内即可。

    int MaxDep(TreeNode* root)
    {
        if(root == NULL)
            return 1;
        return max(MaxDep(root -> left), MaxDep(root -> right)) + 1;
    }
    bool isBalanced(TreeNode* root) {
        if(root == NULL)
            return true;
        int LeftDep = MaxDep(root -> left);
        int RightDep = MaxDep(root -> right);
        if(abs(LeftDep - RightDep) > 1)
            return false;
        return isBalanced(root -> left) && isBalanced(root -> right);
    }

111. 二叉树的最小深度

大概题意:一颗普通二叉树,不一定是满的,要求计算根节点到叶子节点的最小深度。
思路:如果这颗二叉树是一颗所有节点都有叶子节点的二叉树,那么只需要在求深度时进行一个min函数计算,
第一个难点:从根节点开始,每个节点都只有一个子节点,跟理想情况相差甚远,此时拓展思路:
如果遇到子节点只有一个的子树,此时在求其子节点深度时,应该返回的是max(两个子树深度值),因为其中必有空子树,所以采用max做法。

    int minDepth(TreeNode* root) {
        if(root == NULL)
            return 0;
        if(root -> left != NULL && root -> right != NULL)  
            return min(minDepth(root -> left), minDepth(root -> right)) + 1;
        return max(minDepth(root -> left), minDepth(root -> right)) + 1;
    }

112.路径总和

尝试性地写了一下DFS,但是确实写得很烂,看完题解之后大呼优雅,上代码!
我写的垃圾:

class Solution {
public:
    int flag = 0;
    void PathSum(TreeNode* root, int targetSum, int now)
    {
        if(root == NULL)
            return ;
        if(root ->left == NULL && root -> right == NULL)
        {
            if(now + root -> val == targetSum)
                flag = 1;
            return ;
        }
        PathSum(root -> left, targetSum, now + root -> val);
        PathSum(root -> right, targetSum, now + root -> val);
        return ;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root == NULL )
            return false;
        PathSum(root, targetSum, 0);
        if(flag)
            return true;
        return false;
    }
};

题解的优雅代码:

class Solution {
public:
    bool hasPathSum(TreeNode *root, int sum) {
        if (root == nullptr) {
            return false;
        }
        if (root->left == nullptr && root->right == nullptr) {
            return sum == root->val;
        }
        return hasPathSum(root->left, sum - root->val) ||
               hasPathSum(root->right, sum - root->val);
    }
};

二叉树遍历全解,透彻!
二叉树遍历的递归解法不用多说,非常简单(但是如果只会递归解法不是很菜吗!学点迭代挺好

144.二叉树的前序遍历

    vector<int> preorderTraversal(TreeNode* root) {
        vector<int>ans;
        stack<TreeNode*>st;
        TreeNode* now;
        if(root == NULL)
            return ans;
        st.push(root);
        while(!st.empty())
        {
            now = st.top();
            st.pop();
            ans.push_back(now -> val);
            if(now -> right)
                st.push(now -> right);
            if(now -> left)
                st.push(now -> left);
        }
        return ans;
    }

145.二叉树的后序遍历

 // 现在想实现的效果是左右根
 // 可以学习前序遍历,前序遍历的顺序为根 左右 root right left
 // 现在只需要左右根 root left right  然后倒转,即可左右根?
 // 因为栈是先进后出的
    vector<int> postorderTraversal(TreeNode* root) {
        vector<int>ans;
        stack<TreeNode*>st;
        if(root == NULL)
            return ans;
        st.push(root);
        while(!st.empty())
        {
            TreeNode* now = st.top();
            st.pop();
            ans.push_back(now -> val);
            if(now -> left)
                st.push(now -> left);
            if(now -> right)
                st.push(now -> right);
        }
        reverse(ans.begin(), ans.end());
        return ans;
    }

贪心算法

121.买卖股票的最佳时机

第一思路肯定是暴力循环,先试试暴力循环能不能解决问题,直接TLE,暴死
然后就跑去看题解,看到题解也有暴力甚是费解…为什么题解能跑暴力而我不行捏,然后底下说题解的暴力也TLE,笑死
算法大致流程我认为是一种小贪心,计算直到当前这一天的最大利润,实时更新

    int maxProfit(vector<int>& prices) {
        int min_price = prices[0];
        int maxx = 0;
        int length = prices.size();
        for(int i = 1; i < length; i++)
        {
            if(prices[i] - min_price > maxx)
                maxx = prices[i] - min_price;
            if(prices[i] < min_price)
                min_price = prices[i];
        }
        return maxx;
    }

位运算算法题

190.颠倒二进制位

题意就是有一个32位、以二进制表示的无符号型整数,要在其用二进制表示的时候颠倒一下,颠倒的效果即从1000 变为0001(以四位二进制举例)
普通的位运算做法很容易想到,就是第i位,有就移31-i位,

    uint32_t reverseBits(uint32_t n) {
        uint32_t m = 0;
        for (int i = 0;i <= 31 ; i++)
        {
            if((1 << i) & n)
                m += 1 << (31 - i);
        }
        return m;
    }

题解里另一种

模拟思路题

463.岛屿的周长

给定一个二维地图,grid i,j 表示水和陆地的情况,其值为1则表示陆地,否则表示为水域。

可以认为整个地图是海洋,海洋中恰有一座岛屿,岛屿中没有水,要计算岛屿周长。

之前遇到这个题目时一直非常头痛,因为一点思路没有,当时归纳总结了一下本题的主要难点:

1、题目中的岛屿 如何进行遍历查找?也就是如何才能找到整个地图的边界?

2、整个岛屿中,可能存在岛中陆地、也可能存在狭长的临海陆地,是否应该要找出所有的陆地并进行区分,然后再进行周长计算?

我想的是,如果将整个n x m 地图都进行判断的话,可能会出现重复判断的问题。

题解中有一种做法非常巧妙,遍历n x m 的地图,从左上角开始找起,只找与这个点的右边与下边相邻的点,就能够保证找完所有相邻的点。

而如果 有两个岛屿是相邻的 那么这两个岛屿都会损失一条边,一共损失两条边,所以返回的答案是:

岛屿的数量* 4 - 相邻岛屿的数量

    int islandPerimeter(vector<vector<int>>& grid) {
        int n = grid.size(), m = grid[0].size();
        int ludi = 0, lin = 0;
        for (int i = 0; i < n; i++)
            for (int j = 0; j < m; j++)
                if(grid[i][j] == 1)
                {
                    ludi++;
                    if(j + 1 < m && grid[i][j + 1])
                        lin++;
                    if(i + 1 < n && grid[i + 1][j])
                        lin++;
                }
        return ludi * 4 - lin * 2;
    }

拓扑排序

首先要明确拓扑排序的定义:

拓扑排序(Topological Sorting)是一种对有向无环图(DAG)进行排序的算法。在拓扑排序中,图中的顶点表示任务,有向边表示任务之间的依赖关系。拓扑排序可以用来解决任务调度、编译顺序等问题。

拓扑排序的过程是将图中的顶点按照一定的顺序排列,使得任意一条有向边的起点在排列中都出现在终点的前面。换句话说,对于任意一条有向边 (u, v),在排列中顶点 u 出现在顶点 v 的前面。

拓扑排序算法的基本思想是通过不断删除入度为0的顶点,直到所有顶点都被删除为止。具体步骤如下:

  1. 初始化一个队列,并将入度为0的顶点入队。
  2. 循环执行以下步骤,直到队列为空:
    a. 从队列中取出一个顶点,输出该顶点。
    b. 将该顶点的所有邻接顶点的入度减1。
    c. 如果某个邻接顶点的入度减为0,将其入队。
  3. 如果输出的顶点数量等于图中的顶点数量,则说明图中没有环,可以进行拓扑排序;否则,图中存在环,无法进行拓扑排序。

拓扑排序可以用来解决多个任务之间的依赖关系,例如编译顺序、任务调度等。在编译过程中,源代码文件之间存在依赖关系,拓扑排序可以确定编译的顺序,保证每个文件在被编译之前其依赖的文件已经被编译。在任务调度中,拓扑排序可以确定任务的执行顺序,保证每个任务在被执行之前其依赖的任务已经完成。

比较形象的例子就是在做事情时,要一步一步做,一件事情如果要拆分成不同的小事件,那么通常在完成这些小事件时都存在相对应的顺序,通常需要按照一定的顺序完成一系列的事件,最后再完成大事件。

207.课程表

在没有学习拓扑排序这个概念以前,对应课程表这道题的做法是:

学ai之前必须学bi,因此可以认为bi就是ai的父亲节点, 那么需要做的事情就是:按照每条边的关系,给每个节点添加对应的父结点,添加完之后再遍历所有节点,判断是否存在环

问题的关键在于:没想到一个子节点可以有多个父节点(什么吕布,重开!

那就什么也别说了,这边建议直接快进到拓扑排序

    bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int, vector<int>> mp;
        vector<int>In(numCourses);
        queue<int>Q;
        int count = 0;
        for (int i = 0; i < prerequisites.size(); i++)
        {
            int ai = prerequisites[i][0];
            int bi = prerequisites[i][1];
            mp[bi].push_back(ai);
            In[ai]++;
        }
        for (int i = 0; i < numCourses; i++)
            if(In[i] == 0)
                Q.push(i);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            count ++;
            vector<int>nxt = mp[now];
            for (int i = 0; i < nxt.size(); i++)
            {
                In[nxt[i]]--;
                if(In[nxt[i]] == 0)
                    Q.push(nxt[i]);
            }
        }
        return count == numCourses;
    }

210.课程表 II

如果使用队列拓展的话,题目思路基本和题目207保持一致,因为队列拓展得到的答案一定也是最优解,也就是先被拓展的点一定要先上课,那就直接再加个数组保存答案即可。

    vector<int> findOrder(int numCourses, vector<vector<int>>& prerequisites) {
        unordered_map<int, vector<int>> mp;
        vector<int>In(numCourses);
        queue<int>Q;
        vector<int>ans;
        int count = 0;
        for (int i = 0; i < prerequisites.size(); i++)
        {
            int ai = prerequisites[i][0];
            int bi = prerequisites[i][1];
            mp[bi].push_back(ai);
            In[ai]++;
        }
        for (int i = 0; i < numCourses; i++)
            if(In[i] == 0)
                Q.push(i);
        while(!Q.empty())   
        {
            int now = Q.front();
            Q.pop();
            count ++;
            ans.push_back(now);
            vector<int>nxt = mp[now];
            for (int i = 0; i < nxt.size(); i++)
            {
                In[nxt[i]]--;
                if(In[nxt[i]] == 0)
                    Q.push(nxt[i]);
            }
        }
        if(count != numCourses)
            return {};
        return ans;
    }

1462.课程表IV

果不其然,今日签到题还是课程表…

说一下我的暴力思路:

还是熟悉的拓扑排序,但是不同的就是回答ui是否是vi的先修课程,我的做法是对每一对询问都采用在线查找的办法,一对对找,经典TLE…经历过一段时间的崩溃之后想到新思路,做成离线数据库,在线只负责查找,应该会加速很多。

离线数据库需要考虑以下几点:记录谁是谁的爹,还是一样的问题,就是一个人可能会有很多个爹,我首先想到的办法就是遍历所有入度为0的点,用他们的顺序进行染色,具有同一个颜色的一定是一条绳子上的蚂蚱,然后发现这样做下去,又会面临一个问题:你只能知道他们是一条绳子上的蚂蚱,但你并不能知道哪个蚂蚱比较早出现,于是又改…尝试记录蚂蚱们的次序(写到这里终于崩溃了)

经典抄题解:

一个题解:暴力思路一致,但是用vector<set>把当前节点所有的爹全部用查重的方式记下来,我又大为震撼(震惊.jpg)

    vector<bool> checkIfPrerequisite(int numCourses, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {

        vector<vector<int>>fa(numCourses);
        vector<set<int>>father(numCourses);
        vector<int>In_Degree(numCourses);
        vector<bool>ans;
        queue<int>Q;

        for (int i = 0; i < prerequisites.size(); i++)
        {
             int ai = prerequisites[i][0];
             int bi = prerequisites[i][1];
            fa[ai].push_back(bi);
            In_Degree[bi]++;
        }
        for (int i = 0; i < numCourses; i++)
            if(In_Degree[i] == 0)
                Q.push(i);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            for (auto nxt:fa[now])
            {
                In_Degree[nxt]--;
                father[nxt].insert(father[now].begin(), father[now].end());
                father[nxt].insert(now);
                if(In_Degree[nxt] == 0)
                    Q.push(nxt);
            }
        }
        int T = queries.size();
        for ( int i = 0; i < T; i++)
        {
            if(father[queries[i][1]].find(queries[i][0]) != father[queries[i][1]].end())
                ans.push_back(true);
            else
                ans.push_back(false);
        }
        return ans;


    }

另一个题解:拓扑排序+最短路的思想

    vector<bool> checkIfPrerequisite(int numCourses, vector<vector<int>>& prerequisites, vector<vector<int>>& queries) {

        vector<vector<int>>fa(numCourses);
        vector<int>In_Degree(numCourses);
        vector<vector<bool>> isPre(numCourses, vector<bool>(numCourses, false));
        vector<bool>ans;
        queue<int>Q;

        for (int i = 0; i < prerequisites.size(); i++)
        {
             int ai = prerequisites[i][0];
             int bi = prerequisites[i][1];
            fa[ai].push_back(bi);
            In_Degree[bi]++;
        }
        for (int i = 0; i < numCourses; i++)
            if(In_Degree[i] == 0)
                Q.push(i);
        while(!Q.empty())
        {
            int now = Q.front();
            Q.pop();
            for (auto nxt:fa[now])
            {
                isPre[now][nxt] = true;
                In_Degree[nxt]--;
                for (int i = 0;i < numCourses; i++)
                    isPre[i][nxt] = isPre[i][nxt] | isPre[i][now];
                if(In_Degree[nxt] == 0)
                    Q.push(nxt);
            }
        }
        int T = queries.size();
        for ( int i = 0; i < T; i++)
        {
            bool pd = isPre[queries[i][0]][queries[i][1]];
            ans.push_back(pd);
        }
        return ans;

另一个题解:floyd(我擅长把简单问题复杂化)

这个就不写了,就是邻接矩阵 + Floyd算法,判断ui 能否走到 vi

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值