leetcode hot 100 刷题记录(medium)

题目3:无重复字符的最长子串(YES)

  • 解题思路:其实最好想到的方法就是使用两层for,让每个字符都可以是子串的首字符,查看哪个子串的长度最长即可。

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

class Solution {
public:
    int lengthOfLongestSubstring(string s) {
        //暴力的一次for,检查每个字符作为首字符时候的最长子串
        if(s.size()==0)
        {
            return 0;
        }

        int max_ans=INT_MIN;

        for(int i=0;i<s.size();i++)
        {
            unordered_map<int,int>map;
            int temp=0;
            for(int j=i;j<s.size();j++)
            {
                //使用哈希表来检测是否重复
                map[s[j]]++;
                if(map[s[j]]>1)
                {
                    break;
                }
                temp++;
                if(temp>max_ans)
                {
                    max_ans=temp;
                }

            }
        }

        return max_ans;
    }
};

题目146:LRU缓存(YES)

  • 解题思路:使用双链表加上哈希表来实现,哈希表用来查看节点是否存在,双链表用来刷新优先级。这题需要非常关注。

请你设计并实现一个满足 LRU (最近最少使用) 缓存 约束的数据结构。
实现 LRUCache 类:

  • LRUCache(int capacity) 以 正整数 作为容量 capacity 初始化 LRU 缓存
  • int get(int key) 如果关键字 key 存在于缓存中,则返回关键字的值,否则返回 -1 。
  • void put(int key, int value) 如果关键字 key 已经存在,则变更其数据值 value ;如果不存在,则向缓存中插入该组 key-value 。如果插入操作导致关键字数量超过 capacity ,则应该 逐出 最久未使用的关键字。

函数 get 和 put 必须以 O(1) 的平均时间复杂度运行。

//这题使用使用双链表+哈希表
//双链表是用来每次要刷新优先级的时候都将节点移动到表头
//哈希表是用来查看节点中是否存在key对应的节点

struct DLinkList
{
    int key,value;
    DLinkList*prev;//前指针
    DLinkList*next;//后指针

    //无参构成
    //这个主要是为了构造虚拟头尾节点
    DLinkList():key(0),value(0),prev(nullptr),next(nullptr){}

    //有参构造
    DLinkList(int key,int value):key(key),value(value),prev(nullptr),
    next(nullptr){}
};


class LRUCache {
private:
    int size;//当前节点的大小
    int capacity;//实际可存储的大小

    //虚拟头尾节点,主要是方便进行插入删除操作
    DLinkList*head;
    DLinkList*tail;

    //哈希表,用key来查找是否存在节点
    unordered_map<int,DLinkList*>map;

public:
    LRUCache(int capacity) {
        this->size=0;
        this->capacity=capacity;

        //构造虚拟头尾节点
        head=new DLinkList();
        tail=new DLinkList();
        head->next=tail;
        tail->prev=head;
    }
    
    int get(int key) {
        //查看节点是否存在,且要刷新一次优先级
        if(!map.count(key))
        {
            //不存在
            return -1;
        }

        //存在,则刷新优先级,就是移动到表头

        //先取出节点
        DLinkList*node=map[key];
        moveTohead(node);

        return node->value;

    }
    
    void put(int key, int value) {
        //存放节点

        if(!map.count(key))
        {
            //不存在,新建一个节点
            DLinkList*node=new DLinkList(key,value);

            //插入到表头
            InsertToHead(node);
            size++;

            //存入哈希表中
            map[key]=node;

            //判断是否超出
            if(size>capacity)
            {
                //要删除末尾的节点,也就是最久为使用的关键字
                DLinkList*temp = move_tail();

                //要从哈希表中删除这个节点
                map.erase(temp->key);

                //释放要删除的节点
                delete temp;
                size--;
            }
        }else
        {
            //存在,修改value
            //先取出节点
            DLinkList*node=map[key];
            node->value=value;

            //这个也要修改优先级
            moveTohead(node);
        }
    }

