面试150——第七周

目录

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

排序链表

合并k个升序链表

建立四叉树

最大子数组之和

环形子数组的最大和

搜索插入位置

搜索二维矩阵

寻找峰值

搜索旋转排序数组

在排序数组中找第一个位置和最后一个位置

寻找旋转数组的最小值

寻找正序数组的中位数


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

解法:递归

找中间值构建根节点,

根节点的左右子树分别递归左区间与右区间

返回根节点

cpp 复制代码
class Solution
{
public:
    TreeNode *dfs(vector<int> &nums, int begin, int end)
    {
        if (begin > end)
        {
            return nullptr;
        }
        int mid = begin + (end - begin) / 2;
        TreeNode *node = new TreeNode(nums[mid]);
        node->left = dfs(nums, begin, mid - 1);
        node->right = dfs(nums, mid + 1, end);
        return node;
    }
    TreeNode *sortedArrayToBST(vector<int> &nums)
    {
        return dfs(nums, 0, nums.size() - 1);
    }
};

排序链表

思路1:归并排序

快慢双指针找中间节点(不推荐通过计算中间值,遍历的方式找中间节点容易乱),分为两个链表

两个链表通过递归后变成两个有序链表

进行有序链表合并为一个有序链表

返回合并后的新链表

cpp 复制代码
class Solution
{
public:
    // 不使用数组的方法找中点,链表适合快慢双指针找中点
    ListNode *MergeSort(ListNode *begin)
    {
        if (!begin || !begin->next)
        {
            return begin;
        }
        // 这里要保存mid落在第一个链表的尾节点上,所有end要初始化时要先走两步
        // 不然 4->2 的情况下会死递归
        ListNode *mid = begin, *end = begin->next->next;
        while (end && end->next)
        {
            mid = mid->next;
            end = end->next->next;
        }
        end = mid->next;
        mid->next = nullptr;
        ListNode *begin1 = MergeSort(begin);
        ListNode *begin2 = MergeSort(end);
        // 合并 begin1 和 begin2
        ListNode ret(-1);
        ListNode *tail1 = &ret;
        while (begin1 || begin2)
        {
            if (begin1 && begin2)
            {
                if (begin1->val < begin2->val)
                {
                    tail1->next = begin1;
                    begin1 = begin1->next;
                }
                else
                {
                    tail1->next = begin2;
                    begin2 = begin2->next;
                }
                tail1 = tail1->next;
            }
            else if (begin1)
            {
                tail1->next = begin1;
                begin1 = begin1->next;
                tail1 = tail1->next;
            }
            else
            {
                tail1->next = begin2;
                begin2 = begin2->next;
                tail1 = tail1->next;
            }
        }
        return ret.next;
    }
    ListNode *sortList(ListNode *head)
    {
        return MergeSort(head);
    }
};

思路2:归并排序非递归

把原链表采用头节点的方式管理起来,方便后续合并前后找链表头尾节点

使用变量 step 代表每次合并链表的长度,每次 *=2,但 step >= 链表长度就可以返回答案

每次遍历使用 tail 和 cur 进行标记:

tail:合并完成的新有序链表插入到它后面,之后更新tail到新有序链表的尾节点中

cur:找当前待合并的两个step长的链表,之后更新到第二段链表尾节点的next节点上

注意:

cur 找第二段链表可能为空,head2 = cur->next 要特判

cur 找下一组时为空了,nextHead = cur->next 也要特判下

