LeetCode高频算法刷题记录6

1. 编辑距离【困难】

题目链接:https://leetcode.cn/problems/edit-distance/
参考题解:https://leetcode.cn/problems/edit-distance/solution/bian-ji-ju-chi-by-leetcode-solution/

1.1 题目描述

给你两个单词 word1 和 word2, 请返回将 word1 转换成 word2 所使用的最少操作数 。

你可以对一个单词进行如下三种操作:

  • 插入一个字符
  • 删除一个字符
  • 替换一个字符

示例1:

输入:word1 = “horse”, word2 = “ros”
输出:3
解释:
horse -> rorse (将 ‘h’ 替换为 ‘r’)
rorse -> rose (删除 ‘r’)
rose -> ros (删除 ‘e’)

示例2:

输入:word1 = “intention”, word2 = “execution”
输出:5
解释:
intention -> inention (删除 ‘t’)
inention -> enention (将 ‘i’ 替换为 ‘e’)
enention -> exention (将 ‘n’ 替换为 ‘x’)
exention -> exection (将 ‘n’ 替换为 ‘c’)
exection -> execution (插入 ‘u’)

提示:

  • 0 <= word1.length, word2.length <= 500
  • word1 和 word2 由小写英文字母组成

1.2 解题思路

两个字符串之间的所有转换都可以用 3 种形式来表示:把 word1 添加一个字符变成 word2 ;把 word2 添加一个字符变成 word1 ;把 word1 修改一个字符变成 word2 。

  • 如果 word1[0…i-1] 到 word2[0…j-1] 的变换需要消耗 k 步,那么 word1[0…i] 到 word2[0…j] 只需要把 word1[0…i-1] 变换到 word2[0…j-1],消耗 k 步;再把 word1[i] 改成 word2[j],就行了。如果 word1[i] == word2[j],什么也不用做,一共消耗 k 步,否则需要修改,一共消耗 k + 1 步。
  • 如果 word1[0…i-1] 到 word2[0…j] 的变换需要消耗 k 步,那么 word1[0…i] 到 word2[0…j] 先经过 k 步,把 word1[0…i-1] 变换到 word2[0…j],消耗掉 k 步,再把 word1[i] 删除,这样,word1[0…i] 就完全变成了 word2[0…j] 了。一共 k + 1 步。
  • 如果 word1[0…i] 到 word2[0…j-1] 的变换需要消耗 k 步,那么 word1[0…i] 到 word2[0…j] 先经过 k 步,把 word1[0…i] 变换成 word2[0…j-1],消耗掉 k 步,接下来,再插入一个字符 word2[j],那么 word1[0…i] 就完全变成了 word2[0…j] 了。一共 k + 1 步。

因此,word1[0…i] 变换成 word2[0…j] 主要有三种手段,用哪个消耗少,就用哪个。

1.3 代码实现

class Solution {
public:
    int minDistance(string word1, string word2) {
        int len1 = word1.length();
        int len2 = word2.length();
        vector<vector<int>> ans(len1 + 1, vector<int>(len2 + 1));
        for(int i = 0; i < len1 + 1; ++i) {
            for(int j = 0; j < len2 + 1; ++j) {
                if(!i)
                    ans[0][j] = j;
                else if(!j)
                    ans[i][0] = i;
                else {
                    int insert1 = ans[i][j - 1] + 1;
                    int insert2 = ans[i - 1][j] + 1;
                    int replace = ans[i - 1][j - 1];
                    if(word1[i - 1] != word2[j - 1])
                        replace += 1;
                    ans[i][j] = min(min(insert1, insert2), replace);
                }
            }
        }
        return ans[len1][len2];
    }
};

2. 寻找两个正序数组的中位数【困难】

题目链接:https://leetcode.cn/problems/median-of-two-sorted-arrays/
参考题解:https://leetcode.cn/problems/median-of-two-sorted-arrays/solution/xun-zhao-liang-ge-you-xu-shu-zu-de-zhong-wei-s-114/

2.1 题目描述

给定两个大小分别为 m 和 n 的正序(从小到大)数组 nums1 和 nums2。请你找出并返回这两个正序数组的 中位数

算法的时间复杂度应该为 O(log (m+n)) 。

示例1:

输入:nums1 = [1,3], nums2 = [2]
输出:2.00000
解释:合并数组 = [1,2,3] ,中位数 2

示例2:

输入:nums1 = [1,2], nums2 = [3,4]
输出:2.50000
解释:合并数组 = [1,2,3,4] ,中位数 (2 + 3) / 2 = 2.5

提示:

  • nums1.length == m
  • nums2.length == n
  • 0 <= m <= 1000
  • 0 <= n <= 1000
  • 1 <= m + n <= 2000
  • -10^6 <= nums1[i], nums2[i] <= 10^6