    void moveTohead(DLinkList*node)
    {
        //将节点移动到表头,这是节点本来就有的
        
        //先删除这个节点
        node->prev->next=node->next;
        node->next->prev=node->prev;

        //将节点放入到表头

        //未了防止连接断掉,先处理后面的
        head->next->prev=node;
        node->next=head->next;

        head->next=node;
        node->prev=head;
    }

    void InsertToHead(DLinkList*node)
    {
        //这个是刚刚申请的直接插入表头即可

        //先处理后面的
        head->next->prev=node;
        node->next=head->next;

        head->next=node;
        node->prev=head;
    }

    DLinkList*move_tail()
    {
        //删除末尾的节点
        DLinkList*temp=tail->prev;

        tail->prev=temp->prev;
        temp->prev->next=temp->next;

        return temp;
    }

};

/**
 * Your LRUCache object will be instantiated and called as such:
 * LRUCache* obj = new LRUCache(capacity);
 * int param_1 = obj->get(key);
 * obj->put(key,value);
 */

 

题目215:数组中第K个最大元素(NO)

  • 解题思路:使用快速排序算法,切记快速排序算法是递归算法,递归终止条件不要忘。

给定整数数组 nums 和整数 k,请返回数组中第 k 个最大的元素。

请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

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

class Solution {
public:
    //快速排序算法
    void quick_sort(vector<int>&nums,int left,int right)
    {
        //递归终止条件要有
        if(left>=right)
        {
            return ;
        }

        //快速排序的思想就是每次都用中间节点最为排序中间的元素
        //比它大的放后面,比它小的放前面

        int mid=nums[(left+right)/2];//中间元素
        int i=left;
        int j=right;

        while(i<j)
        {
            while(nums[i]<mid)
            {
                i++;
            }

            while(nums[j]>mid)
            {
                j--;
            }

            //找到了要交换的值
            if(i<=j)
            {
                swap(nums[i],nums[j]);
                i++;
                j--;
            }
            
        }

        //检测左边
        if(i<right)
        {
            quick_sort(nums,i,right);
        }

        if(j>left)
        {
            quick_sort(nums,left,j);
        }
    }

    int findKthLargest(vector<int>& nums, int k) {
        //这题看似和简单表面上sort一下就解决了,但是这题考察的就是
        //排序算法的使用,我这题直接使用快排
        
        int len=nums.size();
        quick_sort(nums,0,len-1);
        
        return nums[len-k];
    }
};

题目15:三数之和(NO)

  • 解题思路:排序加双指针

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

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

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

class Solution {
public:
    vector<vector<int>> threeSum(vector<int>& nums) {
        //使用排序加上双指针

        sort(nums.begin(),nums.end());
        vector<vector<int>>ans;
        int len=nums.size();

        //每个节点都可以最为第一个节点
        for(int i=0;i<len-2;i++)
        {
            //判断是否可前面的节点相同
            if(i>0&&nums[i]==nums[i-1])
            {
                //确保首元素不同
                continue;
            }

            //目标元素是当前的相反数
            int target=-nums[i];

            //查找后面的两个元素
            //使用while首尾开始查找
            int left=i+1;
            int right=len-1;

            while(left<right)
            {
                int sum=nums[left]+nums[right];
                if(sum<target)
                {
                    //小了
                    left++;
                }else if(sum>target)
                {
                    //大了
                    right--;
                }else
                {
                    //找到相加为零的三个数了
                    vector<int>temp;
                    temp.push_back(nums[i]);
                    temp.push_back(nums[left]);
                    temp.push_back(nums[right]);
                    ans.push_back(temp);

                    left++;
                    right--;

                    //查看元素是否不重复
                    while (left < right && nums[left] == nums[left-1]) // 跳过重复元素
                        ++left;
                    while (left < right && nums[right] == nums[right+1]) // 跳过重复元素
                        --right;
                }

            }

        }
        return ans;

    }
};

题目53:最大子数组和(NO)

  • 动态规划,典型的人不为己,天诛地灭。

给你一个整数数组 nums ,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