cpp 复制代码
class Solution
{
public:
    ListNode *sortList(ListNode *head)
    {
        if (!head || !head->next)
        {
            return head;
        }
        ListNode *tail = head;
        int cnt = 0;
        while (tail)
        {
            cnt++;
            tail = tail->next;
        }
        ListNode ret(0, head);
        for (int step = 1; step < cnt; step *= 2)
        {
            // tail后面跟的是合并完成的有序链表
            ListNode *tail = &ret;
            // cur当前节点开始,按照step分出两段长step的链表
            ListNode *cur = tail->next;
            while (cur)
            {
                // 第一段
                ListNode *head1 = cur;
                for (int i = 1; i < step && cur; i++)
                {
                    cur = cur->next;
                }
                // 第二段
                ListNode *head2 = nullptr;
                // 原链表[1,2] 第一段 [1,2]
                if (cur)
                {
                    head2 = cur->next;
                    cur->next = nullptr;
                }
                cur = head2;
                for (int i = 1; i < step && cur; i++)
                {
                    cur = cur->next;
                }
                // 保存下一组起点
                ListNode *NextHead = nullptr;
                // 原链表[1,2] cur在2后面了
                if (cur)
                {
                    NextHead = cur->next;
                    cur->next = nullptr;
                }
                // 合并的新节点插入到tail后面
                tail->next = Merget(head1, head2);

                // 更新下一组起点为结束
                cur = NextHead;
                while (tail->next)
                {
                    tail = tail->next;
                }
            }
        }
        return ret.next;
    }
    ListNode *Merget(ListNode *begin1, ListNode *begin2)
    {
        ListNode tmp;
        ListNode *tail = &tmp;
        while (begin1 && begin2)
        {
            if (begin1->val < begin2->val)
            {
                tail->next = begin1;
                begin1 = begin1->next;
            }
            else
            {
                tail->next = begin2;
                begin2 = begin2->next;
            }
            tail = tail->next;
        }
        tail->next = begin1 == nullptr ? begin2 : begin1;
        return tmp.next;
    }
};

合并k个升序链表

解法:模拟

使用优先级队列(小堆)的方式实现,每次拿出最小的进行尾插,把next加入到堆中

cpp 复制代码
class Solution
{
public:
    struct CmpNodeVal
    {
        bool operator()(ListNode *node1, ListNode *node2)
        {
            return node1->val > node2->val;
        }
    };
    ListNode *mergeKLists(vector<ListNode *> &lists)
    {
        priority_queue<ListNode *, vector<ListNode *>, CmpNodeVal> q;
        for (auto &list : lists)
        {
            if (list)
                q.push(list);
        }
        ListNode head;
        ListNode *tail = &head;
        while (!q.empty())
        {
            auto tmp = q.top();
            q.pop();
            tail->next = tmp;
            if (tmp->next)
                q.push(tmp->next);
            tmp->next = nullptr;
            tail = tmp;
        }
        return head.next;
    }
};

建立四叉树

解法:递归

dfs(0, 0 , n -1, m -1)表示以左上角(0,0)到 右下角(n-1,m-1)坐标下建立四叉树,返回根节点

如果(0,0)和(n-1,m-1)包围的面积值都相同,直接构建根节点返回

否则就先构建根节点

依次构建左右上下节点,也就是进行递归...难就难在 左上坐标和右下坐标的传参:先根据当前坐标计算出行和列之和再取中间值,根据规律选择+中间值(可能还要-1才适合)或者不加直接传参

cpp 复制代码
class Solution
{
public:
    Node *dfs(vector<vector<int>> &grid, int leftX, int leftY, int rightX, int rightY)
    {
        bool ok = true;
        int tmp = grid[leftX][leftY];
        for (int i = leftX; i <= rightX; i++)
        {
            for (int j = leftY; j <= rightY; j++)
            {
                if (grid[i][j] != tmp)
                {
                    ok = false;
                    break;
                }
            }
        }
        if (ok)
        {
            return new Node(tmp, true);
        }
        int n = rightX - leftX + 1, m = rightY - leftY + 1;
        Node *root = new Node(tmp, false);
        root->topLeft = dfs(grid, leftX, leftY, leftX + n / 2 - 1, leftY + m / 2 - 1);
        root->topRight = dfs(grid, leftX, leftY + m / 2, leftX + n / 2 - 1, rightY);
        root->bottomLeft = dfs(grid, leftX + n / 2, leftY, rightX, leftY + m / 2 - 1);
        root->bottomRight = dfs(grid, leftX + n / 2, leftY + m / 2, rightX, rightY);
        return root;
    }
    Node *construct(vector<vector<int>> &grid)
    {
        int n = grid.size();
        return dfs(grid, 0, 0, n - 1, n - 1);
    }
};

最大子数组之和

解法:动态规划

dp[i]:以当前i为结尾的最大子数组之和,dp[i] = max(dp[i - 1] + nums[i], nums[i])

使用 ret 每个位置的最大和取max值后返回

cpp 复制代码
class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n+1, 0);
        int ret = INT_MIN;
        for(int i = 1; i <= n; i++)
        {
            dp[i] = max(dp[i-1] + nums[i-1], nums[i-1]);
            if(dp[i] > ret)
            {
                ret = dp[i];
            }
        }
        return ret;
    }
};

