1. Two Sum
给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。
你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。
你可以按任意顺序返回答案。
示例 1:
输入:nums = [2,7,11,15], target = 9
输出:[0,1]
解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。
示例 2:
输入:nums = [3,2,4], target = 6
输出:[1,2]
示例 3:
输入:nums = [3,3], target = 6
输出:[0,1]
提示:
2 <= nums.length <= 104
-109 <= nums[i] <= 109
-109 <= target <= 109
只会存在一个有效答案
进阶:你可以想出一个时间复杂度小于 O(n2) 的算法吗?
Solution 1
双层遍历
Solution 2
哈希表
The basic idea is to maintain a hash table for each element num in nums,
using num as key and its index (0-based) as value. For each num,
search for target - num in the hash table.
If it is found and is not the same element as num, then we are done.
The code is as follows. Note that each time before we add num to mp,
we search for target - num first and so we will not hit the same element.
18. 四数之和
给你一个由 n 个整数组成的数组 nums ,和一个目标值 target 。请你找出并返回满足下述全部条件且不重复的四元组 [nums[a], nums[b], nums[c], nums[d]] (若两个四元组元素一一对应,则认为两个四元组重复):
0 <= a, b, c, d < n
a、b、c 和 d 互不相同
nums[a] + nums[b] + nums[c] + nums[d] == target
你可以按 任意顺序 返回答案 。
示例 1:
输入:nums = [1,0,-1,0,-2,2], target = 0
输出:[[-2,-1,1,2],[-2,0,0,2],[-1,0,0,1]]
示例 2:
输入:nums = [2,2,2,2,2], target = 8
输出:[[2,2,2,2]]
提示:
1 <= nums.length <= 200
-109 <= nums[i] <= 109
-109 <= target <= 109
Solution 1 暴力枚举
Solution 2 双指针法
The two pointers pattern requires the array to be sorted, so we do that first. Also, it's easier to deal with duplicates if the array is sorted: repeated values are next to each other and easy to skip.
For 3Sum, we enumerate each value in a single loop, and use the two pointers pattern for the rest of the array. For kSum, we will have k - 2 nested loops to enumerate all combinations of k - 2 values.
19. 删除链表的倒数第 N 个结点
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点。
示例 1:
输入:head = [1,2,3,4,5], n = 2
输出:[1,2,3,5]
示例 2:
输入:head = [1], n = 1
输出:[]
示例 3:
输入:head = [1,2], n = 1
输出:[1]
提示:
链表中结点的数目为 sz
1 <= sz <= 30
0 <= Node.val <= 100
1 <= n <= sz
Solution1 计算链表长度
Solution2 栈
Solution3 快慢指针
20. 有效的括号
给定一个只包括 ‘(’,‘)’,‘{’,‘}’,‘[’,‘]’ 的字符串 s ,判断字符串是否有效。
有效字符串需满足:
左括号必须用相同类型的右括号闭合。
左括号必须以正确的顺序闭合。
示例 1:
输入:s = “()”
输出:true
示例 2:
输入:s = “()[]{}”
输出:true
示例 3:
输入:s = “(]”
输出:false
示例 4:
输入:s = “([)]”
输出:false
示例 5:
输入:s = “{[]}”
输出:true
Solution1 栈
class Solution {
public:
bool isValid(string s) {
stack<char> st; //taking stack for keep tracking the order of the brackets..
for(auto i:s) //iterate over each and every elements
{
if(i=='(' or i=='{' or i=='[') st.push(i); //if current element of the string will be opening bracket then we will just simply push it into the stack
else //if control comes to else part, it means that current element is a closing bracket, so check two conditions current element matches with top of the stack and the stack must not be empty...
{
if(st.empty() or (st.top()=='(' and i!=')') or (st.top()=='{' and i!='}') or (st.top()=='[' and i!=']')) return false;
st.pop(); //if control reaches to that line, it means we have got the right pair of brackets, so just pop it.
}
}
return st.empty(); //at last, it may possible that we left something into the stack unpair so return checking stack is empty or not..
}
};
26. 删除有序数组中的重复项
给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。
由于在某些语言中不能改变数组的长度,所以必须将结果放在数组nums的第一部分。更规范地说,如果在删除重复项之后有 k 个元素,那么 nums 的前 k 个元素应该保存最终结果。
将最终结果插入 nums 的前 k 个位置后返回 k 。
不要使用额外的空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
判题标准:
系统会用下面的代码来测试你的题解:
int[] nums = […]; // 输入数组
int[] expectedNums = […]; // 长度正确的期望答案
int k = removeDuplicates(nums); // 调用
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}
如果所有断言都通过,那么您的题解将被 通过。
示例 1:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
提示:
1 <= nums.length <= 3 * 104
-104 <= nums[i] <= 104
nums 已按 升序 排列
Solution1 暴力
Solution2 双指针
Solution3 双指针 优化
31. 下一个排列
整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。
例如,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 的下一个排列。
必须 原地 修改,只允许使用额外常数空间。
示例 1:
输入:nums = [1,2,3]
输出:[1,3,2]
示例 2:
输入:nums = [3,2,1]
输出:[1,2,3]
示例 3:
输入:nums = [1,1,5]
输出:[1,5,1]
提示:
1 <= nums.length <= 100
0 <= nums[i] <= 100
Solution1 暴力
Solution2
48. 旋转图像
给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。
你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[[7,4,1],[8,5,2],[9,6,3]]
示例 2:
输入:matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]
输出:[[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]
提示:
n == matrix.length == matrix[i].length
1 <= n <= 20
-1000 <= matrix[i][j] <= 1000
Solution1 原地查找
Solution2 先转置再竖直对称反转
class Solution {
public:
void rotate(vector<vector<int>>& matrix) {
//先转置
for(int row=0;row<=matrix.size()-1;row++){
for(int col=row;col<=matrix[0].size()-1;col++){
swap(matrix[row][col],matrix[col][row]);
}
}
//在竖直对称翻转
for(int row=0;row<=matrix.size()-1;row++){
for(int col = 0;col<=(matrix.size()-1)/2;col++){
swap(matrix[row][col],matrix[row][matrix.size()-1-col]);
}
}
}
};
49. 字母异位词分组
给你一个字符串数组,请你将 字母异位词 组合在一起。可以按任意顺序返回结果列表。
字母异位词 是由重新排列源单词的字母得到的一个新单词,所有源单词中的字母通常恰好只用一次。
示例 1:
输入: strs = [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出: [[“bat”],[“nat”,“tan”],[“ate”,“eat”,“tea”]]
示例 2:
输入: strs = [“”]
输出: [[“”]]
示例 3:
输入: strs = [“a”]
输出: [[“a”]]
提示:
1 <= strs.length <= 104
0 <= strs[i].length <= 100
strs[i] 仅包含小写字母
Solution1 Map
Use an unordered_map to group the strings by their sorted counterparts. Use the sorted string as the key and all anagram strings as the value.
Moreover, since the string only contains lower-case alphabets, we can sort them using counting sort to improve the time complexity.
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
map<string,vector<string>>res;
//遍历
for(auto val:strs){
string tmp=val;
//以排序后的str作为key vector<string>作为value 放进map
sort(val.begin(),val.end());
if(res.find(val)==res.end()){
vector<string> t={tmp};
res.insert({val,t});
}else{
res[val].push_back(tmp);
}
}
//遍历输出结果
vector<vector<string>> result;
for(auto p:res){
result.push_back(p.second);
}
return result;
}
};
54. 螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例 1:
输入:matrix = [[1,2,3],[4,5,6],[7,8,9]]
输出:[1,2,3,6,9,8,7,4,5]
示例 2:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
提示:
m == matrix.length
n == matrix[i].length
1 <= m, n <= 10
-100 <= matrix[i][j] <= 100
spiral-matrix
Algorithm:
First we will iterate in to first row from left to right push back all the elements into a vector. After iterating, we change the top to second row (top++).
Then we will iterate from new top to bottom and push back only right most elements of each row. After iterating, we change the right to second last column (right--).
Then we will iterate in bottom row from right to left and pushback all the elements from new right to left. After iterating, we change the bottom to second last row (bottom--).
Then we will iterate from new bottom to new top and push back only left most element. After iterating, we change the left to second column (left++).
Repeat all these steps until left = right and top = bottom.
Solution1 原地遍历
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
vector<int> res;
if(matrix.empty()){return res;}
int s=0,x=matrix.size()-1,z=0,y=matrix[0].size()-1;
while(true){
//在上边沿
for(int i=s,j=z;j<=y;j++){
res.push_back(matrix[i][j]);
}
if(++s>x) break;
//在右边沿
for(int i=s,j=y;i<=x;i++){
res.push_back(matrix[i][j]);
}
if(--y<z) break;
//在下边沿
for(int i=x,j=y;j>=z;j--){
res.push_back(matrix[i][j]);
}
if(--x<s) break;
//在左边沿
for(int i=x,j=z;i>=s;i--){
res.push_back(matrix[i][j]);
}
if(++z>y) break;
}
return res;
}
};
Solution2 按层遍历
class Solution {
public:
vector<int> spiralOrder(vector<vector<int>>& matrix) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return {};
}
int rows = matrix.size(), columns = matrix[0].size();
vector<int> order;
int left = 0, right = columns - 1, top = 0, bottom = rows - 1;
while (left <= right && top <= bottom) {
for (int column = left; column <= right; column++) {
order.push_back(matrix[top][column]);
}
for (int row = top + 1; row <= bottom; row++) {
order.push_back(matrix[row][right]);
}
if (left < right && top < bottom) {
for (int column = right - 1; column > left; column--) {
order.push_back(matrix[bottom][column]);
}
for (int row = bottom; row > top; row--) {
order.push_back(matrix[row][left]);
}
}
left++;
right--;
top++;
bottom--;
}
return order;
}
};
56. 合并区间
以数组 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 <= 104
intervals[i].length == 2
0 <= starti <= endi <= 104
nitially sort the array and then push the first element into the answer for speculation.
We have two condition if the first elements second part of ans array is greater than or equal to the second element first part of the
interval array.
The other condition we have to tackle is what if its not? then we push the particular element into the ans array which will be then be under speculation.
interval: [[1,3],[2,6],[8,10],[15,18]]
i
We initally push the 1st element into the ans array:
ans=[[1,3]]
j j points to the latest pushed element
Then we i is incremented.
[[1,3],[2,6],[8,10],[15,18]]
i
Now the ans[j][1]>interval[i][0] this means there is a possiblity of merging so we merger them
Remember the way we merge is to take the second element as max(ans[j][1],interval[i][1])
cuz imagine we have this
[1,7][2,4] --->merge should be ---->[1,7]
ans=[[1,6]]
then we move i forward
[[1,3],[2,6],[8,10],[15,18]]
i
Since ans[j][1]<interval[i][0] thus not contributing to the merge.
Thus we will push this into the ans array and speculate.
ans=[[1,6][8,10]]
j <----j is moved forward
i is moved forward
[[1,3],[2,6],[8,10],[15,18]]
i
Since ans[j][1]<interval[i][0] thus not contributing to the merge.
ans=[[1,6][8,10][15,18]]
j
Thus yielding our final answer.
vector<vector<int>> merge(vector<vector<int>>& interval) {
vector<vector<int>> ans;
if(interval.size()==0)return ans;
sort(interval.begin(),interval.end());
ans.push_back(interval[0]);
int j=0;
for(int i=1;i<interval.size();i++)
{
if(ans[j][1]>=interval[i][0])
ans[j][1]=max(ans[j][1],interval[i][1]);
else
{
j++;
ans.push_back(interval[i]);
}
}
return ans;
}
Solution1 排序
复杂度分析
时间复杂度:O(n\log n)O(nlogn),其中 nn 为区间的数量。除去排序的开销,我们只需要一次线性扫描,所以主要的时间开销是排序的 O(n\log n)O(nlogn)。
空间复杂度:O(\log n)O(logn),其中 nn 为区间的数量。这里计算的是存储答案之外,使用的额外空间。O(\log n)O(logn) 即为排序所需要的空间复杂度。
class Solution {
public:
vector<vector<int>> merge(vector<vector<int>>& intervals) {
if (intervals.size() == 0) {
return {};
}
sort(intervals.begin(), intervals.end());
vector<vector<int>> merged;
for (int i = 0; i < intervals.size(); ++i) {
int L = intervals[i][0], R = intervals[i][1];
if (!merged.size() || merged.back()[1] < L) {
merged.push_back({L, R});
}
else {
merged.back()[1] = max(merged.back()[1], R);
}
}
return merged;
}
};
61. 旋转链表
给你一个链表的头节点 head ,旋转链表,将链表每个节点向右移动 k 个位置。
示例 1:
输入:head = [1,2,3,4,5], k = 2
输出:[4,5,1,2,3]
示例 2:
输入:head = [0,1,2], k = 4
输出:[2,0,1]
提示:
链表中节点的数目在范围 [0, 500] 内
-100 <= Node.val <= 100
0 <= k <= 2 * 109
There is no trick for this problem. Some people used slow/fast pointers to find the tail node but I don't see the benefit (in the sense that it doesn't reduce the pointer move op) to do so. So I just used one loop to find the length first.
class Solution {
public:
ListNode* rotateRight(ListNode* head, int k) {
if(!head) return head;
int len=1; // number of nodes
ListNode *newH, *tail;
newH=tail=head;
while(tail->next) // get the number of nodes in the list
{
tail = tail->next;
len++;
}
tail->next = head; // circle the link
if(k %= len)
{
for(auto i=0; i<len-k; i++) tail = tail->next; // the tail node is the (len-k)-th node (1st node is head)
}
newH = tail->next;
tail->next = NULL;
return newH;
}
};
Solution1 暴力
Soluion2 闭环
66. 加一
给定一个由 整数 组成的 非空 数组所表示的非负整数,在该数的基础上加一。
最高位数字存放在数组的首位, 数组中每个元素只存储单个数字。
你可以假设除了整数 0 之外,这个整数不会以零开头。
示例 1:
输入:digits = [1,2,3]
输出:[1,2,4]
解释:输入数组表示数字 123。
示例 2:
输入:digits = [4,3,2,1]
输出:[4,3,2,2]
解释:输入数组表示数字 4321。
示例 3:
输入:digits = [0]
输出:[1]
提示:
1 <= digits.length <= 100
0 <= digits[i] <= 9
My solution is nothing special and isn't clever at all. I decided to post it since I thought the "official" solution article from leetcode was very poorly written and confused me more, even after I solved it on my own.
So, I believe my comments below should explain the idea, but I want to add that it helps to test the more obscure test cases for this problem to understand the algorithm. For example:
[9]
[9090]
class Solution {
public:
vector<int> plusOne(vector<int>& digits) {
int n = digits.size() - 1;
for (int i = n; i >= 0; --i) { // traverse digits from the last element (least significant)
// since we begin with the last digit, increasing that digit by one
// results in overflow. Therefore, all elements PRIOR to digits[0]
// need to be considered since there may be additional nines between
// digits[0], ... , digits[n].
if (digits[i] == 9) {
digits[i] = 0;
} else { // current digit is not 9 so we can safely increment by one
digits[i] += 1;
return digits;
}
}
// if the program runs to this point, each 9 is now a 0.
// to get a correct solution, we need to add one more element with
// a value of zero AND set digits[0] to 1 (in the most significant position)
// to account for the carry digit.
digits.push_back(0);
digits[0] = 1;
return digits;
}
};
Solution 0 暴力
Solution 1 逆序遍历 找到9
75. 颜色分类
给定一个包含红色、白色和蓝色、共 n 个元素的数组 nums ,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
必须在不使用库的sort函数的情况下解决这个问题。
示例 1:
输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]
示例 2:
输入:nums = [2,0,1]
输出:[0,1,2]
提示:
n == nums.length
1 <= n <= 300
nums[i] 为 0、1 或 2
进阶:
你可以不使用代码库中的排序函数来解决这道题吗?
你能想出一个仅使用常数空间的一趟扫描算法吗?
The solution requires the use of tracking 3 positions, the Low, Mid and High.
We assume that the mid is the "Unknown" area that we must evaluate.
If we encounter a 0, we know that it will be on the low end of the array, and if we encounter a 2, we know it will be on the high end of the array.
To achieve this in one pass without preprocessing (counting), we simply traverse the unknown will generating the low and high ends.
Take this example:
Assume our input is: 1 0 2 2 1 0 (short for simplicity).
Running the algorithm by hand would look something like:
1 0 2 2 1 0
^ ^
L H
M
Mid != 0 || 2
Mid++
1 0 2 2 1 0
^ ^ ^
L M H
Mid == 0
Swap Low and Mid
Mid++
Low++
0 1 2 2 1 0
^ ^ ^
L M H
Mid == 2
Swap High and Mid
High--
0 1 0 2 1 2
^ ^ ^
L M H
Mid == 0
Swap Low and Mid
Mid++
Low++
0 0 1 2 1 2
^ ^ ^
L M H
Mid == 2
Swap High and Mid
High--
0 0 1 1 2 2
^ ^
L M
H
Mid <= High is our exit case
Solution1 暴力
根据题目中的提示,我们可以统计出数组中 0, 1, 20,1,2 的个数,再根据它们的数量,重写整个数组。这种方法较为简单,也很容易想到,而本题解中会介绍两种基于指针进行交换的方法。
Solution2 单指针
我们可以考虑对数组进行两次遍历。在第一次遍历中,我们将数组中所有的 00 交换到数组的头部。在第二次遍历中,我们将数组中所有的 11 交换到头部的 00 之后。此时,所有的 22 都出现在数组的尾部,这样我们就完成了排序。
具体地,我们使用一个指针 \textit{ptr}ptr 表示「头部」的范围,\textit{ptr}ptr 中存储了一个整数,表示数组 \textit{nums}nums 从位置 00 到位置 \textit{ptr}-1ptr−1 都属于「头部」。\textit{ptr}ptr 的初始值为 00,表示还没有数处于「头部」。
在第一次遍历中,我们从左向右遍历整个数组,如果找到了 00,那么就需要将 00 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 00 都被交换到「头部」的范围,并且「头部」只包含 00。
在第二次遍历中,我们从「头部」开始,从左向右遍历整个数组,如果找到了 11,那么就需要将 11 与「头部」位置的元素进行交换,并将「头部」向后扩充一个位置。在遍历结束之后,所有的 11 都被交换到「头部」的范围,并且都在 00 之后,此时 22 只出现在「头部」之外的位置,因此排序完成。
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int ptr = 0;
for (int i = 0; i < n; ++i) {
if (nums[i] == 0) {
swap(nums[i], nums[ptr]);
++ptr;
}
}
for (int i = ptr; i < n; ++i) {
if (nums[i] == 1) {
swap(nums[i], nums[ptr]);
++ptr;
}
}
}
};
Solution3 双指针
class Solution {
public:
void sortColors(vector<int>& nums) {
int n = nums.size();
int p0 = 0, p2 = n - 1;
for (int i = 0; i <= p2; ++i) {
while (i <= p2 && nums[i] == 2) {
swap(nums[i], nums[p2]);
--p2;
}
if (nums[i] == 0) {
swap(nums[i], nums[p0]);
++p0;
}
}
}
};
83. 删除排序链表中的重复元素
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表 。
示例 1:
输入:head = [1,1,2]
输出:[1,2]
示例 2:
输入:head = [1,1,2,3,3]
输出:[1,2,3]
提示:
链表中节点数目在范围 [0, 300] 内
-100 <= Node.val <= 100
题目数据保证链表已经按升序 排列
noticed that the solutions posted here are too long and complicated. They use unnecessary variables and/or checks etc.
The solution can be much more concise. Here is my solution:
class Solution {
public:
ListNode *deleteDuplicates(ListNode *head) {
ListNode* cur = head;
while (cur) {
while (cur->next && cur->val == cur->next->val)
cur->next = cur->next->next;
cur = cur->next;
}
return head;
}
};
Note about freeing memory. We need to free memory when we delete a node. But don't use delete node; construct on an interview without discussing it with the interviewer. A list node can be allocated in many different ways and we can use delete node; only if we are sure that the nodes were allocated with new TreeNode(...);.
Solution1 一次遍历
方法一:一次遍历
思路与算法
由于给定的链表是排好序的,因此重复的元素在链表中出现的位置是连续的,因此我们只需要对链表进行一次遍历,就可以删除重复的元素。
具体地,我们从指针 \textit{cur}cur 指向链表的头节点,随后开始对链表进行遍历。如果当前 \textit{cur}cur 与 \textit{cur.next}cur.next 对应的元素相同,那么我们就将 \textit{cur.next}cur.next 从链表中移除;否则说明链表中已经不存在其它与 \textit{cur}cur 对应的元素相同的节点,因此可以将 \textit{cur}cur 指向 \textit{cur.next}cur.next。
当遍历完整个链表之后,我们返回链表的头节点即可。
细节
当我们遍历到链表的最后一个节点时,\textit{cur.next}cur.next 为空节点,如果不加以判断,访问 \textit{cur.next}cur.next 对应的元素会产生运行错误。因此我们只需要遍历到链表的最后一个节点,而不需要遍历完整个链表。
复杂度分析
时间复杂度:O(n)O(n),其中 nn 是链表的长度。
空间复杂度:O(1)O(1)。
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
if (!head) {
return head;
}
ListNode* cur = head;
while (cur->next) {
if (cur->val == cur->next->val) {
cur->next = cur->next->next;
}
else {
cur = cur->next;
}
}
return head;
}
};
86. 分隔链表
给你一个链表的头节点 head 和一个特定值 x ,请你对链表进行分隔,使得所有 小于 x 的节点都出现在 大于或等于 x 的节点之前。
你应当 保留 两个分区中每个节点的初始相对位置。
示例 1:
输入:head = [1,4,3,2,5,2], x = 3
输出:[1,2,2,4,3,5]
示例 2:
输入:head = [2,1], x = 2
输出:[1,2]
提示:
链表中节点的数目在范围 [0, 200] 内
-100 <= Node.val <= 100
-200 <= x <= 200
Approach 1: Two Pointer Approach
Intuition
We can take two pointers before and after to keep track of the two linked lists as described above. These two pointers could be used two create two separate lists and then these lists could be combined to form the desired reformed list.
Algorithm
Initialize two pointers before and after. In the implementation we have initialized these two with a dummy ListNode. This helps to reduce the number of conditional checks we would need otherwise. You can try an implementation where you don't initialize with a dummy node and see it yourself!
Dummy Node Initialization
Iterate the original linked list, using the head pointer.
If the node's value pointed by head is lesser than x, the node should be part of the before list. So we move it to before list.
Else, the node should be part of after list. So we move it to after list.
Once we are done with all the nodes in the original linked list, we would have two list before and after. The original list nodes are either part of before list or after list, depending on its value.
Note: Since we traverse the original linked list from left to right, at no point would the order of nodes change relatively in the two lists. Another important thing to note here is that we show the original linked list intact in the above diagrams. However, in the implementation, we remove the nodes from the original linked list and attach them in the before or after list. We don't utilize any additional space. We simply move the nodes from the original list around.
Now, these two lists before and after can be combined to form the reformed list.
We did a dummy node initialization at the start to make implementation easier, you don't want that to be part of the returned list, hence just move ahead one node in both the lists while combining the two list. Since both before and after have an extra node at the front.
Solution1 双指针
88. 合并两个有序数组
给你两个按 非递减顺序 排列的整数数组 nums1 和 nums2,另有两个整数 m 和 n ,分别表示 nums1 和 nums2 中的元素数目。
请你 合并 nums2 到 nums1 中,使合并后的数组同样按 非递减顺序 排列。
注意:最终,合并后数组不应由函数返回,而是存储在数组 nums1 中。为了应对这种情况,nums1 的初始长度为 m + n,其中前 m 个元素表示应合并的元素,后 n 个元素为 0 ,应忽略。nums2 的长度为 n 。
示例 1:
输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]
解释:需要合并 [1,2,3] 和 [2,5,6] 。
合并结果是 [1,2,2,3,5,6] ,其中斜体加粗标注的为 nums1 中的元素。
示例 2:
输入:nums1 = [1], m = 1, nums2 = [], n = 0
输出:[1]
解释:需要合并 [1] 和 [] 。
合并结果是 [1] 。
示例 3:
输入:nums1 = [0], m = 0, nums2 = [1], n = 1
输出:[1]
解释:需要合并的数组是 [] 和 [1] 。
合并结果是 [1] 。
注意,因为 m = 0 ,所以 nums1 中没有元素。nums1 中仅存的 0 仅仅是为了确保合并结果可以顺利存放到 nums1 中。
提示:
nums1.length == m + n
nums2.length == n
0 <= m, n <= 200
1 <= m + n <= 200
-109 <= nums1[i], nums2[j] <= 109
进阶:你可以设计实现一个时间复杂度为 O(m + n) 的算法解决此问题吗?
This code relies on the simple observation that once all of the numbers from nums2 have been merged into nums1, the rest of the numbers in nums1 that were not moved are already in the correct place.
The way to think about the solution is that we will have to do a reverse sorting.
We initialize k=m+n-1 as that will be the last location of nums1.
We will keep checking for the greater element of the two arrays(i=m-1,j=n-1) and insert the values.
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6], n = 3
nums1 = [1,2,3,0,0,0]
| |
i k
nums2 = [2,5,6]
|
j
nums2[j]>nums1[i] thus nums1[k]=6
k and j are decremented.
nums1 = [1,2,3,0,0,6]
| |
i k
nums2 = [2,5,6]
|
j
nums2[j]>nums1[i] thus nums1[k]=5
k and j are decremented.
nums1 = [1,2,3,0,5,6]
| |
i k
nums2 = [2,5,6]
|
j
We keep following up this procedure and we get the desired reult.
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int i=m-1,j=n-1,k=m+n-1;
while(i>=0&&j>=0)
{
if(nums1[i]>nums2[j])
{
nums1[k]=nums1[i];
i--;
k--;
}
else
{
nums1[k]=nums2[j];
j--;
k--;
}
}
while(i>=0)
nums1[k--]=nums1[i--];
while(j>=0)
nums1[k--]=nums2[j--];
}
Solution1 暴力直接合并后排序
Solution2 双指针
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = 0, p2 = 0;
int sorted[m + n];
int cur;
while (p1 < m || p2 < n) {
if (p1 == m) {
cur = nums2[p2++];
} else if (p2 == n) {
cur = nums1[p1++];
} else if (nums1[p1] < nums2[p2]) {
cur = nums1[p1++];
} else {
cur = nums2[p2++];
}
sorted[p1 + p2 - 1] = cur;
}
for (int i = 0; i != m + n; ++i) {
nums1[i] = sorted[i];
}
}
};
复杂度分析
时间复杂度:O(m+n)O(m+n)。
指针移动单调递增,最多移动 m+nm+n 次,因此时间复杂度为 O(m+n)O(m+n)。
空间复杂度:O(m+n)O(m+n)。
需要建立长度为 m+nm+n 的中间数组 \textit{sorted}sorted。
Solution3 逆向双指针
class Solution {
public:
void merge(vector<int>& nums1, int m, vector<int>& nums2, int n) {
int p1 = m - 1, p2 = n - 1;
int tail = m + n - 1;
int cur;
while (p1 >= 0 || p2 >= 0) {
if (p1 == -1) {
cur = nums2[p2--];
} else if (p2 == -1) {
cur = nums1[p1--];
} else if (nums1[p1] > nums2[p2]) {
cur = nums1[p1--];
} else {
cur = nums2[p2--];
}
nums1[tail--] = cur;
}
}
};
92. 反转链表 II
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表 。
示例 1:
输入:head = [1,2,3,4,5], left = 2, right = 4
输出:[1,4,3,2,5]
示例 2:
输入:head = [5], left = 1, right = 1
输出:[5]
提示:
链表中节点数目为 n
1 <= n <= 500
-500 <= Node.val <= 500
1 <= left <= right <= n
进阶: 你可以使用一趟扫描完成反转吗?
Solution1 原地反转
Algorithm
We define a recursion function that will do the job of reversing a portion of the linked list.
Let's call this function recurse. The function takes in 3 parameters: m being the starting point of the reversal, n being the ending point for the reversal, and a pointer right which will start at the n^{th}n
th
node in the linked list and move backwards with the backtracking of the recursion. If this is not clear at the moment, the diagrams that follow will help.
Additionally, we have a pointer called left which starts from the m^{th}m
th
node in the linked list and moves forward. In Python, we have to take a global variable for this which get's changed with recursion. In other languages, where changes made in function calls persist, we can consider this pointer as an additional variable for the function recurse.
In a recursion call, given m, n, an