子数组是数组中的一个连续部分。

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int pre = 0, maxAns = nums[0];
        for (const auto &x: nums) {

            //原先的最大值可以当作前一个数
            //如果前面的值加上自己比自己小了,这显然拖累自己了果断抛弃
            //如果是自己拖累了前面的,那就不管了

            //典型的人不为己,天诛地灭。
            pre = max(pre + x, x);
            maxAns = max(maxAns, pre);
        }
        return maxAns;
    }
};

题目5:最长回文子串(NO)

  • 解题思路:暴力节,使用s.substr截取每个子串判断是否是回文数

给你一个字符串 s,找到 s 中最长的 回文子串。

class Solution {
public:
    string longestPalindrome(string s) {
        //暴力算法
        //截取出每个子串然后判断是否是回文数
        string res=s.substr(0,1);
        for(int i=0;i<s.size();i++)
        {
            for(int j=i+1;j<s.size();j++)
            {
                if(j-i+1>res.size()&&isPalindrome(s,i,j))
                {
                    //substr是截取子串,i是首字符,j-i+1是长度
                    res=s.substr(i,j-i+1);
                }
            }
        }

        return res;
    }

    bool isPalindrome(string &s,int left,int right)
    {
        while(left<=right)
        {
            if(s[left]!=s[right])
            {
                return false;
            }
            left++;
            right--;
        }

        return true;
    }
};

题目102:二叉树的层序遍历(YES)

  • 这算比较容易的中等难度题了,直接使用层序遍历,也就是广度优先遍历算法即可,关键是对队列的使用。

给你二叉树的根节点 root ,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    vector<vector<int>> levelOrder(TreeNode* root) {
        vector<vector<int>>ans;

        if(root==nullptr)
        {
            return ans;
        }

        //层序遍历
        queue<TreeNode*>que;
        que.push(root);
        while(!que.empty())
        {
            //处理当前层的
            int size=que.size();

            vector<int>ret;
            for(int i=0;i<size;i++)
            {
                TreeNode*temp=que.front();
                que.pop();
                ret.push_back(temp->val);

                if(temp->left!=nullptr)
                {
                    que.push(temp->left);
                }

                if(temp->right!=nullptr)
                {
                    que.push(temp->right);
                }

            }
            ans.push_back(ret);
        }

        return ans;
    }
};

题目33:搜索旋转排序数组(YES)

  • 解题思路:以第一个结点为依据来判断是向上查找还是向下查找。

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

在传递给函数之前,nums 在预先未知的某个下标 k(0 <= 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) 的算法解决此问题。

class Solution {
public:
    int search(vector<int>& nums, int target) {
        //这题的难度在于时间复杂度上的要求

        int len=nums.size();

        //我打算以第一个为基础进行查找
       
        if(nums[0]>target)
        {
            //向下查找
            int i=len-1;
            while(nums[i]!=target)
            {
                i--;
                if(i<0||nums[i]<target)
                {
                    return -1;
                }
            }
            return i;
        }else
        {
            //向上查找
            int i=0;
            while(nums[i]!=target)
            {
                i++;
                if(i>=len||nums[i]>target)
                {
                    return -1;
                }
            }

            return i;
        }

        return -1;
    }
};

题目200:岛屿数量(NO)

  • 解题思路:广度优先遍历,设置访问数组这里很容易出错。

给你一个由 ‘1’(陆地)和 ‘0’(水)组成的的二维网格,请你计算网格中岛屿的数量。

岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。

此外,你可以假设该网格的四条边均被水包围。