class Solution
{
public:
    int maxSubArray(vector<int> &nums)
    {
        int n = nums.size();
        int ret = nums[0], tmp = nums[0];
        for (int i = 1; i < n; i++)
        {
            tmp = max(tmp + nums[i], nums[i]);
            if (tmp > ret)
            {
                ret = tmp;
            }
        }
        return ret;
    }
};

环形子数组的最大和

解法:正难则反

  • 1:不是环形数组的情况(也就是没有边界情况)下最大和是多少(上面的思路)
  • 2:是环形数组的情况(也就是有边界情况)下:先计算环形子数组的最小和,再使用总和减去最小和就得到结果

最终的答案是两种情况取最大值(如果出现第二种情况为0,就直接返回第一种情况)

cpp 复制代码
class Solution
{
public:
    int maxSubarraySumCircular(vector<int> &nums)
    {
        int n = nums.size();
        int prevMin = nums[0], curMin = nums[0], prevMax = nums[0], curMax = nums[0], sum = nums[0];
        for (int i = 1; i < n; i++)
        {
            sum += nums[i];
            prevMin = min(prevMin + nums[i], nums[i]);
            curMin = min(curMin, prevMin);
            prevMax = max(prevMax + nums[i], nums[i]);
            curMax = max(curMax, prevMax);
        }
        if (sum == curMin)
        {
            return curMax;
        }
        int cnt = max(curMax, sum - curMin);
        return cnt;
    }
};

搜索插入位置

解法:二分

右端点模板解决

cpp 复制代码
class Solution
{
public:
    int searchInsert(vector<int> &nums, int target)
    {
        int n = nums.size();
        int left = 0, right = n - 1;
        while (left < right)
        {
            int mid = left + (right - left) / 2;
            if (nums[mid] >= target)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }
        // 特判
        if (nums[left] < target)
        {
            return left + 1;
        }
        return left;
    }
};

搜索二维矩阵

思路1:

从原点开始,右下 和 下右 这两种情况来找 target 看看该值是否存在

cpp 复制代码
class Solution
{
public:
    int t;
    int rowFindTarget(int jBegin, int jEnd, int row, vector<vector<int>> &matrix)
    {
        while (jBegin < jEnd)
        {
            int mid = jBegin + (jEnd - jBegin + 1) / 2;
            if (matrix[row][mid] <= t)
            {
                jBegin = mid;
            }
            else
            {
                jEnd = mid - 1;
            }
        }
        return jBegin;
    }
    int colFindTarget(int iBegin, int iEnd, int col, vector<vector<int>> &matrix)
    {
        while (iBegin < iEnd)
        {
            int mid = iBegin + (iEnd - iBegin + 1) / 2;
            if (matrix[mid][col] <= t)
            {
                iBegin = mid;
            }
            else
            {
                iEnd = mid - 1;
            }
        }
        return iBegin;
    }
    bool searchMatrix(vector<vector<int>> &matrix, int target)
    {
        int n = matrix.size(), m = matrix[0].size();
        t = target;
        // [0,0]开始,右下二分
        int jBegin = rowFindTarget(0, m - 1, 0, matrix);
        int iBegin = colFindTarget(0, n - 1, jBegin, matrix);
        if (matrix[iBegin][jBegin] == target)
        {
            return true;
        }
        // [0,0]开始,下右二分
        iBegin = colFindTarget(0, n - 1, 0, matrix);
        jBegin = rowFindTarget(0, m - 1, iBegin, matrix);
        cout << iBegin << ' ' << jBegin << endl;
        if (matrix[iBegin][jBegin] == target)
        {
            return true;
        }
        return false;
    }
};

思路2:

排除法,从右上角开始:(当坐标不越界时)

如果当前位置 matrix[i][j] < target,i++

如果当前位置 matrix[i][j] > target,j--

剩下的情况就是找到了target,自己返回true

如果没有找到的情况下就返回 false

寻找峰值

思路:二分

按照右端点的思路来实现,比较 nums[i] 与 nums[i+1] 来移动位置

cpp 复制代码
class Solution {
public:
    int findPeakElement(vector<int>& nums) {
        int n = nums.size();
        int left = 0, right = nums.size() - 1;
        while(left < right)
        {
            int mid = left + (right - left)/2;
            if(nums[mid] < nums[mid + 1])
            {
                left = mid + 1;
            }
            else
            {
                right = mid;
            }
        }
        return left;
    }
};