2.2 解题思路

这道题让我们求两个有序数组的中位数,而且限制了时间复杂度为 O(log (m+n)) ,看到这个时间复杂度,自然而然的想到了应该使用二分查找法来求解。那么回顾一下中位数的定义,如果某个有序数组长度是奇数,那么其中位数就是最中间那个,如果是偶数,那么就是最中间两个数字的平均值。这里对于两个有序数组也是一样的,假设两个有序数组的长度分别为 m 和 n ,由于两个数组长度之和 m+n 的奇偶不确定,因此需要分情况来讨论,对于奇数的情况,直接找到最中间的数即可,偶数的话需要求最中间两个数的平均值。为了简化代码,不分情况讨论,我们分别找第 (m+n+1) / 2 个,和 (m+n+2) / 2 个,然后求其平均值即可,这对奇偶数均适用。假如 m+n 为奇数的话,那么其实 (m+n+1) / 2 和 (m+n+2) / 2 的值相等,相当于两个相同的数字相加再除以 2 ,还是其本身。

这里需要定义一个函数来在两个有序数组中找到第 K 个元素,下面重点来看如何实现找到第 K 个元素。首先,为了避免产生新的数组从而增加时间复杂度,我们使用两个变量i和j分别来标记数组 nums1 和 nums2 的起始位置。然后来处理一些边界问题,比如当某一个数组的起始位置大于等于其数组长度时,说明其所有数字均已经被淘汰了,相当于一个空数组了,那么实际上就变成了在另一个数组中找数字,直接就可以找出来了。还有就是如果 K = 1 的话,那么我们只要比较 nums1 和 nums2 的起始位置i和j上的数字就可以了。难点就在于一般的情况怎么处理?因为我们需要在两个有序数组中找到第 K 个元素,为了加快搜索的速度,我们要使用二分法,对 K 二分,意思是我们需要分别在 nums1 和 nums2 中查找第 K / 2 个元素,注意这里由于两个数组的长度不定,所以有可能某个数组没有第 K / 2 个数字,所以我们需要先检查一下,数组中到底存不存在第 K / 2 个数字,如果存在就取出来,否则就赋值上一个整型最大值。如果某个数组没有第 K / 2 个数字,那么我们就淘汰另一个数字的前 K / 2 个数字即可。有没有可能两个数组都不存在第 K / 2 个数字呢,这道题里是不可能的,因为我们的 K 不是任意给的,而是给的 m+n 的中间值,所以必定至少会有一个数组是存在第 K / 2 个数字的。最后就是二分法的核心啦,比较这两个数组的第 K / 2 小的数字 midVal1 和 midVal2 的大小,如果第一个数组的第 K / 2 个数字小的话,那么说明我们要找的数字肯定不在 nums1 中的前 K / 2 个数字,所以我们可以将其淘汰,将 nums1 的起始位置向后移动 K / 2 个,并且此时的K也自减去 K / 2 ,调用递归。反之,我们淘汰 nums2 中的前 K / 2 个数字,并将 nums2 的起始位置向后移动 K / 2 个,并且此时的 K 也自减去 K / 2 ,调用递归即可。

2.3 代码实现

class Solution {
public:
    double findKSortedArrays(vector<int>& nums1, int start1, vector<int>& nums2, int start2, int k) {
        int len1 = nums1.size();
        int len2 = nums2.size();
        if(start1 >= len1)
            return nums2[start2 + k - 1];
        if(start2 >= len2)
            return nums1[start1 + k - 1];
        if(k == 1)
            return min(nums1[start1], nums2[start2]);
        int mid1 = (start1 + k / 2 - 1 < len1) ? nums1[start1 + k / 2 - 1] : INT_MAX;
        int mid2 = (start2 + k / 2 - 1 < len2) ? nums2[start2 + k / 2 - 1] : INT_MAX;
        if(mid1 < mid2)
            return findKSortedArrays(nums1, start1 + k / 2, nums2, start2, k - k / 2);
        else {
            return findKSortedArrays(nums1, start1, nums2, start2 + k / 2, k - k / 2);
        }
    }
    double findMedianSortedArrays(vector<int>& nums1, vector<int>& nums2) {
        int len1 = nums1.size();
        int len2 = nums2.size();
        int k1 = (len1 + len2 + 1) / 2;
        int k2 = (len1 + len2 + 2) / 2;
        return (findKSortedArrays(nums1, 0, nums2, 0, k1) + findKSortedArrays(nums1, 0, nums2, 0, k2)) / 2.0;
    }
};

3. 合并区间【中等】

题目链接:https://leetcode.cn/problems/merge-intervals/
参考题解:https://leetcode.cn/problems/merge-intervals/solution/he-bing-qu-jian-by-leetcode-solution/