class Solution {
public:
    int numIslands(vector<vector<char>>& grid) {
        if (grid.empty() || grid[0].empty()) { // 如果网格为空,返回岛屿数量为0
            return 0;
        }
        
        int row = grid.size(); // 获取网格行数
        int col = grid[0].size(); // 获取网格列数
        int ans = 0; // 岛屿数量
        vector<vector<bool>> visited(row, vector<bool>(col, false)); // 二维visited数组用来记录已访问过的位置
        
        for (int i = 0; i < row; i++) { // 遍历网格行
            for (int j = 0; j < col; j++) { // 遍历网格列
                if (grid[i][j] == '1' && !visited[i][j]) { // 如果当前位置是陆地且未访问过
                    ans++; // 岛屿数量加一
                    queue<pair<int, int>> que; // 定义队列用于广度优先搜索
                    que.push({i, j}); // 将当前陆地位置加入队列
                    visited[i][j] = true; // 标记当前位置已访问
                    
                    while (!que.empty()) { // 开始广度优先搜索
                        int current_x = que.front().first;
                        int current_y = que.front().second;
                        que.pop(); // 弹出当前位置
                        
                        int dx[4] = {-1, 1, 0, 0}; // 定义方向数组
                        int dy[4] = {0, 0, -1, 1};

                        for (int k = 0; k < 4; k++) { // 遍历四个方向
                            int next_x = current_x + dx[k];
                            int next_y = current_y + dy[k];
                            
                            if (next_x >= 0 && next_x < row && next_y >= 0 && next_y < col
                                && grid[next_x][next_y] == '1' && !visited[next_x][next_y]) { // 判断边界,未访问过,以及为陆地
                                que.push({next_x, next_y});
                                visited[next_x][next_y] = true; // 将相邻陆地加入队列并标记为已访问
                            }
                        }
                    }
                }
            }
        }

        return ans; // 返回岛屿数量
    }
};

题目46:全排列(NO)

  • 解题思路:使用了回溯算法,核心的地方其实是递归算法的使用。

给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

class Solution {
public:
    vector<vector<int>> permute(vector<int>& nums) {
        vector<vector<int>> res;
        vector<int> temp; // 用于临时存储排列的容器
        vector<int> visited(nums.size(), 0); // 记录每个数是否已经访问过的标志数组
        backtrack(nums, res, temp, visited);
        return res;
    }
    
    void backtrack(vector<int>& nums, vector<vector<int>>& res, vector<int>& temp, vector<int>& visited) {
        if (temp.size() == nums.size()) { // 如果临时排列的长度等于输入数组的长度,则将当前排列加入结果集
            res.push_back(temp);
            return;
        }
        
        for (int i = 0; i < nums.size(); ++i) {
            if (visited[i] == 0) { // 如果当前数字未被访问过
                visited[i] = 1; // 标记当前数字为已访问
                temp.push_back(nums[i]); // 将当前数字加入临时排列
                backtrack(nums, res, temp, visited); // 递归填充下一个位置
                temp.pop_back(); // 撤销操作,将当前数字移出临时排列
                visited[i] = 0; // 恢复当前数字为未访问状态,以便后续排列中再次使用
            }
        }
    }
};

题目19:删除链表的倒数第N个结点(YES)

  • 解题思路:暴力解,直接先算出链表长度,在算出要删除结点的前一个位置。

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

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* removeNthFromEnd(ListNode* head, int n) {
        //用最暴力的解法
        //先计算结点长度
        int count=0;
        ListNode*temp=head;
        while(temp!=nullptr)
        {
            count++;
            temp=temp->next;
        }

        //此时计算出要删除的前一个结点位置
        int move=count-n-1;

        if(move<0)
        {
            return head->next;
        }else
        {
            temp=head;
            for(int i=0;i<move;i++)
            {
                temp=temp->next;
            }
            temp->next=temp->next->next;
        }

        return head;
    }
};

题目148:排序链表(NO)

  • 解题思路:这题要实现一个链表的排序显然比较麻烦,但是如果转换成两个链表的排序就会简单很多,所有这题的精妙之处就是使用递归将一个链表分成两个来处理。