搜索旋转排序数组

解法:二分(右端点实现)

判断 nums[left] 与 nums[mid] 是在排序左区间还是右区间,再分别看target是否再该区间,从而来移动left 或者 right 的位置

cpp 复制代码
class Solution {
public:
    int search(vector<int>& nums, int target) {
        int n = nums.size();
        int left = 0,right = n-1;
        while(left<right)
        {
            int mid = left + (right-left)/2;
            if(nums[left] <= nums[mid])
            {
                if(nums[left] <= target && target <= nums[mid])
                {
                    right = mid;
                }
                else
                {
                    left = mid + 1;
                }
            }
            else
            {
                // 实现右端点left移动判断的是target在 [mid+1,right]
                if(nums[mid] < target && target <= nums[right])
                {
                    left = mid + 1;
                }
                else
                {
                    right = mid;
                }
            }
        }
        if(nums[left] == target) return left;
        return -1;
    }
};

在排序数组中找第一个位置和最后一个位置

解法:二分

左右端点结合

cpp 复制代码
class Solution {
public:
    vector<int> searchRange(vector<int>& nums, int target) {
        int n = nums.size();
        if(n == 0)
        {
            return {-1, -1};
        }
        int left = 0, right = n - 1;
        int ret_left = 0, ret_right = 0;
        while(left < right)
        {
            int mid = left + (right - left) / 2;
            if(nums[mid] >= target)
            {
                right = mid;
            }
            else
            {
                left = mid + 1;
            }
        }
        if(nums[left] != target)
        {
            ret_left = -1;
        }
        else
        {
            ret_left = left;
        }

        left = 0, right = n - 1;
        while(left < right)
        {
            int mid = left + (right - left + 1) / 2;
            if(nums[mid] <= target)
            {
                left = mid;
            }
            else
            {
                right = mid - 1;
            }
        }
        if(nums[left] != target)
        {
            ret_right = -1;
        }
        else
        {
            ret_right = left;
        }
        return {ret_left, ret_right};
    }
};

寻找旋转数组的最小值

解法:二分

左端点进行解题,不过难在判断什么时候需要移动left的位置?

当left和mid在排序左区间且二者都大于right的位置时,此时最小值一定在mid右区间

cpp 复制代码
class Solution {
public:
    int findMin(vector<int>& nums) {
        int n = nums.size();
        int l = 0, r = n - 1;
        while(l < r)
        {
            int mid = l + (r - l) / 2;
            // 当前[l,mid]处于左区间
            if(nums[l] <= nums[mid] && nums[l] > nums[r] && nums[mid] > nums[r])
            {
                l = mid + 1;
            }
            else
            {
                r = mid;
            }
        }
        return nums[l];
    }
};

寻找正序数组的中位数

思路1:模拟

合并正序数组位一个大的正序数组,计算中位数

cpp 复制代码
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        vector<int> ret;
        int m = nums1.size(), n = nums2.size();
        if(m + n == 0)
        {
            return 0;
        }
        int i = 0, j = 0;
        while(i < m && j < n)
        {
                if(nums1[i] < nums2[j])
                {
                    ret.push_back(nums1[i++]);
                }
                else
                {
                    ret.push_back(nums2[j++]);
                }
        }
        while(i < m)
        {
            ret.push_back(nums1[i++]);
        }
        while(j < n)
        {
            ret.push_back(nums2[j++]);
        }
        if((m + n) % 2 != 0)
        {
            return ret[(m + n) / 2];
        }
        else
        {
            return (double)(ret[(m+n) / 2] + ret[(m+n) / 2 - 1]) / 2;
        }
    }
};

思路2:二分

优化前