3.1 题目描述

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

示例1:

输入:intervals = [[1,3],[2,6],[8,10],[15,18]]
输出:[[1,6],[8,10],[15,18]]
解释:区间 [1,3] 和 [2,6] 重叠, 将它们合并为 [1,6].

示例2:

输入:intervals = [[1,4],[4,5]]
输出:[[1,5]]
解释:区间 [1,4] 和 [4,5] 可被视为重叠区间。

提示:

  • 1 <= intervals.length <= 10^4
  • intervals[i].length == 2
  • 0 <= starti <= endi <= 10^4

3.2 解题思路

如果按照区间的左端点排序,那么在排完序的列表中,可以合并的区间一定是连续的。首先,将列表中的区间按照左端点升序排序。然后将第一个区间加入 merged 数组中,并按顺序依次考虑之后的每个区间:如果当前区间的左端点在数组 merged 中最后一个区间的右端点之后,那么它们不会重合,可以直接将这个区间加入数组 merged 的末尾;否则,它们重合,需要用当前区间的右端点更新数组 merged 中最后一个区间的右端点,将其置为二者的较大值。

3.3 代码实现

class Solution {
public:
    vector<vector<int>> merge(vector<vector<int>>& intervals) {
        sort(intervals.begin(), intervals.end());
        int len = intervals.size();
        vector<vector<int>> ans;
        ans.push_back(intervals[0]);
        for(int i = 1; i < len; ++i) {
            int start = intervals[i][0];
            int end = intervals[i][1];
            if(start > ans.back()[1])
                ans.push_back(intervals[i]);
            else {
                ans.back()[1] = max(ans.back()[1], end);
            }
        }
        return ans;
    }
};

4. 爬楼梯【简单】

题目链接:https://leetcode.cn/problems/climbing-stairs/
参考题解:https://leetcode.cn/problems/climbing-stairs/solution/pa-lou-ti-by-leetcode-solution/

4.1 题目描述

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

示例1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1.1 阶 + 1 阶
2.2 阶

示例2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1.1 阶 + 1 阶 + 1 阶
2.1 阶 + 2 阶
3.2 阶 + 1 阶

提示:

  • 1 <= n <= 45

4.2 解题思路

递归, f(x)=f(x−1)+f(x−2) ,注意好边界条件即可。

4.3 代码实现

class Solution {
public:
    int climbStairs(int n) {
        int fn_2 = 0;
        int fn_1 = 0;
        int fn = 1;
        for(int i = 1; i <= n; ++i) {
            fn_2 = fn_1;
            fn_1 = fn;
            fn = fn_1 + fn_2;
        }
        return fn;
    }
};

5. 排序链表【中等】

题目链接:https://leetcode.cn/problems/sort-list/
参考题解:https://leetcode.cn/problems/sort-list/solution/pai-xu-lian-biao-by-leetcode-solution/

5.1 题目描述

给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表

示例1:
在这里插入图片描述

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

示例2:
在这里插入图片描述

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

示例3:

输入:head = []
输出:[]

提示:

  • 链表中节点的数目在范围 [0, 5 * 10^4] 内
  • -10^5 <= Node.val <= 10^5

进阶: 你可以在 O(n log n) 时间复杂度和常数级空间复杂度下,对链表进行排序吗?

5.2 解题思路

对链表进行二分,在二分的过程中要找到链表每一段的起始和结束节点,并且在合并前要先把中间节点给断开。为了实现二分需要找到链表的中间节点,可以用快慢指针实现,快指针每次后移两下,慢指针每次只后移一下,当快指针只指到链表尾时,慢指针所指的就是中间节点了。

5.3 代码实现

/**
 * 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* mergeTwoLists(ListNode* list1, ListNode* list2) {
        if(!list1) return list2;
        if(!list2) return list1;
        if(list1->val < list2->val) {
            list1->next = mergeTwoLists(list1->next, list2);
            return list1;
        }
        else {
            list2->next = mergeTwoLists(list1, list2->next);
            return list2;
        }
    }
    ListNode* sortSubList(ListNode* start, ListNode* end) {
        if(start == end)
            return start;
        if(start->next == end) {
            start->next = nullptr;
            return start;
        }
        ListNode* fast = start;
        ListNode* slow = start;
        while(fast->next != end) {
            fast = fast->next;
            slow = slow->next;
            if(fast->next != end)
                fast = fast->next;
        }
        ListNode* mid = slow;
        return mergeTwoLists(sortSubList(start ,mid), sortSubList(mid, end));
    }
    ListNode* sortList(ListNode* head) {
        return sortSubList(head, nullptr);
    }
};
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Frankenstein@

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值