/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode*merger(ListNode*L1,ListNode*L2)
    {
        //设置一个头结点方便处理
        ListNode*head=new ListNode(-1);
        ListNode*temp=head;

        while(L1!=nullptr&&L2!=nullptr)
        {
            if(L1->val<=L2->val)
            {
                temp->next=L1;
                L1=L1->next;
                temp=temp->next;
            }else
            {
                temp->next=L2;
                L2=L2->next;
                temp=temp->next;
            }
        }

        //处理最后没完成的
        if(L1!=nullptr)
        {
            temp->next=L1;
        }else
        {
            temp->next=L2;
        }

        return head->next;

    }
    
    ListNode* sortList(ListNode* head) {
        //这题的思想是将一个两边使用的递归的方式每次都分成两个链表

        //递归终止条件
        if(!head||!head->next)
        {
            return head;
        }

        //将一个链表从中间分开
        ListNode*slow=head;
        ListNode*fast=head->next;

        //这里判断的fast在前的目的是如果本身就是nullptr就无需判断了
        while(fast&&fast->next)
        {
            slow=slow->next;
            fast=fast->next->next;
        }

        ListNode*L2=slow->next;
        slow->next=nullptr;
        ListNode*L1=head;

        return merger(sortList(L1),sortList(L2));
    }
};

题目56:合并区间(YES)

  • 解题思路:这里先是对vector<vector<int>>进行了排序,这里运用了对sort函数自定义排序函数的使用,后面就是扫描哪些可以合并,合并后将第二个删除掉。

以数组 intervals 表示若干个区间的集合,其中单个区间为 intervals[i] = [starti, endi] 。请你合并所有重叠的区间,并返回 一个不重叠的区间数组,该数组需恰好覆盖输入中的所有区间 。

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        //这题要涉及对vector进行删除操作,必须用迭代器

        //先对intervals排序
        // 使用lambda表达式自定义排序规则
    sort(intervals.begin(), intervals.end(), [](const vector<int>& a, const vector<int>& b) {
        // 比较两个vector<int>的第一个元素
        return a[0] < b[0];
    });

        vector<vector<int>>::iterator it=intervals.begin();

        for(it;it!=intervals.end();it++)
        {
            vector<vector<int>>::iterator se=(it+1);
            for(se;se!=intervals.end();)
            {
                if((*it)[1]>=(*se)[0])
                {
                    //可以合并
                    if((*it)[1]<(*se)[1])
                    {
                        (*it)[1]=(*se)[1];
                    }
                    

                    //删除se
                    se=intervals.erase(se);
                    continue;
                }
                se++;
            }
        }

        return intervals;
    }
};

题目128:最长连续序列(YES)

  • 解题思路:排序后在进行检测

给定一个未排序的整数数组 nums ,找出数字连续的最长序列(不要求序列元素在原数组中连续)的长度。

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

class Solution {
public:
    int longestConsecutive(vector<int>& nums) {
        if(nums.size()==0)
        {
            return 0;
        }
        if(nums.size()==1)
        {
            return 1;
        }
        int max_size=1;
        int count=1;
        //排序
        sort(nums.begin(),nums.end());
        //想要时间复杂度是O(n),就必须只是遍历一次
        for(int i=1;i<nums.size();i++)
        {
            if(nums[i]-nums[i-1]==0)
            {
                //不计入
                continue;
            }else if(nums[i]-nums[i-1]==1)
            {
                //连续
                count++;
                if(max_size<count)
                {
                    max_size=count;
                }
            }else 
            {
                //不连续
                count=1;
            }
        }
        return max_size;
    }
};

题目31:下一个排列(NO)

  • 解题思路:暂时无法理解

整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。

