文章目录
- 1. 编辑距离【困难】
- 1.1 题目描述
- 1.2 解题思路
- 1.3 代码实现
- 2. 寻找两个正序数组的中位数【困难】
- 2.1 题目描述
- 2.2 解题思路
- 2.3 代码实现
- 3. 合并区间【中等】
- 3.1 题目描述
- 3.2 解题思路
- 3.3 代码实现
- 4. 爬楼梯【简单】
- 4.1 题目描述
- 4.2 解题思路
- 4.3 代码实现
- 5. 排序链表【中等】
- 5.1 题目描述
- 5.2 解题思路
- 5.3 代码实现
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);
}
};