cpp 复制代码
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size() > nums2.size())
        {
            swap(nums1, nums2);
        }
        int m = nums1.size(), n = nums2.size();

        nums1.insert(nums1.begin(), INT_MIN);
        nums1.push_back(INT_MAX);
        nums2.insert(nums2.begin(), INT_MIN);
        nums2.push_back(INT_MAX);
        
        int totalLeftWithSentinels = (m + n + 1) / 2;
        int nums1_left = 0, nums1_right = m + 1;
        while(nums1_left < nums1_right)
        {
            int nums1_mid = nums1_left + (nums1_right - nums1_left + 1) / 2;
            int nums2_mid = totalLeftWithSentinels - nums1_mid;
            if(nums1[nums1_mid] <= nums2[nums2_mid + 1])
            {
                nums1_left = nums1_mid;
            }
            else
            {
                nums1_right = nums1_mid - 1;
            }
        }
        int i = nums1_left;
        int j = totalLeftWithSentinels - i;
        int a = max(nums1[i], nums2[j]);
        if((m + n) % 2 != 0)
        {
            return a;
        }
        int b = min(nums1[i+1], nums2[j+1]);
        return (double)(a + b) / 2;
    }
};

优化后

cpp 复制代码
class Solution {
public:
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        if(nums1.size() > nums2.size())
        {
            swap(nums1, nums2);
        }
        int m = nums1.size(), n = nums2.size();

        // nums1.insert(nums1.begin(), INT_MIN);
        // nums1.push_back(INT_MAX);
        // nums2.insert(nums2.begin(), INT_MIN);
        // nums2.push_back(INT_MAX);
        
        int totalLeftWithSentinels = (m + n + 1) / 2;
        int nums1_left = 0 - 1, nums1_right = m + 1 - 1;// 映射
        while(nums1_left < nums1_right)
        {
            int nums1_mid = nums1_left + (nums1_right - nums1_left + 1) / 2;
            // 这里映射难点:num1_mid要映射,num2_mid也要映射
            int nums2_mid = totalLeftWithSentinels - (nums1_mid + 1) - 1;
            if(nums1_mid < m && nums1[nums1_mid] <= nums2[nums2_mid + 1])
            {
                nums1_left = nums1_mid;
            }
            else
            {
                nums1_right = nums1_mid - 1;
            }
        }
        // 这里还要处理越界问题
        int pos1 = nums1_left, pos2 = (n + m + 1) / 2 - (nums1_left + 1) - 1;
        int a1 = pos1 < 0 ? INT_MIN : nums1[pos1], b1 = pos2 < 0 ? INT_MIN : nums2[pos2];
        int a2 = pos1 + 1 >= m ? INT_MAX : nums1[pos1 + 1], b2 = pos2 + 1 >= n ? INT_MAX : nums2[pos2 + 1];
        double ret1 = max(a1, b1), ret2 = min(a2, b2);
        return (n + m) % 2 == 0 ? (ret1 + ret2) / 2 : ret1;
    }
};

具体实现参考:hot100------第九周

数组第 k 个最大值

解法:小堆

使用优先级的小堆的方式来实现,数组依次如堆:当堆的个数大于k 时,出堆顶...遍历完成后此时堆顶就是答案

cpp 复制代码
class Solution {
public:
    int findKthLargest(vector<int>& nums, int k) {
        priority_queue<int,vector<int>,greater<int>> q;
        for(auto& num: nums)
        {
            q.push(num);
            if(q.size() > k)
            {
                q.pop();
            }
        }
        return q.top();
    }
};

以上便是全部内容,有问题欢迎在评论区指正,感谢观看!

相关推荐
Nontee2 小时前
面试准备(Reids存粹问题版)
java·面试
AI科技星2 小时前
万能学习方法论的理论建构与多领域适配性研究(乖乖数学)
人工智能·学习·算法·机器学习·平面·数据挖掘
Ashore11_2 小时前
蓝桥杯16届Java研究生组
java·算法·蓝桥杯
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 76. 最小覆盖子串 | C++ 滑动窗口题解
c++·算法·leetcode
像素猎人2 小时前
蓝桥杯OJ2049蓝桥勇士【动态规划】【dp[n]不是符合题意的答案,只是以an结尾的子问题的答案】
c++·算法·蓝桥杯·动态规划·区间dp
羊小猪~~2 小时前
LLM--SFT简介
python·考研·算法·ai·大模型·llm·微调
广州灵眸科技有限公司2 小时前
瑞芯微(EASY EAI)RV1126B 人脸98关键点算法识别
开发语言·科技·嵌入式硬件·物联网·算法·php
篮子里的玫瑰2 小时前
FreeRTOS:信号量与互斥量在DMA串口发送中的实战剖析
stm32·单片机·嵌入式硬件·算法
hughnz2 小时前
钻头技术持续突飞猛进:地热钻探领域的创新
人工智能·算法