例如,arr = [1,2,3] ,以下这些都可以视作 arr 的排列:[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。
整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。

例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。
给你一个整数数组 nums ,找出 nums 的下一个排列。

必须 原地 修改,只允许使用额外常数空间。

void nextPermutation(vector<int>& nums) {
    int i = nums.size() - 2;
    // 1. 从右往左找到第一个降序的数字,记为i
    while (i >= 0 && nums[i] >= nums[i + 1]) {
        i--;
    }
    
    if (i >= 0) {
        // 2. 从右往左找到第一个大于nums[i]的数字,记为j
        int j = nums.size() - 1;
        while (j >= 0 && nums[j] <= nums[i]) {
            j--;
        }
        // 3. 交换nums[i]和nums[j]
        swap(nums[i], nums[j]);
    }
    
    // 4. 将nums[i+1:]翻转
    reverse(nums.begin() + i + 1, nums.end());
}

题目(面试题02.05)链表求和(NO)

  • 解题思路:因为本身就是从地位开始,所以分别处理L1和L2就行,重点是要考虑到进位位的处理。

给定两个用链表表示的整数,每个节点包含一个数位。

这些数位是反向存放的,也就是个位排在链表首部。

编写函数对这两个整数求和,并用链表形式返回结果。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {  
        int carry = 0;  //进位位
        ListNode* dummy = new ListNode(0);  
        ListNode* current = dummy;  

        while (l1 || l2 || carry) { //设置可以继续相加的操作 
            int sum = carry;  
            if (l1) {  //处理l1
                sum += l1->val;  
                l1 = l1->next;  
            }  
            if (l2) {  //处理l2
                sum += l2->val;  
                l2 = l2->next;  
            }  

            carry = sum / 10;  
            current->next = new ListNode(sum % 10);  
            current = current->next;  
        }  

        return dummy->next;  
    }  
};

题目538:把二叉树转为累加树(NO)

  • 解题思路:这是对递归压栈的理解,找到规律最重要,递归到右子树,sum从最右边开始叠加。在处理左子树。

给出二叉 搜索 树的根节点,该树的节点值各不相同,请你将其转换为累加树(Greater Sum Tree),使每个节点 node 的新值等于原树中大于或等于 node.val 的值之和。

提醒一下,二叉搜索树满足下列约束条件:

节点的左子树仅包含键 小于 节点键的节点。
节点的右子树仅包含键 大于 节点键的节点。
左右子树也必须是二叉搜索树。

/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    int sum = 0;

    TreeNode* convertBST(TreeNode* root) {
        if (root != nullptr) {
            convertBST(root->right);
            sum += root->val;
            root->val = sum;
            convertBST(root->left);
        }
        return root;
    }
};

题目78:子集(NO)

  • 解题思路:深度优先遍历

给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的
子集
(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

class Solution {
public:
    vector<int> t;
    vector<vector<int>> ans;

    void dfs(int cur, vector<int>& nums) {
        if (cur == nums.size()) {
            ans.push_back(t);
            return;
        }
        t.push_back(nums[cur]);
        dfs(cur + 1, nums);
        t.pop_back();
        dfs(cur + 1, nums);
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        dfs(0, nums);
        return ans;
    }
};


这段代码用于生成给定整数数组 nums 的所有子集(即幂集),下面详细解释其结构和逻辑,并通过示例进行说明。

代码详解

  1. 成员变量:

    • vector<int> t;:存储当前生成的子集。
    • vector<vector<int>> ans;:存储最终的所有子集。
  2. 深度优先搜索 (DFS):

    • 函数 dfs(int cur, vector<int>& nums) 用于构建子集。
    • cur 参数表示当前选择的索引,控制在 nums 中的位置。
    • cur 等于 nums.size() 时,表示遍历完所有元素,此时将当前子集 t 添加到结果 ans 中。
  3. 递归逻辑:

    • dfs中, 先将当前元素 nums[cur] 加入子集 t,然后递归调用 dfs(cur + 1, nums) 处理下一个元素。
    • 回溯时,使用 t.pop_back() 移除最后加入的元素,生成不包含当前元素的子集。
    • 随后再次调用 dfs(cur + 1, nums) 处理下一个元素,不包括当前元素。
  4. 主函数:

    • vector<vector<int>> subsets(vector<int>& nums) 是入口函数,初始化深度优先搜索,从索引 0 开始调用 dfs

示例
假设输入数组为 nums = [1, 2],以下是如何生成所有子集的过程。

  1. 初始状态:

    cur = 0, t = [], ans = []
    调用 dfs(0, nums)

    首先加入元素 1:

    • t = [1]
    • 递归调用 dfs(1, nums)

    cur = 1

    • 加入元素 2:t = [1, 2]
    • 递归调用 dfs(2, nums)
    • 达到 cur = 2,将 t = [1, 2] 加入到 ans
    • 回溯,t = [1]
    • 继续处理,t.pop_back() 后再次调用 dfs(2, nums),这次不包括 2:
    • t = [1],将 t 加入到 ans
    • 结束 cur = 1 的处理,回溯到 cur = 0

    继续在 cur = 0

    • 再次 t.pop_back() 此时 t = [],递归调用 dfs(1, nums),这时不包含 1:
    • 加入元素 2:t = [2]
    • 递归调用 dfs(2, nums),到达 cur = 2,将 t = [2] 加入到 ans
    • 回溯,t = []
    • 再次调用 dfs(2, nums),这次不包括 2,得到空集 []

最终结果 ans 将是:

  • [] (空子集)
  • [1]
  • [2]
  • [1, 2]

代码的输出将是所有可能的子集。

代码总结
该算法通过深度优先搜索 (DFS) 递归生成所有子集。通过两个主要逻辑分支——加入当前元素或不加入当前元素,实现了所有组合的遍历,最终得到完整的子集列表。时间复杂度为 O(2^n),空间复杂度为 O(n)(用于存储当前子集)。

题目2.两数相加(YES)

  • 解题思路:构建一个新的链表用来存储最后的结果,然后遍历处理要相加的链表就行了。

给你两个 非空 的链表,表示两个非负的整数。它们每位数字都是按照 逆序 的方式存储的,并且每个节点只能存储 一位 数字。

请你将两个数相加,并以相同形式返回一个表示和的链表。

你可以假设除了数字 0 之外,这两个数都不会以 0 开头。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    ListNode* addTwoNumbers(ListNode* l1, ListNode* l2) {
        //直接再原来的链表上进行修改,感觉效果不是很好
        //所以准备用构建新的链表用来返回数据

        ListNode*head=new ListNode(-1);
        ListNode*temp=head;
        int count=0;//进位位
        while(l1!=nullptr||l2!=nullptr)
        {
            int val1=l1==nullptr?0:l1->val;
            int val2=l2==nullptr?0:l2->val;

            int sum=val1+val2+count;
            
            //更新进位位
            count=sum/10;
            sum=sum%10;

            ListNode*node=new ListNode(sum);
            temp->next=node;
            temp=temp->next;

            if(l1!=nullptr)
            {
                l1=l1->next;
            }

            if(l2!=nullptr)
            {
                l2=l2->next;
            }


        }

        //处理head的末尾
        if(count==1)
        {
            ListNode*node=new ListNode(1);
            temp->next=node;
            temp=temp->next;
        }
        temp->next=nullptr;

        return head->next;

    }
};

题目912:手撕快速排序(YES)

  • 总结一下记忆的方法,递归结束条件要有等号,第一重划分左右区间要有等号,判断交换要有等号。判断左边,判断右边要有等号。

  • 简单的来说,就是只有找左右不符合条件的才不用等号,因为要严格小于或者大于。

给你一个整数数组 nums,请你将该数组升序排列。

class Solution {
public:
    //使用快速排序算法
    void quick_sort(vector<int>&nums,int left,int right)
    {
        //快速排序是一个递归算法,要有递归结束条件
        if(left>=right)
        {
            return ;
        }

        int i=left;
        int j=right;
        int mid=nums[(i+j)/2];

        while(i<=j)
        {
            //找到左边不符合条件的
            while(nums[i]<mid)
            {
                i++;
            }

            //找到右边不符合条件的
            while(nums[j]>mid)
            {
                j--;
            }

            if(i<=j)
            {
                swap(nums[i],nums[j]);
                i++;
                j--;
            }
        }

        //判断左边
        if(i<=right)
        {
            quick_sort(nums,i,right);
        }

        //判断右边
        if(left<=j)
        {
            quick_sort(nums,left,j);
        }

    }


    vector<int> sortArray(vector<int>& nums) {
        int left=0;
        int right=nums.size()-1;
        quick_sort(nums,left,right);

        return nums;
    }
};


题目143:重排链表(NO)

  • 解题思路:使用vector容器来存储链表结点使之成为线性结构会变得更加的简单。这题这么想倒是不难,难就难在如何处理while里面的操作。

给定一个单链表 L 的头节点 head ,单链表 L 表示为:

L0 → L1 → … → Ln - 1 → Ln
请将其重新排列后变为:

L0 → Ln → L1 → Ln - 1 → L2 → Ln - 2 → …
不能只是单纯的改变节点内部的值,而是需要实际的进行节点交换。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode() : val(0), next(nullptr) {}
 *     ListNode(int x) : val(x), next(nullptr) {}
 *     ListNode(int x, ListNode *next) : val(x), next(next) {}
 * };
 */
class Solution {
public:
    void reorderList(ListNode* head) {
        //这题直接使用vector来存储会比较简单
        vector<ListNode*>ans;

        ListNode*temp=head;

        while(temp!=nullptr)
        {
            //使用emplace_back(temp)是为防止进行拷贝操作,浪费空间
            ans.emplace_back(temp);
            temp=temp->next;
        }

        //此时的链表就线性的存储再容器中
        int i=0;
        int j=ans.size()-1;

        while(i<j)
        {
            ans[i]->next=ans[j];
            i++;
            if(i>=j)
            {
                break;
            }

            ans[j]->next=ans[i];
            j--;
        }

        ans[i]->next=nullptr;

    }
};

题目142:环形链表|| (YES)

  • 解题思路:这题使用哈希表会非常的简单。

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

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

不允许修改 链表。

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode *detectCycle(ListNode *head) {
        //这题使用哈希表显然会非常简单
        ListNode*temp=head;

        unordered_map<ListNode*,int>map;
        while(temp!=NULL)
        {
            if(map.count(temp))
            {
                return temp;
            }
            map[temp]++;
            temp=temp->next;
        }

        return NULL;
    }
};

  • 这里顺便解释一下unordered_map和map的一些区别。

unordered_map 和 map 是 C++ STL 中两个不同的关联容器,它们主要有以下几个区别:

  1. 底层实现:
  • map 是基于红黑树(自平衡二叉搜索树)实现的,因此元素是自动按键排序的。
  • unordered_map 是基于哈希表实现的,因此元素是无序的,查找效率高。
  1. 排序:
  • 在 map 中,元素会根据键的顺序自动排序(有序容器)。
  • 在 unordered_map 中,元素是按照哈希值存储的,没有特定的顺序(无序容器)。
  1. 时间复杂度:
  • map 的插入、删除和查找操作的时间复杂度为 O(log n)。
  • unordered_map 的插入、删除和查找操作的平均时间复杂度为 O(1),最坏情况下为 O(n)。
  1. 内存使用:
  • 由于哈希表的实现,unordered_map 可能会使用更多的内存来存储桶和避免冲突。
  • map 在内存使用上相对较为节省,但因为是树结构,可能有额外的指针开销。
  1. 适用场景:
  • 使用 map 当需要保持元素的顺序时,例如按键的升序排列。
  • 使用 unordered_map 当只需要快速的查找,且不关心元素顺序时。

示例代码

#include <iostream>  
#include <map>  
#include <unordered_map>  

int main() {  
    // 使用 map  
    std::map<int, std::string> ordered_map;  
    ordered_map[3] = "Three";  
    ordered_map[1] = "One";  
    ordered_map[2] = "Two";  

    std::cout << "Map contents (ordered):" << std::endl;  
    for (const auto& pair : ordered_map) {  
        std::cout << pair.first << ": " << pair.second << std::endl;  
    }  

    // 使用 unordered_map  
    std::unordered_map<int, std::string> unordered_map;  
    unordered_map[3] = "Three";  
    unordered_map[1] = "One";  
    unordered_map[2] = "Two";  

    std::cout << "Unordered map contents (unordered):" << std::endl;  
    for (const auto& pair : unordered_map) {  
        std::cout << pair.first << ": " << pair.second << std::endl;  
    }  

    return 0;  
}  

总结

选择使用 map 还是 unordered_map 主要取决于你的具体需求,是否需要有序性和查找效率等因素。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值